2. Linux PCIe子系统架构

PCIe 子系统在 Linux 内核中采用分层架构设计,从上到下依次为 驱动层、核心层 和 总线层。这种分层结构使得 PCIe 设备的枚举、配置空间访问、资源分配与驱动绑定等职责清晰分离,同时也为热插拔事件的传递与回调注册提供了稳定的基础设施。

Linux PCIe 子系统三层架构 驱动层 (Driver Layer) pci_driver → probe() / remove() / .shutdown 热插拔回调:driver->driver.pm->runtime_resume / runtime_suspend 核心层 (PCI Core) pci_alloc_dev() / pci_scan_bus() / pci_bus_add_device() 热插拔事件源:pciehp / shpchp 中断处理 → 总线重扫描 总线层 (Bus Layer) pci_bus_type → 总线属性 / 扫描 / 资源分配 pci_bus 链表管理 / pci_dev 生命周期 热插拔事件流向:硬件中断 → 总线层扫描 → 核心层创建设备 → 驱动层 probe() 回调注册点:驱动层通过 pci_register_driver() 挂载,核心层通过 pciehp 注册中断处理

2.1 三层职责划分

层次 核心职责 关键数据结构/接口 热插拔相关
驱动层 设备功能驱动实现,与具体硬件交互 pci_driver、pci_device_id 注册 probe()/remove() 回调;PM 回调处理热插拔恢复
核心层 设备枚举、配置空间管理、资源分配、热插拔事件调度 pci_dev、pci_bus、pci_ops pciehp/shpchp 中断服务;总线重扫描触发
总线层 总线类型注册、设备/驱动匹配、属性文件导出 pci_bus_type、bus_attribute 通过 bus->probe() 回调驱动层;uevent 通知用户空间
关键理解: 驱动层不直接操作总线扫描或配置空间访问,这些由核心层封装。驱动只需关注 pci_driver 结构体中的回调函数,热插拔事件到达时核心层会自动调用 probe() 或 remove()。

2.2 pci_bus 数据结构

pci_bus 是 PCIe 总线节点的内核表示,每个物理 PCI/PCIe 总线对应一个 pci_bus 实例。它维护了该总线上的设备链表、子总线链表以及资源分配信息。

struct pci_bus {
    struct list_head node;          /* 全局总线链表节点 */
    struct pci_bus  *parent;        /* 父总线(上游) */
    struct list_head children;      /* 子总线链表 */
    struct list_head devices;       /* 该总线上的设备链表 */
    struct pci_dev  *self;          /* 若为桥接设备,指向对应的 pci_dev */
    struct list_head slots;         /* 槽位链表(热插拔相关) */
    struct resource *resource[PCI_BRIDGE_RESOURCE_NUM]; /* 桥接窗口资源 */
    unsigned char    number;        /* 总线号 */
    unsigned char    primary;       /* 主总线号(桥接用) */
    unsigned char    secondary;     /* 次总线号 */
    unsigned char    subordinate;   /* 从属总线号范围上限 */
    unsigned int     is_added : 1;  /* 是否已添加到设备模型 */
    const struct pci_bus_ops *ops;  /* 总线操作(配置空间读写) */
    void *sysdata;                  /* 体系结构私有数据 */
};
pci_bus 数据结构关系图 pci_bus (根总线) number=0, parent=NULL pci_bus (子总线1) number=1, parent=根总线 pci_bus (子总线2) number=2, parent=根总线 pci_dev (设备A) devfn=0x00 pci_dev (设备B) devfn=0x08 children 链表连接父子总线;devices 链表挂载该总线上的所有 pci_dev

2.3 pci_dev 数据结构

pci_dev 是 PCIe 设备在内核中的核心表示,每个物理 PCIe 功能(Function)对应一个 pci_dev 实例。它包含了设备的配置空间信息、资源映射、电源管理状态以及热插拔状态。

