☰
pcie协议学习(3)-枚举
2026/10/14 16:54:04 网站建设 项目流程

1.链路的训练

(硬件上电后,自发行为, 不需要软件参与)

当外部 PCIe 设备链路训练成功(LTSSM → L0)后,链路状态进入L0状态,这个是host 能够枚举到pcie 外设的前提。

只有进入L0阶段, RC才能发送TLP报文给pcie设备,读取PCIE的BAR 寄存器, 获取pcie设备信息

多级拓扑(Root Port → Switch → EP)的时候:每一段物理链路都必须进入 L0,任意一段不通,下游设备全不可见

L0: 正常工作模式

L0S: 省电模式

L1 休眠

L2:深度休眠

2. 从 Bus0 开始:遍历所有 Device / Function

PCIe 固定寻址规则:

  • Bus:0~255(8 位)
  • Device:0~31(5 位)
  • Function:0~7(3 位)

内核循环:for (bus = 0; ...) for (dev = 0; dev < 32; dev++) for (fn = 0; fn < 8; fn++)对每个(bus, dev, fn),做一件事:读配置空间偏移 0x00 的 Vendor ID

PCIe 兼容 PCI 的配置空间寻址机制。在标准的32位配置地址寄存器(Configuration Address Register)中,地址被划分为以下几个部分:

表格

字段位数 (Bits)范围说明
Reserved7-保留位
Bus Number80 ~ 255总线号(决定最大支持256条总线)
Device Number50 ~ 31设备号 (每条总线最多32个设备)
Function Number30 ~ 7功能号 (每个设备最多8个功能)
Register Offset60 ~ 63寄存器偏移 (指向配置空间内的具体寄存器,需左移2位)

3. 判断设备是否存在(最关键一步)

  • 读到0xFFFF:无设备 / 无响应→ 跳过
  • 读到有效 Vendor ID(非全 1):存在设备→ 内核创建pci_dev结构体

4. 区分是「端点 EP」还是「桥 Bridge」

读配置空间0x0E寄存器:Header Type

  • Type 0 (00h)→ Endpoint(普通外设:网卡、GPU、FPGA、USB 控制器)
  • Type 1 (01h)→ PCIe Bridge(Root Port、Switch 上游 / 下游口)

5. 如果是桥(Type1)→ 分配新总线,递归扫描下游

这是PCIe 树形拓扑能被扫全的核心:

  1. 给桥分配:
    • Primary Bus:当前总线(上游)
    • Secondary Bus:新总线号(下游总线起点)
    • Subordinate Bus:预留最大总线号
  2. 以Secondary Bus为新根,递归执行步骤 2~4
  3. 扫完下游,回到上一级继续扫

这就是为什么 Switch 下面的设备也能被扫到:桥 = 通往新一层总线的 “门”

6. 收集所有设备后:统一分配资源

内核遍历所有pci_dev,做:

  • 解析 BAR(大小、类型、预取、32/64 位)
  • 分配物理地址(MMIO),避免冲突
  • 分配 MSI/MSI-X 中断向量
  • 配置 PCIe 能力(PCIe Link、Power、MSI-X 等)

7. 设备与驱动匹配 → 调用你的 probe

内核拿着设备的:

  • Vendor ID
  • Device ID
  • Class Code
  • Subsystem ID

去匹配内核中所有pci_driver的id_table,匹配成功就调用pcie的probe函数, 加载驱动。

pcie设备的地址空间和BAR 寄存器

BAR = 放在 PCIe 设备配置空间里的「地址资源申请表」

枚举的过程本质就是: 主机读取PCI 配置空间(Type0 头对应 Endpoint,Type1 头对应 Switch / 桥),获取设备信息、类型和BAR 寄存器, 同时给BAR寄存器分配内存。

过程:

  • 读Vendor ID(判断设备是否存在)
  • 读Header Type(区分 Type0 / Type1)
  • 读Class Code / Device ID(识别设备类型)
  • 读BAR(获知需要多大内存空间)
  • 给soc内部地址空间中给pcie bar寄存器分配地址,并把这个地址回写到BAR 寄存器中。 相当于完成地址映射。
  • 若为 Type1 桥 → 分配总线号,递归扫下游

什么是pcie配置空间?

