Skip to content

Latest commit

 

History

History
2718 lines (2150 loc) · 93.8 KB

File metadata and controls

2718 lines (2150 loc) · 93.8 KB

Linux 内核 PCI/PCIe 子系统深度分析

基于 Linux 内核源码(commit 8a30aeb0d,约 v6.14-rc 阶段)


目录

  1. PCI/PCIe 总线拓扑
  2. 配置空间:256B 与 4KB
  3. 核心数据结构
  4. 设备枚举流程
  5. BAR 资源分配
  6. MSI/MSI-X 中断机制
  7. IOMMU 与 PCIe ACS 隔离
  8. PCIe 电源管理:ASPM 与设备电源状态
  9. SR-IOV:虚拟化扩展
  10. P2P DMA:设备间直接访问
  11. pci_ops 与 ECAM
  12. sysfs 接口
  13. 关键调用链汇总
  14. PCI 总线枚举深度剖析
  15. 配置空间访问机制
  16. PCIe 链路训练与 LTSSM 状态机
  17. MSI/MSI-X 中断分配深度分析
  18. IOMMU 与 DMA 地址映射
  19. PCIe 热插拔机制
  20. SR-IOV 虚拟化深度分析
  21. AER 高级错误报告
  22. PCIe P2P DMA 深度分析
  23. 电源管理:D 状态与 PME 唤醒
  24. 附录:重要文件速查

1. PCI/PCIe 总线拓扑

1.1 层次结构总览

传统 PCI 是共享总线,PCIe 升级为点对点串行链路,但保留了软件模型的兼容性。内核以树状结构管理整个 PCI 层次:

+-----------------------------------------------------+
|                   CPU / System Memory               |
+---------------------+-------------------------------+
                       |
            +----------+----------+
            |   Root Complex (RC) |
            |   pci_host_bridge   |
            +----+----------+-----+
                 |          |
         +-------+--+    +--+-------+
         | Root Port|    | Root Port|    <- pcie_type = ROOT_PORT
         +----+-----+    +-----+----+
              |                |
    +---------+--+        +----+--------+
    |  PCIe Switch|        |  Endpoint  |   <- pcie_type = ENDPOINT
    | (Upstream)  |        | (NVMe/GPU) |
    +--+-------+--+        +------------+
       |       |
  +----+--+ +--+----+
  |Downstr| |Downstr|   <- pcie_type = DOWNSTREAM
  | Port  | | Port  |
  +---+---+ +---+---+
      |          |
  +---+----+  +--+-----+
  |Endpoint|  |Endpoint|
  |(NIC)   |  |(FPGA)  |
  +--------+  +--------+

1.2 PCIe 端口类型

include/linux/pci.h 中,通过读取 PCIe Capability 寄存器的 Device/Port Type 字段(PCI_EXP_FLAGS_TYPE)来确定端口类型:

宏名 含义
PCI_EXP_TYPE_ENDPOINT 0x0 普通 Endpoint
PCI_EXP_TYPE_LEG_END 0x1 传统 Endpoint
PCI_EXP_TYPE_ROOT_PORT 0x4 Root Port
PCI_EXP_TYPE_UPSTREAM 0x5 Switch 上行口
PCI_EXP_TYPE_DOWNSTREAM 0x6 Switch 下行口
PCI_EXP_TYPE_PCI_BRIDGE 0x8 PCIe-to-PCI/PCI-X 桥
PCI_EXP_TYPE_RC_END 0x9 Root Complex 集成 Endpoint
PCI_EXP_TYPE_RC_EC 0xa Root Complex 事件收集器

drivers/pci/probe.cpci_scan_bridge_extend() 中,通过检测 pci_pcie_type(bridge) 来决定是否进入 DOWNSTREAMROOT_PORT 分支扫描下一级总线。

1.3 总线编号分配

每个 PCI 总线有三个编号寄存器(Type 1 配置头):

  • Primary Bus Number:本桥所在总线
  • Secondary Bus Number:本桥管理的下行总线起始
  • Subordinate Bus Number:本桥下所有子总线的最大编号

内核在 probe.cpci_scan_bridge_extend()(第 1395 行)中动态分配这些编号,采用深度优先遍历,先分配最小的可用编号,待子树扫描完成后回写 Subordinate 编号。


2. 配置空间:256B 与 4KB

2.1 传统 PCI 配置空间(256 字节)

标准 PCI 设备支持 256 字节配置空间,分为:

偏移   大小   含义
0x00   2B     Vendor ID
0x02   2B     Device ID
0x04   2B     Command Register
0x06   2B     Status Register
0x08   1B     Revision ID
0x09   1B     Prog Interface
0x0A   1B     Sub-Class Code
0x0B   1B     Base Class Code
0x0C   1B     Cache Line Size
0x0D   1B     Latency Timer
0x0E   1B     Header Type   <-- bit[7]=MFD, bit[6:0]=type
0x0F   1B     BIST
0x10   4B     BAR0
0x14   4B     BAR1
0x18   4B     BAR2 (or Secondary Bus No. for bridge)
0x1C   4B     BAR3
0x20   4B     BAR4
0x24   4B     BAR5
0x28   4B     Cardbus CIS Pointer
0x2C   2B     Subsystem Vendor ID
0x2E   2B     Subsystem ID
0x30   4B     Expansion ROM Base Address
0x34   1B     Capabilities Pointer
0x3C   1B     Interrupt Line
0x3D   1B     Interrupt Pin
0x3E   1B     Min Grant
0x3F   1B     Max Latency
0x40-0xFF     Device-specific (192B)

2.2 PCIe 扩展配置空间(4 KB)

PCIe 通过 ECAM 机制将配置空间扩展至 4096 字节(0x000~0xFFF):

  • 0x000~0x0FF:兼容 PCI 的标准配置头
  • 0x100~0xFFF:PCIe 扩展能力(Extended Capabilities),每个能力结构以 PCI_EXT_CAP_ID_* 标识

常见扩展能力:

能力 ID 名称 内核宏
0x0001 Advanced Error Reporting PCI_EXT_CAP_ID_ERR
0x0002 Virtual Channel PCI_EXT_CAP_ID_VC
0x0010 SR-IOV PCI_EXT_CAP_ID_SRIOV
0x000D ACS (Access Control Services) PCI_EXT_CAP_ID_ACS
0x001E L1 PM Substates PCI_EXT_CAP_ID_L1SS
0x0019 LTR (Latency Tolerance Reporting) PCI_EXT_CAP_ID_LTR
0x001F PTM (Precision Time Measurement) PCI_EXT_CAP_ID_PTM

内核通过 pci_find_ext_capability(dev, cap_id) 遍历扩展能力链表来查找特定能力的偏移量。在 pci_dev 结构中,频繁使用的能力偏移被缓存为专用字段:

// include/linux/pci.h: 370~573
u8    pcie_cap;    // PCIe capability offset
u8    msi_cap;     // MSI capability offset
u8    msix_cap;    // MSI-X capability offset
u8    pm_cap;      // PM capability offset
u16   l1ss;        // L1SS Capability pointer
u16   acs_cap;     // ACS Capability offset
u16   acs_capabilities; // ACS Capabilities 缓存值

dev->cfg_sizepci_setup_device() 中调用 pci_cfg_space_size(dev) 决定,对于 PCIe 设备通常为 4096。


3. 核心数据结构

3.1 struct pci_devinclude/linux/pci.h,第 343 行)

pci_dev 是描述单个 PCI 功能(function)的核心结构,继承自 struct device

struct pci_dev {
    struct list_head  bus_list;      // 挂在所在总线的设备链表
    struct pci_bus   *bus;           // 所在总线
    struct pci_bus   *subordinate;   // 若为桥,指向下行总线

    unsigned int  devfn;             // 7:3=slot, 2:0=function
    unsigned short vendor;           // Vendor ID
    unsigned short device;           // Device ID
    unsigned int  class;             // 3字节:base/sub/prog-if

    u8    pcie_cap;                  // PCIe Capability 在配置空间的偏移
    u8    msi_cap;                   // MSI Capability 偏移
    u8    msix_cap;                  // MSI-X Capability 偏移
    u8    pm_cap;                    // Power Management 偏移
    u16   l1ss;                      // L1 Sub-States 扩展能力偏移
    u16   acs_cap;                   // ACS 扩展能力偏移

    pci_power_t current_state;       // 当前电源状态 D0~D3cold

    struct pci_driver *driver;       // 绑定的驱动
    u64   dma_mask;                  // DMA 地址掩码

    struct resource resource[DEVICE_COUNT_RESOURCE]; // BAR 资源数组
    // resource[0..5]  = BAR0..BAR5 (PCI_STD_RESOURCES)
    // resource[6]     = Expansion ROM
    // resource[7..12] = SR-IOV BARs (CONFIG_PCI_IOV)
    // resource[13..16]= Bridge windows

    unsigned int  msi_enabled:1;     // MSI 已使能
    unsigned int  msix_enabled:1;    // MSI-X 已使能
    unsigned int  is_physfn:1;       // 是 SR-IOV Physical Function
    unsigned int  is_virtfn:1;       // 是 SR-IOV Virtual Function
    unsigned int  ari_enabled:1;     // ARI 转发已使能
    unsigned int  ats_enabled:1;     // Address Translation Svc 使能
    unsigned int  pasid_enabled:1;   // PASID 使能

    spinlock_t    pcie_cap_lock;     // 保护 RMW 操作的自旋锁

    u32   saved_config_space[16];    // 挂起时保存的配置空间

    // SR-IOV: is_physfn 时指向 pci_sriov,is_virtfn 时指向 physfn
    union {
        struct pci_sriov *sriov;
        struct pci_dev   *physfn;
    };

#ifdef CONFIG_PCI_P2PDMA
    struct pci_p2pdma __rcu *p2pdma; // P2P DMA 状态(RCU 保护)
#endif

#ifdef CONFIG_PCIEASPM
    struct pcie_link_state *link_state; // ASPM 链路状态
    unsigned int aspm_l0s_support:1;
    unsigned int aspm_l1_support:1;
#endif
};

设备标识dev->dev.bus_id 形如 "0000:00:1f.2"(domain:bus:slot.func),在 pci_setup_device()(probe.c:2055)中通过 dev_set_name() 写入。

3.2 struct pci_businclude/linux/pci.h,第 698 行)

pci_bus 描述一条 PCI 总线,承载设备列表和子总线列表:

struct pci_bus {
    struct list_head  node;       // 挂在父总线的 children 链表
    struct pci_bus   *parent;     // 父总线(NULL 表示根总线)
    struct list_head  children;   // 子总线链表
    struct list_head  devices;    // 本总线上的设备链表
    struct pci_dev   *self;       // 桥设备(从父总线视角看)

    struct resource  *resource[PCI_BRIDGE_RESOURCE_NUM]; // 桥窗口资源
    struct list_head  resources;  // 路由到本总线的地址空间
    struct resource   busn_res;   // 总线号范围资源

    struct pci_ops   *ops;        // 配置空间访问操作集
    unsigned char     number;     // 总线编号
    unsigned char     primary;    // 上行桥所在总线编号
    unsigned char     max_bus_speed; // enum pci_bus_speed
    unsigned char     cur_bus_speed;

    pci_bus_flags_t   bus_flags;  // PCI_BUS_FLAGS_NO_MSI 等继承标志
    struct device    *bridge;     // 关联的桥设备
    struct device     dev;        // 内嵌 device 对象
};

根总线通过 pci_is_root_bus(bus) 检测:!(pbus->parent)(pci.h:750)。bus->self == NULL 不能作为根总线判据,因为 SR-IOV 虚拟总线也满足此条件。

3.3 struct pci_host_bridgeinclude/linux/pci.h,第 627 行)

描述主机桥(Root Complex),是整棵 PCI 树的根节点:

struct pci_host_bridge {
    struct device     dev;
    struct pci_bus   *bus;         // 根总线
    struct pci_ops   *ops;         // 配置访问操作
    struct pci_ops   *child_ops;   // 子总线使用的操作(可不同)
    int               busnr;       // 根总线编号
    int               domain_nr;   // PCI 域编号
    struct list_head  windows;     // 地址窗口资源列表
    struct list_head  dma_ranges;  // DMA 范围列表

    // 特性标志(来自 ACPI _OSC 或 OF)
    unsigned int  native_aer:1;           // OS 管理 AER
    unsigned int  native_pcie_hotplug:1;  // OS 管理 PCIe 热插拔
    unsigned int  native_pme:1;           // OS 管理 PME
    unsigned int  native_ltr:1;           // OS 管理 LTR
    unsigned int  native_dpc:1;           // OS 管理 DPC
    unsigned int  preserve_config:1;      // 保留固件资源配置
    unsigned int  msi_domain:1;           // 期望有 MSI 域

    resource_size_t (*align_resource)(...); // 资源对齐回调
};

pci_init_host_bridge() 在 probe.c 第 670 行初始化这些字段,默认将 native_aer/hotplug/pme/ltr/dpc 全部置 1(表示 OS 期望自主管理)。

3.4 struct pci_driverinclude/linux/pci.h,第 1019 行)

驱动侧的核心结构:

