USB Enumeration and Descriptors
How a device that just appeared on the wire tells the host what it is — the descriptor hierarchy, the standard control requests that read it, and the reset-address-configure dance that ends with a driver bound
When a USB device is plugged in, the host knows nothing about it — not its speed, not its identity, not what it can do. Enumeration is the conversation over the mandatory control endpoint (endpoint 0) that turns an anonymous newly-attached device into an addressed, configured device with drivers bound to its interfaces. Everything the host learns comes from the device's descriptors.
The descriptor hierarchy
A device describes itself as a tree of fixed-format structures (defined in the kernel in include/uapi/linux/usb/ch9.h, mirroring "Chapter 9" of the USB specification):
Device descriptor idVendor, idProduct, bDeviceClass,
│ bMaxPacketSize0, bNumConfigurations
└── Configuration descriptor bNumInterfaces, bmAttributes (self/bus powered),
│ bMaxPower
├── Interface descriptor bInterfaceClass/SubClass/Protocol,
│ │ bNumEndpoints, bAlternateSetting
│ ├── Endpoint descriptor bEndpointAddress (number + IN/OUT),
│ │ bmAttributes (transfer type),
│ │ wMaxPacketSize, bInterval
│ └── Endpoint descriptor ...
└── Interface descriptor ...
- Device descriptor — one per device. Carries
idVendor/idProduct(the identity drivers match on), the device class, andbMaxPacketSize0(how big endpoint 0's packets are — needed before anything else can be read reliably). - Configuration descriptor — a complete power/feature profile. Most devices have exactly one; only one is ever active. Reading it returns the interface and endpoint descriptors packed after it in one contiguous blob.
- Interface descriptor — one function. Its
bInterfaceClass(mass storage, HID, CDC, audio…) is what usbcore matches drivers against. Interfaces can have alternate settings — different endpoint arrangements selected at runtime (isochronous devices use this to switch bandwidth, e.g. a webcam picking a resolution). - Endpoint descriptor — one data pipe: its address and direction, its transfer type, its max packet size, and
bInterval(the polling period for interrupt/isochronous endpoints). - String descriptors — optional human-readable UTF-16 strings (manufacturer, product, serial) referenced by index.
Class drivers layer their own class-specific descriptors into this tree — for example the CDC union descriptor, or the short HID descriptor — which the class driver, not usbcore, interprets. Some class data lives outside the tree entirely: a HID device's actual report descriptor isn't packed into the configuration blob but fetched separately with a class-specific GET_DESCRIPTOR(Report).
The standard control requests
Enumeration is driven entirely by standard device requests on endpoint 0 — an 8-byte SETUP packet the host sends, defined once for every USB device:
| Request | What it does |
|---|---|
GET_DESCRIPTOR |
Read a descriptor (device, configuration+children, string) |
SET_ADDRESS |
Assign the device its unique bus address |
SET_CONFIGURATION |
Activate a configuration (this is what "turns the device on") |
SET_INTERFACE / GET_INTERFACE |
Select or query an interface's alternate setting |
GET_STATUS, CLEAR_FEATURE, SET_FEATURE |
Halt/unhalt endpoints, remote-wakeup, etc. |
These are the same requests usb_control_msg() issues, and they work before any driver exists — endpoint 0 is the one pipe guaranteed to be present the instant a device attaches.
The sequence
The hub driver, not the target device, orchestrates this:
- Attach + debounce. A downstream hub port signals a status change; the hub driver debounces it (a device isn't "there" until the line is stable).
- Speed detection. Before any packet, the idle pull-up resistors reveal the basic speed — D+ pulled up = full-speed, D− = low-speed (USB 3 uses separate SuperSpeed lanes). A high-speed-capable device attaches as full-speed and only negotiates high-speed via a chirp handshake during the reset in the next step.
- Reset. The host resets the port. The device wakes at the default address 0.
- First read. The host does
GET_DESCRIPTOR(device)to learnbMaxPacketSize0— it must know endpoint 0's packet size before it can reliably read anything longer. - Address.
SET_ADDRESSmoves the device off address 0 to a unique address (1–127). Because only one device can sit at address 0 at a time, hubs enumerate their ports one at a time — this serialization is why plugging in a big hub full of devices takes a visible moment. - Full read. With a private address, the host reads the full device descriptor, then each configuration descriptor (and its packed interface/endpoint descriptors), then strings.
- Configure.
SET_CONFIGURATIONactivates the chosen configuration. The device's endpoints (beyond 0) now exist and are usable. - Bind. usbcore matches each interface against registered drivers and calls the winner's
->probe().
How drivers match
A USB driver advertises what it handles with a usb_device_id table, matched during step 8:
static const struct usb_device_id my_ids[] = {
{ USB_DEVICE(0x046d, 0xc52b) }, /* exact vendor+product */
{ USB_INTERFACE_INFO(USB_CLASS_HID, 0, 0) }, /* any device of a class */
{ } /* terminator */
};
MODULE_DEVICE_TABLE(usb, my_ids);
Matching on USB_DEVICE binds one specific product; matching on USB_INTERFACE_INFO (class/subclass/protocol) is how generic class drivers like usb-storage and usbhid claim any device that speaks their class. MODULE_DEVICE_TABLE exports this table so a plugged-in device's IDs can auto-load the right module via the modalias mechanism.
The whole enumerated tree is then visible from user space under /sys/bus/usb/devices/ and via lsusb -v.
Further reading
- USB overview — the stack, the device model, and the transfer types these endpoints use
- Kernel docs: the USB core API — descriptors, interfaces, and driver binding
- Kernel docs: writing USB device drivers — the probe/
usb_device_idpath in practice