How do you get the raw descriptor data from a USB HID device in Windows?
Asked Answered
T

2

7

How do you get the raw descriptor data from a HID device in Windows?

Background:

I need to get the Manufacturer, Product Name, and Serial Number from a HID device in Windows. I'm using hid.dll to access the devices using the functions seen here. My question is very similar to this one. I am able to get the manufacturer string and product string from SOME HID devices, but most fail to return this data with HidD_GetManufacturerString returning false. However, I KNOW these devices do have the string information in their descriptors because I am able to see it using USBTreeView.

The interesting thing is, even for the devices that do return manufacturer and product names, the values I'm getting through hid.dll are very different from the values I see using the above tool which gets the raw data from the USB device.

For example, an Xbox 360 controller:

Via USB Tree View:
Device Description       : Xbox 360 Controller for Windows
Language 0x0409          : "©Microsoft Corporation"
iProduct                 : 0x02
Language 0x0409          : "Controller"
iSerialNumber            : 0x03
Language 0x0409          : "0843806"

Via hid.dll using HidD_GetManufacturerString, HidD_GetProductString, and HidD_GetSerialNumberString:
Description              : HID-compliant game controller
Product                  : Controller (XBOX 360 Controller for Windows)
Manufacturer             : FAILS
Serial Number            : FAILS

WinUSB is unable to open these devices at all to retrieve this data as they do not use the winusb.sys driver.

1) I don't understand why the values returned by the HidD functions do not match the values in the USB descriptor. 2) I can't find any way to access the raw USB descriptor data for a HID device because I cannot access them with WinUSB.


Edit 1:

Okay, so I've learned a bit more about HID. It seems the data I'm getting through hid.dll is driver-specified data, not data coming from the USB device. HID can apply to devices on transports other than USB as well. So that's fine. Ultimately, what I really want to know is how can I get the USB device when I have the HID device and what API do I use for that. Besides WinUSB which doesn't work, the only thing I can find are kernel-level functions IOCTL. I don't know if that's suitable for a normal, non admin application.

