Deep Dive Into the Linux USB Subsystem: Implementation and Usage

This article examines the Linux USB subsystem in detail, including its architecture, how USB devices are initialized, USB bus operations, OTG functionality, and implementation of USB device drivers. Additionally, we’ll discuss how users can interact with these devices and test them.


How USB Works in Linux

The Linux USB subsystem operates as a layered architecture, enabling seamless communication between hardware and software. Here’s how it works:

Steps Involved

  1. Device Detection:

    • When a USB device is plugged in, the host controller detects it and triggers an interrupt.
    • The kernel identifies the new device through a signal on the USB bus.
  2. Device Enumeration:

    • The kernel queries the device descriptors (e.g., Vendor ID, Product ID, and Device Class) using control transfers.
    • Descriptors are stored in kernel data structures.
  3. Driver Binding:

    • The USB core matches the device with a driver based on its descriptors.
    • A driver is loaded, and the device is made accessible to userspace.

USB Bus Startup

The USB bus starts when the host controller (e.g., EHCI, XHCI) is initialized. Host controller drivers (HCD) manage communication with USB devices.

Key Steps in USB Bus Startup

  1. Initialization:

    • HCD initializes the USB controller hardware.
    • Example: EHCI driver (drivers/usb/host/ehci-hcd.c).
  2. Root Hub Setup:

    • The host controller initializes the root hub, the logical connection point for devices.
  3. Periodic Polling:

    • USB hubs periodically poll for connected devices using hub events.

Relevant Code: Host Controller Initialization

1
2
3
4
5
6
7
int ehci_hcd_init(void) {
struct usb_hcd *hcd = usb_create_hcd(&ehci_hc_driver, dev, "ehci");
if (!hcd)
return -ENOMEM;

return usb_add_hcd(hcd, irq, IRQF_SHARED);
}

User Interaction

  • Devices are automatically registered in /sys/bus/usb/devices/.
  • Users can list devices with:
    1
    lsusb

USB Architecture

The Linux USB architecture is modular, comprising several layers:

1. Host Controller Driver (HCD)

Manages hardware-specific operations. Examples: EHCI, XHCI, and OHCI drivers.

  • Example: EHCI Driver (drivers/usb/host/ehci-hcd.c).
  • Responsibilities:
    • Initialize USB hardware.
    • Schedule and execute data transfers.

2. USB Core

Manages:

  • Device enumeration.
  • Power management.
  • Driver binding.

Located in /drivers/usb/core/.

3. USB Class Drivers

Handle specific device types, such as keyboards, modems, and mass storage.


USB OTG (On-The-Go)

USB OTG allows devices to act as either a host or a peripheral. This is useful in embedded systems and mobile devices.

OTG Architecture

  1. Dual Role Devices: Devices that can switch roles between host and peripheral.
  2. OTG Controller: Manages role switching.

Implementation: OTG Role Switching

Located in /drivers/usb/otg/.

1
2
3
4
int usb_gadget_probe_driver(struct usb_gadget_driver *driver) {
struct usb_gadget *gadget = driver->gadget;
return gadget->ops->start(gadget, driver);
}

User Interaction

Users can enable OTG features through sysfs:

1
echo "host" > /sys/class/usb_role_switch/usb_role

USB Hub Device Driver

USB hubs allow multiple devices to connect to a single USB port. The Linux USB hub driver handles device enumeration and power management.

Key Functions in the Hub Driver

  1. Initialization:

    • Probes hub hardware and configures it.
    • Example: usb_hub_probe() in drivers/usb/core/hub.c.
    1
    2
    3
    4
    int usb_hub_probe(struct usb_interface *intf, const struct usb_device_id *id) {
    struct usb_hub *hub = kzalloc(sizeof(*hub), GFP_KERNEL);
    return hub_configure(hub, intf);
    }
  2. Device Enumeration:

    • Detects new devices and handles their descriptors.
    1
    2
    3
    4
    void hub_event(struct work_struct *work) {
    struct usb_hub *hub = container_of(work, struct usb_hub, hub_event);
    hub_port_connect_change(hub);
    }

User Interaction

Users can query connected hubs:

1
lsusb -t

USB Device Driver Implementations

1. USB Modem

The cdc-acm driver supports USB modems and is located at drivers/usb/class/cdc-acm.c.

Key Code: Modem Driver Initialization

1
2
3
4
5
static int cdc_acm_probe(struct usb_interface *intf, const struct usb_device_id *id) {
struct cdc_acm *acm = kzalloc(sizeof(*acm), GFP_KERNEL);
usb_set_intfdata(intf, acm);
return 0;
}

User Interaction

  • Modems are accessible as /dev/ttyUSB*.
  • Use tools like minicom for testing:
    1
    minicom -D /dev/ttyUSB0

2. USB Keyboard

The usbhid driver handles USB keyboards (drivers/hid/usbhid/).

Key Code: HID Device Probe

1
2
3
4
5
static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *id) {
struct usb_hid *uhid = kzalloc(sizeof(*uhid), GFP_KERNEL);
usb_set_intfdata(intf, uhid);
return hid_add_device(uhid->hid);
}

User Interaction

  • Keyboards work with input subsystems (/dev/input/).
  • Use evtest to monitor input events:
    1
    2
    sudo apt install evtest
    evtest /dev/input/eventX

3. USB HCI (Host Controller Interface)

HCI drivers handle hardware-level USB communication.

Key Code: HCI Initialization

Example: XHCI driver (drivers/usb/host/xhci.c).

1
2
3
4
int xhci_hcd_init(void) {
struct usb_hcd *hcd = usb_create_hcd(&xhci_hc_driver, dev, "xhci");
return usb_add_hcd(hcd, irq, IRQF_SHARED);
}

User-Space Testing of USB Devices

Using lsusb

  1. List USB devices:
    1
    lsusb
  2. View device hierarchy:
    1
    lsusb -t

Using sysfs

Inspect devices:

1
2
cat /sys/bus/usb/devices/1-1/idVendor
cat /sys/bus/usb/devices/1-1/idProduct

USB Debugging

Enable verbose logging:

1
2
echo "1" > /sys/module/usbcore/parameters/debug
dmesg | grep usb

Conclusion

The Linux USB subsystem efficiently handles diverse USB devices with a layered architecture and a robust driver framework. From USB hubs to OTG and device-specific drivers, Linux ensures seamless communication between hardware and software. Understanding its internals allows developers to debug, test, and enhance USB device support.