
Good afternoon, dear habrovchane. In the
last article, you and I developed a simple USB video card based on the STM32F103 and the Chinese display module on the ILI9325 controller.
We checked it from yuzerspeysa, with the help of LibUSB. Well, the time has come for us to write our own driver, which will allow us to do everything the same, but from under Linux and without additional libraries. We will add this driver to the source tree of OpenWRT and it will settle there on a par with everyone else.
Let's start.
Introduction
Since the iron from the previous article we have not changed, we begin with consideration of what we need to achieve. But before that, as always, I would recommend to get acquainted with the following materials: first of all, this is the
book Linux Device Drivers , which outlines everything necessary for the developer of drivers for Linux. Secondly, I recommend once again to get acquainted with my
article about setting up and finishing OpenWRT of our router, since we have to mess around a lot with the build system and configs. And finally - a
very, very useful resource , presentations from their training seminars are freely available.
Yes, we will develop, of course, from under Linux, so you need to attend to the presence of some distribution kit in advance. I work under Linux Mint, but little will depend on the distribution kit, the only commands specific to it are installation commands from the repository of additional utilities.
Now consider the approach to, in fact, the driver. Anyone who is even slightly familiar with device drivers in Linux immediately comes to mind two well-known types of these same devices - symbolic and block. This is perhaps the first thing that is told in any article about drivers. Usually, after this, there is still a phrase that the network interface drivers stand separately.
However, the full picture is presented far from everywhere. Many drivers are not registered directly as character or block, despite the fact that the system displays as such. Instead, they are registered through a specialized
framework of the appropriate type - for example, framebuffer, input, tty, etc. The framework defines a common interface for drivers and provides a standardized interface to the system.

