固件与启动阶段
内核接手之前,PCIe 已经被"安排"好了——读懂固件的安排
为什么驱动工程师要懂固件阶段?
Linux 枚举设备时,很多参数不是内核决定的:BAR 是否分配、分在 32 位还是 64 位空间、总线号范围、ASPM 策略、甚至"设备压根不出现在 lspci 里"——都可能是固件(UEFI/BIOS 或设备树)先做了决定。本页梳理固件与内核的交接面,覆盖四块最常打交道的部分:描述表(MCFG/设备树)→ 资源声明(_CRS)→ 设备引导代码(Option ROM)→ 可调整 BAR(ReBAR)。
上电
└─ UEFI/BIOS:枚举 PCIe、分配 BAR、记录描述(MCFG/_CRS)
│ ├─ 加载 Option ROM(显卡初始化、iSCSI Boot)
│ └─ 决定 Above 4G / ASPM / ReBAR 策略
▼
内核启动
└─ ACPI/设备树解析(MCFG → PCI 根域;_CRS → 资源窗口)
│
▼
pci_host_probe / pci_scan_root_bus_bridge
├─ 按固件记录复用或重分配资源(pci=realloc 等参数)
└─ 设备驱动 probe(此时设备已可访问)
描述表:ACPI MCFG 与设备树
内核要知道"配置空间在哪里",靠平台描述:
| 平台 | 描述机制 | 关键字段 |
|---|---|---|
| x86 / ACPI | MCFG 表(PCI Segment Group 表) | 每项描述一个 PCI 段:段的基地址(ECAM 基址)、起始总线号、结束总线号、保留字段。内核 acpi_pci_root_get_mcfg_addr() 据此定位 ECAM。一个段对应一个 PCI 域(domain:bus:dev.fn 中的 domain) |
| ARM/ARM64 | 设备树(pcie@ 节点) |
reg/reg-names="config" 给 ECAM;ranges 给 MEM/IO 窗口;bus-range 给总线号;interrupt-map 给 INTx 映射;msi-parent 指向 ITS/GICv3 |
# 查看 MCFG 表(需 root,acpica-tools)
$ acpidump -n MCFG -b
# 或
$ cat /sys/firmware/acpi/tables/MCFG | xxd | head
# 内核视角:每个 PCI 域的 ECAM 与总线范围
$ dmesg | grep -i "ECAM\|PCI host bridge"
$ ls /sys/devices/pci0000:00/ | head # 域 0000 下的总线
# 设备树平台:
$ ls /proc/device-tree/ | grep pcie
$ cat /proc/device-tree/pcie@40000000/ranges | xxd | head
要点:MCFG 只描述 ECAM 位置与总线范围,不描述 MEM/IO 窗口——后者由固件在桥/RC 的 Base/Limit 寄存器里配好,内核通过 _CRS(见下)或直接读寄存器拿到。
资源声明:ACPI _CRS 与 _DSM
ACPI 命名空间在 PCI 根桥(\_SB.PCI0 之类)下提供方法,把固件掌握的资源与策略告诉内核:
| 方法 | 含义 | 内核消费点 |
|---|---|---|
| _CRS(Current Resource Settings) | 当前资源设置:根桥的总线号范围(Bus Number Range)、内存窗口(Memory32/64)、IO 窗口。内核 pci_acpi_root_prepare_resources() 解析它并建立 RC 的资源窗口 |
drivers/pci/pci-acpi.c;解析失败会看到 "no IO and memory resources present in _CRS" |
| _DSM(Device Specific Method) | 厂商/规范定义的策略位。PCI 相关:功能 5"PCI Boot Configuration"(是否允许内核重分配资源)、功能 8(根桥设备属性)、D3cold 延迟等 | pci_acpi_optimize_delay()、pci_acpi_preserve_config() 等 |
| _DSD(Device Specific Data) | 设备属性(键值对)。PCI 用途如根端口的 HotPlugSupportInD3 属性 |
按属性名匹配消费 |
| _HPX / _HPP | 热插拔/热启动参数:设备挂载时的 MPS/MRRS、总线参数等(_HPP 已废弃于 _HPX) | 热插拔编程路径 |
"固件留的资源不够"是第一大坑
_CRS 声明的窗口小于实际需求时,内核要么无法容纳设备 BAR(表现为 BAR 12: no space for ... 或部分设备不出现),要么被 bootloader 的 pci=realloc 救回——pci=realloc 强制内核重新分配总线资源,绕过固件的分配结果;配合 pci=assign-busses(重指派总线号)、pci=hpiosize= 等参数使用。服务器上 BIOS 升级后设备"消失"、加装第二块 GPU 后第一块设备 BAR 变小,都是这类问题的典型现场。
Option ROM:设备自带的引导代码
很多设备把一小段固件烧在板载 ROM 里(显卡 BIOS、网卡 PXE/iSCSI Boot ROM),固件阶段会把它映射到内存并执行——这就是"显卡在 BIOS 界面就能显示"的原因。
- 物理形态:ROM 通过 PCI 扩展 ROM 机制暴露(ROM BAR,标准配置空间偏移 0x30);容量经 ROM BAR 的 sizing 得到。
- 执行环境:传统 BIOS 走 16 位实模式 ROM;UEFI 走 EFI Option ROM(FFS 格式),由固件的 PCI Bus Driver 加载签名与协议。
- 内核阶段:Linux 默认不执行 Option ROM(避免执行未签名代码),只有少数例外(如
vgacon需要时用video=...或通过 sysfsrom文件手动触发校验后执行,见Documentation/PCI/sysfs-pci.rst)。 - 现场价值:机器在 BIOS 下有显示但进内核黑屏——说明 Option ROM 完成了初始化,问题在内核驱动而非硬件;反之则先怀疑 ROM/固件。
# 查看设备的 ROM 信息(lspci 会显示 Expansion ROM 基址)
$ lspci -vvv -s 01:00.0 | grep -i "expansion rom"
# 通过 sysfs 使能 ROM 并 dump(需确认平台允许)
$ echo 1 > /sys/bus/pci/devices/0000:01:00.0/rom
$ cat /sys/bus/pci/devices/0000:01:00.0/rom > gpu.rom
$ echo 0 > /sys/bus/pci/devices/0000:01:00.0/rom
Above 4G 解码与 Resizable BAR
这两个概念经常一起出现,因为它们解决同一类问题:32 位地址空间装不下大 BAR。
Above 4G Decoding
固件选项(BIOS 里通常叫 "Above 4G Decoding"/"64-bit BAR"):启用后,固件可以把设备的 64 位 BAR 分配到 4GB 以上的地址空间。对大显存 GPU(16GB+)几乎是必需——否则显存只能映射到 256MB 的 32 位窗口。调试含义:BIOS 里关掉这项,或平台不支持时,大 BAR 设备会分配失败或降级为窗口映射。
Resizable BAR(ReBAR)
ReBAR 是 PCIe Extended Capability(Cap ID 0x0015,见本站 Capability ID 速查):允许设备的某个 BAR 在硬件支持的尺寸集合里运行时调整大小——GPU 显存 BAR 从 256MB 扩到 16GB,让 CPU 可以直接读写整块显存,游戏/推理/HPC 场景收益明显。
| 层面 | 动作 |
|---|---|
| 设备 | 通过 ReBAR Capability 声明每个 BAR 支持的尺寸列表(Capability Register 的 "Sizes Supported" 位图) |
| 固件 | UEFI 按 ReBAR Capability 选择目标尺寸并配置;部分平台需要 BIOS 选项显式开启 |
| Linux 内核 | 内核可在运行时重设:sysfs 属性 resourceN_resize 读写目标尺寸(底层 pci_rebar_get_possible_sizes() / pci_resize_resource(),需重新分配资源) |
| 驱动 | 使用 resize 后的 BAR(pci_iomap 等按最终尺寸重新映射);有的驱动在 probe 时主动请求特定尺寸 |
# 查看 BAR0 支持哪些尺寸(返回值是尺寸的位图/集合)
$ cat /sys/bus/pci/devices/0000:01:00.0/resource0_resize
# 请求把 BAR0 调整为 8GB(写入尺寸;需平台允许重分配)
$ echo 8G > /sys/bus/pci/devices/0000:01:00.0/resource0_resize
# 查看结果
$ lspci -vvv -s 01:00.0 | grep -i "region 0\|resizable"