struct pci_driver {
    const char                  *name;
    const struct pci_device_id  *id_table; // 设备 ID 匹配表
    int  (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
    void (*remove)(struct pci_dev *dev);
    int  (*suspend)(struct pci_dev *dev, pm_message_t state);
    int  (*resume)(struct pci_dev *dev);
    void (*shutdown)(struct pci_dev *dev);
    // SR-IOV 回调
    int  (*sriov_configure)(struct pci_dev *dev, int num_vfs);
    int  (*sriov_set_msix_vec_count)(struct pci_dev *vf, int count);
    u32  (*sriov_get_vf_total_msix)(struct pci_dev *pf);
    // 错误恢复
    const struct pci_error_handlers *err_handler;
    // sysfs 属性组
    const struct attribute_group **groups;
    const struct attribute_group **dev_groups;
    struct device_driver  driver; // 内嵌通用驱动对象
    struct pci_dynids     dynids; // 动态 ID 列表
    bool driver_managed_dma;      // 驱动自管 DMA(如 VFIO)
};

pci_register_driver() 最终调用 driver_register() 接入内核驱动模型,当 pci_bus_type 上有新设备时会触发 pci_bus_match()pci_device_probe()

3.5 struct pci_opsinclude/linux/pci.h,第 870 行)

平台相关的配置空间访问函数集:

struct pci_ops {
    int  (*add_bus)(struct pci_bus *bus);
    void (*remove_bus)(struct pci_bus *bus);
    void __iomem *(*map_bus)(struct pci_bus *bus,
                             unsigned int devfn, int where);
    int  (*read)(struct pci_bus *bus, unsigned int devfn,
                 int where, int size, u32 *val);
    int  (*write)(struct pci_bus *bus, unsigned int devfn,
                  int where, int size, u32 val);
};

map_bus 由 ECAM 驱动实现,将 (bus, devfn, where) 映射到 MMIO 虚地址;read/write 则通过该地址执行实际的内存访问。


4. 设备枚举流程

4.1 整体流程

平台/ACPI 驱动
    |
    | pci_alloc_host_bridge()
    | 填充 bridge->windows / bridge->ops
    v
pci_scan_root_bus_bridge()   [probe.c:3368]
    |
    | pci_register_host_bridge()
    |   device_add(&bridge->dev)
    |   pci_alloc_bus(NULL)         创建根 pci_bus
    |   list_add_tail(&bus->node, &pci_root_buses)
    v
pci_scan_child_bus(b)        [probe.c:3206]
    |
    | --> pci_scan_child_bus_extend(bus, 0)   [probe.c:3083]
    |         for devfn in [0..255]:
    |             pci_scan_single_device(bus, devfn)
    |                 pci_scan_device(bus, devfn)
    |                     pci_alloc_dev(bus)
    |                     pci_setup_device(dev)  [probe.c:2016]
    |                 pci_device_add(dev, bus)
    |         for each bridge:
    |             pci_scan_bridge_extend()
    |                 pci_add_new_bus()
    |                     pci_alloc_child_bus()  [probe.c:1200]
    |                 pci_scan_child_bus_extend(child, ...)  (递归)
    v
pci_assign_unassigned_resources()   资源分配
pci_bus_add_devices()               注册设备,触发驱动绑定

4.2 pci_scan_root_bus_bridge()(probe.c:3368)

int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge)
{
    // 从 bridge->windows 中找出总线号资源,设定 bridge->busnr
    resource_list_for_each_entry(window, &bridge->windows)
        if (window->res->flags & IORESOURCE_BUS) {
            bridge->busnr = window->res->start;
            found = true;
        }

    ret = pci_register_host_bridge(bridge); // 创建根总线并注册
    b = bridge->bus;

    max = pci_scan_child_bus(b); // 触发完整的子树扫描

    if (!found)
        pci_bus_update_busn_res_end(b, max);
    return 0;
}

4.3 pci_setup_device()(probe.c:2016)

这是枚举过程中最核心的函数,负责从硬件读取设备信息填充 pci_dev

int pci_setup_device(struct pci_dev *dev)
{
    // 1. 读取 Header Type,确定是 Endpoint / Bridge / CardBus
    hdr_type = pci_hdr_type(dev);
    dev->hdr_type = FIELD_GET(PCI_HEADER_TYPE_MASK, hdr_type);
    dev->multifunction = FIELD_GET(PCI_HEADER_TYPE_MFD, hdr_type);

    // 2. 设置设备名,格式为 "domain:bus:slot.func"
    dev_set_name(&dev->dev, "%04x:%02x:%02x.%d",
                 pci_domain_nr(dev->bus), dev->bus->number,
                 PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));

    // 3. 读取 Class Code,设置 dev->class 和 dev->revision
    class = pci_class(dev);
    dev->revision = class & 0xff;
    dev->class = class >> 8;

    // 4. 确定配置空间大小(PCIe 设备为 4096)
    dev->cfg_size = pci_cfg_space_size(dev);

    // 5. 初始 DMA mask 和 MSI addr mask
    dev->dma_mask = 0xffffffff;
    dev->msi_addr_mask = DMA_BIT_MASK(64);

    // 6. 运行早期 fixup
    pci_fixup_device(pci_fixup_early, dev);

    // 7. 根据 Header Type 处理
    switch (dev->hdr_type) {
    case PCI_HEADER_TYPE_NORMAL:    // 普通 Endpoint
        pci_read_irq(dev);
        pci_read_bases(dev, PCI_STD_NUM_BARS, PCI_ROM_ADDRESS); // 读 BAR
        pci_subsystem_ids(dev, ...);
        break;
    case PCI_HEADER_TYPE_BRIDGE:    // P2P 桥
        pci_read_irq(dev);
        pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);  // 桥只有 BAR0/1
        pci_read_bridge_bases(dev->subordinate);   // 读桥窗口
        break;
    }
    return 0;
}

4.4 pci_alloc_child_bus()(probe.c:1200)

为桥创建子总线:

static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
                                           struct pci_dev *bridge, int busnr)
{
    child = pci_alloc_bus(parent);
    child->parent = parent;
    child->sysdata = parent->sysdata;
    child->bus_flags = parent->bus_flags; // 继承 NO_MSI 等标志

    // 继承 ops:优先使用 host->child_ops,否则沿用父总线 ops
    host = pci_find_host_bridge(parent);
    child->ops = host->child_ops ? host->child_ops : parent->ops;

    child->number = child->busn_res.start = busnr;
    child->primary = parent->busn_res.start;
    child->busn_res.end = 0xff;   // 暂设为最大,后续回写实际值

    child->self = bridge;
    bridge->subordinate = child;  // 桥指向子总线

    pci_set_bus_msi_domain(child);  // 继承/搜索 MSI 域
    device_register(&child->dev);
    return child;
}

5. BAR 资源分配

5.1 BAR 的硬件编码

BAR(Base Address Register)有两种类型,由 bit[0] 区分:

Memory BAR:
  bit[0]   = 0(内存空间)
  bit[2:1] = 00(32位)/ 10(64位)/ 01(保留,曾用于1MB以内)
  bit[3]   = Prefetchable
  bit[31:4]= 基址(16字节对齐)

I/O BAR:
  bit[0]   = 1(I/O 空间)
  bit[1]   = 保留
  bit[31:2]= 基址(4字节对齐)

5.2 BAR 大小探测:decode_bar()__pci_read_base()

decode_bar()(probe.c:134)解析一个 BAR 的类型标志:

static inline unsigned long decode_bar(struct pci_dev *dev, u32 bar)
{
    if ((bar & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) {
        flags = bar & ~PCI_BASE_ADDRESS_IO_MASK;
        flags |= IORESOURCE_IO;
        return flags;
    }
    flags = bar & ~PCI_BASE_ADDRESS_MEM_MASK;
    flags |= IORESOURCE_MEM;
    if (flags & PCI_BASE_ADDRESS_MEM_PREFETCH)
        flags |= IORESOURCE_PREFETCH;
    // 处理 32/64 位内存类型
    mem_type = bar & PCI_BASE_ADDRESS_MEM_TYPE_MASK;
    if (mem_type == PCI_BASE_ADDRESS_MEM_TYPE_64)
        flags |= IORESOURCE_MEM_64;
    return flags;
}

__pci_read_base()(probe.c:218)通过写全 1 后回读来获取 BAR 大小掩码(即 size = ~mask + 1 的思路),核心逻辑:

// 对 64 位 BAR,需要读两个相邻的 DWORD
if (res->flags & IORESOURCE_MEM_64) {
    pci_read_config_dword(dev, pos + 4, &l);
    sz = sizes[1];
    l64  |= ((u64)l << 32);
    sz64 |= ((u64)sz << 32);
    mask64 |= ((u64)~0 << 32);
}
sz64 = pci_size(l64, sz64, mask64); // 计算实际大小

__pci_size_stdbars()(probe.c:197)统一对所有标准 BAR 进行批量 size 探测,这样只需一次禁用 decode,减少虚拟化环境开销。

5.3 pci_assign_resource()(setup-res.c:324)

将一个 BAR 分配到实际的系统地址:

int pci_assign_resource(struct pci_dev *dev, int resno)
{
    struct resource *res = pci_resource_n(dev, resno);

    // 固定地址的 BAR(IORESOURCE_PCI_FIXED)直接跳过
    if (res->flags & IORESOURCE_PCI_FIXED)
        return 0;

    res->flags |= IORESOURCE_UNSET;
    align = pci_resource_alignment(dev, res);
    size  = resource_size(res);

    // 调用 _pci_assign_resource(),向上遍历总线树查找空间
    ret = _pci_assign_resource(dev, resno, size, align);

    // 如果分配失败,尝试沿用固件配置的地址
    if (ret < 0)
        ret = pci_revert_fw_address(res, dev, resno, size);

    if (ret == 0) {
        res->flags &= ~IORESOURCE_UNSET;
        pci_update_resource(dev, resno); // 写回配置空间
    }
    return ret;
}

__pci_assign_resource()(setup-res.c:260)按优先级尝试:

  1. 精确匹配 Prefetchable + 64bit 窗口
  2. 回退到 Prefetchable 32bit 窗口(若桥只支持 32 位)
  3. 最后使用非 Prefetchable 窗口

5.4 资源树关系

iomem_resource (全局 I/O 内存资源树根)
  +-- "PCI Bus 0000:00" [MMIO 范围]
       +-- "0000:00:1c.0 PCIe bridge" [桥窗口]
            +-- "0000:01:00.0 NVMe" BAR0 [4KB MMIO]
            +-- "0000:01:00.0 NVMe" BAR4 [256KB MMIO, prefetch]

pci_bus_add_resource() 将桥窗口挂入资源树;request_resource() 在分配时将子资源挂到父资源下,形成层次约束。


6. MSI/MSI-X 中断机制

6.1 传统 INTx vs MSI

特性 INTx(传统) MSI MSI-X
中断数量 最多 4 个(共享) 1~32 个 最多 2048 个
寄存器位置 配置头 0x3C 能力结构 能力 + BAR 表
电平/边沿 电平触发 边沿触发 边沿触发
向量隔离 部分 完整

6.2 MSI 消息格式

MSI 通过向系统内存写一个 32 位数据(MSI Data)到指定地址(MSI Address)来触发中断。在 x86 上:

  • Address0xFEE[Destination ID][RH][DM]XX(指向 LAPIC)
  • Data[TM][Level][Delivery Mode][Vector]

内核在 pci_write_msg_msi()(msi/msi.c:187)中写入这些值到设备配置空间:

static inline void pci_write_msg_msi(struct pci_dev *dev, struct msi_desc *desc,
                                     struct msi_msg *msg)
{
    int pos = dev->msi_cap;
    pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, msg->address_lo);
    if (desc->pci.msi_attrib.is_64) {
        pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_HI, msg->address_hi);
        pci_write_config_word(dev, pos + PCI_MSI_DATA_64, msg->data);
    } else {
        pci_write_config_word(dev, pos + PCI_MSI_DATA_32, msg->data);
    }
}

6.3 MSI-X 表结构

MSI-X 使用设备 BAR 中的一张表(MSI-X Table),每个表项 16 字节:

偏移    大小   含义
0x0     4B    Lower Address
0x4     4B    Upper Address
0x8     4B    Data
0xC     4B    Vector Control(bit[0]=Mask)

pci_write_msg_msix()(msi/msi.c:209)使用 writel() 直接写 MMIO,并遵守"修改 address/data 时必须先 mask"的规范:

if (unmasked)
    pci_msix_write_vector_ctrl(desc, ctrl | PCI_MSIX_ENTRY_CTRL_MASKBIT);

writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR);
writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR);
writel(msg->data,       base + PCI_MSIX_ENTRY_DATA);

if (unmasked)
    pci_msix_write_vector_ctrl(desc, ctrl); // 恢复 unmask

6.4 __pci_enable_msi_range()(msi/msi.c:413)

驱动调用 pci_alloc_irq_vectors() 最终走到此函数:

int __pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec,
                           struct irq_affinity *affd)
{
    if (!pci_msi_supported(dev, minvec) || dev->current_state != PCI_D0)
        return -EINVAL;

    // MSI 和 MSI-X 互斥
    if (dev->msix_enabled)
        return -EINVAL;

    // 检查设备能提供多少个 MSI 向量
    nvec = pci_msi_vec_count(dev);
    if (nvec < minvec)
        return -ENOSPC;

    // 建立 MSI 设备上下文(含 devres 自动释放)
    rc = pci_setup_msi_context(dev);

