Tuesday 12 June 2018

Getting descriptors from a USB device (Windows) - PART 1 (Introduction)

This is the first post of my 3 part series on USB. This introduces some basic concepts, which we will use in PART 2, where we will be fetching the Device descriptor from a USB device, and also in PART 3 where we will try to understand what a billboard device is, how is it related to USB-C alternate mode, and how to get the billboard capability descriptor. If some terms don't make sense to you, don't worry. They will be explained as we progress. You can also directly jump to Part 2, and Part 3 if you already know the basics.

USB Introduction

The 3 letter acronym USB stands for Universal Serial Bus. Why Universal, you say? This is because USB in its most basic form can be seen as a conduit to allow host and devices to copy to-and-fro buffers. The capacity to transfer bytes from host to device and back is all USB tries to offer. The way a device, and its host interprets those bytes gives rise to the myriad of USB devices we see all around us. Therefore, USB is not tied to a single device, or functionality, it can be used to implement any feature which can be expressed in terms of flow of data (bytes) between two participating nodes.

USB as a conduit
USB as a conduit

Any USB device has two mechanisms in place for proper connection and for exchange of data.
Once mechanism is by which a host learns about a USB device. Another mechanism is by which the actual transfer of data takes place. The former is implemented via descriptors, which we will study in next section. The latter is implemented by the concept on end points.

An end point is nothing but a part on device which has buffers which can be sent to a host, or empty buffers which can receive data from host. A USB device implementing a camera would place the pixel data on an end point, which will then be read by the host.

Lets consider a USB device having multiple functionalities on it. Let's say it has a camera, a mic, and a data storage area (like a pen drive). Although we have a single device, but we have multiple "interfaces". An interface is logical grouping of end points. An interface for camera will have end points which are only involved in sending, and receiving camera related data to, and from the host.
Our device in discussion can be said to have 3 interfaces.

All interfaces, and end points are numbered. Numbering of interfaces start at 1, and continues till 255. Numbering of end points start from 0, and end at 255. Every interface must implement end point 0. End point 0 is a special end point which is only responsible for transmission of control information. For transfer of data (eg. pixel data from our USB camera) end point 1 onward can be used.

More complex USB devices can even combine several interfaces, something called Interface association (described by IAD - Interface Association Descriptors), which will not explore any further.

USB Hierarchy

The USB specification allows for a tree/star like hierarchy for all devices involved. At the root of a tree would be USB controller. It can be though of as a chip, or an IC which manages everything on USB bus. Much like the brain in our nervous system. Different devices can connect to a host via a set of intervening hubs. Hub is exactly the hub you see lying all around you. A USB controller has connected to it a special kind of hub, called the root hub. There can only be a single root hub between a USB controller, and a USB device. To the root hub a device can be connected either directly or via a series of hubs. A host can have multiple USB controllers.


USB Descriptors

A USB device has various data structures, called descriptors which store in them the properties of the USB device, like USB class, USB protocol supported (1.1, 2.0, 3.0, etc.). A descriptor can either describe some general properties of the entire device, or it may describe only a specific functionality of the device. When you connect a USB device to a computer, it is through these descriptors that the OS learns that the device is a mouse, or a keyboard, and so on.

