On this page

PCIe and USB Driver Model

Coverage: PCIe enumeration (BAR/MMIO/MSI) → pci_driver → USB protocol stack (URB/endpoint) → usb_driver → hotplug → power management Kernel version: 2.6 ~ 6.x

PCIe

Bus Enumeration

PCIe Bus Self-Enumeration: Automatically discovers topology and allocates resources after power-on Self-enumerating bus, does not require DT/ACPI device descriptions ① Scan PCIe Topology BIOS/UEFI (or kernel) scans bus → bus/device/function ② Read Configuration Space → vendor ID / device ID / class code ③ Allocate BAR (Base Address Register) → MMIO/IO address ranges ④ Allocate MSI/MSI-X → Interrupt vectors Each PCIe device is uniquely identified by BDF (Bus:Device.Function)

pci_dev

// include/linux/pci.h
struct pci_dev {
    struct pci_bus  *bus;               // Bus it belongs to
    unsigned int    devfn;              // device + function
    unsigned short  vendor, device;     // Matches pci_device_id
    unsigned short  subsystem_vendor, subsystem_device;
    u32             class;              // Device class (NVMe, VGA, USB, ...)

    struct resource resource[6];        // BAR 0~5 (MMIO/IO address ranges)
    unsigned int    irq;                // Interrupt number (may be a new number allocated after MSI)
    struct pcie_link_state *link_state; // PCIe link (gen, width)

    // DMA
    u64             dma_mask;           // Addressable range
    struct device   dev;                // Embedded device (device model)
};

pci_driver

struct pci_driver {
    const struct pci_device_id *id_table;  // Match table
    int (*probe)(struct pci_dev *, const struct pci_device_id *);
    void (*remove)(struct pci_dev *);
    int (*suspend)(struct pci_dev *, pm_message_t);
    int (*resume)(struct pci_dev *);
    struct device_driver driver;           // Embedded driver (registered to PCI bus)
};

// Registration: pci_register_driver(&mydrv)
// Binding: PCI bus enumeration → match vendor:device → probe()

MSI/MSI-X

// Message-based interrupts replacing INTx line interrupts:
//   MSI: Single vector or a small number of vectors
//   MSI-X: Up to 2048 independent vectors

// Allocation:
int nvec = pci_alloc_irq_vectors(pdev, 1, 16, PCI_IRQ_MSIX);
// → 16 MSI-X vectors → pci_irq_vector(pdev, i) to get Linux IRQ number

// Each vector has independent affinity → can be bound to different CPUs
// NVMe: One MSI-X per IO queue → parallel interrupt processing

USB

USB Protocol Stack

// drivers/usb/core/
// Layered architecture:
//   USB Core (usbcore): Protocol implementation, URB management
//   Host Controller Driver (HCD): xHCI, EHCI, OHCI
//   Device Driver: usb_driver (e.g., usb-storage, usbhid)

// Descriptor hierarchy for USB devices:
//   Device Descriptor → Config Descriptor → Interface Descriptor → Endpoint Descriptor
//   One USB device: 1 device, N configs, M interfaces, K endpoints

// Endpoint types:
//   Control:     setup packet (configuration commands)
//   Bulk:       Large data transfers (storage, printers)
//   Interrupt:  Periodic polling (HID: keyboard/mouse)
//   Isochronous: Real-time streaming (audio/video, no retransmission)

URB (USB Request Block)

// include/linux/usb.h
struct urb {
    struct usb_device   *dev;       // Target device
    unsigned int        pipe;       // Encodes endpoint + direction
    void                *transfer_buffer;
    u32                 transfer_buffer_length;
    int                 actual_length;

    usb_complete_t      complete;   // Completion callback

    int                 status;     // Completion status (0=OK, -EPROTO, -ETIMEDOUT, ...)
    int                 interval;   // Polling interval for interrupt/isochronous transfers
};

usb_driver

struct usb_driver {
    const struct usb_device_id *id_table;  // VID/PID match
    int (*probe)(struct usb_interface *, const struct usb_device_id *);
    void (*disconnect)(struct usb_interface *);
    struct device_driver drvwrap;          // Embedded driver (registered to USB bus)
};

Key: The object passed to probe is usb_interface, not usb_device—because a USB device can have multiple interfaces (e.g., webcam: video interface + audio interface), and different interfaces can bind to different drivers.


Hotplug

Hotplug: How PCIe and USB events become usable devices PCIe Hotplug Physical (Server): native hotplug → pciehp Virtual (VM): QEMU device_add (hotplug simulation) USB Hotplug Connect: hub detects D+ pull-up → enumerate new device Disconnect: D+ pull-down → disconnect() Kernel sends uevent udev processes Module loading (modalias) + device node creation PCIe and USB trigger paths differ, but both converge to uevent → udev, uniformly completing module loading and device node creation

References

  • Source Code: drivers/pci/, drivers/usb/core/, include/linux/pci.h, include/linux/usb.h
  • Kernel Documentation: Documentation/PCI/, Documentation/driver-api/usb/
  • LWN: "The PCI subsystem", "USB in the Linux kernel"

Keywords: PCIe, BAR, MSI-X, pci_driver, USB, URB, endpoint, usb_driver, hotplug, udev