---》每个pcie设备欸有自己的配置空间, soc 范围pcie的配置空间获取pcie 设备的类型、BAR寄存器。

SOC 怎么获取到pcie 设备的配置空间 地址?

--》是soc的pcie 基地址+遍历pcie 拓扑接口获取的bus等信息。(每个设备的具体配置地址 = soc pcie的基地址 + 按 Bus/Dev/Fn 计算的偏移)

Pcie的配置空间有哪些格式?

---》

PCI 配置空间只有两种头格式:

  • Type 0 Header:给PCIe Endpoint(终端设备)用
  • Type 1 Header:给PCIe Bridge(桥设备)用

配置空间和BAR 寄存器 布局:

AR 寄存器(Base Address Register)总结

BAR 是PCI/PCIe 终端设备(Type 0 配置头)配置空间中(偏移 0x10~0x24,最多 6 个)的专用寄存器,是设备与主机约定物理地址资源的硬件接口,包含地址属性(32/64 位、预取、内存 / I/O 类型)和主机分配的物理基地址。

pci_host_probe的宏观职责和代码的物理动作完美对应了起来:

  1. pci_scan_root_bus_bridge(bridge)→ 对应你标注的“扫描root和ep”。这是发现阶段,内核通过读取配置空间,把整棵总线树和设备节点“摸”出来。
  2. pci_bus_assign_resources(bus)→ 对应你标注的“分配各个ep的bar 地址空间”。这是分配阶段,内核把实际物理地址(基地址)写入每个 EP 设备的 BAR 寄存器,让它们能真正被 CPU 访问到。
这两个函数就是内核初始化 PCIe 总线的两大核心支柱:先扫描,后分配。

主机给 BAR 寄存器分配并回写物理地址后,就拥有了与 PCIe 设备通信的「硬件地址入口」:驱动通过 BAR 映射的地址访问设备寄存器,既可以下发控制配置,也能间接完成网络数据等大量数据的传输,这是 PCIe 设备工作的基础。

其中Type0 Header最多有6个BAR,而Type1 Header最多有两个BAR。这就意味着,对于Endpoint来说,最多可以拥有6个不同的地址空间。但是实际应用中基本上不会用到6个,一般是1-3个BAR

假设插了一块 博通switch 网卡:

  1. 枚举阶段:系统读到 VID=8086、DID=10BF、Class=0200 → 识别是 博通 网卡;
  2. 系统给 BAR0/BAR1 分配物理地址,回写 BAR 寄存器;
  3. 加载 博通专用驱动 → 驱动里写死了 “BAR0 是控制寄存器,BAR1 是 DMA 描述符”;
  4. 驱动通过 BAR0 配置网卡、通过 BAR1 设置数据包缓冲区,实现收发包

个人理解:

配置空间里面很多信息, 其中就有bar寄存器地址。 我们知道bar 寄存器地址后, soc 先向bar寄存器写0xFFFFFFFF,看看返回多少个0,确定需要分配多大ram 空间, soc 配置分配地址以后, 把ram 地址写入到bar 寄存器。 这样bar 地址就映射到了soc内部的ram区域, 可以通过这个ram 区域和pcie 通信。

完整流程可以这样看

RC 写 BAR = 0xFFFFFFFF ↓ EP 硬件把低位不可写位清零,返回掩码 ↓ RC 算出 BAR 大小 ↓ RC/OS 在系统物理地址空间分配一段对齐地址 ↓ RC 把物理基地址写回 BAR ↓ 使能 Command Register 的 Memory Space Enable ↓ CPU 访问该物理地址 → RC 转发成 PCIe Memory TLP → EP 内部寄存器 看一下RC 驱动代码 pci_host_probe(host)

pci_host_probe的宏观职责和代码的物理动作完美对应了起来:

  1. pci_scan_root_bus_bridge(bridge)→ 对应你标注的“扫描root和ep”。这是发现阶段,内核通过读取配置空间,把整棵总线树和设备节点“摸”出来。
  2. pci_bus_assign_resources(bus)→ 对应你标注的“分配各个ep的bar 地址空间”。这是分配阶段,内核把实际物理地址(基地址)写入每个 EP 设备的 BAR 寄存器,让它们能真正被 CPU 访问到。
这两个函数就是内核初始化 PCIe 总线的两大核心支柱:先扫描,后分配。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询