struct pci_dev {
    struct list_head bus_list;      /* 所在总线的设备链表节点 */
    struct pci_bus  *bus;           /* 所属总线 */
    struct pci_bus  *subordinate;   /* 若为桥接设备,指向下游总线 */
    unsigned int    devfn;          /* 设备号+功能号 (device<<3 | function) */
    unsigned short  vendor;         /* Vendor ID */
    unsigned short  device;         /* Device ID */
    unsigned short  subsystem_vendor; /* Subsystem Vendor ID */
    unsigned short  subsystem_device; /* Subsystem Device ID */
    unsigned int    class;          /* Class Code */
    u8              revision;       /* Revision ID */
    u8              hdr_type;       /* 配置空间头部类型 */
    struct resource resource[DEVICE_COUNT_RESOURCE]; /* BAR 资源数组 */
    struct pci_driver *driver;      /* 绑定的驱动 */
    struct device   dev;            /* 内核设备模型基类 */
    unsigned int    is_added : 1;   /* 是否已添加到驱动核心 */
    unsigned int    is_busmaster : 1; /* 总线主控使能 */
    unsigned int    no_d1d2 : 1;    /* 不支持 D1/D2 电源状态 */
    unsigned int    d3_delay;       /* D3 状态切换延迟 */
    unsigned int    current_state;  /* 当前电源状态 (D0/D1/D2/D3hot/D3cold) */
    int             error_state;    /* 通道错误状态 */
    struct pcie_link_state *link_state; /* PCIe 链路状态 */
};
热插拔关键字段:
  • bus — 设备所在总线,热插拔移除时从该总线的 devices 链表摘除
  • driver — 指向当前绑定的 pci_driver,热插拔事件触发 remove() 回调
  • current_state — 电源状态,热插拔恢复时需从 D3cold 切换回 D0
  • error_state — 记录 PCIe 错误状态,热插拔事件可能伴随链路错误

2.4 pci_bus 与 pci_dev 的关联关系

关系类型 数据结构字段 说明
设备 → 总线 pci_dev.bus 指向设备所在的 pci_bus,用于配置空间访问和资源查询
总线 → 设备 pci_bus.devices 链表头,遍历该总线上的所有 pci_dev
桥接设备 → 子总线 pci_dev.subordinate 若设备是 PCIe 桥,指向下游的 pci_bus
总线 → 父总线 pci_bus.parent 指向上游总线,根总线的 parent 为 NULL
总线 → 子总线 pci_bus.children 链表头,遍历所有下游子总线
pci_dev 与 pci_bus 双向关联 pci_bus devices 链表头 children 链表头 pci_dev (功能0) bus → 指向 pci_bus pci_dev (功能1) bus → 指向 pci_bus pci_dev (桥) subordinate → 子 pci_bus 热插拔场景:新设备插入 → 核心层创建 pci_dev → 插入 bus->devices 链表 → 触发驱动匹配 设备拔出 → 从链表摘除 → 调用 driver->remove() → 释放 pci_dev

2.5 热插拔事件在分层架构中的传递路径

  1. 硬件中断触发:PCIe 热插拔控制器(如 pciehp)检测到槽位状态变化(Presence Detect 或 Button Pressed),产生中断。
  2. 总线层响应:中断服务程序调用 pciehp_handle_presence_change(),通过 pci_bus 的 ops 操作配置空间。
  3. 核心层重扫描:调用 pci_scan_slot() / pci_scan_bus() 枚举新设备,分配 pci_dev 并插入 pci_bus.devices 链表。
  4. 驱动层回调:核心层通过 pci_bus_add_device() 将设备添加到驱动核心,触发 pci_driver->probe() 回调。
  5. 移除流程:拔出时核心层调用 pci_stop_and_remove_bus_device(),依次触发 driver->remove() 并释放 pci_dev。
工程注意事项: 驱动层注册热插拔回调时,必须确保 pci_driver 的 probe() 和 remove() 是幂等的(可重复调用),因为热插拔事件可能因电气噪声产生多次触发。同时,pci_dev 的 current_state 在热插拔恢复后可能为 D3cold,驱动需在 probe() 中显式恢复配置空间。

2.6 数据结构在热插拔回调注册中的角色

数据结构 热插拔回调注册中的角色 典型操作
pci_bus 提供总线扫描接口和槽位管理链表 pci_scan_bus() 重新枚举;pci_bus_add_devices() 添加新设备
pci_dev 代表热插拔设备实例,承载驱动绑定和资源信息 pci_dev_get()/pci_dev_put() 引用计数管理
pci_driver 驱动注册入口,包含 probe/remove 回调 pci_register_driver() 注册;pci_unregister_driver() 注销
pciehp_slot 热插拔槽位状态管理,与 pci_bus.slots 关联 注册中断处理函数;设置槽位控制寄存器
学习路径建议: 理解 pci_bus 和 pci_dev 的关系是掌握 PCIe 热插拔回调注册的基础。下一章将深入 pci_driver 结构体,讲解如何在实际工程中注册热插拔感知的回调函数。建议读者在阅读后续章节前,先在内核源码中查找 include/linux/pci.h 中的这两个结构体定义,加深印象。