Writing a Simple USB Driver
April 1st, 2004 by Greg Kroah-Hartman in
Since this column began, it has discussed how a Linux driver writer can create various types of kernel drivers, by explaining the different kernel driver interfaces including TTY, serial, I2C 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 to help figure out how the hardware 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 be 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 how I did it.
The first goal in trying to write a driver for a device is to determine how to control the device. Delcom Engineering is nice enough to 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 is only the documentation for the USB controller in the lamp. It does not explicitly say how to turn on the different color LEDs. For this, we have to do a bit of research.
After opening up the lamp device, making sure not to lose the spring that easily pops out when unscrewing the device, the circuit board can be inspected (Figure 2). Using an ohmmeter, or any kind of device for detecting a closed circuit, it was determined that the three different LEDs are 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 10, Minor 2, Length 0. The command writes the least significant byte of the USB command packet to port 1, and port 1 is defaulted high after reset. So, that is the USB command we need to send to the device to change the different LEDs.
Now that we know the command to enable a port pin, we need to determine which LED color is 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 port pins and then sends the value to the device. This program 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 all pins on the port are enabled (a value of 0x07 hex), no LEDs are on. This matches up with the note in the data sheet that stated, “Port 1 is defaulted high after reset.” It would make sense not to have any LEDs enabled when the device is first plugged in. This means we need to turn port pins low (off) in order to turn on the LED for that pin. Using the table, we can determine that the blue LED is controlled by pin 2, the red LED by pin 1 and the green LED by pin 0.
Armed with our new-found information, we set off to whip up a quick kernel driver. It should be a USB driver, but what kind of interface to user space should we use? A block device does not make sense, as this device does not need to store filesystem data, but a character device would work. If we use a character device driver, however, a major and minor number needs to be reserved for it. And how many minor numbers would we need for this driver? What if someone wanted to plug 100 different USB lamp devices in to this system? To anticipate this, we would need to reserve at least 100 minor numbers, which would be a total waste if all anyone ever used was one device at a time. If we make a character driver, we also would need to invent some way to tell the driver to turn on and off the different colors individually. Traditionally, that could be done using different ioctl commands on the character driver, but we know much better than ever to create a new ioctl command in the kernel.
As all USB devices show up in their own directory in the sysfs tree, so why not use sysfs and create three files in the USB device directory, blue, red and green? This would allow any user-space program, be it a C program or a shell script, to change the colors on our LED device. This also would keep us from having to write a character driver and beg for a chunk of minor numbers for our device.
To start out our USB driver, we need to provide the USB subsystem with five things:
A pointer to the module owner of this 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 is used by the USB core to determine which driver should be matched up to which device; the hot-plug user-space scripts use it to load that driver automatically when a device is plugged in to the system.
A probe() function called by the USB core when a device is found that matches the USB ID table.
A disconnect() function called when the device is 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 is loaded, this led_driver structure must be 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 is 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 has found our USB lamp device. All it needs to do is initialize the device and create the three sysfs files, in the proper location. This is done with the following code:
/* Initialize our local device structure */
dev = kmalloc(sizeof(struct usb_led), GFP_KERNEL);
memset (dev, 0x00, sizeof (*dev));
dev->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);
device_create_file(&interface->dev, &dev_attr_red);
device_create_file(&interface->dev, &dev_attr_green);
dev_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 files:
dev = usb_get_intfdata (interface);
usb_set_intfdata (interface, NULL);
device_remove_file(&interface->dev, &dev_attr_blue);
device_remove_file(&interface->dev, &dev_attr_red);
device_remove_file(&interface->dev, &dev_attr_green);
usb_put_dev(dev->udev);
kfree(dev);
dev_info(&interface->dev,
"USB LED now disconnected\n");
When the sysfs files are read from, we want to show the current value of that LED; when it is written to, we want to set that specific LED. To do this, the following macro creates two functions for each color LED and declares a sysfs device attribute file:
#define show_set(value) \
static ssize_t \
show_##value(struct device *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 that we need to do the same thing for every different value (blue, red and green), a macro was used to reduce typing. This is a common occurrence for sysfs file functions; an example of this in the kernel source tree is the I2C chip drivers in drivers/i2c/chips.
So, to enable the red LED, a user writes a 1 to the red file in sysfs, which calls the set_red() function in the driver, which calls the change_color() function. The change_color() function looks like:
#define BLUE 0x04
#define RED 0x02
#define GREEN 0x01
buffer = kmalloc(8, GFP_KERNEL);
color = 0x07;
if (led->blue)
color &= ~(BLUE);
if (led->red)
color &= ~(RED);
if (led->green)
color &= ~(GREEN);
retval =
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 any LEDs are to be enabled, it turns off only that specific bit. We then send a USB control message to the device to write that color value to the device.
It first seems odd that the tiny buffer variable, which is only 8-bytes long, is created with a call to kmalloc. Why not simply declare it on the stack and skip the overhead of dynamically allocating and then destroying it? This is done because some architectures that run Linux cannot send USB data created on the kernel stack, so all data that is to be sent to a USB device must be created dynamically.
With this kernel driver created, built and loaded, when the USB lamp device is plugged in, the driver is bound to it. All USB devices bound to this driver can be 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 that directory is a symlink back to the real location in the sysfs tree for that USB device. If we look into that directory we can see the files the driver has 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 blue 0 0 0 $ echo 1 > red [greg@duel 4-1.4:1.0]$ echo 1 > blue [greg@duel 4-1.4:1.0]$ cat green red blue 0 1 1
Now that we have created a simple kernel driver for this device, which can be seen in the 2.6 kernel tree at drivers/usb/misc/usbled.c or on the Linux Journal FTP site at (ftp.ssc.com/pub/lj/listings/issue120/7353.tgz), is this really the best way to talk to the device? What about using something like usbfs or libusb to control the device from user space without any special device drivers? In my next column, I will show how to do this and provide some shell scripts to control the USB lamp devices plugged in to 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 write 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 have never gotten finished.
Special Magazine Offer -- 2 Free Trial Issues!
Receive 2 free trial issues of Linux Journal as well as instant online access to current and past issues. There's NO RISK and NO OBLIGATION to buy. CLICK HERE for offer
Linux Journal: delivering readers the advice and inspiration they need to get the most out of their Linux systems since 1994.
Sorry, offer available in the US only. International orders, click here.
Subscribe now!
Recently Popular
| Linux HOWTO: Video Editing Magic with ffmpeg | Jul-23-08 |
| Man vs. Myth: Greg Kroah-Hartman and the Kernel Driver Project | Jul-21-08 |
| Google Gadgets for Linux | Jul-21-08 |
| Building a Call Center with LTSP and Soft Phones | Aug-25-05 |
| Review: HP 2133 Mini-Note | Jul-16-08 |
| Boot with GRUB | May-01-01 |
Featured Videos
Non-linear video editing tools are great, but they're not always the best tool for the job. This is where a powerful tool like ffmpeg becomes useful. This tutorial by Elliot Isaacson covers the basics of transcoding video, as well as more advanced tricks like creating animations, screen captures, and slow motion effects.
Shawn Powers reviews the HP Mini-Note portable computer.
Thanks to our sponsor: Silicon Mechanics
Silicon Mechanics is a leading manufacturer of rackmount servers, storage, and high performance computing hardware. The best warranty offerings available are backed by experts dedicated to customer satisfaction.
From the Magazine
August 2008, #172
There's nuttin like a Cool Project to give you some relief from the summer heat, so get out your parka cuz we got a bunch of em. First up is the BUG, not a bug, The BUG. It's got a GPS, camera and more, in a hand-sized package that's user programmable. The BUG does everything. It's both a floor wax and a dessert topping. Get one now. Need a software version of a Swiss Army knife? Take a look at Billix, and don't leave home without it. Then, chew on this one, an X server on a Gumstix device driving an E-Ink display. Need more storage? How about 16 Terabytes? Can do.
And, of course, we have the usual cast of characters: Marcel, Reuven, Dave, Kyle, Doc, plus the new kid on the block Shawn Powers. But it doesn't stop there: build a MythTV box on a budget, build your own GIS system, set up the tools to monitor your enterprise and more. Finally, remember The War of the Worlds? Now you can play too.