    // 搜索或创建该设备的 MSI IRQ 域
    if (!pci_setup_msi_device_domain(dev, nvec))
        return -ENODEV;

    // 执行核心初始化:分配向量、写配置空间、使能
    return msi_capability_init(dev, nvec, affd);
}

pci_msi_supported()(msi/msi.c:29)会沿总线树向上检查 PCI_BUS_FLAGS_NO_MSI 标志——任何一级桥不路由 MSI 都会导致失败。

6.5 MSI 域与 IRQ 域架构

设备 MSI 能力
    |
    | msi_domain_alloc_irqs()
    v
PCI MSI IRQ Domain(每设备或每根复合体)
    |
    | parent domain ops
    v
GIC-ITS / X86 APIC IRQ Domain
    |
    v
硬件中断控制器

pci_host_bridge_msi_domain()(probe.c:924)通过 OF、ACPI 或 fwnode 三条路径搜索 MSI 域,在 pci_register_host_bridge() 时调用 pci_set_bus_msi_domain() 关联到根总线(probe.c:1044)。若找不到 MSI 域,会设置 PCI_BUS_FLAGS_NO_MSI,禁止所有下游设备使用 MSI。


7. IOMMU 与 PCIe ACS 隔离

7.1 IOMMU 与 DMA 重映射

IOMMU 将设备的总线地址(IOVA)重映射为系统物理地址,阻止设备访问未授权内存。Linux 通过 iommu_map() / iommu_unmap() 管理映射表,驱动通过 DMA API(dma_alloc_coherent, dma_map_single)间接使用。

VFIO 等驱动设置 driver_managed_dma = true(pci.h:1035),绕过内核 DMA API,自行管理 IOVA 空间。

7.2 PCIe ACS(Access Control Services)

ACS 是 PCIe 3.0 引入的能力,用于控制 Peer-to-Peer 访问,防止恶意设备绕过 IOMMU 直接发 DMA 到其他设备内存。

ACS 能力位(PCI_ACS_*):

名称 含义
0 ACS Source Validation 验证 TLP 来源 RID
1 ACS Translation Blocking 阻止 Address Translation 请求
2 ACS P2P Request Redirect 将 P2P 请求重定向到 RC
3 ACS P2P Completion Redirect 将 P2P Completion 重定向到 RC
4 ACS Upstream Forwarding 转发未路由的 TLP 到上行
5 ACS P2P Egress Control 控制 Egress 端口的 P2P 访问
6 ACS Direct Translated P2P 控制直接翻译 P2P 请求

pci_acs_enabled()(pci.c:3553)检测一个设备的 ACS 能力状态:

bool pci_acs_enabled(struct pci_dev *pdev, u16 acs_flags)
{
    // 非 PCIe 设备不支持 ACS
    if (!pci_is_pcie(pdev))
        return false;

    switch (pci_pcie_type(pdev)) {
    case PCI_EXP_TYPE_DOWNSTREAM:
    case PCI_EXP_TYPE_ROOT_PORT:
        // Root Port 和 Switch 下行口必须实现 ACS
        return pci_acs_flags_enabled(pdev, acs_flags);

    case PCI_EXP_TYPE_ENDPOINT:
    case PCI_EXP_TYPE_UPSTREAM:
        // 多功能设备才需要检查 ACS
        if (!pdev->multifunction)
            return true; // 单功能设备天然隔离,视为 ACS 满足
        return pci_acs_flags_enabled(pdev, acs_flags);
    }
    return true;
}

pci_acs_path_enabled()(pci.c:3626)沿整条路径检查 ACS,用于判断两个设备之间是否可以进行 P2P 访问且满足 IOMMU 隔离要求。VFIO 在分配设备给虚机前会调用此函数验证完整路径的 ACS 状态。

7.3 ACS 与 IOMMU Group

IOMMU 将无法互相隔离的设备分到同一 IOMMU Group。对于 PCIe 拓扑:

RC
 +-- Root Port A (ACS enabled)
 |    +-- Switch Upstream Port (ACS enabled)
 |         +-- Switch Downstream Port 0 (ACS enabled) --> EP0 (独立 group)
 |         +-- Switch Downstream Port 1 (ACS enabled) --> EP1 (独立 group)
 +-- Root Port B
      +-- MFD EP (no ACS) --> 所有 function 共享同一 group

若路径上任一节点 ACS 缺失,相关设备会被合并到同一 IOMMU Group,不能单独直通给虚机。


8. PCIe 电源管理:ASPM 与设备电源状态

8.1 设备电源状态 D0~D3

include/linux/pci.h 第 174~180 行定义:

#define PCI_D0      ((pci_power_t __force) 0)   // 完全供电,正常工作
#define PCI_D1      ((pci_power_t __force) 1)   // 低功耗,保持上下文
#define PCI_D2      ((pci_power_t __force) 2)   // 更低功耗
#define PCI_D3hot   ((pci_power_t __force) 3)   // 极低功耗,配置空间可访问
#define PCI_D3cold  ((pci_power_t __force) 4)   // 主电源断,辅助电源维持 PME

pci_power_names[](pci.c:40)对应字符串 "D0"/"D1"/"D2"/"D3hot"/"D3cold"

从 D3cold 恢复到 D0 触发 Conventional Reset,设备必须在 1 秒内就绪(PCI_RESET_WAIT = 1000 ms,pci.c:73);可通过 Request Retry Status 延长,最长容忍 60 秒(PCIE_RESET_READY_POLL_MS = 60000,pci.c:81)。

pci_dev_d3_sleep() 负责 D3hot->D0 的等待:

static void pci_dev_d3_sleep(struct pci_dev *dev)
{
    unsigned int delay_ms = max(dev->d3hot_delay, pci_pm_d3hot_delay);
    if (delay_ms) {
        upper = max(DIV_ROUND_CLOSEST(delay_ms, 5), 1U); // 20% 抖动
        usleep_range(delay_ms * USEC_PER_MSEC,
                     (delay_ms + upper) * USEC_PER_MSEC);
    }
}

8.2 ASPM 链路状态

ASPM(Active State Power Management)在链路空闲时降低链路功耗:

L0  (正常工作)
 |
 +--[Link Idle]--> L0s (快速唤醒,~70ns)
 |
 +--[更长空闲]---> L1  (更省电,~1-4us 唤醒延迟)
                    |
                    +--[L1.1] CLKREQ# 去激活,不关 PLL
                    +--[L1.2] CLKREQ# 去激活,关 PLL (~10us 恢复)

L1 Sub-States(L1.1/L1.2)由扩展能力 PCI_EXT_CAP_ID_L1SS 控制,需要链路两端同时支持。

pci_configure_aspm_l1ss()(aspm.c:70)读取能力偏移:

void pci_configure_aspm_l1ss(struct pci_dev *pdev)
{
    pdev->l1ss = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_L1SS);
    pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_L1SS, 2 * sizeof(u32));
}

pci_save_aspm_l1ss_state() / pci_restore_aspm_l1ss_state()(aspm.c:83, 125)在系统 suspend/resume 时保存和恢复 L1SS 配置寄存器(PCI_L1SS_CTL1, PCI_L1SS_CTL2)。

恢复时必须遵循严格顺序:先禁用两端 L0s/L1,再先关闭 Downstream 的 L1.2,再关闭 Upstream 的,最后按相反顺序重新使能(aspm.c:156~198)。

8.3 LTR(Latency Tolerance Reporting)

LTR 允许设备向 RC 上报自己的延迟容忍度,从而使系统在延迟允许时才进入更深的低功耗状态。pci_save_ltr_state() / pci_restore_ltr_state()(aspm.c:30, 54)在挂起/恢复时维护 PCI_LTR_MAX_SNOOP_LAT 寄存器。

pci_dev 中的 ltr_path 字段(pci.h:430)标志从设备到根的完整路径上 LTR 是否均受支持。

8.4 链路速度

PCIe 代际速度(probe.c:766~783):

const unsigned char pcie_link_speed[] = {
    PCI_SPEED_UNKNOWN,   /* 0 */
    PCIE_SPEED_2_5GT,    /* 1 - Gen1  */
    PCIE_SPEED_5_0GT,    /* 2 - Gen2  */
    PCIE_SPEED_8_0GT,    /* 3 - Gen3  */
    PCIE_SPEED_16_0GT,   /* 4 - Gen4  */
    PCIE_SPEED_32_0GT,   /* 5 - Gen5  */
    PCIE_SPEED_64_0GT,   /* 6 - Gen6 (Flit Mode) */
};

9. SR-IOV:虚拟化扩展

9.1 SR-IOV 架构

SR-IOV(Single Root I/O Virtualization)允许一个物理 PCIe 设备(PF)虚拟出多个轻量级虚拟设备(VF),VF 可直通给虚机。

+----------------------------------+
| Physical Function (PF)           |
|  - 完整的 PCIe 配置头             |
|  - SR-IOV Extended Capability    |
|    - Total VFs(硬件最大)         |
|    - Initial VFs                  |
|    - VF Device ID                 |
|    - First VF Offset              |
|    - VF Stride                    |
|  - SR-IOV BARs(所有 VF 共享映射) |
+--------+--------+--------+-------+
         |        |        |
    +----+--+ +---+---+ +--+----+
    |  VF0  | |  VF1  | |  VF2 |
    | 轻量头 | | 轻量头 | | ...  |
    +-------+ +-------+ +------+
    各 VF 使用 PF SR-IOV BARs 中的固定大小分区

9.2 struct pci_sriov(drivers/pci/pci.h:658)

PF 的 pci_dev->sriov 指向此结构:

struct pci_sriov {
    int   pos;            // SR-IOV 扩展能力在配置空间的偏移
    int   nres;           // VF BAR 数量
    u32   cap;            // SR-IOV Capabilities
    u16   ctrl;           // SR-IOV Control 寄存器当前值
    u16   total_VFs;      // 硬件支持的 VF 总数
    u16   initial_VFs;    // 初始 VF 数
    u16   num_VFs;        // 当前激活的 VF 数
    u16   offset;         // First VF 的 Routing ID 相对 PF 的偏移
    u16   stride;         // 相邻 VF 间的 RID 步长
    u16   vf_device;      // VF 的 Device ID
    u32   pgsz;           // BAR 对齐使用的页大小
    u8    max_VF_buses;   // VF 占用的最大总线数
    u16   driver_max_VFs; // 驱动声明支持的最大 VF 数
    resource_size_t barsz[PCI_SRIOV_NUM_BARS]; // 每个 VF 的 BAR 大小
    bool  drivers_autoprobe; // 是否自动绑定驱动到 VF
};

9.3 pci_enable_sriov()(iov.c:1160)

int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn)
{
    might_sleep();
    if (!dev->is_physfn)
        return -ENOSYS;
    return sriov_enable(dev, nr_virtfn);
}

sriov_enable() 内部流程:

  1. 调用 compute_max_vf_buses()(iov.c:105)计算 VF 所需总线数
  2. 向 SR-IOV Control 写入 PCI_SRIOV_CTRL_VFE(VF Enable)
  3. 为每个 VF 调用 pci_iov_add_virtfn()(iov.c:346)

9.4 VF 创建:pci_iov_add_virtfn()(iov.c:346)

int pci_iov_add_virtfn(struct pci_dev *dev, int id)
{
    // 1. 计算 VF 的总线/设备号
    bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));

    // 2. 分配 pci_dev 并进行基本初始化
    virtfn = pci_iov_scan_device(dev, id, bus);
    virtfn->is_virtfn = 1;
    virtfn->physfn = pci_dev_get(dev);

    // 3. 从 PF 的 SR-IOV BAR 中划分此 VF 的地址区间
    for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
        res = &dev->resource[idx];
        size = pci_iov_resource_size(dev, idx);
        resource_set_range(&virtfn->resource[i],
                           res->start + size * id, size);
        request_resource(res, &virtfn->resource[i]);
    }

    // 4. 注册设备,触发驱动绑定
    pci_device_add(virtfn, virtfn->bus);
    pci_bus_add_device(virtfn);
}

VF 的 BDF(Bus:Device:Function)通过公式计算(iov.c:24~38):

int pci_iov_virtfn_bus(struct pci_dev *dev, int vf_id)
{
    return dev->bus->number +
           ((dev->devfn + dev->sriov->offset +
             dev->sriov->stride * vf_id) >> 8);
}

int pci_iov_virtfn_devfn(struct pci_dev *dev, int vf_id)
{
    return (dev->devfn + dev->sriov->offset +
            dev->sriov->stride * vf_id) & 0xff;
}

9.5 VF MSI-X 管理

驱动可通过 sriov_configure 回调和 sysfs sriov_numvfs 动态调整 VF 数量。sriov_set_msix_vec_count(iov.c:248~287)允许 PF 驱动在 VF 没有绑定驱动时动态修改 VF 的 MSI-X 向量数。


10. P2P DMA:设备间直接访问

10.1 概念

P2P DMA 允许两个 PCIe 设备(如 NVMe SSD 与 GPU)之间直接传输数据,数据不经过主机内存,降低延迟并减轻 CPU 负担。

+--------+     P2P DMA     +--------+
|  NVMe  | --------------> |  GPU   |
+---+----+                 +----+---+
    |                           |
    +----------+----------------+
               |
         PCIe Switch
               |
           Root Port

