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