USB Device Enumeration
When you plug a device into a Universal Serial Bus (USB) port, the kernel must discover it from scratch: a USB device announces nothing by itself, so the kernel detects the electrical connection, resets the port, assigns the device an address, and then interrogates it by reading a hierarchy of standard data structures called descriptors — first the device descriptor, then a configuration, the interfaces within that configuration, and the endpoints within each interface (Linux USB host-side API docs). The kernel builds a
struct usb_devicefor the whole device and onestruct usb_interfaceper interface, and — crucially — USB drivers bind to interfaces, not to whole devices (“Think of them as ‘interface drivers’”). Matching is driven by astruct usb_device_idtable that each driver exports withMODULE_DEVICE_TABLE(usb, ...), consulted by theusbbus_type’s.matchcallback. This note traces the plug-in handshake end to end against the Linux 6.12 source, explains the descriptor tree, and contrasts USB’s hot-plug, descriptor-driven model with the config-space probing of PCI.
Mental Model — A Tree You Walk, and a Catalog You Read
USB is two ideas at once. Physically it is a tree: the host controller is the root, hubs are interior branch nodes, and peripherals are leaves. As the kernel docs put it, “USB uses a tree structure, with the host as the root (the system’s master), hubs as interior nodes, and peripherals as leaves” (usb.html). Logically, each leaf device is a nested catalog of descriptors the kernel reads to learn what the device is and can do. Enumeration is the process of (a) walking the tree to find a newly connected leaf and (b) reading its catalog so a driver can be matched.
The single most important consequence of the logical model is the device/interface split. One physical device may bundle several independent functions — a webcam that is simultaneously a camera (video interface) and a microphone (audio interface) is one usb_device with two usb_interfaces, each potentially driven by a different kernel driver. That is why Linux drivers attach at the interface level.
flowchart TB subgraph TREE["Physical topology (the tree you walk)"] HC["Host Controller<br/>(xHCI/EHCI — a PCI device)"] --> RH["Root Hub"] RH --> H1["External Hub"] RH --> D1["Device A"] H1 --> D2["Device B"] H1 --> D3["Device C"] end subgraph CATALOG["Logical descriptors (the catalog you read)"] DEV["Device Descriptor<br/>idVendor/idProduct<br/>bNumConfigurations"] CFG["Configuration<br/>bNumInterfaces<br/>(only ONE active)"] IF["Interface<br/>bInterfaceClass<br/>(a DRIVER binds HERE)"] EP["Endpoint<br/>bulk / interrupt /<br/>control / isochronous"] DEV --> CFG --> IF --> EP end D2 -.->|"read via control transfers"| DEV
The two faces of USB enumeration. What it shows (left): a hardware tree rooted at the host controller; hubs branch, devices are leaves. What it shows (right): each device exposes a four-level descriptor hierarchy — device → configuration → interface → endpoint — which the kernel reads over the bus. The insight to take: the kernel walks the tree to find a device, then reads its catalog to understand it; drivers attach at the interface node, which is why a single multi-function device can be served by several drivers at once.
The Plug-In Handshake, Step by Step
Everything in USB enumeration is driven by hubs, because only a hub (including the root hub built into the host controller) can detect that a device has been connected to one of its ports and report port status. The kernel’s hub driver runs a per-hub work item, hub_event() (registered as INIT_WORK(&hub->events, hub_event) in drivers/usb/core/hub.c), which is woken whenever a port’s status changes.
1. Connect detection. When a device is plugged in, the hub hardware sets a connect-change bit on the port. The hub driver, polling/notified via its status interrupt endpoint, sees the change and ultimately calls hub_port_connect() (v6.12 hub.c, ~line 5344) for that port.
2. Debounce and port reset. The hub driver first debounces the connection (a device must be stably present), then issues a port reset via hub_port_reset(). Reset is mandatory: it forces the device into a known Default state in which it responds to the reserved address 0. The kernel marks this with usb_set_device_state(udev, USB_STATE_DEFAULT). The reset also lets the hub report the device’s speed (low/full/high/super), since that is signaled electrically during reset.
3. Address assignment. A freshly reset device listens on address 0, but only one device can use address 0 at a time, so the kernel must give it a unique address quickly. This happens inside hub_port_init() (v6.12 hub.c, ~line 4856), whose comment states it “Returns device in USB_STATE_ADDRESS, except on error.” The core loop tries hub_set_address(udev, devnum):
for (operations = 0; operations < SET_ADDRESS_TRIES; ++operations) {
retval = hub_set_address(udev, devnum);
if (retval >= 0)
break;
msleep(200);
}hub_set_address() sends the standard USB_REQ_SET_ADDRESS (request code 0x05, from include/uapi/linux/usb/ch9.h) control transfer, then on success calls usb_set_device_state(udev, USB_STATE_ADDRESS). From this point the device answers on its private address devnum and address 0 is free for the next plug-in.
4. Reading the device descriptor. Even before assigning the address (in the modern “new scheme”), the kernel reads the first 8 bytes of the device descriptor to learn bMaxPacketSize0 — the maximum packet size of endpoint 0, the control endpoint — because every subsequent control transfer needs that value. hub_port_init() comments: “we start with SET_ADDRESS and then try to read the first 8 bytes of the device descriptor to get the ep0 maxpacket value.” After addressing, it reads the full 18-byte device descriptor:
descr = usb_get_device_descriptor(udev);
...
if (initial)
udev->descriptor = *descr;Under the hood every one of these reads is a usb_control_msg(... USB_REQ_GET_DESCRIPTOR ...) (0x06) on the default control pipe — you can see dozens of such usb_control_msg calls throughout hub.c.
5. Reading configurations, interfaces, endpoints. With the device descriptor in hand the kernel knows bNumConfigurations. For each, it reads the configuration descriptor (USB_REQ_GET_DESCRIPTOR with type USB_DT_CONFIG), which arrives as one contiguous blob — the config descriptor followed by all of its interface and endpoint descriptors (the config’s wTotalLength covers the whole blob). The kernel parses this into struct usb_host_config, populating the interface[] and per-interface endpoint arrays. This descriptor-reading phase is usb_enumerate_device(), called from usb_new_device():
int usb_new_device(struct usb_device *udev)
{
...
err = usb_enumerate_device(udev); /* Read descriptors */
...
err = device_add(&udev->dev); /* register with the driver model */
}6. Choosing and setting a configuration. A USB device may advertise several configurations but only one can be active. The kernel picks one (usb_choose_configuration(), considering available bus power and functionality) and activates it with a USB_REQ_SET_CONFIGURATION (0x09) control transfer. Only then do the configuration’s interfaces become live usb_interface objects.
7. Registration and announcement. usb_new_device() calls announce_device() (which logs the familiar usb 1-1: new high-speed USB device number N) and device_add(&udev->dev), which inserts the device into the unified device model graph on the usb bus. For each interface in the chosen configuration the core registers a separate struct usb_interface whose embedded struct device is a child of the usb_device. Adding each of these to the bus is what triggers matching against registered USB drivers.
sequenceDiagram participant HW as Device hardware participant Hub as Hub driver (hub_event) participant Core as USB core Hub->>Hub: connect-change bit set on port Hub->>HW: port reset → device in DEFAULT state (addr 0) Hub->>HW: GET_DESCRIPTOR (first 8 bytes) → bMaxPacketSize0 Hub->>HW: SET_ADDRESS(devnum) → device in ADDRESS state Hub->>HW: GET_DESCRIPTOR (full 18-byte device descriptor) Core->>HW: GET_DESCRIPTOR (each configuration + its interfaces/endpoints) Core->>HW: SET_CONFIGURATION(value) → interfaces go live Core->>Core: device_add(usb_device) + register one usb_interface per interface Core->>Core: bus .match() each interface against driver id_tables → probe()
The enumeration handshake as a message sequence. What it shows: the strict order — detect, reset, peek at ep0 size, address, read the descriptor tree, set a configuration, then register with the device model. The insight to take: addressing must precede full interrogation (a device on address 0 is anonymous), and interfaces, not the device, are what get matched to drivers in the final step.
The Descriptor Hierarchy in Detail
The standard descriptors are defined byte-for-byte in include/uapi/linux/usb/ch9.h (named for USB spec Chapter 9, “USB Device Framework”). Understanding them is understanding what enumeration actually reads.
Device descriptor (USB_DT_DEVICE, type 0x01, 18 bytes). One per device. Its key fields (verbatim from struct usb_device_descriptor): bcdUSB (the USB spec version the device speaks), bDeviceClass/bDeviceSubClass/bDeviceProtocol, bMaxPacketSize0 (ep0 max packet), idVendor and idProduct (the Vendor:Product ID pair, e.g. 0x046d:0xc52b), bcdDevice (device release number), and bNumConfigurations. idVendor/idProduct are the primary keys most drivers match on.
Configuration descriptor (USB_DT_CONFIG, type 0x02, 9 bytes header). From struct usb_config_descriptor: wTotalLength (total length of this config plus all its interface/endpoint descriptors — the size of the blob fetched in one transfer), bNumInterfaces, bConfigurationValue (the value passed to SET_CONFIGURATION), bmAttributes (self-powered? remote-wakeup?), and bMaxPower (bus current draw in 2 mA units). The kernel comment in usb.h is explicit: “USB devices may have multiple configurations, but only one can be active at any time.”
Interface descriptor (USB_DT_INTERFACE, type 0x04, 9 bytes). From struct usb_interface_descriptor: bInterfaceNumber, bAlternateSetting, bNumEndpoints, and bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol. The class codes are standardized (also in ch9.h): USB_CLASS_HID 3, USB_CLASS_MASS_STORAGE 8, USB_CLASS_HUB 9, USB_CLASS_VIDEO 0x0e, USB_CLASS_VENDOR_SPEC 0xff, and so on. An interface is the unit a driver binds to, and a single interface can have several alternate settings (different endpoint configurations, e.g. for different bandwidths) — represented in the kernel as usb_interface.altsetting[] with cur_altsetting pointing at the active one.
Endpoint descriptor (USB_DT_ENDPOINT, type 0x05, 7 bytes). From struct usb_endpoint_descriptor: bEndpointAddress (number plus IN/OUT direction), bmAttributes (transfer type: control, isochronous, bulk, or interrupt), wMaxPacketSize, and bInterval (polling interval for interrupt/isochronous). Endpoints are the actual data sinks/sources; an interface’s bNumEndpoints lists how many it owns (endpoint 0 is implicit and shared — it is the bidirectional control endpoint every device has).
The in-kernel data structures mirror this tree exactly: a struct usb_device holds struct usb_device_descriptor descriptor, a pointer to the active struct usb_host_config *actconfig, and actconfig->interface[] is the array of struct usb_interface * (one per interface), each of which carries altsetting[] arrays of struct usb_host_interface, each holding its endpoint array. The usb_host_config struct caps interfaces at USB_MAXINTERFACES (32) and endpoints at USB_MAXENDPOINTS (30) per usb.h.
Why Drivers Bind to Interfaces, Not Devices
This is the design decision that most often trips up newcomers from the PCI world. The kernel docs state it flatly: “USB device drivers actually bind to interfaces, not devices.” The reason is multi-function devices — “complex devices may expose multiple interfaces, each requiring potentially different drivers.” A USB headset is audio-out + audio-in + HID volume keys: three interfaces, served by the generic USB-audio driver and the HID driver simultaneously, on one physical device.
This split is reflected in two driver structures and two match paths in drivers/usb/core/driver.c. The common case is struct usb_driver, whose probe takes an interface:
struct usb_driver {
const char *name;
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);
...
const struct usb_device_id *id_table;
struct device_driver driver;
...
};The rarer struct usb_device_driver (whose probe takes a whole struct usb_device *) is for code that must own the entire device — the prime example is the generic USB driver that handles configuration selection before any interface driver runs. The usb bus’s .match callback, usb_device_match(), dispatches on which kind of object it is looking at:
static int usb_device_match(struct device *dev, const struct device_driver *drv)
{
if (is_usb_device(dev)) {
/* interface drivers never match devices */
if (!is_usb_device_driver(drv))
return 0;
...
return usb_driver_applicable(udev, udrv);
} else if (is_usb_interface(dev)) {
/* device drivers never match interfaces */
if (is_usb_device_driver(drv))
return 0;
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
id = usb_match_id(intf, usb_drv->id_table);
if (id)
return 1;
...
}
return 0;
}The two if (is_usb_device_driver(drv)) return 0; guards are the whole point: a device-level driver is never offered an interface, and an interface driver is never offered the whole device. This is the mechanical enforcement of “drivers bind to interfaces.”
Matching: usb_device_id and MODULE_DEVICE_TABLE
Matching compares the device/interface descriptors against each driver’s id_table of struct usb_device_id entries. Every entry has a match_flags bitmask saying which fields it cares about, and the comparison only checks the flagged fields. The interface-level part of the check, usb_match_one_id_intf() in driver.c, reads almost like the descriptor fields one for one:
if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
(id->bInterfaceClass != intf->desc.bInterfaceClass))
return 0;
if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) &&
(id->bInterfaceSubClass != intf->desc.bInterfaceSubClass))
return 0;
if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) &&
(id->bInterfaceProtocol != intf->desc.bInterfaceProtocol))
return 0;
...
return 1;and the device-level part, usb_match_device(), compares idVendor, idProduct, bcdDevice (with _lo/_hi range bounds), and bDeviceClass/SubClass/Protocol. A driver thus has two matching styles: specific (match an exact Vendor:Product pair — the right choice for a quirky vendor device) or generic (match a class like “any HID interface” or “any mass-storage interface” — how the standard class drivers cover thousands of devices they have never heard of). Helper macros build the table entries: USB_DEVICE(vid, pid) for a specific match, USB_INTERFACE_INFO(class, subclass, proto) for a class match.
The table is exported with MODULE_DEVICE_TABLE(usb, my_id_table). This macro does double duty. In-kernel, the table is what usb_match_id() walks. Out-of-kernel, the build system extracts the table into a per-module modalias list so that userspace can auto-load the right driver on hot-plug without that module being resident. The hotplug docs explain: “When the USB subsystem knows about a driver’s device ID table, it’s used when choosing drivers to probe()… It will only call probe() if there is a match, and the third argument to probe() will be the entry that matched” (hotplug.html). When a device is enumerated the kernel emits a uevent carrying a MODALIAS=usb:v....p.... string built from the descriptors; udev/systemd matches that against modules.alias (built by depmod from every module’s MODULE_DEVICE_TABLE) and modprobes the matching driver, whose registration then triggers the bus match and probe(). The docs also note the fallback: “If you don’t provide an id_table for your driver, then your driver may get probed for each new device; the third parameter to probe() will be NULL.”
The usb bus itself is a single struct bus_type (drivers/usb/core/driver.c):
const struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match,
.uevent = usb_uevent,
.need_parent_lock = true,
};— so all the generic driver-model machinery applies, and USB-specific behavior is entirely in these three callbacks. (See Device-Driver Matching for the general match→probe flow this specializes.)
How This Differs from PCI
USB and PCI both ultimately deposit a struct device into the same model graph, but their enumeration could hardly be more different — and the contrasts are instructive (deep PCI mechanics in PCI and PCIe Enumeration):
- Hot-plug vs. boot-time scan. USB is hot-plug by design: devices appear and vanish at any moment, and a hub interrupt drives discovery. PCI is classically enumerated once at boot by recursively scanning bus/device/function numbers (PCIe adds hot-plug, but it is the exception, not the rule).
- Descriptor-read vs. config-space. A USB device is interrogated by reading standard descriptors over the wire with control transfers. A PCI device is identified by memory-mapped config-space registers (vendor/device ID at fixed offsets) that the CPU reads directly — no handshake, no addressing step.
- Addressing. USB devices start anonymous (address 0) and are assigned an address during enumeration. PCI devices already have a fixed bus:device.function (BDF) topological address; there is nothing to assign.
- Interface vs. function binding. USB drivers bind to interfaces of a device; PCI drivers bind to a whole function (PCI’s multi-function devices expose up to 8 functions, each its own
pci_dev, but a driver claims a function, not a sub-part of it). - The host controller is itself a PCI device. The USB host controller (xHCI/EHCI) is enumerated by PCI first; USB enumeration runs on top of a PCI device. So on a typical PC the two schemes are layered, not parallel.
Failure Modes and Diagnosis
The signature failure is “device not accepting address”: hub_set_address exhausts its retries and hub.c logs device not accepting address N, error -110 (-ETIMEDOUT). This usually means the device or cable cannot complete the reset/address handshake — bad cable, marginal power, or a flaky device. A related one is descriptor read failures (device descriptor read/64, error -71), where the device resets but its control endpoint misbehaves; the kernel has elaborate retry and quirk machinery (USB_QUIRK_*) precisely because many real devices violate the spec during enumeration. Over-current / insufficient power shows up as the hub refusing to power the port. And a device that enumerates fine but binds no driver (visible in lsusb -t as an interface with no driver) means no id_table matched — either the device needs an out-of-tree or newer driver, or the interface class is one Linux does not handle. The first diagnostic stop is dmesg (the hub_event/hub_port_init log lines) and /sys/bus/usb/devices/ (the descriptor fields are all exposed as sysfs attributes); lsusb -v dumps the full descriptor tree the kernel read.
See Also
- Device-Driver Matching — the general
bus->match()→probe()flow thatusb_device_matchspecializes. - struct bus_type and Bus Registration — the
usb_bus_typeis one instance of this; the model machinery the USB callbacks plug into. - PCI and PCIe Enumeration — the contrasting boot-time, config-space enumeration model; the USB host controller is itself a PCI device.
- The Linux Device Model — where
usb_device/usb_interfaceland asstruct devicenodes. - Linux Device Drivers and Device Model MOC — §4 “Hardware Enumeration”, the parent map.