10.2 struct pci_p2pdma(p2pdma.c:24)

struct pci_p2pdma {
    struct gen_pool *pool;           // 内存池(管理 P2P 内存区域)
    bool             p2pmem_published; // 是否已对外发布
    struct xarray    map_types;       // 缓存各目标设备的 DMA 映射类型
    struct p2pdma_provider mem[PCI_STD_NUM_BARS]; // 来自各 BAR 的内存提供者
};

pci_dev->p2pdma 使用 RCU 保护(pci.h:553),允许无锁读取。

10.3 P2P 路径判断

系统在执行 P2P 传输前必须判断两个端点是否能直接通信,主要考虑:

  1. 硬件拓扑:设备是否共享同一 Switch,Switch 是否支持 P2P(部分交换机会把 P2P 流量转发到 RC)
  2. ACS 配置:路径上的 ACS 是否阻止了 P2P(若 ACS P2P Request Redirect 使能,P2P TLP 被重定向到 RC)
  3. IOMMU:若两端在不同 IOMMU Group,需要特殊处理

map_types xarray 缓存对每个可能目标设备的映射策略(直接 P2P、经过 RC、不支持),避免每次 DMA 都重新计算。


11. pci_ops 与 ECAM

11.1 ECAM 地址映射

ECAM(Enhanced Configuration Access Mechanism)是 PCIe 访问扩展配置空间(4KB/设备)的标准机制。地址计算公式:

配置空间 MMIO 地址 =
    基地址(MCFG 表/OF 节点)
    + 总线号    << bus_shift(通常 20)
    + 设备号    << 15
    + 功能号    << 12
    + 寄存器偏移

对于标准 PCIe ECAM,bus_shift = PCIE_ECAM_BUS_SHIFT = 20,意味着每条总线占用 1MB 地址空间(256 设备 × 8 功能 × 4KB = 8MB,但通常 bus_shift 20 = 1MB/bus 仅覆盖单设备多功能情形,大型系统用更大的移位)。

11.2 pci_ecam_create()(ecam.c:27)

struct pci_config_window *pci_ecam_create(struct device *dev,
        struct resource *cfgres, struct resource *busr,
        const struct pci_ecam_ops *ops)
{
    // 在 64 位系统上,整个配置空间做一次 ioremap
    if (!per_bus_mapping)
        cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz);
    // 32 位系统对每条总线按需 ioremap
    else
        cfg->winp = kzalloc_objs(*cfg->winp, bus_range);
    ...
    dev_info(dev, "ECAM at %pR for %pR\n", &cfg->res, &cfg->busr);
}

11.3 ECAM ops 示例

典型的 ECAM pci_ops 实现(以 pci_generic_ecam_ops 为例):

map_bus:
    return cfg->win + ((bus - cfg->busr.start) << bus_shift)
                    + (devfn << 12)
                    + where;

read:
    addr = map_bus(bus, devfn, where);
    readb/readw/readl(addr) -> val;

write:
    addr = map_bus(bus, devfn, where);
    writeb/writew/writel(val, addr);

11.4 非标准访问:x86 CF8/CFC

在 x86 上,传统 PCI 通过 I/O 端口 0xCF8(地址端口)和 0xCFC(数据端口)访问 256 字节配置空间,PCIe 扩展的 0x100~0xFFF 必须使用 ECAM(通过 MCFG ACPI 表提供基地址)。x86 的 pci_ops 实现在 arch/x86/pci/,通过 raw_pci_read/write 暴露给平台无关代码。


12. sysfs 接口

12.1 设备属性(drivers/pci/pci-sysfs.c

每个 PCI 设备在 /sys/bus/pci/devices/DDDD:BB:SS.F/ 下暴露属性:

// pci-sysfs.c:54~60
pci_config_attr(vendor,           "0x%04x\n");
pci_config_attr(device,           "0x%04x\n");
pci_config_attr(subsystem_vendor, "0x%04x\n");
pci_config_attr(subsystem_device, "0x%04x\n");
pci_config_attr(revision,         "0x%02x\n");
pci_config_attr(class,            "0x%06x\n");

irq_show()(pci-sysfs.c:61)的实现对 MSI 设备返回 pci_irq_vector(pdev, 0)(第一个 MSI 向量),对其他情况返回传统 INTx pdev->irq

12.2 配置空间文件

config 文件(最大 4096 字节)允许用户空间直接读写设备配置空间:

/sys/bus/pci/devices/0000:00:1f.2/config

resource0~5 是各 BAR 对应的 mmap 文件,驱动可以通过此接口实现用户态驱动(如 DPDK、SPDK)。

12.3 SR-IOV sysfs

/sys/bus/pci/devices/DDDD:BB:SS.F/
    sriov_totalvfs    # 硬件支持的最大 VF 数(只读)
    sriov_numvfs      # 当前激活的 VF 数(读写)
    sriov_vf_total_msix  # PF 可分配的 MSI-X 总数
    virtfn0 -> ../DDDD:BB:VF0.0  # 符号链接
    virtfn1 -> ...
/sys/bus/pci/devices/DDDD:BB:VF0.0/
    physfn -> ../DDDD:BB:PF.0    # 指回 PF
    sriov_vf_msix_count          # 此 VF 的 MSI-X 向量数

sriov_numvfs_store() 通过调用 pdev->driver->sriov_configure(pdev, num_vfs) 触发驱动的 VF 数量变更逻辑(iov.c:441~500)。


13. 关键调用链汇总

13.1 系统启动时的枚举路径

start_kernel()
  -> pci_subsys_init() [arch/x86/pci/init.c]
       -> pcibios_init()
            -> pci_acpi_scan_root() [pci-acpi.c]
                 -> pci_alloc_host_bridge()
                 -> acpi_pci_root_create()
                      -> pci_scan_root_bus_bridge()  [probe.c:3368]
                           -> pci_register_host_bridge()
                           -> pci_scan_child_bus()   [probe.c:3206]
                                -> pci_scan_child_bus_extend()
                                     -> pci_scan_single_device()
                                          -> pci_setup_device()
                                     -> pci_scan_bridge_extend()(递归)
            -> pci_assign_unassigned_resources()
                 -> pci_assign_resource()            [setup-res.c:324]
            -> pci_bus_add_devices()
                 -> pci_device_add()
                      -> device_add() -> bus_probe_device()
                           -> driver_probe_device()
                                -> pci_device_probe()
                                     -> drv->probe()

13.2 MSI 使能路径

drv->probe()
  -> pci_alloc_irq_vectors(dev, min, max, PCI_IRQ_MSI)
       -> __pci_enable_msi_range()              [msi/msi.c:413]
            -> pci_msi_supported()              [msi/msi.c:29]
            -> pci_setup_msi_context()          [msi/msi.c:100]
            -> pci_setup_msi_device_domain()
            -> msi_capability_init()            [msi/msi.c:393]
                 -> msi_setup_msi_desc()        [msi/msi.c:285]
                 -> pci_msi_setup_msi_irqs()
                      -> msi_domain_alloc_irqs()
                 -> pci_msi_set_enable(dev, 1)  [msi/msi.c:274]

13.3 SR-IOV 激活路径

echo N > /sys/bus/pci/devices/.../sriov_numvfs
  -> sriov_numvfs_store()
       -> pdev->driver->sriov_configure(pdev, N)
            -> pci_enable_sriov(dev, N)         [iov.c:1160]
                 -> sriov_enable()
                      -> pci_iov_set_numvfs()   [iov.c:89]  写 NumVFs
                      -> pci_iov_add_virtfn() × N [iov.c:346]
                           -> pci_iov_virtfn_bus/devfn()
                           -> pci_iov_scan_device()
                           -> request_resource() 划分 BAR 子段
                           -> pci_device_add()
                           -> pci_iov_sysfs_link() 创建符号链接

13.4 ASPM 恢复路径(系统 Resume)

pci_pm_resume_noirq()
  -> pci_restore_state()
       -> pci_restore_pcie_state()      保存的 PCIe 能力
       -> pci_restore_aspm_l1ss_state() [aspm.c:125]
            -> 先清除 L1.2 使能位(下行端先,上行端后)
            -> 写回 CTL2(公共模式恢复时间)
            -> 写回 CTL1(不含 L1.2 使能)
            -> 最后重写 L1.2 使能位

14. PCI 总线枚举深度剖析

14.1 pci_scan_slot() 工作原理

pci_scan_slot()(probe.c:2390)是枚举单个 slot 的入口函数,它迭代同一 slot 下所有可能的功能号(function 0~7):

int pci_scan_slot(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;
    int fn, nr = 0;

    // function 0 必须先检查,如果 fn0 不存在则整个 slot 为空
    dev = pci_scan_single_device(bus, devfn);
    if (!dev)
        return 0;  // slot 为空,不继续扫描其他 function

    nr++;

    // 只有多功能设备才扫描 fn1~fn7
    if (!dev->multifunction) {
        if (dmi_matches_slot(bus, PCI_SLOT(devfn)))
            ; // 某些平台需要扫描所有功能
        else
            return nr;
    }

    for (fn = 1; fn < 8; fn++) {
        dev = pci_scan_single_device(bus, devfn + fn);
        if (dev) {
            nr++;
            if (!dev->multifunction)
                break; // 遇到非多功能则停止
        }
    }
    return nr;
}

关键点:function 0 的 Header Type 的 bit[7](PCI_HEADER_TYPE_MFD)决定是否继续扫描其他 function。单功能设备(bit[7]=0)探测到 fn0 后即停止。

14.2 pci_scan_bridge_extend() 两阶段扫描

桥扫描分两个 pass(probe.c:1395):

Pass 0(first pass):
  目的:处理已被固件配置好的桥(Secondary/Subordinate 已有效)
  行为:
    if (secondary != 0 && subordinate >= secondary):
        直接使用固件配置的总线范围
        递归扫描 [secondary, subordinate]
        记录实际使用的最大总线号
    else:
        标记 broken=1,推迟到 Pass 1 重新配置

Pass 1(second pass):
  目的:为未配置或配置错误的桥分配总线号
  行为:
    分配 next_busnr = max + 1 作为 secondary
    写入 Secondary Bus Number
    设置 Subordinate = 0xFF(暂时占满,允许子树扫描)
    递归扫描
    扫描完成后回写实际的 Subordinate Bus Number

这种两 pass 方式避免了固件配置的总线范围和内核新分配的总线范围发生冲突。

14.3 总线号分配与热插拔预留

对于热插拔 capable 的桥(dev->is_hotplug_bridge),内核在分配总线号时会预留额外空间(pci_dev_hotplug_bridge_bus_budget(),probe.c:1291):

available_buses 参数控制子树可用的总线号数量。
  - 非热插拔桥:恰好分配自身及子设备所需的最少总线号
  - 热插拔桥:  额外预留一定数量,避免热插后需要重新分配总线号
               导致设备地址冲突

热插拔桥的 Subordinate Bus Number 设置公式(probe.c:1543):

// 在不超过上级桥允许范围的前提下,尽量多分配总线号给热插拔桥
child->busn_res.end = min(parent->busn_res.end,
                          secondary + hotplug_busnr_budget - 1);

14.4 pci_scan_device()~0 读取

内核探测设备是否存在的方法(probe.c:2337):读取 Vendor ID,若返回 0xFFFFFFFF 则表示无设备(总线上没有响应):

static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;
    u32 l;

    if (pci_bus_read_dev_vendor_id(bus, devfn, &l, 60))
        return NULL;   // 返回非 ~0 但超时或错误

    if (PCI_POSSIBLE_ERROR(l))  // l == 0xFFFFFFFF
        return NULL;   // 无设备响应

    dev = pci_alloc_dev(bus);
    if (!dev)
        return NULL;

    dev->devfn = devfn;
    dev->vendor = l & 0xffff;
    dev->device = (l >> 16) & 0xffff;

    pci_set_of_node(dev);
    if (pci_setup_device(dev)) {
        pci_release_dev(dev);
        return NULL;
    }
    return dev;
}

PCI_POSSIBLE_ERROR(l) 宏(include/linux/pci.h)检测 l == 0xFFFFFFFF,这是 PCI 总线上"无设备"的标准响应。

14.5 ARI(Alternative Routing-ID Interpretation)

ARI 是 PCIe 2.1 引入的功能,允许在单一 slot 上部署超过 8 个功能(传统 3bit 功能号只支持 fn0fn7)。在 ARI 模式下,整个 8bit devfn 字段被重新解释为一个线性的 function 编号(0255),不再区分 device/function。

内核在 pci_configure_ari()(pci.c:2120)中处理:

if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI) &&
    pci_find_ext_capability(bridge, PCI_EXT_CAP_ID_ARI) &&
    bridge_has_ari_forwarding_enabled(bridge)):
        dev->ari_enabled = 1
        下游扫描时使用 ARI 步长而非 devfn+1

14.6 多 PCI 域(Domain)支持

Linux 通过 pci_domain_nr() 获取设备所在的 PCI 域编号。多个 PCI 主机桥(Root Complex)可以拥有相互独立的总线号空间(各自从 0 开始),通过 domain 编号区分:

/sys/bus/pci/devices/0000:00:1f.2   <- Domain 0
/sys/bus/pci/devices/0001:00:00.0   <- Domain 1(另一个 Root Complex)