Following are some of the type of USB descriptors you may find on a device. This list is not exhaustive.

  1. Device descriptor
    This is the very first descriptor a host reads from device. All USB devices (except the most simple ones perhaps, like USB-C analog audio accessory mode - the USB-C to eaphone jack converters you see) must have this descriptor. This can specify the USB class in bDevice class field. If this field is 0x00 then the USB class is specified for each interface separately and is given by the corresponding interface descriptor. What this means that the device has interfaces belonging to different classes (as our multi functional USB device discussed earlier).

  2. Configuration descriptor
    This describes the various configurations which a device can support. A device can operate only in one configuration at a time. Most devices are simple and support only a single configuration. Things such as how the device is powered (if self/bus - powered - only USB1.1, newer devices report power the device needs from the bus in bMaxPower field in units of 2 milli ampere), max power consumption, if the device supports remote wake up, and number of interfaces. When a configuration descriptor is read, it returns its entire sub hierarchy comprising of all related interface, and endpoint descriptors.

  3. Interface descriptor
    Interface refers to a feature or a function a USB device specifies. A feature may use more than 1 interface required (in which case an Interface Association descriptor - IAD must also be used). For eg. a camera on the USB device may use one interface for video, and another for audio. This can also specify class type for an interfaces, if different interfaces on the device belong to different USB classes.

  4. Endpoint descriptor
    An Endpoint in USB refers to source or sink of data. You can visualize it as a buffer of bytes kept on a device. bNumEndPoints field of the interface descriptor specifies the number of end points in the interface except end point zero (control end point). All interfaces must have end point zero, and if there are no end points for the interface other than end point zero this field is 0. Each end point except the 0 end point must have a descriptor. End point zero doesn't have a descriptor.
    The descriptor specifies direction of data transfer (host-to-device/device-to-host), type of transfer (control/isochronous/bulk/interrupt), and address of the end point.

    The following image shows the hierarchical relation between Device, Configuration, Interface, and Endpoint descriptors.

    USB descriptors
    USB descriptors

  5. Binary Object Store (BOS) descriptor and device capability descriptor
    These two descriptor types were introduced in USB 3.0, and USB 2.0 Low Power Mode specification. Their aim is to provide an extensible framework to give information specific to a technology or a device. Capability descriptors give information about certain USB capabilities. For eg. if the device supports Wireless USB, there will be a Wireless USB capability descriptor in the device. An application wanting to know more about the wireless USB capability of the device can read the descriptor. The format of each capability descriptor can be different (except for the standard fields like bLength, bDescriptortype, etc. which all USB descriptors should have), this gives the aforementioned extensibility. Right now we have the following capability types (the values in Hex next to them can be ignored for now).         
               1. Wireless USB (0x01)
               2. USB 2.0 extension (0x02)
               3. Superspeed USB (0x03)
               4. Container ID (0x04)
               5. Platform (0x05)
               6. Power delivery (0x06)
               7. Battery info (0x07)
               8. Power delivery consumer port (0x08)
               9. Power delivery provider port (0x09)
               10. Superspeed plus (0x0A)
               11. Precision time measurement (0x0B)
               12. Wireless USB extension (0x0C)
               13. Billboard (0x0D)
               14. 0x00, 0x0E - 0x0F are reserved
    The BOS descriptor functions as a base descriptor for one or more device capability descriptors. Thus if the USB device supports (say) Wireless USB, Superspeed USB, and Superspeed Plus capabilities, then we will have 3 capability descriptors following a BOS descriptor.
    BOS descriptor, and Capability descriptors
    BOS descriptor, and Capability descriptors
    Individual capability descriptors cannot be fetched, always the BOS + all capability descriptors are fetched as s single unit. The BOS descriptor can be fetched individually.

  6. String descriptor
    In all other descriptors, whenever there is a need for providing a string (device name, url for further information, etc.), an index into string descriptor is given. For eg. iManufacturer field of device descriptor refers to manufacturer string. This field is not a string, instead this string gives an integer which refers to string descriptor where the string is stored. For example to get manufacturer name, we will have to fetch DeviceDescriptor->iManufacturerth string descriptor. The string descriptor 0 occupies a special positing among all the string descriptors. It stores a list of all language IDs. For eg. if you would like to know if a USB device has strings for English (US) language, the language ID for which is 0x0409, you will have to fetch string descriptor 0, and check if 0x0409 is their in the list of IDs.
Common USB descriptor

This is not a descriptor present in a USB device per say, but is the data structure which represents the common fields that all USB descriptors may have. In windows this structure is called USB_COMMON_DESCRIPTOR (usbspec.h), but using a structure provided by OS API is not necessary, as the definition is given by USB spec, hence you can define the structure on your own.
Common USB descriptor
Common USB descriptor
This can be used to iterate over descriptors without going into details of each descriptor. For eg. if we are only interested in knowing about device descriptor in a list of USB descriptors provided to us, we can iterate through all descriptors using this common data structure, and check bDescriptorType for each. Device descriptor will have the field bDescriptorType set to 0x01.

IOCTLs - Input/Output control

Like system calls IOCTLs are also a way to request an operation from OS kernel. However while system calls are implemented by an OS, IOCTLs are specific to devices, and handled by the corresponding device drivers. Thus while system calls remain static for a system, IOCTL for a device can change with a newer driver, and will be different for different devices. In Linux a device driver exposes a file in /dev file system. An applications opens a handle to this device, and requests ioctl using ioctl() system call. In Windows an application first finds the device path (don't confuse it with device instance path shown in device manager). It then obtains a handle using CreateFile(), and requests ioctl using DeviceIoControl() function. Let's say tomorrow a new USB device comes which has a driver only provided by the vendor. How does an application in the user space now communicate with the device? As you might have guessed, the vendor must provide a set of IOCTLs which the application can use.

References

  1. A more comprehensive list of language ID can be found in the following document - Link.
  2. USB in a nutshell has a ton of information written in a very accessible manner (in terms of difficulty to read). The following page on the site talks about USB descriptors - Link.