Linux-based USB Devices
Bill Gatliff
12/17/2002 4:29 PM EST
The Universal Serial Bus is a useful communications interface and more popular than ever. Here are three approaches to adding USB support to an embedded system running the Linux operating system
Linux-based USB devices use one of three different strategies for communicating with USB hosts.
The most ambitious and complicated devices use custom-written kernel modules to implement sophisticated, high-level protocols that run over the standard USB bus. A corresponding custom driver and applications for the USB host complete the connection.
Other Linux-based USB devices use the bus as a simple, point-to-point serial connection to an application running on the host. The host application uses the USB programming interface provided with the host operating system, but otherwise behaves as though it were communicating through a typical serial port.
Finally, some devices make USB look like an Ethernet network; they connect the USB device to an interoffice LAN or the Internet using the host computer as a gateway. Specialized, but generally available, host drivers make this possible.
The correct approach for your application depends on how much time you want to spend in development and what you want the USB interface to look like to your embedded application. To help you make the right decision, the following sections describe each of the three approaches as they apply to Linux-based USB devices. But first, a more general introduction to the subject.
USB 101
The Universal Serial Bus (USB) is a fast and flexible interface for connecting gadgets to computer workstations. The USB specification allows devices like mice and keyboards, audio playback and recording hardware, cameras, mass storage devices, and many others to connect to a host computer at speeds of up to 480Mbps. Careful documentation of the sophisticated, master-slave protocol used on the USB helps to assure interoperability and compatibility between all these devices. For example, the protocol dictates that USB devices speak only when spoken to and that USB hosts will request data from USB devices using certain formats at specific intervals depending on the type of device.
USB connections are established through dedicated bus controller chips. On USB hosts, controller chips with names like UHCI and OHCI are provided as add-on cards or integrated onto the workstation's motherboard. A host-side bus controller driver manages the host controller chip and keeps track of which USB devices are connected to the bus and how to communicate with them.
Bus controllers come in a variety of forms for USB devices like cameras and mice. They include standalone chips that have USB on one side and a serial, I2C, or parallel interface on the other. USB controllers (both host and device versions) can also be found integrated into microcontrollers like the Intel StrongARM and the Hitachi H8. These chips and peripheral components are like Ethernet and CAN controllers, but for USB media and protocols.
Most people know that the Linux operating system contains drivers for USB host controllers, making USB keyboards, cameras, and other devices usable on desktop workstations that run Linux. Few seem to know that Linux also includes a handful of drivers for USB device controllers, in particular for the controller integrated into the StrongARM SA1110 processor. These controller drivers enable Linux-based embedded systems to use USB to communicate with a host computer (which may or may not be running Linux as well).
In most USB implementations, the communication process is two-sided. The host uses a kernel module or driver to communicate with the USB device, and the device uses its own driver for communicating with the host. Depending on the style of communications used by the host and device (there are several to choose from), the drivers can vary from plain and simple to complex and challenging. This article is mostly concerned with the device side of the communications process, but includes information for the host side of the implementation where appropriate.
Pay careful attention to the terminology in the following discussion. This article is about how to use Linux on USB devices like Linux-based cameras and PDAs. When I use the term USB device, I mean it in the strict USB sense, complete with the square connector rather than the flatter rectangular one. The other end of the connection, which is usually your PC workstation, is the USB host.
For more detailed information about USB packet formats and communication parameters, consult the references at the end of this article.
Kernel modules
The first and most ambitious approach to adding USB to a Linux-based device is to write a custom Linux kernel module. This approach usually requires development of a corresponding driver for the host operating system (Windows, Linux, or what have you) as well.
By implementing a custom kernel module in the device, you can do rather sophisticated things like emulate a filesystem to allow the embedded application to treat its USB host as a remote storage device. Another potential use for this approach is to implement a store-and-forward character device that buffers data streaming from the embedded application until the USB host connection is established.
For StrongARM-based Linux devices, a kernel module that uses USB calls sa1100_usb_open() to initialize kernel code that manages the chip's onboard USB device controller peripheral. The module then invokes sa1100_usb_get_descriptor_ptr() and sa1100_usb_set_string_descriptor() to set the USB descriptors given to a USB host during enumeration. These descriptors include the device's numeric vendor and product identifiers, and text strings that the host can use to recognize the device. There is even a serial number field, so that the host can uniquely identify a device or distinguish between multiple instances of the same device on USB.
A kernel module must set up the USB descriptors before beginning USB communications, because the enumeration process is driven by the USB device controller and proceeds automatically once a USB host connects. When everything is ready to go, the USB device module calls sa1100_usb_start() to tell the kernel to accept USB connection requests from a host. If the module calls sa1100_set_configured_callback() before a USB host connects, then the kernel will invoke the supplied callback function at the end of the enumeration process. The callback function is a good place to beep or provide visual indication on the device that a connection has been established.
If USB communications are no longer desired, the device's kernel module calls sa1100_usb_stop(), then sa1100_usb_close(), to shut down the SA1100's USB controller.
The StrongARM USB controller supports bulk-in and bulk-out transactions for data transfer. To receive a data packet from the USB host, a kernel module calls sa1100_usb_recv(), passing it the address of a data buffer and a callback function. The kernel's underlying USB-device control code will then retrieve a bulk-out packet from the host, place the contents into the buffer, and invoke the callback.
The callback function should extract the data from the receive buffer and save it somewhere else or add the buffer space to a queue and allocate a new buffer for reception of the next packet. The callback then reinvokes sa1100_usb_recv() to enable reception of another packet, if another is expected.
The process is similar for data transmission to the USB host. After gathering up a frame's worth of data, the kernel module passes the address of the data, its length, and the address of a callback to sa1100_usb_send(). When transmission is complete, the kernel invokes the callback function.
A good example of a device-side Linux USB module for the SA1110 is called usb-char (located at ftp://ftp.embedded.com/pub/2003/01gatliff.zip under the name arch/arm/mach-sa1100/usb-char.c). This module turns the USB connection to the host into a high-speed serial link. Another good example is in the usb-eth module (arch/arm/mach-sa1100/usb-eth.c), which turns USB into an emulated Ethernet-style network. We'll look at both of these modules in some detail in just a moment.
On the host
Several good examples of host-side USB drivers are provided with the mainstream Linux distributions and in the raw kernel sources distributed from The Linux Kernel Archives (kernel.org). The module for the Handspring Visor (drivers/usb/serial/visor.c) is one of the more clearly written and easily-understood ones, as is the template for USB host-side modules (drivers/usb/usb-skeleton.c).
High-speed serial
For most practical purposes, a USB bus can be thought of as just a high speed serial port. It makes sense, then, to model it as such in some types of embedded devices and applications. The Linux kernel for StrongARM processors provides a ready-made USB device driver that does exactly that, called usb-char.
When communications with a USB host are desired, the Linux USB device application simply opens a connection to its usb-char device node (type character, major number 10, minor 240), then starts reading and writing data. The read() and write() operations will return an error until the USB host connects. Once the connection is established and enumeration is complete, communications proceeds as though USB were a point-to-point serial port.
Because this approach to USB data transfer is so straightforward and functional, the usb-char device is highly useful as provided. It also makes a great baseline for implementing other approaches to USB communications.
The real action in usb-char begins in the usbc_open() function, a portion of which is shown in Listing 1. In the interest of brevity I have edited the code a bit, and removed the error and timeout handlers. My apologies to Brad Parker, Nicolas Pitre, and Ward Willats, the original authors of this code.
Listing 1: Opening a serial connection over USB
static int usbc_open(struct inode *pInode, struct file *pFile)
{
int retval = 0;
/* start usb core */ sa1100_usb_open("usb-char");
/* allocate memory for in-transit USB packets */ tx_buf = (char*) kmalloc(TX_PACKET_SIZE, GFP_KERNEL | GFP_DMA); packet_buffer = (char*) kmalloc(RX_PACKET_SIZE, GFP_KERNEL | GFP_DMA);
/* allocate memory for the receive buffer; the contents of this buffer are provided during read() */ rx_ring.buf = (char*) kmalloc(RBUF_SIZE, GFP_KERNEL);
/* set up USB descriptors */ twiddle_descriptors();
/* enable USB i/o */ sa1100_usb_start();
/* set up to receive a packet */ kick_start_rx(); return 0; }
The twiddle_descriptors() function sets up the device's USB descriptors. With the descriptors all set, we're ready to enumerate and receive a frame of data from the USB host. The code for kick_start_rx() is mostly just a call to sa1100_usb_recv() to set up the callback.
When the USB host sends a packet of data, the device's kernel invokes the function rx_done_callback_packet_buffer() via callback, which moves the contents of the packet into a FIFO queue to be returned via read() on the usb-char device node.
On the host
For USB hosts running Linux, the corresponding USB host module for usb-char is called usbserial. The usbserial module is included with most Linux distributions, although it is not usually loaded automatically. Load usbserial with modprobe or insmod before the USB connection with the device is established.
Once the USB device is enumerated, an application on the host communicates with the device using one of the usbserial device nodes (character, major 188, minor 0 and up). These nodes are usually named /dev/ttyUSBn. The usbserial module reports which node it assigns to a USB device in the kernel message log:
usbserial.c: Generic converter detected
usbserial.c: Generic converter now attached to ttyUSB0
Once the connection is established, an application on the USB host communicates with a USB device by reading and writing to the specified node.
At the moment, I'm not aware of an existing usbserial-workalike for Win32 or other hosts. However, any example USB driver for these hosts that can do bulk-in and bulk-out transfers is probably close to complete, needing only tweaks to the product and vendor IDs that it will bind to.
An alternative to the usbserial module on Linux hosts is a library called libusb (libusb.sourceforge.net). This library uses low-level kernel system calls to perform USB data transfers, instead of going though the usbserial module, which makes it somewhat easier to set up and use across Linux kernel versions. The libusb library also provides lots of useful debugging capabilities, which can be helpful when troubleshooting a complicated communications protocol running over a USB link.
To use libusb to communicate with a USB device that uses usb-char, the Linux host application establishes a connection with the device using libusb's usb_open() function. The application then uses usb_bulk_read() and usb_bulk_write() to exchange data with the device. Several example programs are included with libusb.
Ethernet over USB
If using a USB link as a high-speed serial port isn't what you want, another alternative is to treat USB as an Ethernet network. Linux has modules to implement both the host and device sides of this capability. The Linux kernel for the iPAQ uses this communications strategy exclusively, since the iPAQ hardware has neither an accessible serial port nor a dedicated network interface.
In the StrongARM Linux kernel, the usb-eth module (arch/arm/mach-sa1100/usb-eth.c) emulates an imaginary Ethernet device that uses USB as the physical media. Once created, this network interface can be assigned an IP address and otherwise treated as though it were ordinary Ethernet hardware. Once the USB host connects, the usb-eth module allows the USB device to "see" the Internet (if the Internet is there), ping other IP addresses, and even "talk" DHCP, HTTP, NFS, telnet, and e-mail. In short, any applications that work over real Ethernet interfaces will work over a usb-eth interface without modification, because they can't tell that they aren't using real Ethernet hardware.
On the host
On Linux hosts, the corresponding Ethernet-over-USB kernel module is called usbnet. When the usbnet module is installed and the USB connection to the device is established, the usbnet module creates an imaginary Ethernet interface that looks just like the real thing to the host-side kernel and user applications. A host-side application can check for the presence of the USB device by running a ping for the IP address of the device. If the ping is successful, the device has attached.
A recently-announced usbnet-style driver for Win32 hosts is called the Bahia Network Driver. More information is about this driver is available at www.bahia21.com/download.htm.
Troubleshooting
Alas, there aren't very many utilities to help you track down USB communications problems between USB hosts and Linux USB devices. Apart from the debugging features found in libusb (which are extensive, but stop at the kernel's system-call interface), only the actual kernel source code and logs can provide clues as to what's going on during a failed enumeration or transfer attempt. I add printk() calls liberally to USB host and device code during development, but this introduces overhead and changes the performance of the USB code itself, which is counterproductive in some situations.
A popular choice for Linux developers wanting to reverse-engineer USB device interfaces or troubleshoot their own products is a program called USB Snoopy (home.jps.net/~koma). The ironic thing is that USB Snoopy only runs on Win32 hosts. For more information about USB Snoopy and debugging USB in general, read Jan Axelson's "USB Debug Tips" cited at end of this article.
Universal Linux
Linux isn't just for USB hosts anymore, it's now a handy choice for USB devices as well. In fact, USB communications under Linux are so flexible and easy to use, my days of using That Other Easy-to-Use Serial Interface, RS-232, may finally be coming to an end. And to me, that's a good thing.
Bill Gatliff is an embedded consultant and free software nerd. His wife and kids wouldn't limit his nerdiness to Free software, but Bill doesn't care because he's too busy writing about it and using it in all his projects to notice. Send comments to bgat@billgatliff.com.
REFERENCES
Ganssle, Jack. "An Introduction to USB Development," Embedded Systems Programming, March 2000, p.79.
Axelson, Jan. "HIDs Up," Embedded Systems Programming, October 2000, p.61.
Axelson, Jan. "USB Debug Tips," Embedded Systems Programming, April 2002, p.36.
http://www.eetimes.com/design/embedded/4024491/Linux-based-USB-Devices