pci_domain_busn_res_list(probe.c:40)维护每个 domain 的总线号资源,确保同一 domain 内的总线号唯一。


15. 配置空间访问机制

15.1 pci_read_config_word() 调用链

内核提供一组标准化的配置空间访问函数,最常用的是:

pci_read_config_byte(dev, where, val)    // 读 1 字节
pci_read_config_word(dev, where, val)    // 读 2 字节(需 2 字节对齐)
pci_read_config_dword(dev, where, val)   // 读 4 字节(需 4 字节对齐)

完整调用链:

pci_read_config_word(dev, where, val)
    -> pci_read_config(dev, where, 2, val)   [access.c:~50]
        -> pci_bus_read_config_word(dev->bus, dev->devfn, where, val)
            -> raw_pci_read(bus->number, devfn, where, 2, val)
                  [平台实现,x86 使用 CF8/CFC I/O 或 ECAM MMIO]

pci_bus_read_config_word() 实际上通过 dev->bus->ops->read() 调用总线操作集(include/linux/pci.h:1548)。

15.2 PCIe Capability 快捷访问

对于频繁访问的 PCIe 标准能力寄存器,内核提供 pcie_capability_read_word() 系列函数(pci.c:1670~),这些函数使用 dev->pcie_cap 缓存的偏移量,避免每次都遍历能力链表:

int pcie_capability_read_word(struct pci_dev *dev, int pos, u16 *val)
{
    int ret;

    *val = 0;
    if (pos & 1)
        return PCIBIOS_BAD_REGISTER_NUMBER;

    // pcie_cap 在 pci_setup_device() 中通过 pci_find_capability() 填充
    if (!pci_is_pcie(dev))
        return -EINVAL;

    ret = pci_read_config_word(dev, pci_pcie_cap(dev) + pos, val);
    ...
    return ret;
}

常用偏移(相对于 PCIe Capability 起始):

PCI_EXP_DEVCAP     0x04   Device Capabilities
PCI_EXP_DEVCTL     0x08   Device Control
PCI_EXP_DEVSTA     0x0A   Device Status
PCI_EXP_LNKCAP     0x0C   Link Capabilities
PCI_EXP_LNKCTL     0x10   Link Control
PCI_EXP_LNKSTA     0x12   Link Status
PCI_EXP_SLTCAP     0x14   Slot Capabilities
PCI_EXP_SLTCTL     0x18   Slot Control
PCI_EXP_SLTSTA     0x1A   Slot Status
PCI_EXP_RTCTL      0x1C   Root Control
PCI_EXP_RTCAP      0x1E   Root Capabilities
PCI_EXP_RTSTA      0x20   Root Status

15.3 配置空间访问保护

某些寄存器在修改时需要原子的 Read-Modify-Write 操作,内核使用 dev->pcie_cap_lock 自旋锁保护:

// pcie_capability_set_word() 的安全实现(pci.c:1796)
int pcie_capability_set_word(struct pci_dev *dev, int pos, u16 set)
{
    u16 val;
    pcie_capability_read_word(dev, pos, &val);
    val |= set;
    return pcie_capability_write_word(dev, pos, val);
}

注意:这不是原子操作,若有并发访问风险,调用者需自己加锁。dev->pcie_cap_lock 仅用于 MSI Mask 寄存器的 RMW(pci_msi_update_mask(),msi.c:123)。

15.4 扩展能力链表遍历

PCIe 扩展能力(0x100~0xFFF)采用链表形式组织,每个能力结构头部格式:

偏移  大小  含义
0x0   2B    Capability ID (PCI_EXT_CAP_ID_*)
0x2   2B    Capability Version + Next Capability Offset
            bit[15:4] = Next Capability Pointer(4字节对齐)
            bit[3:0]  = Capability Version

pci_find_ext_capability()(search.c:85)遍历此链表:

u16 pci_find_ext_capability(struct pci_dev *dev, int cap)
{
    u32 header;
    int ttl;
    u16 pos = PCI_CFG_SPACE_SIZE;  // 从 0x100 开始

    if (dev->cfg_size <= PCI_CFG_SPACE_SIZE)
        return 0;

    if (pci_read_config_dword(dev, pos, &header) != PCIBIOS_SUCCESSFUL)
        return 0;

    ttl = PCI_FIND_CAP_TTL;  // 防止无限循环
    while (ttl--) {
        if (PCI_EXT_CAP_ID(header) == 0x0000 ||
            PCI_EXT_CAP_ID(header) == 0xFFFF)
            break;

        if (PCI_EXT_CAP_ID(header) == cap)
            return pos;

        pos = PCI_EXT_CAP_NEXT(header);
        if (pos < PCI_CFG_SPACE_SIZE)
            break;

        if (pci_read_config_dword(dev, pos, &header) != PCIBIOS_SUCCESSFUL)
            break;
    }
    return 0;
}

15.5 配置空间保存与恢复

在系统进入挂起状态前,内核保存设备配置空间中的关键寄存器(pci_save_state(),pci.c:1475):

int pci_save_state(struct pci_dev *dev)
{
    // 保存标准配置头的前 16 个 DWORD(64 字节)
    for (i = 0; i < 16; i++)
        pci_read_config_dword(dev, i * 4,
                              &dev->saved_config_space[i]);

    // 保存各 PCI 能力(MSI、MSIX、PCIe、PM、L1SS、LTR 等)
    pci_save_pcie_state(dev);
    pci_save_msi_state(dev);
    pci_save_msix_state(dev);
    pci_save_ltr_state(dev);    // aspm.c:30
    pci_save_aspm_l1ss_state(dev); // aspm.c:83
    ...
    return 0;
}

pci_restore_state()(pci.c:1529)按相反顺序恢复,并注意先写 PCIe 能力再写标准头,防止 AER 等功能在中途开始工作。


16. PCIe 链路训练与 LTSSM 状态机

16.1 LTSSM 概述

LTSSM(Link Training and Status State Machine)是 PCIe 物理层的核心,负责链路建立、训练、低功耗管理和错误恢复。它由 PCIe 端口硬件自主运行,操作系统通过 PCI_EXP_LNKSTA 寄存器观察其结果。

LTSSM 状态分为 11 个主要状态:

+----------+
|  Detect  |  <-- 上电复位后进入,检测链路对端是否存在
+----+-----+
     | 检测到对端
     v
+-----------+
|  Polling  |  发送 TS1/TS2 有序集,协商链路参数
+----+------+
     | 双方锁定
     v
+-------------+
| Configuration|  配置链路宽度(x1/x2/x4/x8/x16)和通道
+----+--------+
     | 配置完成
     v
+-----------+
|    L0     |  <-- 正常工作状态
+---+---+---+
    |   |
    |   +----> L0s  (快速入口,约 ~3ns 恢复)
    |
    +--------> L1   (ASPM L1,需双方协商)
                |
                +--> L2   (D3cold 模式下,仅辅助电源)
                |
                +--> L3   (完全关闭)

其他状态:
  Recovery  -- 从 L0s/L1 返回 L0,或链路出错时重训练
  Disabled  -- 链路被软件强制禁用
  Loopback  -- 测试模式
  Hot-Reset -- 桥的 Secondary Bus Reset

16.2 链路训练过程

链路训练(Link Training)是 LTSSM 从 Detect 到 L0 的完整过程:

阶段 1 - Detect.Quiet / Detect.Active
  发送端检测接收端阻抗(是否存在对端)
  通过注入直流偏置或低频信号检测 120Ω 终端阻抗

阶段 2 - Polling.Active
  双方以最低速(Gen1 2.5GT/s)交换 TS1 有序集(Training Sequence 1)
  协商链路参数:通道数、支持的速度等级

阶段 3 - Polling.Configuration / Compliance
  交换 TS2 有序集,确认协商结果
  若启用了合规测试则进入 Compliance 模式

阶段 4 - Configuration.Linkwidth.Start
  协商实际使用的通道数(x1/x2/x4/x8/x16/x32)
  高位编号通道用于链路宽度缩减(Partial Width)

阶段 5 - Configuration.Lanenum
  各通道编号分配,确定各通道在链路中的逻辑位置

阶段 6 - Configuration.Complete / Idle
  完成配置,进入 L0 正常工作状态

可选阶段 - 速度升级(Gen2/Gen3/Gen4/Gen5/Gen6)
  在 L0 中通过 Data Link Layer Packets 协商升速
  进入 Recovery -> Recovery.Speed 完成速率切换

16.3 内核如何等待链路训练完成

pciehp_check_link_status()(hotplug/pciehp_hpc.c)等待热插后链路稳定:

int pciehp_check_link_status(struct controller *ctrl)
{
    struct pci_dev *pdev = ctrl_dev(ctrl);
    bool found;
    u16 lnk_status;

    // 等待数据链路层激活(DLLSC)
    if (!pcie_wait_for_link(pdev, true)) {
        ctrl_info(ctrl, "Slot(%s): No link\n", slot_name(ctrl));
        return -ENODEV;
    }

    // 检查 Link Bandwidth Management Status
    // 读取实际协商的链路速度和宽度
    pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
    ctrl_dbg(ctrl, "%s: lnksta = %04x\n", __func__, lnk_status);

    found = pciehp_query_power_fault(ctrl);
    ...
}

pcie_wait_for_link()(pci.c:4856)轮询 PCI_EXP_LNKSTA 寄存器的 PCI_EXP_LNKSTA_DLLLA(Data Link Layer Link Active)位,最长等待 1 秒。

16.4 链路速率协商

PCIe 链路速率协商的核心寄存器(在 PCIe Capability 中):

PCI_EXP_LNKCAP (Link Capabilities,只读):
  bit[3:0]  = Max Link Speed (1=2.5GT, 2=5GT, 3=8GT, 4=16GT, 5=32GT, 6=64GT)
  bit[9:4]  = Maximum Link Width
  bit[11:10]= ASPM Support (01=L0s, 10=L1, 11=both)
  bit[17]   = Clock Power Management

PCI_EXP_LNKCTL (Link Control,可读写):
  bit[1:0]  = ASPM Control (00=disabled, 01=L0s, 10=L1, 11=both)
  bit[6]    = Common Clock Configuration
  bit[8]    = Clock Power Management Enable

PCI_EXP_LNKSTA (Link Status,只读):
  bit[3:0]  = Current Link Speed
  bit[9:4]  = Negotiated Link Width
  bit[10]   = Link Training (=1 时链路正在训练)
  bit[13]   = Data Link Layer Link Active

内核通过 pcie_set_speed_cap()pcie_change_speed() 触发链路降速(如出于功耗或兼容性原因)。

16.5 PCIe 带宽控制

drivers/pci/pcie/bwctrl.c 实现了带宽控制服务,监听 PCI_EXP_LNKSTA 的 Link Bandwidth Management Status(LBMS)位,在链路自主调整带宽(如由于信号质量问题降速)时通知用户空间。

pcie_reset_lbms()(pcie/bwctrl.c)在热插拔完成后清除 LBMS,防止历史事件干扰新设备的监控。


17. MSI/MSI-X 中断分配深度分析

17.1 MSI 能力结构

MSI 能力(Capability ID = 0x05)包含以下寄存器:

偏移    大小  含义
0x00    2B   Capability ID (= 0x05)
0x02    2B   Next Capability Pointer
0x04    2B   Message Control
              bit[0]    = MSI Enable
              bit[3:1]  = Multiple Message Capable (2^n vectors)
              bit[6:4]  = Multiple Message Enable (实际分配数)
              bit[7]    = 64-bit Address Capable
              bit[8]    = Per-Vector Masking Capable
0x06    4B   Message Address Low (32-bit 对齐)
0x0A    4B   Message Address High (仅 64-bit 设备有)
0x0E    2B   Message Data
0x10    4B   Mask Bits (仅 Per-Vector Masking 设备有)
0x14    4B   Pending Bits (仅 Per-Vector Masking 设备有)

pci_msi_vec_count()(msi/msi.c:370)读取 PCI_MSI_FLAGS_QMASK 字段,返回设备声明支持的最大 MSI 向量数(1、2、4、8、16 或 32)。

17.2 MSI 向量分配流程

pci_alloc_irq_vectors(dev, min, max, PCI_IRQ_MSI)
    |
    v
__pci_enable_msi_range(dev, min, max, NULL)   [msi/msi.c:413]
    |
    +-- pci_msi_supported(dev, min)      检查全局开关、NO_MSI 标志
    |
    +-- pci_setup_msi_context(dev)       建立 devres 上下文
    |       msi_setup_device_data()      分配 msi_device_data
    |       pcim_setup_msi_release()     注册 devres 自动释放
    |
    +-- pci_setup_msi_device_domain()    创建 per-device IRQ 域
    |
    v
msi_capability_init(dev, nvec, affd)     [msi/msi.c:393]
    |
    +-- msi_setup_msi_desc(dev, nvec)    分配 msi_desc 描述符
    |       填充 msi_attrib.is_64
    |       填充 msi_attrib.can_mask
    |       设置 mask_pos
    |
    +-- pci_msi_setup_msi_irqs()
    |       msi_domain_alloc_irqs()      向 IRQ 域申请向量号
    |           -> arch_setup_msi_irqs() [x86: set APIC routing]
    |
    +-- pci_read_msi_msg()              读取分配到的 address/data
    +-- pci_write_msg_msi()            写入设备 MSI 能力寄存器
    +-- pci_msi_set_enable(dev, 1)     设置 MSI Enable 位

