Parsing descriptors on the USB stack 

Like on Linux, you now need to determine the correct endpoint to perform I/O at so that you can open a pipe and submit a URB for processing. If you have one specific qualifying device, this may already be known to you. However, if this driver is more general purpose, or you wish to store or display information about any given device, you need to parse the USB stack and its appropriate descriptors. You can read more about descriptors on this USB Descriptors page.


USB stack

Note that in the io-usb implementation of the USB stack, Endpoint '0' for any given interface is reserved for control, and functions as such.

Descriptors are implemented as the following data types in libusbdi:

Descriptor data types

typedef struct usbd_device_descriptor {
    uint8_t                  bLength;
    uint8_t                  bDescriptorType;
    uint16_t                 bcdUSB;
    uint8_t                  bDeviceClass;
    uint8_t                  bDeviceSubClass;
    uint8_t                  bDeviceProtocol;
    uint8_t                  bMaxPacketSize0;
    uint16_t                 idVendor;
    uint16_t                 idProduct;
    uint16_t                 bcdDevice;
    uint8_t                  iManufacturer;
    uint8_t                  iProduct;
    uint8_t                  iSerialNumber;
    uint8_t                  bNumConfigurations;
} usbd_device_descriptor_t;

typedef struct usbd_configuration_descriptor {
    uint8_t                  bLength;
    uint8_t                  bDescriptorType;
    uint16_t                 wTotalLength;
    uint8_t                  bNumInterfaces;
    uint8_t                  bConfigurationValue;
    uint8_t                  iConfiguration;
    uint8_t                  bmAttributes;
    uint8_t                  MaxPower;
} usbd_configuration_descriptor_t;

typedef struct usbd_interface_descriptor {
    uint8_t                  bLength;
    uint8_t                  bDescriptorType;
    uint8_t                  bInterfaceNumber;
    uint8_t                  bAlternateSetting;
    uint8_t                  bNumEndpoints;
    uint8_t                  bInterfaceClass;
    uint8_t                  bInterfaceSubClass;
    uint8_t                  bInterfaceProtocol;
    uint8_t                  iInterface;
} usbd_interface_descriptor_t;

typedef struct usbd_endpoint_descriptor {
    uint8_t                    bLength;
    uint8_t                    bDescriptorType;
    uint8_t                    bEndpointAddress;
    uint8_t                    bmAttributes;
    uint16_t                   wMaxPacketSize;
    uint8_t                    bInterval;
} usbd_endpoint_descriptor_t;

Methods of parsing

To parse a device, you must have an attached usbd_device_instance_t available to parse. Once this is available, you can use the *_descriptor() functions to parse specific levels of the tree, which are shown below:

Parsing descriptors

/* Device level descriptor */
usbd_device_descriptor_t *usbd_device_descriptor(
    struct usbd_device *device,
    struct usbd_desc_node **node );

/* Configuration level descriptor */
usbd_configuration_descriptor_t *usbd_configuration_descriptor(
        struct usbd_device *device,
        uint8_t cfg,
        struct usbd_desc_node **node );

/* Interface level descriptor */
usbd_interface_descriptor_t *usbd_interface_descriptor(
       struct usbd_device *device,
       uint8_t cfg,
       uint8_t ifc,
       uint8_t alt,
       struct usbd_desc_node **node );

/* Endpoint level descriptor */
usbd_endpoint_descriptor_t *usbd_endpoint_descriptor(
       struct usbd_device *device,
       uint8_t config,
       uint8_t iface,
       uint8_t alt,
       uint8_t endpoint,
       struct usbd_desc_node **node );

The parameters represent the following:

device

An attached device to parse.

node

The address to a pointer where a reference to the found tree node will be stored. Can be used as a root node for searching in some functions.

cfg

(If applicable) The configuration ID to search for or through (stored in usbd_configuration_descriptor: bConfigurationValue).

ifc

(If applicable) The interface ID to search for or through (stored in usbd_interface_descriptor: bInterfaceNumber).

alt

(If applicable) The interface's alternate identifier to search for or through (stored in usbd_interface_descriptor: bAlternateSetting).

endpoint

(If applicable) The endpoint ID to search for (stored in usbd_endpoint_descriptor: bEndpointAddress).

When parsing, note the following:

  • Endpoint 0 is always the control endpoint, and will have a device entry in the tree while parsing

  • The amount of children for any given node can be queried:

    • usbd_configuration_descriptor: bConfigurationValue ranges from 0 to usbd_device_descriptor: bNumConfigurations - 1

    • usbd_interface_descriptor: bInterfaceNumber ranges from 0 to usbd_configuration_descriptor: bNumInterfaces - 1

    • usbd_endpoint_descriptor: bEndpointAddress ranges from 0 to usbd_interface_descriptor: bNumEndpoints - 1 (This will always be at least 1, and 0 is reserved for control)

Parsing descriptors 2

