
The USB architecture contains several levels. At the lowest level, a specially trained hardware, called the
host controller , communicates with the USB device with special signals. Signals encode bits, bits add up into packets, packets form
transactions , transactions make up
transfers .
I’m talking about USB software support, so the levels below are
almost uninteresting: the host controller is responsible for them. But it is important which interface represents the host controller software. Three interfaces are now distributed, and the fourth is gradually spreading:
At the same level of interaction with controllers, there are files
kernel / trunk / bus / usb / hccommon.inc , where some functions common to all controllers are implemented, and
kernel / trunk / bus / usb / init.inc , which starts the whole subsystem. However, do not rush into the code yet - firstly, I haven’t told yet what higher levels are expected of it, and secondly, after showing the general scheme, I will return to the individual components with details.

Some theory
From a software point of view, any USB device is a set of endpoints to which you can open pipes and organize
transfers of various types. Each endpoint has its own number, from 0 to 15. Two endpoints can have one number only if they are both unidirectional, one of which is intended for transmissions from host to device, and the other in the opposite direction.
Depending on the type of gear being processed, there are four types of end points.
- Management transfers (control transfers) are small (as a rule) transfers used for configuration, control commands and status requests. The only type of bidirectional exchange: here information is transmitted from the host to the device, and from the device to the host. The control transmission consists of three stages ( stages ), one of which may be absent:
- The setup stage transmits 8 bytes of the prescribed format information from the host to the device. These bytes include the direction and length of the data. The general format and standard queries are described in the main USB specification, and later I will talk about some of them.
- The data stage (absent if the data length is zero) transmits the data in the direction specified in the previous step.
- The status stage transmits in the opposite direction a sign of success.
Any USB device must have a control endpoint number 0: with its help, the USB infrastructure recognizes general information about the device (including “which driver to load”) and performs the initial setup. Almost all (if not all) devices lack a single control endpoint, which may also handle device-specific requests. - Transfers of data arrays (bulk transfers) . The main “workhorse”, when there is a lot of data, and it is important that they reach without distortion, but it doesn’t matter how much time it takes, flash drives, printers, etc. For example, a typical USB flash drive has three endpoints — a mandatory zero control and two for transmitting data in two directions.
- Interrupt transfers (interrupt transfers) . They are used when there is little data, but it is important that they are processed within a certain time - mice, keyboards. For example, a typical mouse has two endpoints - a mandatory zero control and one type of interrupt with the requirement to poll every 10 milliseconds (more often); the required interval is a property of the end point, it can vary.
- Isochronous transfers (isochronous transfers) . The only type that does not guarantee data delivery. It is used when there is a lot of data, and it is important that they are processed within a certain time, but data loss is allowed - multimedia: webcams, USB-speakers.

USB bus time is measured in
frames or
microframes . Frames appeared in USB1, one frame - one millisecond. Microframes appeared in USB2, in one frame 8 microframes. The USB infrastructure plans interrupt and isochronous transfers to specific (micro) frames so as to guarantee the requested time interval between transfers. Planning has no right to use more than 90% of the frame or more than 80% of the microframe. The remaining time (at least 10% / 20%, although it may be more, if not all the time is planned) is occupied by active control transmissions and, by residual, transmission of data arrays.
Channel Support Level
The file
kernel / trunk / bus / usb / pipe.inc contains the implementation of channel handling functions. Enough detailed documentation is in
kernel / trunk / docs / usbapi.txt . Now 4 functions are implemented. The first two of them are:
- The function of opening the channel USBOpenPipe , in the source code called
usb_open_pipe
. It takes as input a previously opened channel, from which it copies the characteristics of the device, including its position on the USB bus, and the characteristics of the new channel, and returns the handle of the new channel, or zero on error. So far it does not matter what the handle is. - The function of closing the channel USBClosePipe , in the source code called
usb_close_pipe
. Its purpose and sole parameter are fairly obvious.
The channel support level also handles a device shutdown event, closing all channels associated with the device. So explicitly closing the channel is optional. The attentive reader may be indignant here: “So, the channel may suddenly close by itself?” But you should not worry - the device disconnect event, in addition to processing at the current level, is also broadcast “upward”. The channel is finally closed only after everyone has a chance to handle the device disconnect event. More precisely, the channel handle can be used at least up to the moment when the handlers “from above” finish their work. The handler in the driver is described in the documentation as DeviceDisconnected, although the real name can be any — the driver provides a pointer to this function.
')
Before introducing the following two functions, I should note the following. Each channel has its own transmission
queue . In the same queue can be several transmissions at the same time, but only one of them can be active - the one that is in the head of the queue. The next transfer will begin only after the active transfer is fully successful. If any transfer fails, the queue will stop. Why do I need a queue? For efficiency: software processing of the completed transfer may take some time, the host controller may well not wait for the handler's reaction, and in the meantime, proceed further. Queues of different channels are independent, transmissions for different channels are performed in parallel.
- The USBNormalTransferAsync data transfer function, called
usb_normal_transfer_async
in the source code, serves two types of transfers at once: the transfer of data arrays and the transmission by interrupt. For both types, you need to know the data itself for transmission in the form of a pointer + length, a channel in the form of a handle returned by USBOpenPipe , and some flags; so far, only one flag has been defined that allows or denies short transmissions in the direction from the device to the host. The direction itself is not necessary to specify: it is uniquely determined at the time of opening the channel. The “Async” suffix indicates that the function only puts the transmission into a queue and then returns immediately; when the transfer is completed - completed successfully, unsuccessfully or canceled altogether due to the device being disconnected - the callback function will be called, the pointer to which is also passed by one of the USBNormalTransferAsync arguments. In order for the callback function to pass any additional information, an arbitrary parameter is passed along with the pointer to the function, which will be passed unchanged. - The control transfer function USBControlTransferAsync , in the source code, called
usb_control_async
. The interface is identical to the previous function with the addition of one parameter - a pointer to 8 bytes for the setup stage. The direction is still not needed, but for a different reason: it is extracted from the data for the setup stage. Strictly speaking, the length of the data could also be omitted for the same reason, but it is left for the unification of the interface.
The attentive reader, no doubt, noticed a lack of functions. I'm working on it.
The channel support level also includes the file
kernel / trunk / bus / usb / scheduler.inc ; he is responsible for scheduling gears that are time sensitive.
Logic level
In the theory summary, I have already said that any USB device must have a zero control endpoint, through which the infrastructure polls the device and performs the initial configuration. The file
kernel / trunk / bus / usb / protocol.inc is exactly what it does, based on the level of support for the channels. The result of this level: a loaded device driver that received a call to the function described in the documentation as
AddDevice . Then everything is in the hands of the driver. The first argument to the
AddDevice function is the handle of the channel open to the null endpoint. Using it, the driver can open the additional channels needed by him, as well as make additional tuning through the zero end point. The return value of
AddDevice — either zero on error, or an abstract parameter that the USB infrastructure does not interpret, except for a comparison with zero — stores it inside the device information and then passes it to the
DeviceDisconnected function, which I already
mentioned .
To describe the two remaining parameters of the
AddDevice function
, I will need a little more theory.