17.3 MSI-X 能力结构与 BAR 布局

MSI-X 能力(Capability ID = 0x11):

偏移    大小  含义
0x00    2B   Capability ID (= 0x11)
0x02    2B   Next Capability Pointer
0x04    2B   Message Control
              bit[10:0] = Table Size - 1 (最多 2048 项)
              bit[14]   = Function Mask(全局 mask)
              bit[15]   = MSI-X Enable
0x06    4B   Table Offset + Table BIR
              bit[2:0]  = BAR Index (Table 所在 BAR)
              bit[31:3] = Table Offset(8字节对齐)
0x0A    4B   Pending Bit Array (PBA) Offset + PBA BIR
              bit[2:0]  = PBA 所在 BAR
              bit[31:3] = PBA Offset(8字节对齐)

MSI-X Table 和 PBA 通常共享同一 BAR,Table 在低地址,PBA 在高地址(4KB 对齐分隔),也可以分布在不同 BAR 中。

BAR2 MMIO 区域示意:
  [0x0000 - 0x7FFF]  MSI-X Table(512 个 16字节表项 = 8KB)
  [0x8000 - 0x8FFF]  PBA(512 个向量的 Pending Bit = 64 字节)

17.4 向量亲和性(IRQ Affinity)

pci_alloc_irq_vectors_affinity() 允许指定每个向量的 CPU 亲和性,用于高性能驱动(如 NVMe 每队列对应一个 CPU 核):

struct irq_affinity desc = {
    .pre_vectors  = 1,  // 第一个向量不设置亲和性(用于错误处理)
    .post_vectors = 0,  // 最后几个向量不设置亲和性
};
nvec = pci_alloc_irq_vectors_affinity(pdev, 1, max_queues,
                                       PCI_IRQ_MSIX, &desc);

内核通过 irq_create_affinity_masks() 根据系统 CPU 拓扑(NUMA node、物理核、超线程)生成亲和性掩码,分发给各向量。

17.5 MSI 掩码机制

MSI Per-Vector Masking(can_mask = 1)允许单独屏蔽某个 MSI 向量:

// pci_msi_update_mask() - msi/msi.c:114
void pci_msi_update_mask(struct msi_desc *desc, u32 clear, u32 set)
{
    struct pci_dev *dev = msi_desc_to_pci_dev(desc);
    raw_spinlock_t *lock = &dev->msi_lock;
    unsigned long flags;

    if (!desc->pci.msi_attrib.can_mask)
        return;

    raw_spin_lock_irqsave(lock, flags);
    desc->pci.msi_mask &= ~clear;
    desc->pci.msi_mask |= set;
    pci_write_config_dword(dev, desc->pci.mask_pos, desc->pci.msi_mask);
    raw_spin_unlock_irqrestore(lock, flags);
}

使用 raw_spinlock_t 而非普通 spinlock,是因为 MSI masking 可能在 NMI 上下文中调用(如内核 KGDB 调试路径)。

17.6 MSI 中断处理流程

硬件触发:
  设备 MSI 表项中 address/data 写入 LAPIC/GIC-ITS
                |
                v
  LAPIC/GIC 向对应 CPU 派发中断向量
                |
                v
  CPU 执行 ISR(中断服务例程)
    do_IRQ(vector)
      -> handle_edge_irq() 或 handle_fasteoi_irq()
         -> irq_chip->irq_mask()  (可选,edge 中断无需 mask)
         -> handle_irq_event()
              -> action->handler(irq, dev_id)  <- 驱动实现的 handler
         -> irq_chip->irq_unmask()

18. IOMMU 与 DMA 地址映射

18.1 DMA API 层次结构

Linux DMA API 提供了硬件无关的接口,底层由 IOMMU 驱动或直接映射(identity mapping)实现:

驱动调用
  dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL)
  dma_map_single(dev, vaddr, size, DMA_TO_DEVICE)
        |
        v
  struct dma_map_ops (dev->dma_ops)
        |
  +-----+---------------------+
  |                           |
  v                           v
IOMMU DMA ops          直接映射(物理=总线地址)
  iommu_dma_alloc()    dma_direct_alloc()
  iommu_dma_map_sg()   dma_direct_map_sg()
        |
        v
  IOVA 分配器(iova_domain)
  IOMMU 页表(iommu_domain)
        |
        v
  iommu_map(domain, iova, paddr, size, prot)
  -> 写入 SMMU/VT-d/AMD-Vi 页表

18.2 IOVA 分配

IOVA(I/O Virtual Address)是设备看到的地址。内核在 drivers/iommu/dma-iommu.c 中实现 IOVA 分配器:

struct iova_domain {
    spinlock_t  iova_rbtree_lock;  // 保护红黑树
    struct rb_root rbroot;          // IOVA 空间红黑树
    struct rb_node *cached_node;    // 最近分配节点缓存
    unsigned long  start_pfn;      // IOVA 空间起始(页帧号)
    unsigned long  dma_32bit_pfn;  // 32bit DMA 边界
    struct iova_rcache rcache[IOVA_RANGE_CACHE_MAX_SIZE]; // per-CPU 缓存
};

per-CPU rcache 用于无锁的快速 IOVA 分配,显著降低高频 DMA 场景的锁竞争。

18.3 IOMMU Group 与 Domain

每个 IOMMU Group 对应一个独立的 iommu_domain(地址空间),Group 内所有设备共享同一地址翻译表:

iommu_group
  +-- device 1  (pci_dev)
  +-- device 2  (pci_dev)
  |
  v
iommu_domain (IOMMU 硬件页表)
  IOVA --> Physical Address 映射

iommu_attach_device() 将设备绑定到 domain:

// 驱动/VFIO 调用
iommu_group_get(dev)           // 获取设备所在 group
iommu_domain_alloc(bus)        // 分配新 domain
iommu_attach_group(domain, group)  // 绑定 group 到 domain
iommu_map(domain, iova, paddr, size, prot)  // 建立映射

18.4 PCIe ATS(Address Translation Service)

ATS 允许支持 IOMMU 的 PCIe 设备在本地缓存地址翻译结果(Translation Cache),减少每次 DMA 都访问 IOMMU 页表的延迟:

设备发出 DMA 请求 (IOVA)
    |
    v
设备本地 Translation Cache 命中?
    是 -> 直接用缓存的物理地址发起 DMA
    否 -> 向 IOMMU 发送 Translation Request TLP
              IOMMU 查表后返回 Translation Completion
              设备缓存翻译结果,用物理地址发起 DMA

内核在 drivers/pci/ats.c 中管理 ATS 使能和失效化(invalidation)。设备驱动通过 pci_enable_ats(dev, ps) 使能 ATS(ps 为页大小对齐)。

18.5 PASID(Process Address Space ID)

PASID 扩展允许设备(通常是 GPU、FPGA)在多进程场景中使用不同进程的虚拟地址空间:

进程 A: PASID 0 --> 进程 A 的地址空间(CR3)
进程 B: PASID 1 --> 进程 B 的地址空间
设备   使用 PASID 标记每个 TLP,IOMMU 根据 PASID 选择翻译表

这是 SVA(Shared Virtual Addressing)的核心机制,使 GPU 等设备可直接访问用户态虚拟地址,无需预先注册 DMA 缓冲区。


19. PCIe 热插拔机制

19.1 热插拔架构总览

PCIe 原生热插拔(pciehp)是内核中最完整的热插拔实现,基于 PCIe Slot Capability 寄存器集:

PCIe Slot Capabilities (PCI_EXP_SLTCAP):
  bit[0]   = Attention Button Present
  bit[1]   = Power Controller Present
  bit[2]   = MRL Sensor Present
  bit[3]   = Attention Indicator Present
  bit[4]   = Power Indicator Present
  bit[5]   = Hot-Plug Surprise
  bit[6]   = Hot-Plug Capable
  bit[12]  = Electromechanical Interlock Present
  bit[13]  = No Command Completed Support
  bit[31:19]= Physical Slot Number

19.2 struct controller 状态机

热插拔控制器的状态机(pciehp.h:133~138):

状态定义:
  OFF_STATE       = 0   slot 已关闭,无设备枚举
  BLINKINGON_STATE = 1  等待 5 秒后开机(Attention Button 按下)
  BLINKINGOFF_STATE = 2 等待 5 秒后关机(Attention Button 再按)
  POWERON_STATE   = 3   正在上电
  POWEROFF_STATE  = 4   正在下电
  ON_STATE        = 5   slot 已上电,设备已枚举

状态转换(简化):
  OFF_STATE
    --[Presence Detect]--> POWERON_STATE
    --[Attention Button]--> BLINKINGON_STATE

  BLINKINGON_STATE
    --[5秒超时]----> POWERON_STATE
    --[Attention Button(取消)]--> OFF_STATE

  POWERON_STATE
    --[Power On OK]--> ON_STATE
    --[Power Fault]---> OFF_STATE

  ON_STATE
    --[Surprise Removal]--> OFF_STATE(立即)
    --[Attention Button]---> BLINKINGOFF_STATE

  BLINKINGOFF_STATE
    --[5秒超时]----> POWEROFF_STATE
    --[Attention Button(取消)]--> ON_STATE

19.3 中断与轮询双模式

pciehp 支持两种工作模式(pciehp_hpc.c:60~84):

中断模式(默认)

request_threaded_irq(irq, pciehp_isr, pciehp_ist,
                     IRQF_SHARED, "pciehp", ctrl);
  • pciehp_isr:硬中断上半部,读取 Slot Status,清除中断标志,唤醒线程
  • pciehp_ist:线程化中断下半部,执行实际的热插拔动作

轮询模式(通过 pciehp_poll_mode=1 内核参数启用):

ctrl->poll_thread = kthread_run(&pciehp_poll, ctrl,
                                "pciehp_poll-%s", slot_name(ctrl));

定期读取 Slot Status 寄存器检查变化,适用于中断路由有问题的系统。

19.4 热插入完整流程

硬件事件:插入设备,Presence Detect State 变为 1
    |
    v
pciehp_isr() 检测到 PCI_EXP_SLTSTA_PDC(Presence Detect Changed)
    |
    | atomic_or(PCI_EXP_SLTSTA_PDC, &ctrl->pending_events)
    | irq_wake_thread()
    v
pciehp_ist() 在线程上下文执行
    |
    +-- 读取 pending_events
    +-- 检测到 PDC 事件
    +-- 状态机:OFF_STATE -> POWERON_STATE
    v
board_added(ctrl)   [pciehp_ctrl.c:62]
    |
    +-- pciehp_power_on_slot(ctrl)     上电(写 Slot Control 的 Power Controller)
    |
    +-- pciehp_check_link_status(ctrl) 等待链路训练完成
    |     pcie_wait_for_link()  轮询 LNKSTA.DLLLA
    |
    +-- pciehp_configure_device(ctrl)  枚举新设备
    |     pci_scan_slot()
    |     pci_assign_unassigned_bus_resources()
    |     pci_bus_add_devices()
    |
    +-- pciehp_set_indicators(...)     点亮 Power Indicator(绿色)

19.5 热拔出流程

用户按下 Attention Button(或 sysfs disable_slot)
    |
    v
pciehp_request(ctrl, DISABLE_SLOT)
    |
    v
pciehp_disable_slot(ctrl)   [pciehp_ctrl.c:205]
    |
    +-- remove_board(ctrl, SAFE_REMOVAL)
    |     pciehp_unconfigure_device(ctrl, true)
    |       pci_stop_and_remove_bus_device()
    |           pci_remove_bus_device()
    |               pci_destroy_dev()
    |
    +-- pciehp_power_off_slot(ctrl)
    |     msleep(1000)  // 规范要求下电后等待 1 秒
    |
    +-- 更新状态为 OFF_STATE
    +-- pciehp_set_indicators(OFF, OFF)

19.6 命令完成同步

写 Slot Control 寄存器后,硬件需要一段时间执行命令(如上电),内核通过 PCI_EXP_SLTSTA_CC(Command Completed)位等待(pciehp_hpc.c:86~120):

static int pcie_poll_cmd(struct controller *ctrl, int timeout)
{
    struct pci_dev *pdev = ctrl_dev(ctrl);
    u16 slot_status;

    do {
        pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
        if (slot_status & PCI_EXP_SLTSTA_CC) {
            pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
                                       PCI_EXP_SLTSTA_CC);
            ctrl->cmd_busy = 0;
            smp_mb();
            wake_up(&ctrl->queue);
            return 1;
        }
        msleep(10);
        timeout -= 10;
    } while (timeout > 0);
    return 0;
}

规范要求相邻两次写 Slot Control 之间至少间隔 1 秒(cmd_started + HZ 或等待 CC 中断)。


20. SR-IOV 虚拟化深度分析

20.1 SR-IOV 配置空间结构

SR-IOV 扩展能力(Capability ID = 0x10)包含以下关键字段:

偏移    大小  含义
0x00    4B   Extended Capability Header
0x04    4B   SR-IOV Capabilities
              bit[0] = VF Migration Capable
0x08    2B   SR-IOV Control
              bit[0] = VF Enable
              bit[1] = VF Migration Enable
              bit[3] = VF MSE (Memory Space Enable for VFs)
              bit[4] = ARI Capable Hierarchy
