What Is a USB Descriptor Overview?
The hierarchical structure of USB descriptors: Device, Configuration, Interface, Endpoint - wTotalLength, bDescriptorType, and what commonly breaks enumeration.
A descriptor is a set of structs a device uses to introduce itself to the host during enumeration. The host knows nothing about a device before reading its descriptors. Class, speed, endpoint count, transfer types, all of it comes from here.
On a first read of the spec, these descriptors look like a bunch of disconnected structs. But while debugging, it’s more useful to think of them as a tree: get a parent node wrong, and the host usually can’t make it to the child nodes at all.
1. The hierarchy
Device Descriptor (1)
└── Configuration Descriptor (1..n)
└── Interface Descriptor (1..n)
├── Class-specific Descriptor (HID, CDC...)
└── Endpoint Descriptor (1..n)
The Device Descriptor describes the device at the highest level: USB version, VID, PID, class, and number of configurations.
The Configuration Descriptor describes one operating mode for the device. Most devices
only have 1 configuration. It carries wTotalLength, the total byte size of the entire
configuration block, including every Interface and Endpoint Descriptor inside it.
The Interface Descriptor describes one “function” within a configuration. A composite device (CDC + HID, for example) has 2 interfaces. Each interface has its own class, subclass, and protocol.
The Endpoint Descriptor describes a single data channel belonging to an interface: the endpoint address, transfer type, max packet size, and polling interval.
2. The first two bytes of every descriptor
Every USB descriptor starts with the same two bytes:
Offset 0: bLength - the total byte size of this descriptor
Offset 1: bDescriptorType - what kind of descriptor this is
bDescriptorType:
0x01 = Device
0x02 = Configuration
0x03 = String
0x04 = Interface
0x05 = Endpoint
0x21 = HID
0x22 = HID Report Descriptor
The host uses bLength to know how many bytes to read for this descriptor, then
bDescriptorType to know how to parse it. Get either one wrong, and the host can’t
correctly parse anything after it.
3. wTotalLength: the field most likely to break things
Among the Configuration Descriptor’s fields, wTotalLength is a small field that causes far
more trouble than its size suggests.
wTotalLength is the total byte count of the entire configuration block, including the
Configuration Descriptor itself and every Interface, Endpoint, and class-specific descriptor
inside it.
wTotalLength = sizeof(Configuration Descriptor) 9 bytes
+ sizeof(Interface Descriptor) 9 bytes
+ sizeof(HID Descriptor) 9 bytes
+ sizeof(Endpoint Descriptor) 7 bytes
─────────────────────────────────────────────
34 bytes
4. What order does the host read descriptors in?
The exact order can vary by host, but here’s the common flow:
1. GET_DESCRIPTOR(Device, 8 bytes) learns bMaxPacketSize0
2. GET_DESCRIPTOR(Device, full) VID, PID, bNumConfigurations
3. GET_DESCRIPTOR(Configuration, 9 bytes) learns wTotalLength
4. GET_DESCRIPTOR(Configuration, full) the entire config block
5. GET_DESCRIPTOR(String, 0) the language ID list
6. GET_DESCRIPTOR(String, n) Manufacturer, Product, Serial...
The host typically reads the first 9 bytes of the Configuration Descriptor first, just to
learn wTotalLength, and only then requests the full configuration block. By the end of
step 4, the host has everything it needs to load a driver.
5. Class-specific descriptors
Some USB classes add their own descriptor between the Interface and Endpoint Descriptors. HID adds a 9-byte HID Descriptor carrying information about the HID Report Descriptor:
Interface Descriptor (bInterfaceClass = 0x03 = HID)
├── HID Descriptor (bDescriptorType = 0x21)
└── Endpoint Descriptor IN Interrupt
For HID, the host also reads the HID Report Descriptor separately, via GET_DESCRIPTOR
with descriptor type 0x22 on the HID interface. This is a descriptor defined by the HID
class itself, and doesn’t live directly inside the configuration block.
6. Common mistakes
| Item | Value | Note |
|---|---|---|
| Wrong wTotalLength | The host reads too little or too much, endpoints go unrecognized | |
| Wrong bLength in a single descriptor | The host misparses everything after it | |
| bNumEndpoints doesn't match the actual Endpoint Descriptors | Some endpoints get skipped | |
| Duplicate endpoint address/direction | Transfers fail with no clear reason | |
| Missing or misplaced HID Descriptor | The host never loads the HID driver |
When debugging descriptors, it’s worth remembering that this is a small field with an outsized effect on how the host understands everything that follows it.
Related posts
- What Is the USB Enumeration Process?
- What Are USB Transfer Types?
- What Is an HID Report Descriptor?
- USB Descriptor Field by Field, and the HID Report Descriptor (blog)
Public references
- USB 2.0 Specification, Chapter 9.6: Standard USB Descriptor Definitions
- USB in a NutShell, beyondlogic.org, Chapter 5: USB Descriptors
Notes from a real project
- CubeMX generates a default mouse descriptor even when HID Keyboard is selected. Always verify with USBView after enumeration, not by reading the source.
wTotalLengthhas to be recalculated by hand any time an interface is added. Astatic_assertcomparing it againstsizeof(array)catches this at build time.- A composite device needs
bDeviceClass=0xEF/0x02/0x01when using an IAD. Without this triplet, USBView reports*!*ERROR: device class should be Multi-interface Function 0xEF.
See more detail: post 3, USB Descriptor
Found this article useful?
Share, give feedback, or support if you find this content valuable.
Nội dung liên quan
Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.
What Is a USB Descriptor?
The concept and structure of USB descriptors, explained with real examples. Includes a look at how Boot Protocol fits into the Configuration Descriptor.
What Is the HID Keyboard Report Format?
The 8-byte structure of an HID keyboard report: the modifier byte, the reserved byte, and 6 keycode slots. How to send a report from STM32 middleware.
Finishing a Real HID Keyboard, from Keypress to Release
Turning key press and release events into HID reports and sending them to the host, plus what to watch out for with Boot Protocol and tap-style null reports.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.