このページの目次

PCIe と USB ドライバモデル

カバー範囲: PCIe 列挙 (BAR/MMIO/MSI) → pci_driver → USB プロトコルスタック (URB/エンドポイント) → usb_driver → ホットプラグ → 電源管理 カーネルバージョン: 2.6 ~ 6.x

PCIe

バス列挙

PCIe バスの自己列挙: 電源投入後にトポロジを自動検出し、リソースを割り当てる 自己列挙バス。デバイス記述のために DT/ACPI は不要 ① PCIe トポロジのスキャン BIOS/UEFI(またはカーネル)がバスをスキャン → bus/device/function ② 設定スペースの読み取り → ベンダー ID / デバイス ID / クラスコード ③ BAR(ベースアドレスレジスタ)の割り当て → MMIO/IO アドレス範囲 ④ MSI/MSI-X の割り当て → 割り込みベクター 各 PCIe デバイスは BDF(Bus:Device.Function)によって一意に識別される

pci_dev

// include/linux/pci.h
struct pci_dev {
    struct pci_bus  *bus;               // 所属バス
    unsigned int    devfn;              // デバイス + 機能
    unsigned short  vendor, device;     // pci_device_id とのマッチング用
    unsigned short  subsystem_vendor, subsystem_device;
    u32             class;              // デバイスクラス (NVMe, VGA, USB, ...)

    struct resource resource[6];        // BAR 0〜5 (MMIO/IO アドレス範囲)
    unsigned int    irq;                // 割り込み番号 (MSI 割り当て後の新しい番号の場合あり)
    struct pcie_link_state *link_state; // PCIe リンク (gen, width)

    // DMA
    u64             dma_mask;           // アドレス指定可能な範囲
    struct device   dev;                // 埋め込み device(デバイスモデル)
};

pci_driver

struct pci_driver {
    const struct pci_device_id *id_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;           // 埋め込み driver(PCI バスに登録)
};

// 登録: pci_register_driver(&mydrv)
// バインディング: PCI バス列挙 → vendor:device のマッチ → probe()

MSI/MSI-X

// メッセージベースの割り込みは INTx 線割り込みの代替:
//   MSI: 単一ベクターまたは少数のベクター
//   MSI-X: 最大 2048 個の独立したベクター

// 割り当て:
int nvec = pci_alloc_irq_vectors(pdev, 1, 16, PCI_IRQ_MSIX);
// → 16 個の MSI-X ベクター → pci_irq_vector(pdev, i) で Linux IRQ 番号を取得

// 各ベクターは独立したアフィニティを持つ → 異なる CPU にバインド可能
// NVMe: 各 IO キューに MSI-X を 1 つ割り当て → 割り込みの並列処理

USB

USB プロトコルスタック

// drivers/usb/core/
// 階層構造:
//   USB Core (usbcore): プロトコル実装、URB 管理
//   ホストコントローラドライバ (HCD): xHCI, EHCI, OHCI
//   デバイスドライバ: usb_driver(例: usb-storage, usbhid)

// USB デバイスの記述子階層:
//   デバイス記述子 → 構成記述子 → インターフェース記述子 → エンドポイント記述子
//   1 つの USB デバイス: 1 デバイス、N 構成、M インターフェース、K エンドポイント

// エンドポイントの種類:
//   Control:     セットアップパケット(設定コマンド)
//   Bulk:       大量データ転送(ストレージ、プリンタ)
//   Interrupt:  定期的ポーリング(HID: キーボード/マウス)
//   Isochronous: リアルタイムストリーム(音声/映像、再送なし)

URB(USB リクエストブロック)

// include/linux/usb.h
struct urb {
    struct usb_device   *dev;       // 宛先デバイス
    unsigned int        pipe;       // エンドポイント + 方向のエンコーディング
    void                *transfer_buffer;
    u32                 transfer_buffer_length;
    int                 actual_length;

    usb_complete_t      complete;   // 完了コールバック

    int                 status;     // 完了ステータス (0=OK, -EPROTO, -ETIMEDOUT, ...)
    int                 interval;   // インターラプト/アイソクロナス転送のポーリング間隔
};

usb_driver

struct usb_driver {
    const struct usb_device_id *id_table;  // VID/PID マッチング
    int (*probe)(struct usb_interface *, const struct usb_device_id *);
    void (*disconnect)(struct usb_interface *);
    struct device_driver drvwrap;          // 埋め込み driver(USB バスに登録)
};

重要⁠: probe の対象は usb_device ではなく usb_interface である。1 つの USB デバイスに複数のインターフェースが存在する場合があるため(例: ウェブカメラのビデオインターフェースとオーディオインターフェース)、異なるインターフェースに異なるドライバをバインドできる。


ホットプラグ

ホットプラグ: PCIe と USB のイベントが如何使用可能なデバイスに変換されるか PCIe ホットプラグ 物理(サーバー): ネイティブホットプラグ → pciehp 仮想(VM): QEMU device_add(ホットプラグのシミュレーション) USB ホットプラグ 接続: ハブが D+ プルアップを検出 → 新規デバイスを列挙 切断: D+ プルダウン → disconnect() カーネルが uevent を送信 udev が処理 モジュールロード (modalias) + デバイスノード PCIe と USB のトリガーパスは異なるが、どちらも uevent → udev に収束し、モジュールロードとデバイスノード作成を統一して行う

参考

  • ソースコード⁠: drivers/pci/, drivers/usb/core/, include/linux/pci.h, include/linux/usb.h
  • カーネルドキュメント⁠: Documentation/PCI/, Documentation/driver-api/usb/
  • LWN: 「The PCI subsystem」、「USB in the Linux kernel」

キーワード: PCIe, BAR, MSI-X, pci_driver, USB, URB, endpoint, usb_driver, hotplug, udev