0x0A    2B   SR-IOV Status
0x0C    2B   Initial VFs (只读)
0x0E    2B   Total VFs (只读)
0x10    2B   Num VFs (读写,设置激活的 VF 数)
0x12    1B   Function Dependency Link
0x14    2B   First VF Offset(相对于 PF 的 RID 偏移,每次写 NumVFs 后可能变化)
0x16    2B   VF Stride(相邻 VF 的 RID 步长)
0x1A    2B   VF Device ID
0x1C    4B   Supported Page Sizes
0x20    4B   System Page Size
0x24-0x38    VF BAR 0~5(每个 4B/8B)
0x3C    4B   VF Migration State Array Offset

20.2 compute_max_vf_buses() 总线预算(iov.c:105)

在使能 SR-IOV 之前,内核需要确认 VF 所需的总线数量不超过当前总线号空间:

static int compute_max_vf_buses(struct pci_dev *dev)
{
    struct pci_sriov *iov = dev->sriov;
    int nr_virtfn, busnr, rc = 0;

    // 遍历所有可能的 NumVFs 值,找出所需的最大总线号
    for (nr_virtfn = iov->total_VFs; nr_virtfn; nr_virtfn--) {
        pci_iov_set_numvfs(dev, nr_virtfn);
        if (!iov->offset || (nr_virtfn > 1 && !iov->stride)) {
            rc = -EIO;
            goto out;
        }

        busnr = pci_iov_virtfn_bus(dev, nr_virtfn - 1);
        if (busnr > iov->max_VF_buses)
            iov->max_VF_buses = busnr;
    }
out:
    pci_iov_set_numvfs(dev, 0);  // 恢复为 0
    return rc;
}

20.3 VF BAR 地址布局

所有 VF 共享同一套物理 BAR(存储在 PF 的 SR-IOV 能力中),但每个 VF 占用其中一个固定大小的段:

PF SR-IOV BAR0(物理地址 0xF0000000,大小 16MB = 16 个 VF × 1MB)

VF0 BAR0: [0xF0000000, 0xF00FFFFF]  1MB
VF1 BAR0: [0xF0100000, 0xF01FFFFF]  1MB
VF2 BAR0: [0xF0200000, 0xF02FFFFF]  1MB
...
VFn BAR0: [0xF0000000 + n*1MB, ...]

pci_iov_resource_size(dev, resno) 返回每个 VF 的单个 BAR 大小,pci_iov_add_virtfn() 据此用 resource_set_range() 划分子范围。

20.4 SR-IOV 与 ACS 的交互

当 PF 在 Switch 下行口后面时,VF 之间通过 P2P 通信可能绕过 IOMMU。正确部署 SR-IOV 需要:

  1. PF 所在的 Switch 下行口启用 ACS(阻止 VF 间 P2P)
  2. 每个 VF 分配独立的 IOMMU Group
  3. VF 直通给不同虚机时,各虚机有独立的 IOMMU Domain
Root Port (ACS enabled)
  |
Switch Downstream Port (ACS enabled)
  |
PF (VF Enable)
  +-- VF0 --> IOMMU Group X --> VM1
  +-- VF1 --> IOMMU Group Y --> VM2
  +-- VF2 --> IOMMU Group Z --> VM3

20.5 VF 驱动绑定控制

drivers_autoprobe 字段(pci_sriov.drivers_autoprobe)控制 VF 创建后是否自动探测驱动(通过 sysfs sriov_drivers_autoprobe 属性可配置):

# 禁止自动探测,由管理工具手动绑定
echo 0 > /sys/bus/pci/devices/0000:01:00.0/sriov_drivers_autoprobe
echo 4 > /sys/bus/pci/devices/0000:01:00.0/sriov_numvfs

# 手动绑定 VF 到 vfio-pci 驱动
echo "0000:01:01.0" > /sys/bus/pci/drivers/vfio-pci/bind

21. AER 高级错误报告

21.1 AER 寄存器结构

AER 扩展能力(Capability ID = 0x0001)包含:

偏移    大小  含义
0x00    4B   Extended Capability Header
0x04    4B   Uncorrectable Error Status    (PCI_ERR_UNCOR_STATUS)
0x08    4B   Uncorrectable Error Mask      (PCI_ERR_UNCOR_MASK)
0x0C    4B   Uncorrectable Error Severity  (PCI_ERR_UNCOR_SEVER)
0x10    4B   Correctable Error Status      (PCI_ERR_COR_STATUS)
0x14    4B   Correctable Error Mask        (PCI_ERR_COR_MASK)
0x18    4B   AER Capabilities & Control    (PCI_ERR_CAP)
0x1C    4B   Header Log DW0
0x20    4B   Header Log DW1
0x24    4B   Header Log DW2
0x28    4B   Header Log DW3
0x2C    4B   TLP Prefix Log DW0 (可选)
... (Root Port 额外有 Root Error Command/Status/Source ID)

21.2 错误类型分类

可纠正错误(Correctable):硬件自动恢复,仅记录统计(PCI_ERR_COR_STATUS):

宏名 含义
0 PCI_ERR_COR_RCVR Receiver Error(接收器错误)
6 PCI_ERR_COR_BAD_TLP Bad TLP(TLP 完整性错误)
7 PCI_ERR_COR_BAD_DLLP Bad DLLP(数据链路层包错误)
8 PCI_ERR_COR_REP_ROLL REPLAY_NUM Rollover(重传计数溢出)
12 PCI_ERR_COR_REP_TIMER Replay Timer Timeout(重传超时)
13 PCI_ERR_COR_ADV_NFAT Advisory Non-Fatal(建议性非致命错误)
14 PCI_ERR_COR_INTERNAL Corrected Internal Error(内部可纠正错)

不可纠正错误(Uncorrectable):分为 Non-Fatal(可恢复)和 Fatal(不可恢复):

宏名 默认严重度 含义
4 PCI_ERR_UNC_DLP Fatal Data Link Protocol Error
5 PCI_ERR_UNC_SURPDN Fatal Surprise Down Error
12 PCI_ERR_UNC_POISON_TLP Non-Fatal Poisoned TLP Received
13 PCI_ERR_UNC_FCP Fatal Flow Control Protocol Error
14 PCI_ERR_UNC_COMP_TIME Non-Fatal Completion Timeout
15 PCI_ERR_UNC_COMP_ABORT Non-Fatal Completer Abort
16 PCI_ERR_UNC_UNX_COMP Non-Fatal Unexpected Completion
17 PCI_ERR_UNC_RX_OVER Fatal Receiver Overflow
18 PCI_ERR_UNC_MALF_TLP Fatal Malformed TLP
19 PCI_ERR_UNC_ECRC Non-Fatal ECRC Error(若启用 ECRC)
20 PCI_ERR_UNC_UNSUP Non-Fatal Unsupported Request

21.3 AER 中断处理流程

AER 使用 Root Port 的 MSI/MSI-X 向量触发中断,通过 PCIe Port Service 驱动注册:

AER 错误发生(设备检测到错误)
    |
    | 设备发送 ERR_COR / ERR_NONFATAL / ERR_FATAL 消息给 Root Port
    v
Root Port 收到错误消息
    |
    | 写入 Root Error Status (PCI_ERR_ROOT_STATUS)
    | 写入 Root Error Source ID (PCI_ERR_ROOT_ERR_SRC)
    | 触发 MSI 中断
    v
aer_isr() 中断处理(aer.c)
    |
    | 读取 Root Error Status 和 Source ID
    | kfifo_in(&rpc->aer_fifo, &src, 1)  // 入队
    | schedule_work(&rpc->dpc_handler)
    v
aer_isr_work() 工作队列处理
    |
    +-- 从 kfifo 取出错误源
    +-- find_source_device()  按 Source ID 查找出错设备
    |     pci_get_domain_bus_and_slot(domain, bus, devfn)
    |
    +-- aer_get_device_error_info()  读取设备的 AER 寄存器
    |     读取 Uncorrectable/Correctable Error Status
    |     读取 Header Log(出错的 TLP 头部)
    |
    +-- pci_aer_handle_error(dev, info)
          |
          +-- Correctable: 清除状态位,调用 driver->err_handler->cor_error_detected()
          |
          +-- Non-Fatal:   pcie_do_recovery(dev, pci_channel_io_normal, ...)
          |
          +-- Fatal:       pcie_do_recovery(dev, pci_channel_io_frozen, ...)

21.4 pcie_do_recovery() 错误恢复协议(err.c:210)

pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
    pci_channel_state_t state,
    pci_ers_result_t (*reset_subordinates)(struct pci_dev *))
{
    // 1. 广播 error_detected 给受影响子树的所有设备驱动
    pci_walk_bridge(bridge, report_frozen_detected, &status);
    // 驱动回调:drv->err_handler->error_detected(dev, state)
    // 返回 PCI_ERS_RESULT_CAN_RECOVER / NEED_RESET / DISCONNECT

    // 2. 若驱动认为可以通过重新使能 MMIO 恢复
    if (status == PCI_ERS_RESULT_CAN_RECOVER)
        pci_walk_bridge(bridge, report_mmio_enabled, &status);
    // 驱动回调:drv->err_handler->mmio_enabled(dev)

    // 3. 若需要 slot reset(FLR 或 Secondary Bus Reset)
    if (status == PCI_ERS_RESULT_NEED_RESET)
        reset_subordinates(bridge);  // 执行复位
    pci_walk_bridge(bridge, report_slot_reset, &status);
    // 驱动回调:drv->err_handler->slot_reset(dev)

    // 4. 恢复完成,通知驱动恢复正常操作
    pci_walk_bridge(bridge, report_resume, &status);
    // 驱动回调:drv->err_handler->resume(dev)

    // 5. 清除 AER 错误状态
    if (host->native_aer || pcie_ports_native) {
        pcie_clear_device_status(dev);
        pci_aer_clear_nonfatal_status(dev);
    }
}

驱动实现 pci_error_handlers 来参与错误恢复:

static const struct pci_error_handlers nvme_err_handler = {
    .error_detected = nvme_error_detected,
    .mmio_enabled   = nvme_mmio_enabled,
    .slot_reset     = nvme_slot_reset,
    .resume         = nvme_error_resume,
};

21.5 ECRC(End-to-End CRC)

ECRC 对 TLP 的数据负载追加 4 字节 CRC,提供端到端完整性检查(而非仅链路级别)。由于有性能影响,内核通过策略参数控制:

// aer.c:148~239
// pcie_ecrc=[bios|off|on] 内核参数
static int ecrc_policy = ECRC_POLICY_DEFAULT;

void pcie_set_ecrc_checking(struct pci_dev *dev)
{
    if (!pcie_aer_is_native(dev))
        return;
    switch (ecrc_policy) {
    case ECRC_POLICY_ON:
        enable_ecrc_checking(dev);  // 写 PCI_ERR_CAP_ECRC_GENE|CHKE
        break;
    case ECRC_POLICY_OFF:
        disable_ecrc_checking(dev);
        break;
    }
}

21.6 AER 统计与 sysfs

AER 为每个支持 AER 的设备维护错误计数器,通过 sysfs 暴露(aer_attr_group):

/sys/bus/pci/devices/0000:01:00.0/aer/
    correctable_ratelimit_interval_ms  # 可纠正错误报告速率限制(毫秒)
    correctable_ratelimit_burst        # 可纠正错误报告速率限制(突发量)
    nonfatal_ratelimit_interval_ms     # 非致命错误报告速率限制
    nonfatal_ratelimit_burst

更详细的计数器(dev_cor_errs[]dev_fatal_errs[] 等)在内核中统计,但需要通过 aer_get_device_error_info() 读取。


22. PCIe P2P DMA 深度分析

22.1 P2P DMA 使用场景

PCIe P2P DMA 在以下场景中显著降低延迟和 CPU 负担:

场景 1:NVMe SSD -> GPU 直接传输(AI/ML 训练数据加载)
  传统路径:NVMe DMA -> 主存 -> GPU DMA -> GPU 显存
            延迟高,占用内存带宽

  P2P 路径:NVMe DMA -> Switch -> GPU 显存(BAR)
            绕过主存,延迟低,不占用 CPU 和内存带宽

场景 2:RDMA NIC -> GPU 直接传输(NCCL/GPUDirect RDMA)
  NIC 接收数据后直接写入 GPU 显存

场景 3:FPGA -> GPU/CPU 直接数据传输

22.2 P2P 内存注册流程

提供 P2P 内存的设备(Provider)需要将其 BAR 注册为 ZONE_DEVICE 内存:

// pci_p2pdma_add_resource(pdev, bar, size, offset)   p2pdma.c:383
int pci_p2pdma_add_resource(struct pci_dev *pdev, int bar, size_t size,
                             u64 offset)
{
    // 1. 初始化 p2pdma 结构
    pcim_p2pdma_init(pdev);       // 分配 pci_p2pdma,关联 RCU
    pci_p2pdma_setup_pool(pdev);  // 创建 gen_pool,添加 sysfs 属性

    // 2. 分配 p2p_pgmap,设置 BAR 物理地址范围
    pgmap->range.start = pci_resource_start(pdev, bar) + offset;
    pgmap->range.end   = pgmap->range.start + size - 1;
    pgmap->type = MEMORY_DEVICE_PCI_P2PDMA;

    // 3. 将 BAR 映射为 ZONE_DEVICE 内存(获得 struct page)
    addr = devm_memremap_pages(&pdev->dev, pgmap);

    // 4. 加入 gen_pool(使用总线地址作为 dma_addr)
    gen_pool_add_owner(p2pdma->pool, (unsigned long)addr,
                       pci_bus_address(pdev, bar) + offset,
                       range_len(&pgmap->range), ...);
}

MEMORY_DEVICE_PCI_P2PDMA 类型告知内存管理子系统这些页来自 PCIe BAR,不可用于普通内存操作,但可作为 DMA 目标/来源。

22.3 P2P 映射类型判断

pci_p2pdma_map_type() 在 DMA map 时根据拓扑关系确定映射策略:

+-- 两设备在同一 Root Complex?
|     +-- 路径上无 IOMMU? --> PCI_P2PDMA_MAP_BUS_ADDR(直接总线地址)
|     +-- IOMMU 存在?    --> PCI_P2PDMA_MAP_THRU_HOST(通过主机内存映射)
|
+-- 不在同一 Root Complex?
      --> PCI_P2PDMA_MAP_NOT_SUPPORTED

xarray map_types 缓存每对 (provider, client) 的映射类型,用 RCU 保护:

xa_lock(&p2pdma->map_types);
map_type = xa_load(&p2pdma->map_types, (unsigned long)client);
xa_unlock(&p2pdma->map_types);

if (!map_type) {
    map_type = pci_p2pdma_map_type_calc(provider, client);
    xa_store(&p2pdma->map_types, (unsigned long)client, map_type, GFP_ATOMIC);
}

22.4 pci_p2pdma_map_segment() DMA 操作

实际的 P2P DMA 操作(scatter-gather 场景):

// 使用者(如 NVMe 驱动)准备 P2P DMA 描述符
struct pci_p2pdma_map_state state;
pci_p2pdma_map_state_init(&state);

// 遍历 scatterlist,将 P2P 内存的 sg 项转为总线地址
for_each_sg(sgl, sg, nents, i) {
    if (is_pci_p2pdma_page(sg_page(sg))) {
        pci_p2pdma_map_segment(&state, dev, sg);
        // 直接使用 BAR 的总线地址(pci_bus_address)
        // 不经过 IOMMU 翻译(MAP_BUS_ADDR 情形)
    } else {
        dma_map_sg(dev, sg, 1, dir);
    }
}

22.5 P2P 内存的页生命周期

P2P 内存的 struct page 通过 percpu_ref 管理引用计数:

pci_p2pdma_add_resource()
    devm_memremap_pages()
        pgmap->ref 初始化为 percpu_ref
        ZONE_DEVICE 页的 page->pgmap = pgmap

DMA 操作持有引用:
    get_page(p2p_page)   --> percpu_ref_get(pgmap->ref)
    put_page(p2p_page)   --> percpu_ref_put(pgmap->ref)

设备移除时:
    devm 机制触发 p2pdma_pgmap_ops->kill()
    percpu_ref_kill(pgmap->ref)
    等待所有引用释放后才能安全移除硬件

23. 电源管理:D 状态与 PME 唤醒

23.1 D 状态转换规则

PCIe 规范对 D 状态转换有严格限制:

允许的转换:
  D0 -> D1 -> D2 -> D3hot -> D3cold  (下电方向)
  D3cold -> D0  (通过 Conventional Reset 上电)
  D3hot -> D0   (通过写 PMCSR.PowerState)
  D1/D2 -> D0   (通过写 PMCSR.PowerState)

禁止的转换:
  D3cold -> D1/D2/D3hot  (必须先到 D0)
  D1 -> D2 直接转换(某些平台可能支持,但规范未要求)

pci_set_power_state()(pci.c:1280)实现状态转换,检查 dev->d1_supportdev->d2_support 等标志,确认设备是否支持目标状态。

23.2 PME(Power Management Event)机制

PME# 是设备请求唤醒的信号。PCIe 中 PME 以带内消息形式传递(PME 消息 TLP),最终到达 Root Port。

PME 使能:

// 驱动设置设备可以从低功耗状态唤醒
pci_enable_wake(dev, PCI_D3hot, true);
    -> pci_pme_capable(dev, PCI_D3hot)  // 检查 dev->pme_support 位
    -> pci_enable_pme(dev)
         pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
         pmcsr |= PCI_PM_CTRL_PME_ENABLE;
         pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr);

PME 中断处理(PCIe 原生 PME,pme.c):

// Root Port 的 PCI_EXP_RTSTA 寄存器记录 PME 请求者
pcie_pme_handle_request(port, req_id)
    |
    +--  req_id  bus/devfn 查找设备
    +-- pci_check_pme_status(dev)   // 检查并清除 PME Status
    +-- pci_wakeup_event(dev)       // 触发 PM 唤醒事件
    +-- pm_request_resume(&dev->dev) // 请求设备从低功耗唤醒

pci_check_pme_status()(pci.c:2268)详细实现:

bool pci_check_pme_status(struct pci_dev *dev)
{
    int pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
    u16 pmcsr;

    pci_read_config_word(dev, pmcsr_pos, &pmcsr);
    if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
        return false;

    // 清除 PME Status(写 1 清除)
    pmcsr |= PCI_PM_CTRL_PME_STATUS;
    if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
        // 暂时禁用 PME,防止中断风暴
        pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
        ret = true;
    }
    pci_write_config_word(dev, pmcsr_pos, pmcsr);
    return ret;
}

23.3 PME 轮询

对于不支持 PCIe 原生 PME 中断的设备(如某些传统 PCI 设备),内核使用定时器轮询:

// pci.c:57~66
static LIST_HEAD(pci_pme_list);
static DEFINE_MUTEX(pci_pme_list_mutex);
static DECLARE_DELAYED_WORK(pci_pme_work, pci_pme_list_scan);
#define PME_TIMEOUT 1000  // 每 1 秒轮询一次

pci_pme_list_scan() 遍历 pci_pme_list 中注册的设备,依次调用 pci_pme_wakeup() 检查 PME Status 位。

23.4 Runtime PM 与 PCIe D3

Linux Runtime PM 框架允许设备在不使用时自动进入低功耗状态:

pci_pm_runtime_suspend(dev)
    -> drv->pm.runtime_suspend(dev)  // 驱动特定的挂起操作
    -> pci_prepare_to_sleep(dev)
         根据 pm_qos 要求选择目标 D 状态(D1/D2/D3hot)
         pci_set_power_state(dev, target)
    -> pci_enable_wake(dev, target, wakeup_capable)

pci_pm_runtime_resume(dev)
    -> pci_set_power_state(dev, PCI_D0)
    -> pci_restore_state(dev)
    -> drv->pm.runtime_resume(dev)

PCIe 的 D3cold 需要平台(ACPI)的配合来断电,内核通过 acpi_pci_set_power_state() 调用 ACPI _PS3 方法控制电源轨。

23.5 平台协调:_OSC 与原生控制权

在 ACPI 系统中,OS 必须通过 _OSC(OS Capabilities)方法向固件申请各 PCIe 功能的原生控制权:

pci_acpi_osc_support():
    申请的能力位(bits in _OSC arg3):
        OSC_PCI_EXPRESS_CAPABILITY             -- PCIe 基本
        OSC_PCI_EXPRESS_PME_CONTROL            -- PME 控制
        OSC_PCI_EXPRESS_AER_CONTROL            -- AER 控制
        OSC_PCI_EXPRESS_NATIVE_HP_CONTROL      -- 热插拔控制
        OSC_PCI_EXPRESS_LTR_CONTROL            -- LTR 控制
        OSC_PCI_EXPRESS_DPC_CONTROL            -- DPC 控制

    根据固件返回的结果设置:
        host->native_pcie_hotplug
        host->native_aer
        host->native_pme
        host->native_ltr
        host->native_dpc

native_aer = 0,则 AER 中断由固件(SMM)处理,内核不直接管理 AER 寄存器。


24. 附录:重要文件速查

24.1 内核源码关键文件

文件路径 主要功能
include/linux/pci.h 核心数据结构定义
include/uapi/linux/pci_regs.h PCI/PCIe 寄存器定义
drivers/pci/pci.c PCI 核心,电源管理,ACS
drivers/pci/probe.c 枚举流程,BAR 读取,桥扫描
drivers/pci/setup-res.c BAR 资源分配
drivers/pci/setup-bus.c 总线资源分配(桥窗口)
drivers/pci/access.c 配置空间访问底层实现
drivers/pci/msi/msi.c MSI/MSI-X 初始化与管理
drivers/pci/msi/api.c MSI 公开 API
drivers/pci/msi/irqdomain.c MSI IRQ 域实现
drivers/pci/iov.c SR-IOV 实现
drivers/pci/p2pdma.c Peer-to-Peer DMA
drivers/pci/ecam.c ECAM 配置空间映射
drivers/pci/pci-sysfs.c sysfs 接口
drivers/pci/pcie/aspm.c ASPM、L1SS、LTR 电源管理
drivers/pci/pcie/aer.c AER 中断处理与错误报告
drivers/pci/pcie/err.c PCIe 错误恢复协议(pcie_do_recovery)
drivers/pci/pcie/pme.c PCIe 原生 PME 服务
drivers/pci/pcie/portdrv.c PCIe Port 服务驱动框架
drivers/pci/pcie/dpc.c DPC(Downstream Port Containment)
drivers/pci/hotplug/pciehp_core.c PCIe 热插拔核心
drivers/pci/hotplug/pciehp_ctrl.c 热插拔控制器状态机
drivers/pci/hotplug/pciehp_hpc.c 热插拔中断处理
drivers/pci/hotplug/pciehp.h pciehp 内部数据结构
drivers/pci/ats.c ATS/PASID/PRI 实现
drivers/pci/pci.h 内部数据结构(pci_sriov 等)
drivers/pci/quirks.c 设备特定修复(数千行 quirks)
drivers/iommu/dma-iommu.c IOMMU DMA API 实现

24.2 关键宏与函数速查

函数/宏 位置 功能
pci_read_config_word(dev, pos, val) include/linux/pci.h 读取配置空间 16 位寄存器
pci_find_capability(dev, cap) drivers/pci/search.c 查找标准能力链表
pci_find_ext_capability(dev, cap) drivers/pci/search.c 查找扩展能力链表
pcie_capability_read_word(dev, pos, val) drivers/pci/pci.c 读取 PCIe Capability 相对偏移
pci_alloc_irq_vectors(dev, min, max, flags) drivers/pci/msi/api.c 分配 MSI/MSI-X/INTx 向量
pci_enable_sriov(dev, n) drivers/pci/iov.c 激活 SR-IOV,创建 n 个 VF
pci_p2pdma_add_resource(dev, bar, size, off) drivers/pci/p2pdma.c 注册 P2P 内存资源
pcie_do_recovery(dev, state, fn) drivers/pci/pcie/err.c 执行 AER 错误恢复协议
pci_set_power_state(dev, state) drivers/pci/pci.c 设置设备电源状态 D0~D3cold
pci_enable_wake(dev, state, enable) drivers/pci/pci.c 控制设备 PME 唤醒
pci_acs_enabled(dev, flags) drivers/pci/pci.c 检查 ACS 能力使能状态
pci_scan_slot(bus, devfn) drivers/pci/probe.c 扫描单个 PCI slot
pci_scan_bridge_extend(bus, dev, max, avail, pass) drivers/pci/probe.c 扫描桥后的总线
pci_iov_virtfn_bus(dev, vf_id) drivers/pci/iov.c 计算 VF 的总线号
pci_iov_virtfn_devfn(dev, vf_id) drivers/pci/iov.c 计算 VF 的 devfn

24.3 调试与诊断

查看 PCI 设备树

lspci -tv                          # 设备树形视图
lspci -vvv -s 0000:01:00.0        # 详细能力信息
cat /sys/bus/pci/devices/0000:01:00.0/config | xxd | head -8

AER 错误信息(内核日志):

pcieport 0000:00:1c.0: AER: Corrected error received: 0000:01:00.0
nvme 0000:01:00.0: AER:   PCIe Bus Error: severity=Corrected
nvme 0000:01:00.0: AER:     [12] Timeout

MSI-X 向量分配

cat /proc/interrupts | grep -E "NVMe|MSI"
ls /sys/bus/pci/devices/0000:01:00.0/msi_irqs/

SR-IOV 操作

# 查看支持的最大 VF 数
cat /sys/bus/pci/devices/0000:01:00.0/sriov_totalvfs

# 创建 4 个 VF
echo 4 > /sys/bus/pci/devices/0000:01:00.0/sriov_numvfs

# 查看 VF 列表
ls -la /sys/bus/pci/devices/0000:01:00.0/virtfn*

P2P DMA 状态

ls /sys/bus/pci/devices/0000:01:00.0/p2pmem/
cat /sys/bus/pci/devices/0000:01:00.0/p2pmem/size
cat /sys/bus/pci/devices/0000:01:00.0/p2pmem/available
cat /sys/bus/pci/devices/0000:01:00.0/p2pmem/published

ASPM 状态

# 查看当前 ASPM 策略
cat /sys/module/pcie_aspm/parameters/policy

# 强制策略:performance | powersave | powersupersave | default
echo powersave > /sys/module/pcie_aspm/parameters/policy

由 Claude Code 分析生成