3. Linux内核PCIe子系统架构:核心数据结构与驱动模型

PCIe子系统是Linux内核中设备模型最成熟的实现之一。理解pci_bus、pci_dev、pci_driver三者之间的关系,以及内核如何枚举PCIe树,是进行PCIe驱动开发和调试的基础。本章将拆解这些核心数据结构,并梳理从硬件复位到驱动绑定的完整流程。

3.1 核心数据结构关系总览

PCIe子系统围绕三个核心结构体构建:pci_bus表示一条PCI总线,pci_dev表示一个PCI设备或桥,pci_driver表示一个PCI驱动程序。它们通过Linux设备模型(device/device_driver)连接,形成树状拓扑。

PCIe核心数据结构关系图 struct pci_bus parent, children, self devices (链表头) number, primary, secondary struct pci_dev bus (所属pci_bus) dev (内嵌device) vendor, device, class struct pci_driver id_table (匹配表) probe, remove 回调 driver (内嵌device_driver) devices链表 driver绑定 Linux设备模型核心 (driver core) struct device (内嵌于pci_dev) ↔ struct device_driver (内嵌于pci_driver) bus_type = pci_bus_type (匹配、探测、sysfs) 关键关系说明: 1. pci_bus 通过 devices 链表挂载该总线上的所有 pci_dev。 2. pci_dev 通过内嵌的 struct device 注册到设备模型。 3. pci_driver 通过 id_table 声明支持的设备 VID/PID 列表。 4. 内核通过 pci_bus_type 的 match 函数完成 pci_dev 与 pci_driver 的配对。 5. 配对成功后调用 pci_driver.probe(),驱动开始初始化设备。

3.2 数据结构深度解析

3.2.1 struct pci_bus — 总线节点

pci_bus 代表一条PCI/PCIe总线。在PCIe层次结构中,每个总线段(Bus Segment)对应一个 pci_bus 实例。关键字段如下:

字段类型说明
parentstruct pci_bus *父总线(上游总线)指针
childrenstruct list_head子总线链表头
devicesstruct list_head本总线上的设备链表头
numberunsigned char本总线的总线号
primaryunsigned char主总线号(用于桥接)
secondaryunsigned char从属总线号(桥下游起始)
subordinateunsigned char最大从属总线号
bridgestruct device *指向桥接设备的 device 结构
selfstruct pci_dev *指向桥接设备自身的 pci_dev
resourcestruct resource *总线上的资源(IO/MEM)数组
调试提示: 在 /sys/devices/pci0000:00/ 下可以看到每个 pci_bus 对应的目录。使用 lspci -t 可以查看总线拓扑树。

3.2.2 struct pci_dev — 设备节点

pci_dev 是PCIe子系统中最核心的数据结构,代表一个PCIe设备(包括Endpoint、Switch、Root Port等)。每个 pci_dev 内嵌一个 struct device,从而融入Linux设备模型。

字段类型说明
busstruct pci_bus *设备所在的总线
devstruct device内嵌设备模型结构
vendorunsigned short厂商ID (VID)
deviceunsigned short设备ID (DID)
subsystem_vendorunsigned short子系统厂商ID
subsystem_deviceunsigned short子系统设备ID
classunsigned int类别代码(Base Class + Sub Class)
devfnunsigned int设备功能号(Device + Function)
current_statepci_power_t当前电源状态
resourcestruct resource[DEVICE_COUNT_RESOURCE]BAR空间资源数组
driverstruct pci_driver *绑定的驱动指针

3.2.3 struct pci_driver — 驱动节点

pci_driver 是驱动开发者需要实现的结构体。它通过 id_table 声明支持的设备,通过 probe/remove 回调与设备生命周期交互。

// 一个典型的pci_driver定义示例
static struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(0x10EE, 0x9034) },  // Xilinx DMA
    { PCI_DEVICE(0x8086, 0x1533) },  // Intel I210
    { 0, }  // 终止条目
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    int ret;
    // 1. 启用设备
    ret = pci_enable_device(pdev);
    if (ret)
        return ret;

    // 2. 请求BAR资源
    ret = pci_request_regions(pdev, "my_driver");
    if (ret)
        goto err_disable;

    // 3. 设置DMA掩码
    ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
    if (ret)
        goto err_release;

    // 4. 读取配置空间关键信息
    u16 vendor = pdev->vendor;
    u16 device = pdev->device;
    u8 rev = pdev->revision;

    dev_info(&pdev->dev, "Probed: %04x:%04x rev %02x\n", vendor, device, rev);
    return 0;

err_release:
    pci_release_regions(pdev);
err_disable:
    pci_disable_device(pdev);
    return ret;
}

static void my_pci_remove(struct pci_dev *pdev)
{
    pci_release_regions(pdev);
    pci_disable_device(pdev);
}

static struct pci_driver my_pci_driver = {
    .name     = "my_pci_driver",
    .id_table = my_pci_ids,
    .probe    = my_pci_probe,
    .remove   = my_pci_remove,
};

module_pci_driver(my_pci_driver);

3.3 PCIe枚举流程

PCIe枚举是系统启动时由内核自动完成的,核心逻辑在 drivers/pci/probe.c 中。枚举过程本质上是深度优先遍历PCIe树,为每个设备分配总线号、配置空间和资源。

PCIe枚举流程(简化) 1. 扫描Root Complex pci_scan_root_bus() 2. 读取配置空间Header pci_scan_device() → pci_setup_device() 3. 分配pci_dev结构 pci_alloc_dev() → 初始化字段 4. 判断设备类型 Header Type & Class Code 5a. Endpoint处理 读取BAR → 分配资源 5b. Bridge处理 分配子总线号 → 递归扫描 5c. 特殊设备 CardBus、PCIe-PCI桥 6. 设备注册与驱动匹配 pci_bus_add_device() → driver_attach()

3.3.1 枚举关键步骤

  1. Root Complex扫描:系统启动时,pci_scan_root_bus() 从总线0开始扫描。对于PCIe,通常由ACPI或设备树提供Root Complex的配置信息。
  2. 配置空间读取:对每个总线上的每个设备/功能(共32个设备×8个功能),读取配置空间Header(前64字节),判断是否存在设备(Vendor ID != 0xFFFF)。
  3. pci_dev分配:如果设备存在,分配 pci_dev 结构,填充VID、DID、Class Code、Revision等基本信息。
  4. 类型判断:根据Header Type判断设备类型:
    • Type 0:Endpoint设备
    • Type 1:PCI/PCIe桥(Bridge)
    • Type 2:CardBus桥
  5. 资源分配:
    • Endpoint:读取BAR寄存器,获取设备需要的MMIO/IO空间大小,分配资源。
    • Bridge:读取桥的Base/Limit寄存器,分配子总线号,然后递归扫描下游总线。
  6. 设备注册:调用 pci_bus_add_device() 将设备注册到设备模型,触发 pci_bus_type 的 match 和 probe。

3.4 驱动模型与匹配机制

PCIe驱动模型的核心是 pci_bus_type,它定义了设备与驱动的匹配规则。当一个新的 pci_dev 注册到内核时,设备模型核心会遍历所有已注册的 pci_driver,调用 pci_bus_match() 进行匹配。

3.4.1 匹配规则

匹配方式匹配依据优先级
VID/DID精确匹配PCI_DEVICE(vendor, device)最高
VID + Class匹配PCI_DEVICE_CLASS(vendor, class_mask)中
Class Only匹配PCI_CLASS(class_mask)低
通配符匹配PCI_ANY_ID最低(极少使用)
// pci_bus_match 核心逻辑(简化)
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{
    struct pci_dev *pci_dev = to_pci_dev(dev);
    struct pci_driver *pci_drv = to_pci_driver(drv);
    const struct pci_device_id *ids = pci_drv->id_table;

    if (!ids)
        return 0;

    // 遍历id_table,调用pci_match_one_device
    while (ids->vendor || ids->subvendor || ids->class_mask) {
        if (pci_match_one_device(ids, pci_dev))
            return 1;  // 匹配成功
        ids++;
    }
    return 0;  // 无匹配
}

3.4.2 probe回调执行顺序

当匹配成功后,内核按以下顺序调用驱动回调:

  1. pci_driver.probe():驱动初始化入口,执行设备使能、资源申请、DMA设置等。
  2. pci_enable_device():通常在probe内部调用,使能I/O和Memory空间访问。
  3. pci_request_regions():请求BAR资源,防止冲突。
  4. dma_set_mask():设置DMA地址掩码,确保DMA操作正确。
  5. pci_set_master():设置设备为Bus Master,允许发起DMA。
常见调试陷阱: 如果 probe 没有被调用,请检查:
1. id_table 中的VID/DID是否与设备配置空间一致。
2. 设备是否被其他驱动占用(如 pci-stub)。
3. 内核是否编译了该驱动(lsmod | grep driver_name)。
4. 使用 lspci -vvv -s 01:00.0 查看 Kernel driver in use 字段。

3.5 实战:查看枚举结果

在调试过程中,可以通过以下方式验证枚举结果:

# 查看PCIe设备树拓扑
lspci -t

# 查看某个设备的详细信息(包括BAR、IRQ、驱动)
lspci -vvv -s 01:00.0

# 查看sysfs中的设备层次
ls -l /sys/devices/pci0000:00/

# 查看驱动绑定的设备
ls -l /sys/bus/pci/drivers/my_pci_driver/

# 手动绑定/解绑(调试时常用)
echo "0000:01:00.0" > /sys/bus/pci/drivers/my_pci_driver/bind
echo "0000:01:00.0" > /sys/bus/pci/drivers/my_pci_driver/unbind
关键调试命令: lspci -vvv 可以显示设备的完整配置空间内容,包括Capability链表、MSI-X表、BAR地址等,是PCIe调试的第一利器。

3.6 本章小结

本章深入剖析了PCIe子系统的三大核心数据结构:pci_bus(总线)、pci_dev(设备)、pci_driver(驱动),以及它们通过Linux设备模型形成的树状拓扑关系。枚举流程从Root Complex扫描开始,经过配置空间读取、设备类型判断、资源分配,最终完成设备注册与驱动匹配。理解这些基础架构,是后续进行配置空间解析、BAR映射、DMA调试的前提。