The USB stack and library

USB (Universal Serial Bus) is a hardware and protocol specification for interconnecting various devices to a host controller. We supply a USB stack that implements the USB protocol and allows user-written class drivers to communicate with USB devices.

We also supply a USB driver library (usbd_*()) for class drivers to use in order to communicate with the USB stack. Note that a class driver can be considered a “client” of the USB stack.

The stack is implemented as a standalone process that registers the pathname of /dev/io-usb/io-usb (by default). Currently, the stack contains the hub class driver within it.

Host Controller Interface (HCI) types

The stack supports the three industry-standard HCI types:

We provide separate servers for each type (devu-ohci.so, devu-uhci.so, and devu-ehci.so). Note that USB devices don't care whether a computer has an OHCI, UHCI, or an EHCI controller.

Data buffers

The client library provides functions to allocate data buffers in shared memory; the stack manages these data buffers and gives the client library access to them. This means that all data transfers must use the provided buffers.

As a result, a class driver must reside on the same physical node as the USB stack. The clients of the class driver, however, can be network-distributed. The advantage of this approach is that no additional memory copy occurs between the time that the data is received by the USB stack and the time that it's delivered to the class driver (and vice versa).

USB enumerator

With the QNX Neutrino OS, the USB enumerator attaches to the USB stack and waits for device insertions. When a device insertion is detected, the enumerator looks in the configuration manager's database to see which class driver it should start. It then starts the appropriate driver, which provides for that class of device. For example, a USB Ethernet class driver would register with io-pkt* and bring the interface up.

For small, deeply embedded systems, the enumerator isn't required. The class drivers can be started individually — they'll wait around for their particular devices to be detected by the stack. At that point, they'll provide the appropriate services for that class of device, just as if they'd been started by the enumerator. When a device is removed, the enumerator will shut down the class driver.

For more information about device enumeration, see the Controlling How Neutrino Starts chapter of the Neutrino User's Guide.

How a class driver works

A class driver typically performs the following operations:

  1. Connect to the USB stack (usbd_connect()) and provide two callbacks: one for insertion and one for removal.
  2. In the insertion callback:
    1. Connect to the USB device (usbd_attach()).
    2. Get descriptors (usbd_descriptor()).
    3. Select the configuration (usbd_select_config()) and interface (usbd_select_interface()).
    4. Set up communications pipes to the appropriate endpoint (usbd_open_pipe()).
  3. In the removal callback, detach from the USB device (usbd_detach()).
  4. Set up all data communications (e.g. reading and writing data, sending and receiving control information, etc.) via the usbd_setup_*() functions (usbd_setup_bulk(), usbd_setup_interrupt(), etc.).
  5. Initiate data transfer using the usbd_io() function (with completion callbacks if required).

Note: In this context, the term “pipe” is a USB-specific term that has nothing to do with standard POSIX “pipes” (as used, for example, in the command line ls | more). In USB terminology, a “pipe” is simply a handle; something that identifies a connection to an endpoint.