基于 Linux 内核源码(commit 8a30aeb0d,约 v6.14-rc 阶段)
- PCI/PCIe 总线拓扑
- 配置空间:256B 与 4KB
- 核心数据结构
- 设备枚举流程
- BAR 资源分配
- MSI/MSI-X 中断机制
- IOMMU 与 PCIe ACS 隔离
- PCIe 电源管理:ASPM 与设备电源状态
- SR-IOV:虚拟化扩展
- P2P DMA:设备间直接访问
- pci_ops 与 ECAM
- sysfs 接口
- 关键调用链汇总
- PCI 总线枚举深度剖析
- 配置空间访问机制
- PCIe 链路训练与 LTSSM 状态机
- MSI/MSI-X 中断分配深度分析
- IOMMU 与 DMA 地址映射
- PCIe 热插拔机制
- SR-IOV 虚拟化深度分析
- AER 高级错误报告
- PCIe P2P DMA 深度分析
- 电源管理:D 状态与 PME 唤醒
- 附录:重要文件速查
传统 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) |
+--------+ +--------+
在 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.c 的 pci_scan_bridge_extend() 中,通过检测 pci_pcie_type(bridge) 来决定是否进入 DOWNSTREAM 或 ROOT_PORT 分支扫描下一级总线。
每个 PCI 总线有三个编号寄存器(Type 1 配置头):
- Primary Bus Number:本桥所在总线
- Secondary Bus Number:本桥管理的下行总线起始
- Subordinate Bus Number:本桥下所有子总线的最大编号
内核在 probe.c 的 pci_scan_bridge_extend()(第 1395 行)中动态分配这些编号,采用深度优先遍历,先分配最小的可用编号,待子树扫描完成后回写 Subordinate 编号。
标准 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)
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_size 由 pci_setup_device() 中调用 pci_cfg_space_size(dev) 决定,对于 PCIe 设备通常为 4096。
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() 写入。
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 虚拟总线也满足此条件。
描述主机桥(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 期望自主管理)。
驱动侧的核心结构:
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()。
平台相关的配置空间访问函数集:
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 则通过该地址执行实际的内存访问。
平台/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() 注册设备,触发驱动绑定
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;
}这是枚举过程中最核心的函数,负责从硬件读取设备信息填充 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;
}为桥创建子总线:
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;
}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字节对齐)
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,减少虚拟化环境开销。
将一个 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)按优先级尝试:
- 精确匹配 Prefetchable + 64bit 窗口
- 回退到 Prefetchable 32bit 窗口(若桥只支持 32 位)
- 最后使用非 Prefetchable 窗口
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() 在分配时将子资源挂到父资源下,形成层次约束。
| 特性 | INTx(传统) | MSI | MSI-X |
|---|---|---|---|
| 中断数量 | 最多 4 个(共享) | 1~32 个 | 最多 2048 个 |
| 寄存器位置 | 配置头 0x3C | 能力结构 | 能力 + BAR 表 |
| 电平/边沿 | 电平触发 | 边沿触发 | 边沿触发 |
| 向量隔离 | 无 | 部分 | 完整 |
MSI 通过向系统内存写一个 32 位数据(MSI Data)到指定地址(MSI Address)来触发中断。在 x86 上:
- Address:
0xFEE[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);
}
}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驱动调用 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 都会导致失败。
设备 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。
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 空间。
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 状态。
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,不能单独直通给虚机。
在 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) // 主电源断,辅助电源维持 PMEpci_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);
}
}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)。
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 是否均受支持。
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) */
};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 中的固定大小分区
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
};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() 内部流程:
- 调用
compute_max_vf_buses()(iov.c:105)计算 VF 所需总线数 - 向 SR-IOV Control 写入
PCI_SRIOV_CTRL_VFE(VF Enable) - 为每个 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;
}驱动可通过 sriov_configure 回调和 sysfs sriov_numvfs 动态调整 VF 数量。sriov_set_msix_vec_count(iov.c:248~287)允许 PF 驱动在 VF 没有绑定驱动时动态修改 VF 的 MSI-X 向量数。
P2P DMA 允许两个 PCIe 设备(如 NVMe SSD 与 GPU)之间直接传输数据,数据不经过主机内存,降低延迟并减轻 CPU 负担。
+--------+ P2P DMA +--------+
| NVMe | --------------> | GPU |
+---+----+ +----+---+
| |
+----------+----------------+
|
PCIe Switch
|
Root Port
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),允许无锁读取。
系统在执行 P2P 传输前必须判断两个端点是否能直接通信,主要考虑:
- 硬件拓扑:设备是否共享同一 Switch,Switch 是否支持 P2P(部分交换机会把 P2P 流量转发到 RC)
- ACS 配置:路径上的 ACS 是否阻止了 P2P(若 ACS P2P Request Redirect 使能,P2P TLP 被重定向到 RC)
- IOMMU:若两端在不同 IOMMU Group,需要特殊处理
map_types xarray 缓存对每个可能目标设备的映射策略(直接 P2P、经过 RC、不支持),避免每次 DMA 都重新计算。
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 仅覆盖单设备多功能情形,大型系统用更大的移位)。
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);
}典型的 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);
在 x86 上,传统 PCI 通过 I/O 端口 0xCF8(地址端口)和 0xCFC(数据端口)访问 256 字节配置空间,PCIe 扩展的 0x100~0xFFF 必须使用 ECAM(通过 MCFG ACPI 表提供基地址)。x86 的 pci_ops 实现在 arch/x86/pci/,通过 raw_pci_read/write 暴露给平台无关代码。
每个 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。
config 文件(最大 4096 字节)允许用户空间直接读写设备配置空间:
/sys/bus/pci/devices/0000:00:1f.2/config
resource0~5 是各 BAR 对应的 mmap 文件,驱动可以通过此接口实现用户态驱动(如 DPDK、SPDK)。
/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)。
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()
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]
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() 创建符号链接
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 使能位
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 后即停止。
桥扫描分两个 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 方式避免了固件配置的总线范围和内核新分配的总线范围发生冲突。
对于热插拔 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);内核探测设备是否存在的方法(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 总线上"无设备"的标准响应。
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
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 内的总线号唯一。
内核提供一组标准化的配置空间访问函数,最常用的是:
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)。
对于频繁访问的 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
某些寄存器在修改时需要原子的 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)。
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;
}在系统进入挂起状态前,内核保存设备配置空间中的关键寄存器(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 等功能在中途开始工作。
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
链路训练(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 完成速率切换
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 秒。
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() 触发链路降速(如出于功耗或兼容性原因)。
drivers/pci/pcie/bwctrl.c 实现了带宽控制服务,监听 PCI_EXP_LNKSTA 的 Link Bandwidth Management Status(LBMS)位,在链路自主调整带宽(如由于信号质量问题降速)时通知用户空间。
pcie_reset_lbms()(pcie/bwctrl.c)在热插拔完成后清除 LBMS,防止历史事件干扰新设备的监控。
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)。
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 位
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 字节)
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、物理核、超线程)生成亲和性掩码,分发给各向量。
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 调试路径)。
硬件触发:
设备 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()
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 页表
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 场景的锁竞争。
每个 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) // 建立映射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 为页大小对齐)。
PASID 扩展允许设备(通常是 GPU、FPGA)在多进程场景中使用不同进程的虚拟地址空间:
进程 A: PASID 0 --> 进程 A 的地址空间(CR3)
进程 B: PASID 1 --> 进程 B 的地址空间
设备 使用 PASID 标记每个 TLP,IOMMU 根据 PASID 选择翻译表
这是 SVA(Shared Virtual Addressing)的核心机制,使 GPU 等设备可直接访问用户态虚拟地址,无需预先注册 DMA 缓冲区。
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
热插拔控制器的状态机(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
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 寄存器检查变化,适用于中断路由有问题的系统。
硬件事件:插入设备,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(绿色)
用户按下 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)
写 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 中断)。
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
在使能 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;
}所有 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() 划分子范围。
当 PF 在 Switch 下行口后面时,VF 之间通过 P2P 通信可能绕过 IOMMU。正确部署 SR-IOV 需要:
- PF 所在的 Switch 下行口启用 ACS(阻止 VF 间 P2P)
- 每个 VF 分配独立的 IOMMU Group
- 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
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/bindAER 扩展能力(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)
可纠正错误(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 |
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, ...)
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,
};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;
}
}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() 读取。
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 直接数据传输
提供 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 目标/来源。
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);
}实际的 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);
}
}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)
等待所有引用释放后才能安全移除硬件
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_support、dev->d2_support 等标志,确认设备是否支持目标状态。
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;
}对于不支持 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 位。
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 方法控制电源轨。
在 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 寄存器。
| 文件路径 | 主要功能 |
|---|---|
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 实现 |
| 函数/宏 | 位置 | 功能 |
|---|---|---|
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 |
查看 PCI 设备树:
lspci -tv # 设备树形视图
lspci -vvv -s 0000:01:00.0 # 详细能力信息
cat /sys/bus/pci/devices/0000:01:00.0/config | xxd | head -8AER 错误信息(内核日志):
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/publishedASPM 状态:
# 查看当前 ASPM 策略
cat /sys/module/pcie_aspm/parameters/policy
# 强制策略:performance | powersave | powersupersave | default
echo powersave > /sys/module/pcie_aspm/parameters/policy由 Claude Code 分析生成