The USB stack

For all of you who have been over my code and thought: “What is this guy doing?”, I can firmly respond that I sometimes haven’t a clue either. But when I’m actually reviewing my own code, I occassionally get the feeling that I do have a direction I’m persueing. In order to define this direction more, and share you in the fun, I have written this document. It’s a rather superficial account (without actually quoting any code), but it should be enough to get you started.

First of all, it’s always good to know that the USB stack is modularised. This means that there is a backend - a module - which connects to a whole range of modules that are in touch with the host controllers (the actual USB hardware). The combination of these modules is what we call the USB stack. The job of the backend (in CVS at /current/src/add-ons/kernel/bus_managers/usb) is to perform generic operations like managing device connects and disconnects, and pushing commands from drivers to the host-controller modules. The host controller modules are in direct contact with the hardware, meaning that they translate actual commands into something the host controller can work with and can transmit over the wires. Additionally, these host controller modules simulate the root hub: they provide a seamless interface for the backend module to communicate with the root hub.

In the first experiments of my USB stack, there was a special ‘C’ interface for the host controller modules. This interface was clumsy, because all the objects (the backend is in C++) were constantly harvesting data all over the place because they couldn’t simply send references of themselves. The second attempt of the USB stack introduces a libusb.a (the kernel doesn’t support shared libraries), which contains the basic structure of the stack, and which is linked in by both the backend and the host controller modules. Instead of having a host_controller_module struct with function pointers, the host controller modules now simply returned an extended ‘class Busmanager’. The downside is that whenever the libusb.a is changed, all the host controller modules have to be relinked in order to avoid disasters. (This is also something which prevents me from providing a nice C++ interface for drivers). However, this limitation is much easier to work around than the separate C-interface.

The basic structure of the stack (and of libusb.a) can be found in the usb_p.h file in the CVS location of the backend. You’ll notice a few objects which I’ll quickly go over. First of all, there’s stack, which is basically a container for the individual Bus managers. The Busmanager class encapsulates a bus manager. The Device class represents a connected device, and the Hub class (inherits Device), manages the communication with the hubs. For the area of communication there are a few classes that are interesting. The Pipe class represents a data pipe. A ‘pipe’ is more an interface than something that is actually interesting during USB communication. A pipe encapsulates the properties regarding what kind of data is transmitted to where. Each Pipe type (corresponding to the four types of transfer), will be a subclass of Pipe. The objects of Pipe will all be associated with a device. Finally there’s the Transfer class. The objects of this class are created by the Pipes, and contain the actual data for the transfers (including references to the callbacks).

All these classes would be useless, if they weren’t interacting with each other. The basic idea of the stack is the following. As soon as the USB bus_manager module is opened by a driver (for the first time), a Stack object is initialized. This object starts looking for host controller modules. Every host controller tries to find and initialize their cards, and if they succeed, they add a BusManager object to the stack. This bus manager object contains an object of the hub class, and the default pipes. The generic backend implementation starts a usb_explore thread, which will poll the status of the hubs and keep the internal administration up to date. As soon as a hub signals that a new device is connected, the Busmanager starts a procedure to connect the device. If it’s a hub, a Hub is created, else a Device is created. This device object creates the default pipe, and contains all the data related to it.

New transfers are initialized by Pipes. The Transfer objects will be created by the Pipe subclasses only, and the ownership of the objects will go to the Busmanager that has to transmit them (that will take care of deletion). The SubmitTransfer function of the Busmanagers are the gate to the actual transfer. What happens is host controller dependent. The implementation of the host controller should take care of finishing a transfer.

As for closing, this design lacks some objects that correspond to USB objects. For example, there are no special Endpoint objects. At the moment, passing adresses is sufficient, but perhaps it should change in order to complete the design. Also, there is currently no idea how locking should work. Finally, the Stack class currently includes some memory management for usb, however, checking from the specs, only UHCI requires manual scheduling, OHCI and EHCI manage the scheduling and packaging itself. That should move. But at the moment, this design works.

Finally, this document gives an idea of how it should work. For details: check the USB specs, host controller specs and the code. If anything stays unclear, please comment on my weblog, or e-mail me.