/* Generic parse */
usbd_descriptors_t *usbd_parse_descriptors(
       struct usbd_device *device,
       struct usbd_desc_node *root,
       uint8_t type,
       int index,
       struct usbd_desc_node **node );

When using the generic parse function, specify a type to search for and an index of which to return. Note that it returns NULL if no corresponding instance can be found.

device

The attached device to parse.

root

The root node to search from (gotten from another call's node).

type

One of USB_DESC_ENDPOINT, USB_DESC_INTERFACE, USB_DESC_DEVICE, and USB_DESC_CONFIGURATION.

index

The index of that type found.

node

The address to a pointer where a reference to the found tree node will be stored. Can be used as a root node for searching in some functions.

Examples

Find code examples for different use-cases of parsing below.

Parsing example: direct

/* Searching for a Specific Node */
/*    We can check down a specific path of the tree using the specific
 *    parsers as needed. For example, lets look at configuration 0, interface 1
 */
struct usbd_device *device;
// Attach Device Here - see above for info

usbd_interface_descriptor * interface_ptr;
struct usbd_desc_node *node;

/* look up interface */
interface_ptr = usbd_interface_descriptor(device, 0, 1, 0, &node);

/* Operation on node here */
if(interface_ptr == NULL){
    printf("Interface Not Found\n");
}else{
    /* Use desc to perform operations on the endpoint */
    printf("Interface Found\n-- bLength: %d\n-- bDescriptorType: %d\n-- bInterfaceNumber: %d\n-- bAlternateSetting: %d\n-- bNumEndpoints: %d\n-- bInterfaceClass: %d\n-- bInterfaceSubClass: %d\n-- bInterfaceProtocol: %d\n-- iInterface: %d\n\n",
            interface_ptr->bLength, interface_ptr->bDescriptorType, interface_ptr->bInterfaceNumber, interface_ptr->bAlternateSetting, interface_ptr->bNumEndpoints,
            interface_ptr->bInterfaceClass, interface_ptr->bInterfaceSubclass, interface_ptr->bInterfaceProtocol, interface_ptr->iInterface);
}

Parsing example: all of a type

/* Parsing All of a Type */
/*    Assuming we have an attached device and a handle, but do not have other information, we can quickly
 *    parse all nodes of a type using usbd_parse_descriptors.
 */

struct usbd_device *device;
// Attach Device Here - see above for info

usbd_endpoint_descriptor_t *desc;
struct usbd_desc_node *node;

/* We pass NULL to parse the tree from the root */
for (int endpoint_index = 0; (desc = (usbd_endpoint_descriptor_t*) usbd_parse_descriptors(device, NULL, USB_DESC_ENDPOINT,
                                            endpoint_index, &node)) != NULL, ++endpoint_index)
{
    /* Use desc to perform operations on the endpoint */
    printf("Endpoint @ Index %d\n-- bLength: %d\n-- bDescriptorType: %d\n-- bEndpointAddress: %d\n-- bmAttributes: %d\n-- wMaxPacketSize: %d\n-- bInterval: %d\n\n",
            endpoint_index, desc->bLength, desc->bDescriptorType, desc->bEndpointAddress, desc->bmAttributes, desc->wMaxPacketSize, desc->bInterval);
}

Parsing example: entire tree

/* Parsing the Entire tree */
/*     Assuming we have an attached device and its handle, but no other information, we can perform a depth-first
/*     search of the tree using the specific descriptors
 */

struct usbd_device *device;
//Attach device here - see above for info

usbd_device_descriptor_t *dev_desc;
struct usbd_desc_node *node;

/* Get the device's node */
dev_desc = usbd_device_descriptor(device, &node);

printf("Device %d %d\n", dev_desc->idVendor, dev_desc->idProduct);

/* Iterate through its configurations */
/*    Note that configurations range from 1 to n */
for(int c = 1; c <= dev_desc->bNumConfigurations; c++){
    usbd_configuration_descriptor_t *conf_desc;
    conf_desc = usbd_configuration_descriptor(device, c, &node);
    if(conf_desc == NULL) continue;
    printf("  Configuration %d\n", conf_desc->iConfiguration);

    /* Iterate though interfaces */
    /*    Note that interfaces range from 0 to n-1 */
    for(int i = 0; i < conf_desc->bNumInterfaces; i++){
        usbd_interface_descriptor_t *intf_desc;
        intf_desc = usbd_interface_descriptor(device, c, i, 0, &node);
        if(intf_desc == NULL) continue;
        printf("    Interface %d\n", intf_desc->bInterfaceNumber);

        /* Iterate through endpoints */
        /*    Note that endpoint 0 is control, while endpoints 1 to n are I/0. */
        for(int e = 0; e <= intf_desc->bNumEndpoints; e++){
            usbd_endpoint_descriptor_t *endp_desc;
            endp_desc = usbd_endpoint_descriptor(device, c, i, 0, e, &node);
            if(endp_desc == NULL) continue;
            printf("      Endpoint %d\n", endp_desc->bEndpointAddress);
        }
    }
}
Page updated: