Saturday 14 July 2018

Getting descriptors from a USB device (Windows) - PART 2 (Getting the device descriptor)

As explained in Part 1, the Device descriptor is the most fundamental descriptor a USB device has.

Getting USB Descriptor : Basics

Getting any descriptor from a USB device is a two step process. We first prepare an input buffer in which we put information about the descriptor we need to get, for ex. in the buffer we will tell the type of descriptor we want to get. We then pass the buffer to an IOCTL call, which fills the required output in an output buffer. The ioctl to be used for getting descriptors will be IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION.

IOCTL call
For the sake of simplicity, we will use the same buffer for input and output. This is how Microsoft's USBView program fetches descriptors. It is open sourced by Microsoft, and during my initial days of learning I found the source code to be immensely helpful, specially the enum.c file in the source, which fetches all the descriptors. Github Link.

The buffer consists of two parts - a part in which we specify the descriptor we want to get from the USB device, and a part in which the IOCTL call fills the required information. This information can be for eg. a Device descriptor if we are requesting it.
The first part is typically the same (not always), and consists of a structure called USB_DESCRIPTOR_REQUEST. The second part varies according to the descriptor we are requesting.
Out common buffer would look something like the following.

IOCTL Buffer
Conceptually, the location of a USB device is given by handle of the parent hub, and the port number of the hub to which the USB device is attached. Thus you will see that whenever an IOCTL call is made to get descriptor from a USB device we will always be using these two things for identifying the device.
Let's have a look at some of the important members of USB_DESCRIPTOR_REQUEST.

The USB_DESCRIPTOR_REQUEST Structure

This structure as discussed is used to tell the IOCTL call what information we require, and from which device. The following definition of the structure is copied from usbioctl.h file (located at c:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\shared\usbioctl.h on my computer).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
typedef struct _USB_DESCRIPTOR_REQUEST {
    ULONG ConnectionIndex;
    struct {
        UCHAR bmRequest;
        UCHAR bRequest;
        USHORT wValue;
        USHORT wIndex;
        USHORT wLength;
    } SetupPacket;
    UCHAR Data[0];
} USB_DESCRIPTOR_REQUEST, *PUSB_DESCRIPTOR_REQUEST;

  1. Connection Index : Port number of the USB hub to which a device is connected.
  2. bmRequest : It is a bit-field which specifies direction of request, type of request, and recipient. For getting USB descriptors this will always be 0x80.
  3. bRequest: The request being made. Since we are trying to get descriptors, this will be set to USB_REQUEST_GET_DESCRIPTOR. This is a macro which evaluates to 0x06.
  4. wValue : Request dependent parameter. For getting descriptors, this will specify the descriptor we want. Its format would be something like this.

    wValue = (DescryptorType << 8)  | descriptorIndex

    Descriptor index is used when we have several descriptors of the same kind. For ex. it will be used for getting string descriptors, interface descriptors, etc. Since there can be only one device descriptor in a device, the descriptor index will be zero. For getting Device Descriptor, the wValue field will be:

    wValue = (USB_DEVICE_DESCRIPTOR_TYPE << 8 | 0)
  5. wIndex : This is again a request dependent parameter. This will not be used when getting Device descriptors, but for ex. if we want to get string descriptors this field will specify the language ID.
  6. wLength : This in technical terms specifies the number of bytes to be transferred in the data phase of a control request. Do not get overwhelmed by it. In simpler terms this corresponds to the data we are expecting to get from the device. For ex. if we are trying to get Device descriptor, this field will be set to sizeof(USB_DEVICE_DESCRIPTOR). The term data phase relates to the mechanism of data transfer on the USB bus - how a transfer is divided into stages, phases, frames, etc. This is beyond the scope of this post. Refer USB Complete book by Jan Axelson.
  7. Data: Bytes following this member correspond to the output that the ioctl gives.

The SetupDi_ , and CM_ API in Windows

SetupDi_ API in windows is a set of API which can be used to interact with devices present on a system. For ex. If you want to get names of all the monitors connected to a system, use this; if you want to get information about all the USB devices present in the system, use this. Similarly the CM_ API can be used to get information about devices. 
Di, and CM specify the prefixes that the functions in the API sets have, ex. SetupDiGetClassDevs(), CM_Get_Parent(), etc.
To enumerate all the USB devices in the system, we will have to use the following code.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    /*A set containing info about all USB devices present on the system*/
    HDEVINFO usbDeviceInfoSet = SetupDiGetClassDevs(&GUID_CLASS_USB_DEVICE, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

    /*Iterate over the set to obtain info about each device in it*/
    SP_DEVINFO_DATA deviceData;
    deviceData.cbSize = sizeof(SP_DEVIFO_DATA);

    for(int i = 0; SetupDiEnumDeviceInfo(usbDeviceInfoSet, i, &deviceData); i++)
    {
        /*The tuple <usbDeviceInfoSet, deviceData> uniquely identifies a USB device in the system*/
    }

We will not be looking more into these API. The above code will be enough for further exploration. We will encounter more functions from these API sets as we progress.

Getting the Device Descriptor

Now that we have understood the basics, let us move to actually getting the device descriptor of a USB device. Steps 1 to 3 are for getting information from SetupDi_* API, and if already familiar with the API, you can directly jump to step 4.

Step 1: Get <usbDeviceInfoSet, deviceData> tuple

As we learned previously the tuple will uniquely identify a USB device. Use the code given above to enumerate through all devices, and get the tuples for them. To simplify things we will simply be getting device descriptors of all USB devices in the system.

Step 2: Get parent hub handle

We will use the deviceData member from the tuple to get DEVINST structure of the parent device. The parent device of a USB device will always be a HUB.
1
2
DEVINST parentDevInst=0;
 CM_Get_Parent(&parentDevInst, deviceInfoData.DevInst, 0);

Note that we used deviceInfoData.DevInst in the above code. Using parentDevInst we must get device path of the parent device. For this we must iterate through all the hubs on the system using the SetupDi_ APIs we used for USB devices, but this time using GUID_CLASS_USBHUB, instead of GUID_CLASS_USB_DEVICE. Once we have the <usbDeviceInfoSet, deviceData> tuple for the USB hubs, We will compare the DEVINST  with parentDevInst obtained above. For the hub which matches, we can use SetupDiGetDeviceInterfaceDetail() function to get SP_DEVICE_INTERFACE_DETAIL_DATA structure, which contains the device path.

Instead of going the longer way, I have used a shortcut (described here - stackoverflow.com) to get parent device path directly from parentDevInst. The complete code for getting parent hub handle for a USB device is the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    /*A set containing info about all USB devices present on the system*/
    HDEVINFO usbDeviceInfoSet = SetupDiGetClassDevs(&GUID_CLASS_USB_DEVICE, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

    /*Iterate over the set to obtain info about each device in it*/
    SP_DEVINFO_DATA deviceData;
    deviceData.cbSize = sizeof(SP_DEVINFO_DATA);

    for(int i = 0; SetupDiEnumDeviceInfo(usbDeviceInfoSet, i, &deviceData); i++)
    {
        /*The tuple <usbDeviceInfoSet, deviceData> uniquely identifies a USB device in the system*/

        /*Get parent hub handle*/
        DEVINST parentDevInst = 0;
        CM_Get_Parent(&parentDevInst, deviceData.DevInst, 0);
        wchar_t deviceId[MAX_PATH];
        CM_Get_Device_ID(parentDevInst, deviceId, MAX_PATH, 0);
        std::wstring devIdWStr(deviceId);

        //convert device id string to device path - https://stackoverflow.com/a/32641140/981766
        devIdWStr = std::regex_replace(devIdWStr, std::wregex(LR"(\\)"), L"#"); // '\' is special for regex
        devIdWStr = std::regex_replace(devIdWStr, std::wregex(L"^"), LR"(\\?\)", std::regex_constants::format_first_only);
        devIdWStr = std::regex_replace(devIdWStr, std::wregex(L"$"), L"#", std::regex_constants::format_first_only);

        constexpr int sz64 = 64;
        wchar_t guidString[sz64];//guid is 32 chars+4 hyphens+2 paranthesis+null => 64 should be more than enough
        StringFromGUID2(GUID_CLASS_USBHUB, guidString, sz64);
        devIdWStr.append(guidString);

        std::wstring& usbHubPath = devIdWStr; //devIdWStr now contains USB hub path
        HANDLE hUsbHub = CreateFile(usbHubPath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

        /*hUsbHub now contains the handle to the parent hub of a device*/

    }

Note that this is a modified version of code presented earlier to enumerate USB devices in the system. Also, note that this is a shortcut, a hack, and therefore is not guaranteed to work always. A better (albeit with poorer performance) is the one discussed at the beginning of this step.


Step 3: Get USB port number 

Getting the port number to which a USB device is connected on a hub (the handle to which we obtained in the previous step) is fairly easy. We just have to use SetupDiGetDeviceRegistryProperty() function. Add the following code to the code shown in previous step. To make it easier to understand where this code has to be pasted, the comment "/*hUsbHub now contains the handle to the parent hub of a device*/" is pasted as is from previous code


1
2
3
4
5
6
7
        /*hUsbHub now contains the handle to the parent hub of a device*/

        /*Get port number to which the usb device is attached on the hub*/
        DWORD usbPortNumber = 0, requiredSize = 0;
        SetupDiGetDeviceRegistryProperty(usbDeviceInfoSet, &deviceData, SPDRP_ADDRESS, nullptr, (PBYTE)&usbPortNumber, sizeof(usbPortNumber), &requiredSize);

        /*We now have the port number*/


Step 4: Prepare USB request packet (USB_DESCRIPTOR_REQUEST)

Prepare USB_DESCRIPTOR_REQUEST structure by filling in required information. We have already discussed fields of the structure and the buffer used to send data to and get data from IOCTL, hence we can directly move to code. Again the comment " /*We now have the port number*/" from previous code is kept to give a context of where the following piece of code lies.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
        /*We now have the port number*/

        /*Prepare USB request packet (USB_DESCRIPTOR_REQUEST)*/
        USB_DESCRIPTOR_REQUEST* requestPacket = nullptr;
        USB_DEVICE_DESCRIPTOR* deviceDescriptor = nullptr;
        int bufferSize = sizeof(USB_DESCRIPTOR_REQUEST) + sizeof(USB_DEVICE_DESCRIPTOR);
        BYTE *buffer = new BYTE[bufferSize];

        /*We know from out previous discussion that the first part of the buffer contains the request packet, and latter part contains the data to be filled by the IOCTL - in our case the device descriptor*/
        requestPacket = (USB_DESCRIPTOR_REQUEST*)buffer;
        deviceDescriptor = (USB_DEVICE_DESCRIPTOR*)((BYTE*)buffer + sizeof(USB_DESCRIPTOR_REQUEST));

        //fill information in packet
        requestPacket->SetupPacket.bmRequest = 0x80;
        requestPacket->SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR;
        requestPacket->ConnectionIndex = usbPortNumber;
        requestPacket->SetupPacket.wValue = (USB_DEVICE_DESCRIPTOR_TYPE << 8 | 0 /*Since only 1 device descriptor => index : 0*/);
        requestPacket->SetupPacket.wLength = sizeof(USB_DEVICE_DESCRIPTOR);

Step 5 : Issue IOCTL, and print some data 

Now we simply have to issue the ioctl, passing the prepared request packet, and data buffer.


1
2
3
4
5
6
7
        /*Issue ioctl*/
        DWORD bytesReturned = 0;
        BOOL err = DeviceIoControl(hUsbHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, buffer, bufferSize, buffer, bufferSize, &bytesReturned, nullptr);

        /*print some data*/
        std::cout << "0x" << std::hex << (int)deviceDescriptor->bDescriptorType << std::endl;  //should be 0x01 for device descriptor
        std::cout << "0x" << std::hex << (int)deviceDescriptor->bDeviceClass << std::endl;

Complete code

The complete code, consisting of all the individual code fragments we have developed so far is the following. Note that before running this code you must add Setupapi.lib in Linker's Additional dependencies. The application I built, and for which the code is given is a Native console application written in C++.

My system configuration :
                   Visual Studio 2017 Professional
                   Windows 10 14393 build



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// USBGetDeviceDescriptor.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <SetupAPI.h>
#include <cfgmgr32.h >
#include <initguid.h>
#include <usbiodef.h>
#include <usbioctl.h>
#include <regex>

void USBGetDeviceDescriptor()
{
    /*A set containing info about all USB devices present on the system*/
    HDEVINFO usbDeviceInfoSet = SetupDiGetClassDevs(&GUID_CLASS_USB_DEVICE, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

    /*Iterate over the set to obtain info about each device in it*/
    SP_DEVINFO_DATA deviceData;
    deviceData.cbSize = sizeof(SP_DEVINFO_DATA);

    for(int i = 0; SetupDiEnumDeviceInfo(usbDeviceInfoSet, i, &deviceData); i++)
    {
        /*The tuple <usbDeviceInfoSet, deviceData> uniquely identifies a USB device in the system*/

        /*Get parent hub handle*/
        DEVINST parentDevInst = 0;
        CM_Get_Parent(&parentDevInst, deviceData.DevInst, 0);
        wchar_t deviceId[MAX_PATH];
        CM_Get_Device_ID(parentDevInst, deviceId, MAX_PATH, 0);
        std::wstring devIdWStr(deviceId);

        //convert device id string to device path - https://stackoverflow.com/a/32641140/981766
        devIdWStr = std::regex_replace(devIdWStr, std::wregex(LR"(\\)"), L"#"); // '\' is special for regex
        devIdWStr = std::regex_replace(devIdWStr, std::wregex(L"^"), LR"(\\?\)", std::regex_constants::format_first_only);
        devIdWStr = std::regex_replace(devIdWStr, std::wregex(L"$"), L"#", std::regex_constants::format_first_only);

        constexpr int sz64 = 64;
        wchar_t guidString[sz64];//guid is 32 chars+4 hyphens+2 paranthesis+null => 64 should be more than enough
        StringFromGUID2(GUID_CLASS_USBHUB, guidString, sz64);
        devIdWStr.append(guidString);

        std::wstring& usbHubPath = devIdWStr; //devIdWStr now contains USB hub path
        HANDLE hUsbHub = CreateFile(usbHubPath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

        /*hUsbHub now contains the handle to the parent hub of a device*/

        /*Get port number to which the usb device is attached on the hub*/
        DWORD usbPortNumber = 0, requiredSize = 0;
        SetupDiGetDeviceRegistryProperty(usbDeviceInfoSet, &deviceData, SPDRP_ADDRESS, nullptr, (PBYTE)&usbPortNumber, sizeof(usbPortNumber), &requiredSize);

        /*We now have the port number*/

        /*Prepare USB request packet (USB_DESCRIPTOR_REQUEST)*/
        USB_DESCRIPTOR_REQUEST* requestPacket = nullptr;
        USB_DEVICE_DESCRIPTOR* deviceDescriptor = nullptr;
        int bufferSize = sizeof(USB_DESCRIPTOR_REQUEST) + sizeof(USB_DEVICE_DESCRIPTOR);
        BYTE *buffer = new BYTE[bufferSize];

        /*We know from out previous discussion that the first part of the buffer contains the request packet, and latter part contains the data to be filled by the IOCTL - in our case the device descriptor*/
        requestPacket = (USB_DESCRIPTOR_REQUEST*)buffer;
        deviceDescriptor = (USB_DEVICE_DESCRIPTOR*)((BYTE*)buffer + sizeof(USB_DESCRIPTOR_REQUEST));

        //fill information in packet
        requestPacket->SetupPacket.bmRequest = 0x80;
        requestPacket->SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR;
        requestPacket->ConnectionIndex = usbPortNumber;
        requestPacket->SetupPacket.wValue = (USB_DEVICE_DESCRIPTOR_TYPE << 8 | 0 /*Since only 1 device descriptor => index : 0*/);
        requestPacket->SetupPacket.wLength = sizeof(USB_DEVICE_DESCRIPTOR);

        /*Issue ioctl*/
        DWORD bytesReturned = 0;
        BOOL err = DeviceIoControl(hUsbHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, buffer, bufferSize, buffer, bufferSize, &bytesReturned, nullptr);

        /*print some data*/
        std::cout << "0x" << std::hex << (int)deviceDescriptor->bDescriptorType << std::endl;  //should be 0x01 for device descriptor
        std::cout << "0x" << std::hex << (int)deviceDescriptor->bDeviceClass << std::endl;


    }
    
}

int main()
{
    USBGetDeviceDescriptor();
    return 0;
}

Output

Here is the annotated output from one run of the application on my system.
Sample run

References

  1. USB request data structure explained on USB in a nutshell - https://www.beyondlogic.org/usbnutshell/usb6.shtml
  2.  Information on data stage, and related stuff in chapter 5 of USB COMPLETE book (5th edition) by Jan Axelson. Must read this book if you are developing any non trivial USB application.
  3. Getting handle to parent hub directly from <usbDeviceInfoSet, deviceData> tuple of a device - https://stackoverflow.com/q/28007468/981766
  4. SPDRP_ADDRESS property gives the port number in case of USB devices is mentioned here - https://web.archive.org/web/20100109085245/http://msdn.microsoft.com/en-us/library/dd852021.aspx
  5. A list of USB class codes (till USB 1.0) - http://www.usb.org/developers/defined_class

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.