Writing a simple USB Driver
From Issue #120
April 2004
APR, 2004 by Greg Kroah-hartman in
Give your Linux box A multicolored light can see from across the class, and learn how to write a simple driver for the Next piece of hardware you want to hooks up.
Since This column began, it had discussed how a Linux driver writer can create various types of kernel drivers, by explain ing the different kernel driver interfaces including TTY, serial, i²c and the driver core. It is time-to-move on now and focus on writing real drivers for real hardware. We start by explaining how to determine what kind of kernel driver interface to use, tricks Dware actually works and a lot of other real-world knowledge.
Let's begin with a-goal of making a simple USB lamp device work-well with Linux. Editor Don Marti pointed out a neat device, the USB Visual Signal Indicator, manufactured by Delcom Engineering and shown In Figure 1. I have no relationship with this company; I just think they make nice products. This device can is ordered on-line from the delcom Web site, www.delcom-eng.com. Don challenged me to get the device working on Linux, and this article explains what I did it.
Figure 1. Delcom ' s USB Visual Signal Indicator is a simple first USB programming project.
The Hardware Protocol
The first goal in trying to write a driver for a device are to determine how to control the device. Delcom Engineering is nice enough-ship the entire USB protocol specification their devices use with the product, and it Also is available on-line for free. This documentation shows "what commands" the USB controller chip accepts and how to use them. They also provide a Microsoft Windows DLL to help users of other operating systems write code to control the device.
The documentation for this device was only the documentation for the USB controller in the lamp. It does not explicitly say what to turn on the different color LEDs. For this, we had to do a bit of the.
No Docs? Reverse Engineer It!
If The USB protocol for this device had not been documented or available to me, I would has had to reverse engineer th Is information from the device itself. A handy tool for this kind of work are a free program called USB snoopy,www.wingmanteam.com/usbsnoopy; Another version of it is snoopypro,usbsnoop.sourceforge.net. These programs is both Windows programs, allow users to capture the USB data, is sent to and received from any US B device on a Windows system. All a developer needs to do are find a Windows machine, install the Windows driver provided by the manufacturer for the Dev Ice and run the Snoop program. The data is captured to a file to be analyzed later. Perl scripts can help filter some of the extra noise in the output of these snoop programs into a easier format to unders Tand.
Another method a few people has used to reverse engineer the USB protocol of a device are to run a Windows instance using VMware on top of Linux. VMware enables the Windows instance-to-talk-to-all of the USB devices plugged-to-the-Linux machine by sending data to L Inux though the USBFS. A simple modification to the USBFS causes all data flowing though it to is logged to the kernel log. Using this, the full USB traffic stream can be captured and later analyzed.
After opening up the lamp device, making sure not to lose the spring of the easily pops out when unscrewing the device, the Circuit board can be inspected (Figure 2). Using a ohmmeter, or any kind of device for detecting a closed circuit, it is determined that the three different LEDs a Re connected to the first three pins of Port 1 on the main controller chip.
In reading the documentation, the USB command to control the levels of the port 1 pins is Major, Minor 2, Lengt H 0. The command writes the least significant byte of the USB command packet to Port 1, and Port 1 are defaulted high after Rese T. So, which is the USB command we need to send to the device to change the different LEDs.
Figure 2. The three LEDs is connected to the first three pins of the controller chip.
Which LED is Which?
Now this we know the command to enable a port pin, we need to determine which LED color are connected to which pin. This is easy-to-do with a simple program, that runs through all possible combinations of different values for the three por T pins and then sends the value to the device. This program is enabled me to create a table of values and LED colors (table 1).
Table 1. Port Values and the resulting LED Patterns
Port value in hex |
Port value in binary |
LEDs | on
0x00 |
000 |
Red, Green, Blue |
0x01 |
001 |
Red, Blue |
0x02 |
010 |
Green, Blue |
0x03 |
011 |
Blue |
0x04 |
100 |
Red, Green |
0x05 |
101 |
Red |
0x06 |
110 |
Green |
0x07 |
111 |
No LEDs on |
So, if any pins on the port is enabled (a value of 0x07 hex), no LEDs is on. This matches to the note in the data sheet that stated, "Port 1 was defaulted high after reset." It would make sense isn't to has any LEDs enabled while the device is first plugged in. This means we need to turn port pins low (off) in order to turn on the LEDs for that pin. Using The table, we can determine that the blue led are controlled by pin 2, the red led by pin 1 and the green led by pin 0.
A Kernel Driver
Armed with our New-found information, we set off to whip up a quick kernel driver. It should is a USB driver, but what is kind of interface to the user space should we use?a block device does do sense, as this device does not need to store filesystem data, but a character device Wou LD Work. If we use a character device driver, however, a major and minor number needs-to is reserved for it. And how many minor numbers would we need for this driver? What if the someone wanted to plug the different USB lamp devices in to the this system? To anticipate this, we would need to reserve at least minor numbers, which would is a total waste if all anyone ever u SED is one device at a time. If we make a character-driver, we also would need to invent some-to-tell the driver-turn on and off the different C Olors individually. Traditionally, that could is done using different IOCTL commands on the character driver, but we know much better than Eve R to create a new IOCTL command in the kernel.
As all USB devices show on their own directory in the Sysfs tree, "so" not "use Sysfs and" create three files in the US B device directory, blue, red and green? This would allow any User-space program, is it a C program or a shell script with the to change the colors on our LED device. This also would keep us from have to write a character driver and beg for a chunk of minor numbers for our device.
To start out of our USB driver, we need to provide the USB subsystem with five things:
A pointer to the module owner's driver:this allows the USB core to control the module reference count of the driver properly.
The name of the USB driver.
A List of the USB IDs this driver should provide:this table are used by the USB core to determine which driver should be M Atched up to which device; The Hot-plug user-space scripts use it to load this driver automatically when a device was plugged in to the system.
A probe () function called by the USB core while a device is found that matches the USB ID table.
A Disconnect () function called when the device was removed from the system.
The driver retrieves this information with the following bit of code:
static struct Usb_driver Led_driver = {. Owner =this_module,.name = "Usbled",. Probe =led_probe,.disconnect =led_ Disconnect,. Id_table =id_table,};
The id_table variable is defined as:
static struct usb_device_id id_table [] = {{Usb_device (vendor_id, product_id)},{},}; module_device_table (USB, id_table);
The Led_probe () and Led_disconnect () functions are described later.
When the driver module was loaded, this led_driver structure must was registered with the USB core. This is accomplished with a single call to the Usb_register () function:
retval = Usb_register (&led_driver); if (retval) err ("Usb_register failed." " Error number%d ", retval);
Likewise, when the driver was unloaded from the system, it must unregister itself from the USB core:
Usb_deregister (&led_driver);
The Led_probe () function is called when the USB core have found our USB lamp device. All it needs to do are initialize the device and create the three SYSFS files, in the proper location. This is do with the following code:
/* Initialize Our local device structure */dev = kmalloc (sizeof (struct usb_led), gfp_kernel); memset (Dev, 0x00, sizeof (*d EV));d Ev->udev = Usb_get_dev (Udev), Usb_set_intfdata (interface, dev);/* Create Our three sysfs files in the usb* device Directory */device_create_file (&interface->dev, &dev_attr_blue);d Evice_create_file (&interface- >dev, &dev_attr_red);d evice_create_file (&interface->dev, &dev_attr_green);d Ev_info (& Interface->dev, "USB LED device now attached\n"); return 0;
The Led_disconnect () function is equally as simple, as we need-only to free our allocated memory and remove the Sysfs file S
dev = Usb_get_intfdata (interface), Usb_set_intfdata (interface, NULL);d evice_remove_file (&interface->dev, &dev_attr_blue);d evice_remove_file (&interface->dev, &dev_attr_red);d evice_remove_file (& Interface->dev, &dev_attr_green), Usb_put_dev (Dev->udev), Kfree (Dev);d ev_info (&interface->dev, "USB LED now disconnected\n");
This place is for the function to be written in the set too TM Classic
When the Sysfs files is read from, we want to show the current value of this LED; When it was written to, we want to set that specific LED. To does this, the following macro creates the functions for each color LED and declares a SYSFS device attribute file:
#define SHOW_SET (value) static ssize_t show_# #value (struct D Evice *dev, char *buf) {struct Usb_interface *intf = To_usb_interface (Dev); struct usb_led *led = Usb_get_intfdata (intf); Return sprintf (buf, "%d\n", Led->value); } Static ssize_t set_# #value (struct device *dev, const char *BUF, size_t count) {struct Usb_interface *intf = to_ Usb_interface (Dev); struct usb_led *led = Usb_get_intfdata (intf); int temp = Simple_strtoul (buf, NULL, 10); Led->value = temp; Change_color (LED); return count; } static device_attr (value, S_iwugo | S_irugo, show_# #value, set_# #value); Show_set (blue); Show_set (red); Show_set (green);
This creates six functions, Show_blue (), Set_blue (), show_red (), set_red (), Show_green () and Set_green (); and three attribute structures, Dev_attr_blue, dev_attr_red and Dev_attr_green. Due to the simple nature of the Sysfs file callbacks and the fact, we need to does the same thing for every different VA Lue (blue, red and green), a macro was used to reduce typing. This was a common occurrence for SYSFS file functions; An example of the kernel source tree is the I²C chip drivers in drivers/i2c/chips.
So, to enable the red LEDs, a user writes a 1 to the red file in Sysfs, which calls the set_red () function in the driver, W Hich calls the Change_color () function. The Change_color () function looks like:
#define Blue0x04#define red0x02#define green0x01 buffer = Kmalloc (8, gfp_kernel); color = 0x07; if (led->blue) color &= ~ (blue); if (led->red) color &= ~ (red); if (led->green) color &= ~ (green); retval = //Not through URB, but through usb_control_msg
Usb_control_msg (Led->udev, Usb_sndctrlpipe (Led->udev, 0),
0x12, 0xc8, (0x02 * 0x100) + 0x0a,
(0x00 * 0x100) + color, buffer, 8, 2 * HZ);
Kfree (buffer);
This function starts out by setting all bits in the variable color to 1. Then, if the any LEDs is to be enabled, it turns off is only that specific bit. We then send a USB control message to the device to write this color value to the device.
It first seems odd that the tiny buffer variable, which are only 8-bytes long, was created with a call to Kmalloc. Why isn't simply declare it on the stack and skip the overhead of dynamically allocating and then destroying it? This is do because some architectures that run Linux cannot send USB data created on the kernel stack, so all data is to being sent to a USB device must be created dynamically.
LEDs in Action
With this kernel driver created, built and loaded, when the USB lamp device was plugged in, the driver was bound to it. All USB devices bound to this driver can is found in the SYSFS directory for the driver:
$ Tree/sys/bus/usb/drivers/usbled//sys/bus/usb/drivers/usbled/'--4-1.4:1.0-> /.. /.. /.. /devices/pci0000:00/0000:00:0d.0/usb4/4-1/4-1.4/4-1.4:1.0
The file in this directory is a symlink back to the real location of the Sysfs tree for that USB device. If we look into the. Directory we can see the files the driver have created for the LEDs:
$ tree/sys/bus/usb/drivers/usbled/4-1.4:1.0//sys/bus/usb/drivers/usbled/4-1.4:1.0/|--bAlternateSetting|-- binterfaceclass|--binterfacenumber|--binterfaceprotocol|--binterfacesubclass|--bNumEndpoints|--Blue| -Detach_state|--Green|--iinterface|--power| '--State'--Red
Then, by writing either 0 or 1 to the blue, green and red files in that directory, the LEDs change color:
$ cd/sys/bus/usb/drivers/usbled/4-1.4:1.0/$ Cat Green Red blue000$ echo 1 > Red[[email protected] 4-1.4:1.0]$ Echo 1 & Gt Blue[[email protected] 4-1.4:1.0]$ cat Green Red blue011
This produces the color shown in Figure 3.
Figure 3. The Device with the Red and Blue LEDs on
Is there a Better?
Now there is created a simple kernel driver for this device, which can is seen in the 2.6 kernel tree at drivers/usb/m ISC/USBLED.C or on the Linux Journal FTP site at (ftp.linuxjournal.com/pub/lj/listings/issue120/7353.tgz), is th is really-the best-of-the-talk-to-the-device? What is the using something like USBFS or LIBUSB to control the device from user space without any special device drivers? In my next column, I'll show how to does this and provide some shell scripts to control the USB lamp devices plugged The system easily.
If you would like to see kernel drivers written for any other types of devices, within Reason-i ' m not going to try to writ E an NVIDIA video card driver from scratch-please let me know.
Thanks to Don Marti for bugging me to get this device working on Linux. Without his prodding it would has never gotten finished.
Writing a USB driver (copied on a foreign website)