USB Concepts for Linux Kernel Driver Developers

USB Concepts for Linux Kernel Driver Developers

A Practical Guide to Architecture, Transfers, and Enumeration

1. USB Architecture: Tiered Star Topology

USB uses a host-centric, tiered star topology managed by the Linux kernel through:

  • Host Controllers (e.g., xHCI for USB 3.0)
  • Hubs (expand connectivity)
  • Devices (keyboards, cameras, storage)

Linux handles hub connections via drivers/usb/core/hub.c.


2. USB Device Framework: Descriptors in Depth

Hierarchical Structure

USB devices describe themselves through a tree of descriptors:

Key Descriptors in Linux:

  1. Device Descriptor (struct usb_device_descriptor):
  2. Interface Descriptor (struct usb_interface_descriptor):
  3. Endpoint Descriptor (struct usb_endpoint_descriptor):

Composite vs. Compound Devices:

  • Composite: Single device with multiple interfaces (e.g., a USB webcam with video + audio).
  • Compound: Contains an embedded hub (e.g., a keyboard with a built-in USB port).


3. USB Data Transfers: URBs & Endpoints

URB (USB Request Block) Lifecycle

URBs are the backbone of USB communication in Linux:

Transfer Types in Practice:

  1. Control Transfers:
  2. Interrupt Transfers:
  3. Bulk Transfers:
  4. Isochronous Transfers:


4. Device Enumeration: Step-by-Step

Detailed Enumeration Flow

When a device is plugged in:

  1. Hub Detection (hub_irq): Root hub detects port status change via an interrupt.
  2. Port Reset & Address Assignment: Host sends USB_PORT_FEAT_RESET, then assigns an address (1-127).
  3. Descriptor Retrieval: Host reads descriptors via control transfers:

// From drivers/usb/core/message.c  
usb_get_device_descriptor(udev, 8);  // Initial 8-byte read  
usb_get_configuration(udev);         // Full configuration          

  1. Driver Matching: Kernel matches idVendor/idProduct or class codes to drivers:

static struct usb_device_id my_table[] = {  
{ USB_DEVICE(0xabcd, 0x1234) },  
{ USB_INTERFACE_INFO(USB_CLASS_AUDIO, 1, 0) },  
{}  
};          

  1. Interface Binding: Drivers bind to interfaces, not devices:

static int my_probe(struct usb_interface *intf,  
const struct usb_device_id *id) {  
struct usb_device *udev = interface_to_usbdev(intf);  
// ...  
}          

Sysfs Representation:

/sys/bus/usb/devices/  
├── 1-0:1.0    # Root hub  
├── 2-1        # Device at port 1  
└── 2-1:1.0    # Interface of device 2-1          

5. USB Classes: HID Deep Dive

HID Report Descriptor Example

A mouse descriptor defines buttons and movement axes:

// Example HID Report Descriptor  
static __u8 mouse_report_desc[] = {  
  0x05, 0x01,        // Usage Page (Generic Desktop)  
  0x09, 0x02,        // Usage (Mouse)  
  0xA1, 0x01,        // Collection (Application)  
  0x09, 0x01,        //   Usage (Pointer)  
  0xA1, 0x00,        //   Collection (Physical)  
  0x05, 0x09,        //     Usage Page (Buttons)  
  0x19, 0x01,        //     Usage Minimum (Button 1)  
  0x29, 0x03,        //     Usage Maximum (Button 3)  
  0x15, 0x00,        //     Logical Minimum (0)  
  0x25, 0x01,        //     Logical Maximum (1)  
  0x95, 0x03,        //     Report Count (3 buttons)  
  0x75, 0x01,        //     Report Size (1 bit per button)  
  0x81, 0x02,        //     Input (Data,Var,Abs)  
  0x95, 0x01,        //     Report Count (1 padding bit)  
  0x75, 0x05,        //     Report Size (5 bits)  
  0x81, 0x03,        //     Input (Const,Var,Abs)  
  0x05, 0x01,        //     Usage Page (Generic Desktop)  
  0x09, 0x30,        //     Usage (X)  
  0x09, 0x31,        //     Usage (Y)  
  0x16, 0x00, 0x80,  //     Logical Minimum (-32768)  
  0x26, 0xFF, 0x7F,  //     Logical Maximum (32767)  
  0x75, 0x10,        //     Report Size (16 bits)  
  0x95, 0x02,        //     Report Count (2 fields: X/Y)  
  0x81, 0x06,        //     Input (Data,Var,Rel)  
  0xC0,              //   End Collection  
  0xC0,              // End Collection  
};  
        

HID Driver Workflow in Linux:

  1. Probe:

static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id) {  
struct hid_device *hid;  
hid = hid_allocate_device();  
usbhid = kzalloc(sizeof(*usbhid), GFP_KERNEL);  
// Parse report descriptor  
hid_parse_report(hid, report_desc, report_len);  
hidinput_connect(hid);  // Create /dev/input/eventX node  
}  
        

  1. Input Events:

  • Data from interrupt transfers is parsed into input_event structs:

input_report_key(hidinput->input, BTN_LEFT, button_state & 0x01);  
input_sync(hidinput->input);  
        

Debugging HID:

  • Use usbhid-dump to capture raw HID reports.
  • Inspect parsed descriptors with lsusb -v.



6. Power Management States

  • Suspend: Kernel stops URBs to save power.
  • Resume: Drivers re-initialize URBs (e.g., my_resume() callback).


Conclusion

For Linux driver developers, focus on:

  1. Descriptor Interpretation: Match devices via usb_device_id.
  2. URB Management: Properly allocate/submit/free URBs.
  3. Enumeration Flow: Debug using dmesg or lsusb.

Use tools like usbmon for advanced debugging. For specifications, refer to USB Concepts.

要查看或添加评论,请登录

David Zhu的更多文章

社区洞察