An Overview of Linux USB

by Rami Rosen

The USB subsystem is based on message passing transactions. The messages are called URBs, which stands for USB request block. URBs are sent by calling the usb_submit_urb method, struct urb *urb, int mem_flags). This is an asynchronous call, and it returns immediately. The URB is put in a queue, and later, it reaches a completion handler. The completion handler is a member of the URB structure called complete, a pointer to a function in the URB struct. In the completion handler, you can check urb -> status to see if any errors have been detected.

To cancel pending requests, use usb_unlink_urb(). URBs are allocated by calling usb_alloc_urb(), and they are freed with a call to usb_free_urb(). Three helper methods are available to help fill URBs: usb_fill_control_urb(), usb_fill_bulk_urb and usb_fill_int_urb.

The URB struct resides in include/usb/usb.h, which is one of the two most important headers that define the USB interface. The second is include/linux/usb_ch9.h. Another little file also is called usb.h and can be found in usb/core, but it is not important in this context.

The header file called usb_ch9.h hold constants and structures for USB. The name comes from chapter 9 of the USB 2.0 specification. Currently, there are two specifications for USB, 2.0 and 1.1. USB 2.0 operates at high speed, defined as 60MB/s (480Mb/s), which is 40 times faster than USB full speed. USB 1.1 operates either at full speed, which is 1.5MB/s (12Mb/s), or at low speed, which is 1.5Mb/s. When you connect high speed devices to USB 1.1 systems, they operate at USB 1.1 speeds.

The USB_SPEED_LOW, USB_SPEED_FULL and USB_SPEED_HIGH constants are defined in usb_ch9.h. The constants are encountered here and there in the USB sources in the kernel.

Four types of transfers exist: control transfers, bulk transfers, interrupt transfers, and high speed isochronous (ISO) transfers. USB Webcams usually use ISO transfer, and you rarely find use of bulk transfers there.

USB Host Controllers

In the kernel source tree, under /drivers/usb/host, you find five types of USB host controllers defined:

  1. EHCI for USB2.0, EHCI stands for enhanced host controller interface.

  2. OHCI, or OpenHCI, the open host controller interface. According to /usb/host/Kconfig, OHCI "does more in hardware than Intel's UHCI specification" does. For example, sending a 4KB buffer takes one TD with OHCI, covering 64+ packets; with UHCI, each packet takes one TD. In addition, OHCI has separate schedules for control, bulk and periodic transfers, whereas UHCI has only one.

  3. UHCI, the universal host controller interface, originated from Intel. Boards with Intel PCI chipsets usually use this standard.

  4. SL811 host controller, manufactured by Cypress. It has 256MB of internal SRAM buffer.

  5. SA-1111 host controller, the drivers for these controllers are located under /drivers/usb/host. They sometimes are referred to as HCD, which stands for host controller driver.

So what do we have in the USB initialization? Let's take a look at /drivers/usb/core/usb.c for that information. We call six initialization routines, but only three of them are part of the sub-subsystem.

  1. bus_register(&usb_bus_type): the bus_register() method is not a part of the USB subsystem. It's a general bus method, and its implementation is found /drivers/base/bus.c.

  2. usb_host_init(): this calls class_register(), but it is not a part of the USB subsystem. Its implementation is stored in /drivers/base/class.c.

  3. usb_major_init(): this calls register_chre_dev() as well as devfs_mk_dir("usb") and class_register(). The major number of USB is 180. All entries under /dev/usb have major number 180, except /dev/ttyUSB*, which have major number 188. The call to class_register() is relatively new and did not exist in the 2.4 kernel.

  4. usbfs_init(): in most distros, the USBFS--which also is called usbdevfs--is mounted under /proc/bus. Of course, this can be changed by issuing umount and then mount on a different mounting point or by changing /etc/fstab.

    USBFS is a filesystem that in some ways resembles ordinary filesystems, such as ext3 or XFS. They resemble one another in that you call register_filesystem(), in /drivers/usb/core/inode.c, in order to register the filesystem, and you pass a pointer to file_system_type. Most of the relevant code for the USBFS is stored in usb/core/inode.c, where you can find methods such as usbfs_fill_super(), which usually is found in super.c in other filesystems. You also can find file operations there, including default_read_file() and default_write_file().

    USBFS is responsible for creating and updating the devices file, /proc/bus/usb/devices. The devices file is updated dynamically; if you insert a Webcam or any other USB device and run cat /proc/bus/usb/devices, you will find that some configuration entries have been added to the devices file. Similarly, if you remove the Webcam and run cat /proc/bus/usb/devices, these devices are removed from the devices file.

    Creating the devices file eventually happens by calling regular filesystem methods from dcache.c.

  5. usb_hub_init(): creates a kernel daemon thread named khubd; its implementation is in /drivers/usb/core/hub.c. The khubd thread is responsible for configuring devices.

  6. driver_register(): this method is not a part of the USB subsystem. Its implementation can be found in /drivers/base/driver.c

Plugging and Unplugging a USB Device