Thus, having defined the necessary structures with our driver and registering it through the framework of the same framebuffer, we no longer need to worry about telling the system “see, we have a display” - this is the task of the framework, and all applications (and drivers) who use the framebuffer in their work will immediately see our device. For example, these include the console driver (fbcon), which uses a framebuffer for output, which can output a picture and colored text to the console when it is loaded.
But let's not hurry and dive into this jungle, let's leave the framebuffer driver for the next article. In the same article, we will write a character device driver, which, when writing to it from a user space, will create a package for our video card (calculating the position on the screen based on the position of the recording in the file) and send it via USB.
Preparing the workspace
The driver itself will be fairly simple, but we will consider important questions about adding it to the source tree. Let's start with the helical frame driver.
')
Hellowworld kernel module#include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #include <linux/init.h> /* Needed for the macros */ #define DRIVER_AUTHOR "Amon-Ra" #define DRIVER_DESC "USB STM32-based LCD module driver" static int __init lcddriver_init(void) { printk(KERN_INFO "Hello, world!\n"); return 0; } static void __exit lcddriver_exit(void) { printk(KERN_INFO "Goodbye, world!\n"); } module_init(lcddriver_init); module_exit(lcddriver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC);
This code does not need explanations, it is the most hello worldly - we describe two callbacks and register them as functions that are called during module initialization and during its unloading. In them, we simply display messages.
Save this code as usblcd.c.
Now let's remember how the system, which generates the output of our firmware, works in general. This is a very complex product called BuildRoot, which, in fact, takes care of everything: it pumps out and compiles the toolchain for cross-assembling the code, downloads the source code and patches for the kernel and third-party utilities, generates configuration files for all of this in accordance with high-level settings, compiles, compiles the compiled into the package, generates the file system, puts it all together and packs it into the final image.
It follows from this that configs for assembling, for example, the kernels (and generally for everything that is possible) are dynamically generated each time, and there is no sense to edit the gene config or even the makefile, they will be overwritten.
In the standard image configuration menu, which we call make menuconfig, we also will not find the kernel settings. So how to be?
It turns out that the config for the kernel is generated from the base one, which we can change. To do this, just call
make kernel_menuconfig . Here you have to be careful, this is the base for generating the target-specificity of the config, and you will not be able to roll back through make clean just by downloading the source code from the repository again. Therefore, in it we act carefully and remember what is turned on and what is turned off!
True, this is not what we need now, because in order to compile our driver, we have to make edits not only to the config, but also to the Makefile files (determining which modules will be compiled) and Kconfig (on the basis of which the menu is generated from
make kernel_menuconfig and which contains options for the Makefile) - without this, in the menu we will not even find any mention of our driver.
In addition, we need our usblcd.c to appear in the directory with the Linux sources, in the
/ drivers / video subdirectory.
And the first thing we need to understand at the moment is the
generation of the target-specificity of the source tree using the files in target / linux / <target> /Buildroot takes clean kernel sources and puts them into
build_dir / <target architecture> / <target platform> / linux- <kernel version>After that, additional source files from
target / linux / <target> / files are copied there - usually, there are drivers that are specific to the target platform and other platform-specific code.
After that, the patch set in
target / linux / <target> / patches is applied. These patches usually contain an addition to the Makefile and Kconfig, which adds the appropriate drivers to the build process. In addition to the platform-specific patches, patches from
target / linux / genegic are also applied, more general, not sharpened for a specific device.
After this, the config for the kernel build is generated and, in fact, the build of what lies in build_dir is launched.
Now let's go successively and point by point, as in the previous article, and begin by placing usblcd.c.
- Go to target / linux / ar71xx / files / drivers and execute mkdir video there , creating a directory for our driver. Buildroot scatters the contents of files in the appropriate directories of the source tree. We place the file usblcd.c in the newly created folder.
- In target / linux / ar71xx / modules.mk we add an entry for the new kernel-packet. Here it should be remembered that the kernel packet is not the same as the kernel modules. Kernel packs are packages designed for the opkg utility in OpenWRT, they can be inserted into an image, or compiled as a single-sided package, but this will not affect in any way how the corresponding kernel module will be built.
Simply put, if we have some binary, we can either push it into the package anymore (option M in make menuconfig ), leaving it in the directory with crashed packages or inserting it into the file system of the image under construction, that is, preset its default. At the same time, if this binary is a kernel module, then, when compiling the kernel, of course, it will be brought down exactly as a separate module, so that it can be packed into a package. You can embed a kernel module into the kernel itself only via make kernel_menuconfig (or via a patch for these very kernels of configs).
This may at first cause some confusion and finding out why the packet, which is explicitly listed as built-in, generates separate kernel-modules. However, if you comprehend this mechanism, everything becomes clear and logical.
We will build the driver as a separate module, and it will be possible to solve it every time in FS or not by reconfiguring the build via make menuconfig.
Entry in modules.mk define KernelPackage/usb-lcd SUBMENU:=$(USB_MENU) TITLE:=USB STM32-based LCD module DEPENDS:=@TARGET_ar71xx KCONFIG:=CONFIG_STM32_USB_LCD FILES:=$(LINUX_DIR)/drivers/video/usblcd.ko endef define KernelPackage/usb-lcd/description Kernel module for USB STM32-based LCD module. endef $(eval $(call KernelPackage,usb-lcd))
Here we set the rules for the new entry, called make menuconfig - we say that the package will be in the USB submenu, with the title given to us relating to the platform based on the AR71xx.
Next, we inform you which kernel option it activates - call it CONFIG_STM32_USB_LCD.
Then comes the name of the file that the system will load into the package - after compilation, our module will become usblcd.ko, and indicate it. Then we set the description and do not forget the last line pulling the script processing of all this.
- From the root of OpenWRT, we run make menuconfig and search for our package in Kernel modules / USB, and say that we want to include it in the resulting image.
- This is still not enough for the module to be assembled; the choice of this package will only determine the CONFIG_STM32_USB_LCD option when building the kernel. In order for this option to mean anything, you need to edit the Kconfig and Makefile files from the <kernel source directory> / drivers / video . However, if we go to build_dir for this, we will not get anything good, we have already found out that the content is generated anew each time. The correct way is to create a board-specific patch for the kernel files and place it in / target / linux / ar71xx / patches . For this we need quilt - a system for working with patches. You can read more about this in the article with Wiki.OpenWRT .
Install quilt (sudo apt-get install quilt) and configure it for an article from the wiki:
cat > ~/.quiltrc <<EOF QUILT_DIFF_ARGS="--no-timestamps --no-index -pab --color=auto" QUILT_REFRESH_ARGS="--no-timestamps --no-index -pab" QUILT_PATCH_OPTS="--unified" QUILT_DIFF_OPTS="-p" EDITOR="nano" EOF
- We execute the command
make target/linux/{clean,prepare} V=s QUILT=1
It will prepare us the source tree in build_dir for further use.
- Go to the directory with the kernel source in build_dir. The version may differ, so you will need to adjust the path according to the kernel version number.
After that, apply all the patches that are intended for our assembly.
cd build_dir/target-mips_r2_uClibc-0.9.33.2/linux-ar71xx_generic/linux-3.6.9/ quilt push –a
- Next, you need to create a patch with a name that matches a specific rule - it must begin with a digit that is larger than that of any of the existing patches - this affects the order in which the patches are applied and avoids conflicts. To find out what patches are applied to this version of the kernel, use the quilt serries command and get a large-large list containing generic and platform-specific patches. Since we will have a patch specific for the platform, look at the Platform / xxx- <name> .patch . For the revision that I have, the last one was platform / a06-rb750_nand-add-buffer-verification.patch so the number will be b00.
We perform
quilt new platform/b00-usb-lcd.patch
- We tell Kilt what files we will patch:
quilt add drivers/video/Kconfig quilt add drivers/video/Makefile
- Editing files than convenient. Let's start with Kconfig, which contains definitions for the kernel configuration menu. Our driver will not be an “honest” framebuffer driver yet, so we will add it to the root, that is, before the lines
menuconfig FB
tristate "Support for frame buffer devices"
We write:
config STM32_USB_LCD tristate "USB STM32-based LCD module support" help Simple USB STM32-based LCD module driver
This will add a new item to the kernel configuration menu, located in the Drivers menu - Graphics Support.
Now edit the Makefile, adding to it somewhere after # Hardware specific drivers go first string
obj-$(CONFIG_STM32_USB_LCD) += usblcd.o
This will add our module to the list of compiled ones, if, of course, it is selected in the config.
- Make sure that everything is fine by issuing the quilt diff command , which should output the resulting patch file.
After we say quilt refresh , saving the resulting patch.
- Go back to the build root and say make target / linux / update V = s
check that the last lines of the command output will be approximately
`/home/ra/openwrt/trunk/build_dir/target-mips_r2_uClibc-0.9.33.2/linux-ar71xx_generic/linux-3.6.9/patches/platform/b00-usb-lcd '->` ./patches-3.6/b00 -usb-lcd '
- We issue the command make clean && make , generating an image for the firmware, upload it to the router (in / tmp, for example) and reflash. It is possible through mtd, it is possible through sysupgrade - in the second case, you can save your / etc settings, which may be useful (switch -c):
scp bin/openwrt-ar71xx-generic-tl-mr3020-v1-squashfs-sy supgrade.bin root@192.168.0.48:/tmp sysupgrade -c openwrt-ar71xx-generic-tl-mr3020-v1-squashfs-sy supgrade.bin
- We go to the SSH router, see what we have in / lib / modules - if everything was done correctly, among other kernel modules there will be our usblcd.ko
Run insmod usblcd && rmmod usblcd
Nothing should fall and generally respond.
We write dmesg - and this is what we should see in recent posts:
[291.630000] Hello, world!
[291.640000] Goodbye, world!
Congratulations, the module is successfully assembled and implemented. It is not necessary to rebuild the whole image each time and rewrite the router, just say
make clean && make target / compile , which rebuilds only the kernel with the modules, after which the necessary modules can be transferred to the router via SCP. Now you can proceed to the actual writing of the driver.
Driver
Here, the source code of the driver called usb-skeleton will help us a lot, which can be viewed online
here .
In addition, when googling found a
good article in which just describes the process of developing a USB-driver for your device.
- First of all, we must declare a structure by which the core will know which devices we can serve. We declare the usb_device_id table:
static struct usb_device_id lcd_table[]={ {USB_DEVICE(DEVICE_VENDOR_ID, DEVICE_PRODUCT_ID)}, { } };
Here, DEVICE_VENDOR_ID and DEVICE_PRODUCT_ID, the defined VID and PID are equal, respectively, 0xDEAD and 0xF00D - the same as our video card.
- Immediately after the ad call the macro
MODULE_DEVICE_TABLE (usb, lcd_table);
telling userspace that we are processing the devices listed in the table.
- We declare callbacks
void LCDProbe (struct usb_interface * interface, const struct usb_device_id * id)
and
void LCDDisconnect (struct usb_interface * interface)
They will be called upon connecting and disconnecting the device. For now, leave them empty.
- We declare an important structure in which we combine all of the above:
Usb_driver structure struct usb_driver usblcd_driver={ .owner = THIS_MODULE, .name = "usblcd", .probe = LCDProbe, .disconnect = LCDDisconnect, .id_table = lcd_table, };
- Usually, registration of a new USB driver is performed in module_init , and deregistration is in module_exit , but instead of describing these two callbacks, there is a convenient macro that eliminates the need to manually describe the initialization and deinitialization functions of the module, they will be included in the code when using a macro and will contain calls to usb_register (...) and usb_deregister (...) , registering / deregistering our driver, and nothing more is required of them:
module_usb_driver(usblcd_driver);
The time has come to check the driver - we save what we have written, give the command
make clean && make target / compile V = s , carefully watching that there are no errors during the compilation process, then we transfer the resulting usblcd.ko to the device, replacing the old version.
Now we do
insmod usblcd.ko on the router, connect the display, wait a second or two, disable it and do
rmmod usblcd . Then we call
dmesg .
It should get something like this:
[ 6002.060000] usbcore: registered new interface driver usblcd [ 6010.850000] usb 1-1: new full-speed USB device number 2 using ehci-platform [ 6011.010000] USB STM32-based LCD module connected [ 6015.140000] usb 1-1: USB disconnect, device number 2 [ 6015.140000] USB STM32-based LCD module disconnected [ 6024.240000] usbcore: deregistering interface driver usblcd
Remember that printk is not flush if \ n is not at the end of the line, so if we want to see information from the driver in chronological order and without delay, do not forget to end our debug messages with the newline character.
It's time to write something useful in the driver. Since we are not going to write a real framebuffer driver yet, let's register the so-called USB device driver class. When registering it, we, just like with a character device, specify callbacks for file operations through the file_operations descriptor structure for normal file operations — opening, closing, reading, writing, etc. In this case, the system will not care that dangles at the other end of the cord - the display, sound, or mouse. You can use them in this quality only through your user-space applications that work with devices reading / writing to device files in the required format.
Start X, for example, on this display does not work - X needs a framebuffer, providing a standardized interface.
Such a driver is registered using the structure
static struct usb_class_driver usblcd_class = { .name = "lcd%d", .fops = &LCD_fops, .minor_base = LCD_MINOR_BASE, };
Here, the first field specifies the device name (% d is used to substitute the device number after the name), the second is a pointer to the file operation structure, the third is the number from which the minors for the device will be displayed.
The order is as follows:
- We declare callbacks (for a start, you can leave them without a code, only with return 0)
Kolbeki static int LCDOpen(struct inode *inode, struct file *filp); static int LCDrelease(struct inode *inode, struct file *file); static ssize_t LCDwrite(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos);
- We declare the file_operations structure:
File_operations structure static struct file_operations LCD_fops = { .owner = THIS_MODULE, .write = LCDwrite, .open = LCDOpen, .release = LCDrelease, };
The absence of a .read callback means write-only access (you can do the same without adding write. The absence of open or release will not mean that the device file cannot be opened or closed, but, on the contrary, it means that these operations are always successful).
- Almost always there is a need to store some global data associated with the device and transfer them to all these callbacks, so usually get their own custom structure, one of the fields of which will be a pointer to usb_device, given to us by the system when calling the probe callback (or rather, received from the interface transmitted into the probe), the remaining fields can be set at your discretion. So, we describe the structure-descriptor of the device:
Structure descriptor device usblcd struct usblcd { struct usb_device *udev; struct usb_interface *interface; unsigned char minor; struct usb_endpoint_descriptor *bulk_out_ep; unsigned int bulk_out_packet_size; unsigned char *videobuffer; };
- We describe the Probe callback. In it, we initialize the device structure and perform all initialization of the device-related buffers. If we have buffers that are individual for each open instance of the device file, then they need to be initialized in LCD_fops.open
Probe Callback Code static int LCDProbe(struct usb_interface *interface, const struct usb_device_id *id) { struct usblcd *dev; struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; int retval = -ENODEV; int i; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { dev_err(&interface->dev, "Out of memory\n"); retval = -ENOMEM; goto exit; } dev->udev=interface_to_usbdev(interface); mutex_init(&dev->io_mutex); dev->interface = interface; iface_desc = interface->cur_altsetting; for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { endpoint = &iface_desc->endpoint[i].desc; if(usb_endpoint_is_bulk_out(endpoint)) { dev->bulk_out_ep=endpoint; dev->bulk_out_packet_size = le16_to_cpu(endpoint->wMaxPacketSize); break; } } if(!dev->bulk_out_ep) { dev_err(&interface->dev, "Can not find bulk-out endpoint!\n"); retval = -EIO; goto error_dev; } dev->videobuffer=kmalloc(TOTAL_BUFFER_SIZE, GFP_KERNEL); if (!dev->videobuffer) { dev_err(&interface->dev, "Out of memory\n"); retval = -ENOMEM; goto error_dev; } usb_set_intfdata(interface, dev); retval = usb_register_dev(interface, &usblcd_class); if (retval) { dev_err(&interface->dev, "Not able to get a minor for this device."); usb_set_intfdata(interface, NULL); goto error_buff; } dev->minor = interface->minor; dev_info(&interface->dev, "USB STM32-based LCD module connected as lcd%d\n",dev->minor-LCD_MINOR_BASE); return 0; error_buff: kfree(dev->videobuffer); error_dev: kfree(dev); exit: return retval; }
Pointer to usb_device * udev is obtained from the pointer passed to the kernel by usb_interface * usb_interface via interface_to_usbdev (interface) . Since there can be several multiple interfaces within one device, we get the current interface descriptor ( usb_host_interface ) from the field of the same structure ( interface-> cur_altsetting (); )
Next we go through all the interface endpoints, checking whether there are any needed ones, saving pointers to them and acceptable packet size for further quick access to our device descriptor structure. For bulk endpoints, the size of the buffer when sending can be specified more than this value, the kernel itself will break your data into packets of a suitable size, so, in principle, this value is not used anywhere in the code, I saved it just for my own peace of mind, making sure that there are exactly those 0x40 bytes that I indicated in the descriptor. Checking whether a point belongs to a particular type is performed by macros of the form usb_endpoint_is_bulk_out (endpoint) . Do not forget that to correctly get the value of numeric fields, you need to bring their byte order in accordance with the processor used by means of functions like le16_to_cpu (little endian 16-bit in order cpu). When everything is initialized, the pointer to the handle is stored in the kernel structure itself, which it passes into callbacks, via the call usb_set_intfdata (interface, dev); The final step in the Probe function is to register the USB class driver. From this point on, the file / dev / lcd [n] appears in / dev, and the callback functions from LCD_fops can be called at any time (when accessing the device file). Successful completion of the registration will bring the minor assigned to us into interface-> minor . Subtracting from it LCD_MINOR_BASE we get the number under which the device will be visible in / dev / - because we have one display, then the minor will be LCD_MINOR_BASE, respectively / dev / lcd0 will appear in / dev. - Immediately symmetrically write the code of the Disconnect callback, getting a pointer to our usblcd descriptor via the call dev = usb_get_intfdata (interface);
Disconnect Callback Code static void LCDDisconnect(struct usb_interface *interface) { struct usblcd *dev; int minor; dev = usb_get_intfdata(interface); minor=dev->minor; usb_set_intfdata(interface, NULL); usb_deregister_dev(interface, &usblcd_class); dev->interface = NULL; kfree(dev->videobuffer); kfree(dev); dev_info(&interface->dev,"USB STM32-based LCD module lcd%d disconnected\n",minor-LCD_MINOR_BASE); }
Set the pointer to the data in the interface to NULL and deregister the driver class. From this moment on, no one will pull us out of the user space. After that, we clean all our allocated buffers and the last step is to clear the memory from under the dev structure itself. It is important to remember that any callback from LCD_fops can be called at any time, that is, if you suddenly pull out the device, any other process can currently be in the middle of an opening, reading or writing procedure. , , . « , Disconnect Open? Write?»
, , write. - , .
, .
, , . , , , , : static atomic_t DeviceFree=ATOMIC_INIT(1);
Open , Linux Device Drivers:
if(!atomic_dec_and_test(&DeviceFree)) { atomic_inc(&DeviceFree); return -EBUSY; } return 0;
atomic_dec_and_test , , (DeviceFree = 1), (DeviceFree = 0), true, .. true .
(DeviceFree = 0), DeviceFree = -1 false. (.. ) — (EBUSY, ). , , , — , .. DeviceFree -2 false. - Immediately add to Release
atomic_inc (& DeviceFree);
telling that the device is free.
Now you can test the performance of the code by compiling it and transferring it to the router.After running insmod usblcd.ko and connecting the device, dmesg should say something like: [ 9323.880000] usbcore: registered new interface driver usblcd [ 9334.640000] usb 1-1: new full-speed USB device number 4 using ehci-platform [ 9334.800000] usblcd 1-1:1.0: USB STM32-based LCD module connected as lcd0
The / dev / lcd0 device should appear in / dev /, which cannot be read from root@OpenWrt:~
And the record is possible only when someone else does not write to the device (we execute the same command in two instances of ssh): root@OpenWrt:~
We continue the implementation.- We include the header file <linux / mutex.h> , add the
struct mutex field io_mutex to the descriptor structure ;
in callback Probe - its initialization by calling mutex_init (& dev-> io_mutex); - Disconnect, -:
Disconnect static void LCDDisconnect(struct usb_interface *interface) { struct usblcd *dev; int minor; dev = usb_get_intfdata(interface); minor=dev->minor; usb_set_intfdata(interface, NULL); usb_deregister_dev(interface, &usblcd_class); mutex_lock(&dev->io_mutex); dev->interface = NULL; mutex_unlock(&dev->io_mutex); kfree(dev->videobuffer); kfree(dev); dev_info(&interface->dev,"USB STM32-based LCD module lcd%d disconnected\n",minor-LCD_MINOR_BASE); }
, , - dev->interface . , io_mutex, , ( dev->interface ), ( NULL).
- Open:
, ,
subminor = iminor(inode);
usb_interface interface = usb_find_interface(&usblcd_driver, subminor);
dev = usb_get_intfdata(interface); . , mutex_lock(&dev->io_mutex); , , .
, , LCD_fops:
filp->private_data = dev;
Open static int LCDOpen(struct inode *inode, struct file *filp) { struct usblcd *dev; struct usb_interface *interface; int retval = 0; int subminor; filp->private_data=NULL; if(!atomic_dec_and_test(&DeviceFree)) { atomic_inc(&DeviceFree); retval = -EBUSY; goto exit; } subminor = iminor(inode); interface = usb_find_interface(&usblcd_driver, subminor); if (!interface) { printk(KERN_ERR "usblcd driver error, can't find device for minor %d\n", subminor); retval = -ENODEV; goto exit; } dev = usb_get_intfdata(interface); if (!dev) { retval = -ENODEV; goto exit; } mutex_lock(&dev->io_mutex); if(!dev->interface) { retval = -ENODEV; goto unlock_exit; } filp->private_data = dev; dev_info(&interface->dev, "usblcd: opened successfuly"); unlock_exit: mutex_unlock(&dev->io_mutex); exit: return retval; }
- , , ( ) X (unsigned short), Y (unsigned short), (unsgned long). Little Endian.
Write static ssize_t LCDwrite(struct file *filp, const char __user *user_buf, size_t count, loff_t *ppos) { struct usblcd *dev; struct usb_interface *interface; int retval = -ENODEV; int usbSent; int writtenCount=count; int x,y; if(*ppos>=VB_SIZE*2) { retval = -ENOSPC; goto exit; } if(*ppos+count>VB_SIZE*2) writtenCount=VB_SIZE*2-*ppos; dev = filp->private_data; if (!dev) { printk(KERN_ERR "usblcd driver error, no device found\n"); retval = -ENODEV; goto exit; } mutex_lock(&dev->io_mutex); interface = dev->interface; if (!interface) { printk(KERN_ERR "usblcd driver error, no device found\n"); retval = -ENODEV; goto exit; } if (copy_from_user(dev->videobuffer+8, user_buf, writtenCount)) { retval = -EFAULT; goto unlock_exit; } y=((int)((*ppos)>>1)/(int)WIDTH); x=((int)((*ppos)>>1))-y*WIDTH; *(unsigned short*)(dev->videobuffer)=cpu_to_le16(x); *(unsigned short*)(dev->videobuffer+2)=cpu_to_le16(y); *(unsigned long*)(dev->videobuffer+4)=cpu_to_le32(writtenCount>>1); retval = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 1),dev->videobuffer, 8+writtenCount, &usbSent, HZ*5); if (!retval) { retval = writtenCount; *ppos+=writtenCount; } else { retval=-EIO; goto unlock_exit; } unlock_exit: mutex_unlock(&dev->io_mutex); exit: return retval; }
, — 320*240*2 . — . *ppos, . , , , dev->interface — NULL . , , ( Endian ) usb_bulk_msg .
, , , , .
, , , , .. . .
We have just a little bit left: we compile the driver (we already know how to do this, having trained on the hello world), fill it in with the router, perform insmod usblcd.ko .We make sure that the system does not crash, the device is detected upon connection, and / dev / lcd0 is created .Let's check how it went!We display random on the screen: cat /dev/urandom > /dev/lcd0
Now let's see how the system's main binary looks like - busybox: cat /bin/busybox > /dev/lcd0
Isn't it true, the difference between the executable file and the pure random file is visible?Finally, we will display some image: cat hobgoblin.raw > /dev/lcd0

Conclusion
, - , linux OpenWRT.
— , - , , . .
!
Hail to the speaker,
Hail to the knower,
Joy to him who has understood,
Delight to those who have listened.