在裸机系统中,在读取厂商ID以判断总线上是否有设备之前,是否需要对PCIe总线做些什么?

编程语言 2026-07-12

我在一块ARM开发板上处理PCI Express(PCIe)总线,具体来说是使用Cix Sky1 SoC的板子,在裸机UEFI启动场景中。根据 OSDev wikiPCIe规范 的资料,一旦我从PCI配置空间获得到某一总线的基地址,我就应该读取该配置空间中的VendorID寄存器——如果设备未插入,此读取将返回 0xffff,然后我再从那里继续处理下一个总线:

 void checkDevice(uint8_t bus, uint8_t device) {
     uint8_t function = 0;

     uint16_t vendorID = getVendorID(bus, device, function);
     if (vendorID == 0xFFFF) return; // Device doesn't exist
     // ... device does exist, enumerate it
 }

这些指令在我测试过的x86 PCIe总线以及QEMU的 PCIe总线上都能正常工作。然而,在我正在处理的这块板子上,当我用固件提供的Device Tree启动时,如果我尝试读取一个没有设备插入的PCI-to-PCI桥的VendorID,CPU将会完全锁死——甚至不会产生陷阱:

void checkDevice (uint8_t bus, uint8_t device) {
    uint8_t function = 0;
    printf ("Checking device %d %d %d", bus, device, function)
    uint16_t vendorId = getVendorID (bus, device, function); // hangs here
    printf ("Vendor ID %d", vendorId)
    if (vendorId == 0xFFFF) return;
    // ...
}

在PCIe的场景中,是否在读取配置空间之前需要做其他事情来判断读取是否有效?

到目前为止我查阅/尝试过的内容:

  • PCIe规范确实提到,如果设备初始化时间较长,配置请求可能会被阻塞。然而,我在两个空槽上等待Vendor ID的配置请求超过3 分钟,仍然没有任何动作。
  • 固件基于EDK2,能够成功遍历PCIe总线,主要原因是它能够从连接到它们的NVMe和 USB驱动器中启动我的镜像。遗憾的是,我还没能找到EDK2如何对PCIe主机桥进行枚举,因此也就不知道它是如何避免这个问题的。
  • 从固件获得的Device Tree确实用兼容性字符串 cix,sky1-pcie-host 将所有PCIe总线列出,而不是通用的 pci-ecam-host-generic。不过,所有节点的状态都被报告为 okay
  • 我无法访问PCIe能力(例如控制CRS Software Visibility的那个),因为这颗芯片并没有一个总的根总线,而是有5 条独立的根总线,具有不同的总线范围,每条根总线恰好只有一个PCI-to-PCI桥,背后可能还连接着设备。为了获取PCIe能力,我必须先检测每条根总线是否存在,而我通过读取VendorID来实现的尝试恰恰会导致堵塞。
  • 使用ACPI表引导时,PCIe枚举会按预期工作,Vendor ID的读取会瞬间返回 0xffff。(编辑:实际情况是PCIe主机桥被排除在MCFG表之外,我误读了日志输出,以为这些总线被跳过了。)我现在更倾向使用设备树,因为此时我不太想为了访问板上的非PCIe设备(如内置的USB端口)而编写一个完整的AML解释器。

解决方案

就完全符合PCIe规范来说,似乎并没有必须要做的额外步骤。我在遇到需要较长初始化时间且无法使用CRS Software Visibility的 PCIe设备时,确实会出现这样的阻塞,但我应该至少能够通过一个快速初始化的主机桥或PCI-to-PCI桥,在其上先启用CRS Software Visibility。

然而,对于CIX Sky1 SoC,还有别的需要先做的事情。五个PCIe桥的连通状态由根固件处理,并通过一个定制的启动协议以及大量其他动态启动信息暴露给操作系统:

https://github.com/radxa/edk2-platforms/blob/cix_p1_dev_2025q4/Silicon/CIX/Sky1/Library/PciHostBridgeLib/PciHostBridgeLib.c#L201

  // gCixConfigParamsManageProtocolGuid = {0xC86EADA7, 0xD376, 0x474A, {0x9F, 0xAC, 0xCC, 0x4F, 0xC5, 0xFD, 0x41, 0x7B}};
  Status = gBS->LocateProtocol (&gCixConfigParamsManageProtocolGuid, NULL, (VOID **)&ConfigManage);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "Could not locate CIX Config Params Manage Protocol\n"));
    FreePool (Bridges);
    return NULL;
  }

  ConfigData = ConfigManage->Data;

  if (ConfigData == NULL) {
    DEBUG ((EFI_D_ERROR, "[%a]ConfigData is NULL\n", __FUNCTION__));
    FreePool (Bridges);
    return NULL;
  }

  for (Loop = 0; Loop < PCIE_MAX_ROOTBRIDGE; Loop++) {
    if (ConfigData->Pcie.PcieLinkUpStatus[Loop] == FALSE) {
      continue;
    }

    Status = ConstructRootBridge (&Bridges[*Count], &mPcieResourceAppeture[Loop], Loop);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "[%a:%d] - ConstructRootBridge failed!\n", __FUNCTION__, __LINE__));
      continue;
    }

    (*Count)++;
  }

也就是说,我需要从固件表中读取五个布尔值,以判断每个PCIe桥是否有效可用。ACPI固件代码读取这些布尔值,并据此动态生成MCFG ACPI表;但如果改为使用设备树启动,操作系统则应在自身驱动初始化时获取并读取这些布尔值。

哦,我还发现我这块板上的数据结构形状实际上并不与本仓库中的完全一致,PCIe数据在结构体中的起始索引是1,而不是下标50,尽管两者版本号相同。就这样吧。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章