The nine mandatory ingredients of the USBcore are: usb.c, hub.c, hcd.c, urb.c, message.c, config.c, file.c, buffer.c and sysfs.c. If, when configuring the kernel, you set CONFIG_USB_DEVICEFS to yes, add three more items to the USBcore list: devio.c, inode.c and devices.c. inode.c implements the USBFS, and it makes a call to register_filesystem.

khubd is a daemon thread. After we create it, we call daemonize(), which blocks all signals. See daemonize() implementation in /kernel/exit.c for more details. We want to be able to send a kill signal (SIGKILL) when cleaning up, so we enable this by calling allow_signal(SIGKILL) immediately after creating khubd.

Usually, when you run ps -aux and look at the khubd process, you see that it is sleeping, denoted by the letter S. When we plug a USB device into the USB port, the hardware layer initiates an interrupt; we reach the hub_irq() method. The hub data, represented by struct usb_hub, is passed to the hub_irq() method as part of the URB, the context member of struct urb.

A global list of hub events, called hub_event_list, is available. If this list is empty, we add an event to this list, so that the khubd thread can handle it. We also call wake_up(&khubd_wait), as khubd is in waiting status. Waking up causes us to call hub_events(). We also reach the host controller IRQ. For example, when I am working with OHCI, I reach ohci_irq()). The hub driver writers admit that restarting the list every time to avoid creating a deadlock by deleting hubs downstream from this current hub is not the most efficient method, but it is safe.

When we unplug a USB device out from it USB port, the process described above is repeated until we reach hub_events(). There we call the hub_port_connect_change() method, which is implemented in hub.c. The second parameter of this method is the port number, so this method disconnects all existing devices on this port by calling usb_disconnect(). usb_disconnect(struct usb_device **pdev) also is a USBcore method implemented in usb.c.

The usb_disconnect() calls usb_disable_device() which disables all the endpoints for a USB device. It also removes that USB device endpoints from /proc/bus/usb/devices by calling usbfs_remove_device(dev). Notice, however, that hub_disconnect() is not called during this process. hub_disconnect() is called when we perform rmmod ohci_hcd, for example, when working with OHCI. It also is called in the probing process at bootime; more specifically, hub_probe() calls hub_disconnect() as it's last step before returning. The entries under /sys/usb/usb/drivers/hub are not removed after unplugging a device. They are removed, for example, after rmmod ohci_hcd is executed, assuming that you're working with ohci_hcd.

Tools and Libraries

When working with the USB subsystem, I found some tools and libraries to be especially helpful. USBView is a GTK+ 1.2-based tool written by Greg Kroah-Hartman that enables you to see the characteristics of the USB devices. This tool displays the topography of the devices plugged into the USB bus on a Linux machine. When plugging and unplugging the USB device, the display is updated and refreshed in real time. The source code also is available.

Other tools and libraries that might help include UsbSnoop, which is some kind of USB sniffer that runs only on Windows. A new patch called USBMon, written by Pete Zaitcev, is available that enables you to monitor USB traffic. Finally, the libusb project is trying to create a library that can be used by user-level applications to access USB devices regardless of OS.

USB Mass Storage Driver for Linux

I end this article with a brief discussion of the USB Mass Storage Driver. This driver is intended for mass storage USB devices, including USB disks and USB DiskOnKeys. What follows below is relaxant to USB DiskOnKeys, but I assume USB disks also behave more or less the same.

To mount a DiskOnKey, you first should create a mounting point, let's say /mnt/dok, and then run:

mount /dev/sda /mnt/dok

The code for the USB Mass Storage Driver resides, naturally, under drivers/usb/storage. The module name is usb-storage.ko; it uses SCSI emulation.

The us_data structure, from storage/usb.h, probably is the most important struct in storage mass storage. The protocol we use is transparent SCSI. The transport layer is bulk, which corresponds to US_PR_BULK in usb/transport.h.

The use of SCSI emulation appears also in ATAPI devices, such as CD-RW devices. It is a good question to ask why we use this emulation at all. Why not use the IDE interface? I personally wonder what the reason is. To me, it does not seem to be related to the fact that IDE disks are not hot plugged while USB disk can be hot plugged.

The usb_stor_control_thread() is a daemon thread that controls the USB device. It adds the PF_NOFREEZE flag to the current process. Aside from this module, use of PF_NOFREEZE appears in only two other places under the drivers subtree of the Linux kernel source: in block/loop.c in the loop_thread(void *data) method and in scsi/scsi_error.c in scsi_error_handler(void *data). The PF_NOFREEZE flag is used for Swsusp, which stands for software suspend in Linux and is the equivalent of Windows' hibernate mode.

Note

The code cited in this article is from the USB layer in the 2.67 kernel; it has changed little in newer versions.

Rami Rosen is a software engineer who recently has been working on Linux software for a startup company that develops videoconference and VoIP solutions. He is a devoted Linux kernel hacker, and he also loves music, reading and running.

Load Disqus comments

Firstwave Cloud