Termitarium answered 4/10, 2014 at 10:1 Comment(2)
I think that the USB device will be the parent of the HID device (that's how it appears in Device Manager, at least on my system) so perhaps you could use CM_Get_Parent and associated functions?Sneaker
@HarryJohnston Thanks for the tip. Unfortunately, the parent is not always the USB device as some HID devices are nested several levels deep. Ultimately, the key was the CM functions though. I'll add an answer.Termitarium
T
2

I finally found the solution. The main problem was just associating a HID device to its parent USB device. This is the basic process:

Assuming you already have the HID device and the SP_DEVINFO_DATA for it:

  1. Enumerate all USB devices as seen here.
  2. Find all children of the USB devices with CM_GetChild and CM_GetSibling.
  3. Compare the known HID device's instance handle (SP_DEVINFO_DATA->DevInst) with each child device's instance handle that was returned by the CM functions to determine which USB device is the parent.
  4. From there, you can get any USB info you want including the descriptor.
Termitarium answered 13/10, 2014 at 18:45 Comment(4)
Can't you just call CM_Get_Parent repeatedly to follow the chain up until you find a USB device?Sneaker
@HarryJohnston You're probably right. I didn't think of that, mainly because I didn't really understand the relationship between USB devices and HID devices until after I went through the process. You'd also have to be able to identify which is the top-most USB device as there could be multiple. I'd guess you'd want to stop when you hit a hub and go -1.Termitarium
"From there, you can get any USB info you want including the descriptor." i'm trying to figure out how to do exactly this. When you say parent USB device, does that mean the Hub to which the HID is connected? Because even once I have the USB device instance handle, and the device path, I can't get the USB descriptor. It appears you need to call DeviceIoControl passing in the hub, and specifying the port that your device is connected to in order to get the descriptor. Is there some other way to get it just from the USB device instance handle?Bart
@Malaise: When you have a SP_DEVINFO_DATA, you can use SetupDiGetDeviceProperty calls to get the information. For example, manufacturer corresponds to DEVPKEY_Device_Manufacturer.Bellona
B
0

I did that here. There was a bunch of things you need to keep in mind - like composite USB device support. Main algo was something like this:

std::string usbHubInterface;
std::string compositeDeviceInstanceId;

for (std::string deviceInstanceId = GetParentDevice(hidDeviceInstanceId); !deviceInstanceId.empty(); deviceInstanceId = GetParentDevice(deviceInstanceId))
{
    std::string usbDeviceInterface = GetDeviceInterface(deviceInstanceId, &GUID_DEVINTERFACE_USB_DEVICE);
    if (!usbDeviceInterface.empty())
    {
        m_DeviceInterfacePath = usbDeviceInterface;
    }

    std::string usbHub = GetDeviceInterface(deviceInstanceId, &GUID_DEVINTERFACE_USB_HUB);
    if (!usbHub.empty())
    {
        usbHubInterface = usbHub;
        break;
    }

    // May be composite USB device. Save it for later use.
    if (usbDeviceInterface.empty()) 
    {
        compositeDeviceInstanceId = deviceInstanceId;
    }
}

if (usbHubInterface.empty())
{
    DBGPRINT("UsbDevice: cannot get parent USB hub interface");
    return;
}

if (!m_DeviceInterfacePath.empty())
{
    m_DeviceInstanceId = GetDeviceFromInterface(m_DeviceInterfacePath);

    DEVINST devNodeHandle = OpenDevNode(m_DeviceInstanceId);

    // Get device index in parent USB hub
    // https://learn.microsoft.com/windows-hardware/drivers/ddi/wdm/ns-wdm-_device_capabilities#usb
    m_UsbPortIndex = PropertyDataCast<ULONG>(GetDevNodeProperty(devNodeHandle, &DEVPKEY_Device_Address, DEVPROP_TYPE_UINT32));

    // Composite USB device
    if (IsCompositeUSBDevice(m_DeviceInstanceId))
    {
        // Need to acquire interface number in parent USB device
        // https://learn.microsoft.com/windows-hardware/drivers/usbcon/usb-common-class-generic-parent-driver
        if (!GetInterfaceNumber(compositeDeviceInstanceId, m_UsbInterfaceNumber))
        {
            DBGPRINT("UsbDevice: cannot get interface number from composite USB device");
            return;
        }
    }
}
else
{
    DBGPRINT("UsbDevice: cannot get parent USB device interface");
    return;
}

// Open device handle first to wake up device from S3 suspend state
ScopedHandle usbInterfaceHandle = OpenDeviceInterface(m_DeviceInterfacePath);
ScopedHandle hubInterfaceHandle = OpenDeviceInterface(usbHubInterface, true);

USB_DEVICE_DESCRIPTOR deviceDescriptor;
if (!GetDeviceDescriptor(hubInterfaceHandle, m_UsbPortIndex, deviceDescriptor))
    return;

m_VendorId = deviceDescriptor.idVendor;
m_ProductId = deviceDescriptor.idProduct;
m_VersionNumber = deviceDescriptor.bcdDevice;

// Assume that we are always using first configuration
const UCHAR configurationIndex = 0;
if (!GetFullConfigurationDescriptor(hubInterfaceHandle, m_UsbPortIndex, configurationIndex, m_ConfigurationDescriptor))
    return;

// Search for interface descriptor
PUSB_INTERFACE_DESCRIPTOR interfaceDescriptor = nullptr;
if (!SearchInterfaceDescriptor(m_ConfigurationDescriptor, m_UsbInterfaceNumber, interfaceDescriptor))
    return;

std::wstring stringBuffer;
// Get the array of supported Language IDs, which is returned in String Descriptor 0
if (!GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, 0, 0, stringBuffer))
    return;

// Use first supported language
USHORT languageID = stringBuffer[0];
if (GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, deviceDescriptor.iManufacturer, languageID, stringBuffer))
    m_Manufacturer = utf8::narrow(stringBuffer);

// Get interface name instead of whole product name, if present
UCHAR productStringIndex = interfaceDescriptor->iInterface ? interfaceDescriptor->iInterface : deviceDescriptor.iProduct;
if (GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, productStringIndex, languageID, stringBuffer))
    m_Product = utf8::narrow(stringBuffer);

if (GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, deviceDescriptor.iSerialNumber, languageID, stringBuffer))
    m_SerialNumber = utf8::narrow(stringBuffer);

// Get HID Descriptor
PHID_DESCRIPTOR hidDescriptor = nullptr;
if (!GetHidDescriptor(interfaceDescriptor, hidDescriptor))
    return;

// Get raw HID Report Descriptor
if (!GetHidReportDescriptor(hubInterfaceHandle, m_UsbPortIndex, hidDescriptor->DescriptorList[0].wReportLength, interfaceDescriptor->bInterfaceNumber, m_HidReportDescriptor))
{
    DBGPRINT("UsbDevice: cannot get raw HID Report Descriptor");
}
Betray answered 29/8, 2022 at 10:7 Comment(2)
For the standard descriptors you should be able to skip all this and just call SetupDiGetDeviceProperty. For example DEVPKEY_Device_ManufacturerBellona
Agree. But this code was written to experiment with getting USB_INTERFACE_DESCRIPTOR and HID_DESCRIPTORBetray

© 2022 - 2024 — McMap. All rights reserved.