A single physical device can provide multiple
interfaces that can be programmed to one degree or another independently. In particular, each interface has its own set of endpoints. A good example is the numerous PS / 2 to USB adapters that provide two inputs for mouse and keyboard; This USB device provides two unlinked interfaces. A less obvious, but more common example: USB-keyboards with special buttons are often presented as two independent interfaces, one is a keyboard with standard buttons, the other is additional buttons.
A USB device is required to support a request for various
descriptors , in particular, a
configuration descriptor . In response to a request for a configuration descriptor, the device also returns a lot of related information, including descriptors for all interfaces and all endpoints associated with the interface. The endpoint descriptor contains all the information necessary to open a channel to it.
The second argument to the
AddDevice function is a pointer to the data associated with the configuration descriptor, starting with the configuration descriptor itself; one of its fields is the total data length. The third argument to the
AddDevice function is a pointer to an interface handle for which the driver is responsible.
If a device implements multiple interfaces, then the logic device level will trigger the driver — or several drivers — several times. The responsibility of one
AddDevice call extends from the interface descriptor that was transferred to it, to the next interface descriptor, or to the end of the data; On this interval are descriptors of all end points of this interface.
Device drivers
This is the highest level in USB architecture. Device drivers use the API level support channels and information collected by the level of the logic device to support the desired functionality, depending on the device itself.
Among the drivers, the hub driver
kernel / trunk / bus / usb / hub.inc stands out because in some aspects it is close to the support level of the host controller and is an integral part of the USB infrastructure. The USB specification allocates the part of the host controller responsible for controlling USB ports to the
root hub in a special entity; The root hub is related to the individual hubs interface for other levels, but they are fundamentally different in terms of programming.
Hub support code interface: when a device is connected, the code informs the host device support code of the new device; when the device is disconnected, the code transmits information about this level of channel support; for the logic device level, the code provides the
AddDevice +
DeviceDisconnected functions , which in the source text are called
usb_hub_init
and
usb_hub_disconnect
respectively, as well as the function of blocking the port to which the new device is connected.
Mice and keyboards are supported by the
kernel / trunk / drivers / usbhid.asm driver . The flash driver is supported by the
kernel / trunk / drivers / usbstor.asm driver .
Host Controllers Support Code Interface
Finally, I am ready to explain the entire interface provided by the host controllers support code to other levels. When a new device is connected, the
usb_new_device
function of the
usb_new_device
level is called. Here I should note the following:
usb_new_device
will try to open the channel to zero point. But the channel open function requires an already open channel, from where it copies the characteristics of the device. For this to work, the host controller level creates a pseudo-channel, in the structure of which it fills only the fields with the characteristics of the device. Opening a channel to the zero point will create a fully-fledged channel. When the host controller has reconciled itself with the thought of the disappearance of the channel, the function
usb_pipe_closed
the channel support level is called. In addition, the logic level during the initial setup changes the channel parameters; when the host controller confirms that the changes are accepted, one of the
usb_after_set_address
and
usb_after_set_endpoint_size
level
usb_after_set_endpoint_size
the logical unit is called. In more detail about why this is needed, I will describe in the framework of the analysis of the level of the logic device.
Functions specific to a particular host controller and called from another code are assembled into the
usb_hardware_func
structure from
hccommon.inc . It includes:
- Functions for working with the hardware of channels and queues used by the level of support for channels.
- Functions for obtaining and changing the device address on the bus, as well as the size of the channel packet used by the level of the logic device.
- Functions for working with USB ports: blocking, reset, used by the level of the logic device.
- A function that prepares parameters for
usb_new_device
and calls usb_new_device
. It is called from two places: from the inside of the same level and from the hub support code. - A couple of functions for the controller-non-specific part of the same level.
All articles of the series
Part 1: general scheme
Part 2: Basics of working with host controllersPart 3: Host Controller Support CodePart 4: Channel Support LevelPart 5: logic levelPart 6: hub driverPS If someone else does not know: we collect some money on Kickstarter to spend your Summer of Code. So far, 65% has been collected, and the fundraising ends May 31 (tomorrow). Article:
habrahabr.ru/post/180197