Delicious
Digg
Reddit
Newsvine
Technorati







ThinkGeek USB Rocket Launcher
On February 19th, 2008 Anonymous (not verified) says:
You should do a Linux device driver for the USB Rocket Launcher:
http://www.thinkgeek.com/geektoys/warfare/8a0f/
USB dirver
On December 27th, 2007 Anonymous (not verified) says:
Hey was wondering which would be the best method for writing a USB game control driver e.g. Would you make the driver listen for when a button is pressed or would you write the driver and then a program in say C to retrieve the current state of the button?
im wrting a driver programm..Help me...
On June 4th, 2007 Vadivelu N (not verified) says:
Im writing a driver program for transfering data's between PC and my SWITCH(Its an telcom equipment)in Linux platform.Getting cofused in "how to program for send and receive data's b/w PC and my switch.Please help me to get clear idea about that.
USB driver for MPEG 4
On May 24th, 2007 Sid (not verified) says:
Hi I want to write a driver for USB streaming of MPEG 4 Video. I am not sure how and where to start. I have MPEG 4 video capture and the video is saved as a file onto the system. How should I start writing the driver for streaming the video instead of saving it in the system?
Doing it
On July 24th, 2007 Renderman (not verified) says:
Their are a few ways to do this. The hardest way,would be to write a "pure driver". To do that, you would have to first know how to the USB HID's Subclass and the exact paramters on using it. Also, if you told it to save the data to the device, and then later retrive it, it would deffinitly reduc bottlenecks. There is lots more and the hardest part would have to do with the audio. The easiest way to do it,is with Java. Get the Windows Media SDK, use Java to interface with the device. If you look at some documentation for the WMSDK, it should tell you all the video paramters and how to set it up. There should be a HID protocol for streaming data between devices maybe MTP, I am not fully sure on the semantics,but that should help you.
USB cable to work as a communication medium
On April 6th, 2007 Peyman (not verified) says:
How can I write a driver for a simple two headed USB cable that enables it to work as a communication medium? The only thing I want it to do is to be able to send and receive data from one machine to/from another, and they're connected to each other using this USB cable. /*the cable is a wire, with two USB plugs on both ends*/I appreciate any pointer to a reference or help.
error when i passs command line argument
On March 13th, 2007 syed (not verified) says:
hi,
when i pass this argument then this error appear.
plz help me.
[root@localhost 4-1:1.0]# echo '1' > blue
bash: echo: write error: Success
thnx
Write Error
On June 30th, 2008 Anonymous (not verified) says:
I get the same write error. Did you find a resolution?
try passing it without the
On August 7th, 2007 Anonymous (not verified) says:
try passing it without the '' like echo 1 > blue
try passing it without the
On August 7th, 2007 Anonymous (not verified) says:
try passing it without the '' like echo 1 > blue
Help needed
On September 27th, 2006 hssiddhu says:
Hi Greg,
I saw your posts, its very nice and i got a lot of knowledge from them. Can you do me a favour? I'm writing a host side USB driver for ucos. Can you help me how to start....
It should be like it should check for the vendor id and for product id then it should load the driver. It should also have routines for endpoint creations. If you have any idea on this just mail to the below address
hssiddhu@yahoo.com
Thanks in advance
Siddhu
i'm writing a kernel driver for usb on arm linux-2.6.20
On March 5th, 2007 Anonymous (not verified) says:
Hai
I'm pavan. I'm writing the kernel driver for usb.All the code is available on the kernel and i enabled the relavent features in the menuconfig .when i insert the following modules ,usbcore.ko,hid.ko,usbhid.ko,usb-stotage.ko and ohci-hcd.ko.Its giving the messages that
insmod usbcore.ko
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
insmod usbhid.ko
usbcore: registered new interface driver usbhid
drivers/usb/input/hid-core.c: v2.6:USB HID core driver
insmod usb-storage.ko
Initializing USB Mass Storage driver...
usbcore: registered new interface driver usb-storage
USB Mass Storage support registered.
insmod ohci-hcd.ko
probe in platform.c is working<7>In ohci_hcd_pxa27x_drv_probe
in last module i.e ohci-hcd.ko ,its terminating by -ENODEV this i got by printk. i hope the usb is not detecting the devices ,inorder to detect devices what changes i have to made in the kenel.
i'm struck from 3days to debug this problem ,please if anyone have idea about this please share with me.
with regards
pavan
i'm writing a kernel driver for usb on arm linux-2.6.20
On March 5th, 2007 pavan (not verified) says:
Hai
I'm writing the kernel driver for usb.All the code is available on the kernel and i enabled the relavent features in the menuconfig .when i insert the following modules ,usbcore.ko,hid.ko,usbhid.ko,usb-stotage.ko and ohci-hcd.ko.Its giving the messages that
insmod usbcore.ko
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
insmod usbhid.ko
usbcore: registered new interface driver usbhid
drivers/usb/input/hid-core.c: v2.6:USB HID core driver
insmod usb-storage.ko
Initializing USB Mass Storage driver...
usbcore: registered new interface driver usb-storage
USB Mass Storage support registered.
insmod ohci-hcd.ko
probe in platform.c is working<7>In ohci_hcd_pxa27x_drv_probe
in last module i.e ohci-hcd.ko ,its terminating by -ENODEV this i got by printk. i hope the usb is not detecting the devices ,inorder to detect devices what changes i have to made in the kenel.
please if anyone have idea about this please share with me.
Thanks
i'm writing a kernel driver for usb on arm linux-2.6.20
On March 5th, 2007 pavan (not verified) says:
Hai
I'm pavan. I'm writing the kernel driver for usb.All the code is available on the kernel and i enabled the relavent features in the menuconfig .when i insert the following modules ,usbcore.ko,hid.ko,usbhid.ko,usb-stotage.ko and ohci-hcd.ko.Its giving the messages that
insmod usbcore.ko
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
insmod usbhid.ko
usbcore: registered new interface driver usbhid
drivers/usb/input/hid-core.c: v2.6:USB HID core driver
insmod usb-storage.ko
Initializing USB Mass Storage driver...
usbcore: registered new interface driver usb-storage
USB Mass Storage support registered.
insmod ohci-hcd.ko
probe in platform.c is working<7>In ohci_hcd_pxa27x_drv_probe
in last module i.e ohci-hcd.ko ,its terminating by -ENODEV this i got by printk. i hope the usb is not detecting the devices ,inorder to detect devices what changes i have to made in the kenel.
i'm struck from 3days to debug this problem ,please if anyone have idea about this please share with me.
with regards
pavan
usbreplay
On March 25th, 2006 Markus Rechberger (not verified) says:
http://linuxtv.org/v4lwiki/index.php/USBVideo
please have a look at usbreplay and co. these tools might ease up everything quite alot
Now Delcom has variable intensity LED lights?
On April 20th, 2004 Anonymous says:
Looks like their latest products allow setting brightness per color?
Also looks like they cost around eighty bucks?!?
Re: Writing a Simple USB Driver
On March 30th, 2004 phonghtn (not verified) says:
Hi there,
I am planing to write a virtual device which use USB port to talk with the computer. So, my idea is I want to developt a virtual device connect with the PC through USB port. I mean, if you have an usb lamp and you begin write a drive for it. You have a specification of the lamp. And you plug the lamp to the pc. In my case, i want to build my lamp by the software with all properties like a real lamp. So, my first problem is i don't know how to write a virtual device run on both Linux and Windows. So any idea help me now.
Thanks a lot.
Phong
Re: Writing a Simple USB Driver
On March 29th, 2004 Anonymous says:
Greg:
Excellent article. I went with the Delcom "USB Numeric Display", which is a numeric counter.
I noticed something on my system. When I create device files under the /sys/bus/usb/drivers/mydevice directory, and send data to it with the echo command, I can hear the disk access on my computer... It's really getting hit with a lot of data; Perl script that writes lots of changing data to the USB device. Is this normal? I'm making changes in my program so that data goes to the device from a "/proc/mydevice/entries" instead, since "/proc" seems to be mounted in ram vs. /sys on the hard drive.
I'm running 2.6.4 and /sys was mounted with "sysfs /sys sysfs defaults 0 0" in my /etc/fstab. Here's a link to how I setup 2.6
http://osdn.dl.sourceforge.net/sourceforge/souptonuts/README_26.txt
Its seems problematic if new directory trees created under /sys/bus... for input will require disk reads and writes. Maybe I'm overlooking something?
Regards,
Mike Chirico
Re: Writing a Simple USB Driver
On April 6th, 2004 Anonymous says:
I stand corrected.... I had the debug commands "dev_dbg" writing to the log files. Ok, good. I'm on my way.
Regards,
Mike Chirico
Re: Writing a Simple USB Driver
On April 9th, 2004 mchirico (not verified) says:
Here's the code if interested
code
Regards,
Mike Chirico
Re: Writing a Simple USB Driver
On March 26th, 2004 Anonymous says:
Great article. I'd like see an article on creating your own USB device and then creating a kernel driver for it.
Re: Writing a Simple USB Driver
On March 29th, 2004 Anonymous says:
Very good article, in this case, you writing a simple driver for a Led lamp. But I am researching now a virtual program simulate a led lamp and I am not found any document for this.
Re: Writing a Simple USB Driver
On March 11th, 2004 Anonymous says:
Looks very cool -- thanks for the article, Greg. I've ordered one of these already to set up a weather-forcast indicator to be installed by my front door.
It looks from the documentation that the device has a programmable flash rate -- it'd be cool to see support for that in the driver. And maybe the buzzer, although I don't think I have a use for that.