The Universal Serial Bus, or USB, is a new type of peripheral interconnect that has become popular in recent years and is now standard equipment on most PCs and laptops. USB is an external interface, in that USB devices plug into ports outside of your PC's case; this is as opposed to internal interfaces, such as PCI.
|Figure One: A simple USB layout consisting of a PC with a USB keyboard and mouse, as well as an associated USB tree.|
The Universal Serial Bus, or USB, is a new type of peripheral interconnect that has become popular in recent years and is now standard equipment on most PCs and laptops. USB is an external interface, in that USB devices plug into ports outside of your PC’s case; this is as opposed to internal interfaces, such as PCI.
USB has a number of attractive features. First, it is multiplatform, so USB devices work across different types of host hardware (for example, Intel-based PCs as well as Macintosh computers). Second, USB devices are both hot-swappable and “plug and play” — not only can you add and remove USB devices while the machine is running, but the devices automatically configure themselves to use particular port and interrupt numbers, saving the user from having to do this configuration. USB can support up to 127 devices plugged into the host using a tree structure.
USB devices connect to the host using standardized cables that carry both power and data; the data link rate is 12 Mbits/sec with an optional “low cost” link rate of 1.5 Mbit/ sec. The lower link rate is intended for devices that don’t require the full bandwidth and can be made more cheaply (in part because less EMF protection is required at the lower signalling rate).
USB is a standard backed by most major hardware manufacturers, including Compaq, Intel, Microsoft, and NEC. If you want more information, you can find details and the specification at http://www.usb.org.
Until recently, the Linux kernel had no support for USB devices. However, experimental USB support has been available since the Linux 2.2.7 kernel tree. The latest developments in USB are going on in the 2.3.x trees, and Linux 2.4 should be available sometime in the fall, complete with USB support.
This discussion is based on version 2.3.99-pre3 of the Linux kernel, the one that is current at time of this writing. I expect there to be little or no differences between this version and the 2.4 version that will be released in a few months. I chose to compile the USB subsystem as modules and discuss the role of individual modules. The modularized form makes it easier to understand the overall design, as compared to a kernel with the whole USB subsystem linked in.
A USB device is still, at its lowest levels, a “character device” or a “block device,” but the programming interface offered to device-driver writers has been simplified in order to take advantage of the common hardware features offered by those devices, and to offer arbitration of the bus and coordination in its use by the various drivers. We will explore the concepts behind USB on a theoretical level this month, and next month we will build on these ideas with some actual code-based examples.
A Quick Tour of the Hardware
While USB stands for Universal Serial Bus, its physical structure is not a bus, but rather a tree. Each transmission link can only connect two nodes, called the “upstream” and “downstream” nodes, respectively.
To make the distinction clearer and to avoid endless cabling problems like those we are accustomed to daily with serial interfaces, the USB specification requires different connectors at the upstream and the downstream end of the cables. What you see lurking in the back of your computer is not “the USB connector” but rather “the upstream USB connector,” which can also be referred to as type “A.”
Each non-leaf node in the tree is a USB hub. The host controller (the hardware that is installed in the computer) implements a USB hub, as this is the only way to spit out several communication channels toward USB devices.
|Figure Two: A “text-book” overly complex USB layout including hubs and other peripherals in addition to a keyboard and mouse.|
To preserve the tree structure, it is not possible for a device to have two upstream connections — while there exist USB devices that can interconnect two computers by connecting to two different USB busses, those devices are conceptually two separate USB peripherals, each connected to its own bus. Figure One and Figure Two (pg. 82) depict two typical USB environments — simple and complex –with their respective physical structures represented as trees.
As far as the host controller is concerned, all the implementations currently fall under one of two classes: “Open Host Controller Interface” (OHCI) and “Universal Host Controller Interface” (UHCI). In Linux you can thus choose between two device drivers for your USB subsystem: usb-ohci.o and usb-uhci.o. Also, the module usbcore.o is required no matter what hardware you happen to have.
When a device is plugged into the bus, it identifies itself to the system as one of several classes of peripherals; in order to use the device you’ll need to load a driver that claims ownership of that device and handles the associated communication protocol.
The USB protocol allows for a variety of device types and bandwidth usage. To keep the discussion simple, I’ll only stick to simple input devices, like mice and keyboards. Those devices don’t need a sustained data rate or strict timing, so are the easiest to understand, as well as the most commonly used.
The Various Modules and Their Interrelations
|Figure Three: The various dependencies of the different Linux kernel modules when the USB subsystem is compiled in modules.|
When you compile the USB Linux subsystem as modules, you’ll end up with several kernel modules. While the internal communication among them is somewhat complex, the dependencies of the modules are quite straightforward, as depicted in Figure Three. Table One lists the main entry points exported and used by each module.
Table One: Main Entry Points Exported and Used by Each Module
|Symbols Exported by the Module|
|Symbols Used at Load and Run Time|
As shown, the usbcore module offers all the software infrastructure needed for hardware handling while input offers a generic input-management framework; all other modules stack on those two, registering as either producers or consumers of information.
As far as hardware is concerned, the host controller device driver (ohci or uhci) registers its functionality within usbcore’s data structures, as do the device-specific drivers (for example, usbmouse and usbkbd). Their role differs in that the host controller produces information (collected from the wire) while the other drivers consume information (by decoding the data and pushing it to its final destination).
Input management operates in a similar way: hardware drivers (usbmouse and usbkbd) produce data and the generic input handlers (mousedev and keybdev) consume data. Both kinds of modules stack on input.o, registering their own entry points.
The question still unanswered is how can a module push information to another module by only registering a callback. If you are comfortable with generic kernel drivers, you’ll remember that things are usually set up the other way around: the consumer module registers its callback to be invoked whenever there is new data to consume. The USB driver is laid out differently because it is designed as a message-passing environment, instead of being a data-flow system like most of the Linux kernel. The key data structure, the “message,” is called URB, short for USB Request Block.
How URBs Keep It All Together
When a USB hardware driver is loaded, it calls usb_alloc_ bus() to register its own usb_operations data structure with usbcore.o (the data structure is defined in <linux/ usb.h>, as is all other header material relevant to this article). The registered operations include submit_urb. and unlink_urb, so the software layer can initiate data transfer to the hardware driver.
With the software layer and the hardware drivers in place, a specific peripheral driver (like usbmouse.o) can register its own usb_driver data structure by calling usb_ register().
The data structure includes pointers to a probe and a disconnect function. The former is called by the USB framework whenever a new device is detected and the latter whenever the device is unplugged from the bus.
When the probe succeeds (i.e., the new USB device can be handled by this device driver), the function submits its URB structure to the USB engine (by calling usb_submit_ urb()), including in the URB a pointer to its “completion handler,” a callback that will be invoked at the end of each hardware transaction.
Within usb_submit_urb, the URB is passed back to the host controller interface, so that the hardware driver can fill the URB buffers with relevant information and call the proper completion handler whenever a data transfer happens.
While this description referred to mice and keyboards, the same design rules apply to other USB peripheral devices. The difference is mainly in how the hardware handles the individual transactions: not every device works like a keyboard or mouse.
A digital video camera, to use one example, will need to push a sustained data rate into the bus. The USB specification describes several different transaction types, and all of them are handled by the USB Linux subsystem using URBs.
Although the issue is not directly related to USB hardware, I think it’s interesting to see how USB input devices, like keyboards and mice, are designed as part of a more generic input mechanism, implemented in the source file drivers/ usb/input.c.
As already outlined (and shown in Table One), the USB modules for keyboards and mice stack on the input module as “producers” of data, while more generic modules (mousedev and keybdev) stack on input as consumers of data.
Whenever a USB input driver’s completion handler is invoked, it pushes the data just received to the input management engine, by calling input_event().
A “consumer” module registers its own callbacks within input.o, by calling input_register_handler() at load time. The input_handler data structure includes pointers to three callbacks: connect, disconnect, and event — which are all that’s needed for proper device management. What actually consumes input data is the event handler.
The role of input_event(), as called by the completion function of the peripheral USB driver, is that of distributing the input data to all the registered handlers. Each handler will just ignore data it is not interested in. Thus, if you didn’t load keybdev.o, your USB keyboard will be ignored by the system, even though you loaded the usbkbd module and key-presses are correctly decoded and passed to the input engine.
The input handling machinery is very interesting, in my opinion, as it allows insertion of custom kernel modules in the event chain, both as producers and consumers of input events.
A new event producer could push keyboard or mouse events down the system’s chain even though it is not physically a keyboard, while a new event consumer can associate special actions (any action) to input events. This provides a flexible tool for people who play with nonstandard devices or need to implement nonstandard behaviors in their kernels.
In my next column we’ll look at some code-based examples of the USB module material that we have outlined here. Stay tuned.
Contains the USB specification and other interesting information about USB, including general software issues.
This directory documents device-specific aspects of USB support. The file “URB.txt” is particularly interesting as it thoroughly describes the URB data structure.
The “Linux-USB HOWTO.” A lot of information on setting up and using USB under Linux.
Alessandro Rubini is an independent consultant based in Italy. Even though he claims to be a programmer, his activities consist mostly of advocating libre software and documenting what real programmers do. He runs the GNU/Linux operating system on his computers, and can be reached at email@example.com.