8

我需要扫描我的 PCI 总线并从特定供应商处获取特定设备的信息。我的目标是找到 AMD 显卡的 PCI 区域大小,以便将该卡的 PCI 内存映射到用户空间,以便进行 i2c 传输并查看来自各种传感器的信息。

为了扫描 PCI 总线,我在一年前下载并编译了适用于 Windows x64 的 pciutils 3.1.7。据说它使用 DirectIO。

这是我的代码。

int scan_pci_bus()
{
    struct pci_access *pci;
    struct pci_dev *dev;
    int i;

    pci = pci_alloc();
    pci_init(pci);

    pci_scan_bus(pci);

    for(dev = pci->devices; dev; dev = dev->next) 
    {
        pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_CLASS | PCI_FILL_IRQ | PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES | PCI_FILL_PHYS_SLOT);
        if(dev->vendor_id == 0x1002 && dev->device_id == 0x6899)
        {
            //Vendor is AMD, Device ID is a AMD HD5850 GPU
            for(i = 0; i < 6; i++) 
            {
                printf("Region Size %d %x ID %x\n", dev->size[i], dev->base_addr[i], dev->device_id);
            }
        }
    }


    pci_cleanup(pci);

    return 0;
}

正如您在我的 printf 行中看到的那样,我尝试打印一些数据,我正在成功打印device_idbase_addr但是size应该包含此设备的 PCI 区域大小始终为 0。我预计,循环中至少有一个循环显示大小 > 0。

我的代码基于使用相同代码的 Linux 应用程序,尽管它使用 Linux 附带的 pci.h 标头(pciutils 显然具有相同的 API)。显然,Windows(在我的情况下是 Windows 7 x64)没有显示此信息,或者至少没有暴露给 PCIUtils。

你建议我如何获得这些信息?如果有 Windows 的 pciutils 的替代品并提供此信息,我很高兴获得它们的链接。

编辑:我仍然没有找到解决方案。如果我的问题有任何解决方案并且也适用于 32 位 Windows,我们将不胜感激。

4

2 回答 2

4

这是如何工作的非常复杂。PCI 设备用于Base Address Registers让 BIOS 和操作系统决定其内存区域的位置。每个 PCI 设备都可以指定它想要的几个内存或 IO 区域,并让 BIOS/OS 决定将其放置在哪里。使事情复杂化的是,只有一个寄存器用于指定大小和地址。这是如何运作的?

当卡第一次上电时,它的 32 位地址寄存器中会有类似 0xFFFF0000 的内容。任何二进制 1 表示“操作系统可以改变它”,任何二进制 0 表示“必须保持零”。所以这告诉操作系统,前 16 位中的任何一个都可以设置为操作系统想要的任何值,但后 16 位必须保持为零。这也意味着这个内存区域占用了 16 位地址空间,即 64k。因此,内存区域必须与其大小对齐。如果一张卡需要 64K 的地址空间,操作系统只能将它放在 64K 的倍数的内存地址上。当操作系统决定了它想在哪里定位这张卡的 64K 内存空间时,它会将它写回这个寄存器,覆盖那里的初始 0xFFFF0000。

换句话说,卡告诉操作系统它需要内存的大小/对齐方式,然后操作系统用内存地址覆盖相同的寄存器/变量。完成此操作后,您无法在不重置地址的情况下从寄存器中恢复大小。

这意味着没有可移植的方式来询问一张卡它的区域有多大,你只能问它的区域在哪里。

那么为什么这在 Linux 中有效呢?因为它正在向内核询问这些信息。内核有一个 API 来提供这些东西,与 lspci 的工作方式相同。我不是 Windows 专家,但我不知道应用程序以任何方式向 Windows 内核询问此信息。可能有一个 API 可以以某种方式执行此操作,或者您可能需要编写一些在内核端运行的东西以将此信息传回给您。如果您查看 libpci 源代码,对于 windows,它调用 pci_fill_info() 的“通用”版本,它返回:

return flags & ~PCI_FILL_SIZES;

这基本上意味着“我将退回您要求的所有东西,但尺寸除外。”

但是,无论如何这可能无关紧要。如果您所做的只是想读/写 I2C 寄存器,它们通常(总是?)在控制/配置区域的前 4K 中。您可能只映射 4K(一页)而忽略可能有更多的事实。另请注意,您可能需要采取其他步骤来阻止此卡的真正驱动程序在您处于读取/写入状态时进行读取/写入。如果您手动对 I2C 总线进行位敲击,并且驱动程序同时尝试这样做,则可能会导致总线混乱。

也可能有一种现有的方法可以让 radeon 驱动程序为您执行 I2C 请求,这可能会避免所有这些。

(另请注意,我正在简化和掩盖 BAR 如何工作的许多细节,包括 64 位地址、I/O 空间等,如果您想了解更多信息,请阅读 PCI 文档)

于 2012-11-22T23:42:18.360 回答
3

好吧 whamma 给出了一个很好的答案 [但是] 他错了一件事,那就是区域大小。区域大小很容易找到,这里我将展示两种方法,第一种是从栏的地址中破译它,第二种是通过 Windows 用户界面。

假设 E2000000 是基址寄存器的地址。如果我们将其转换为二进制,我们得到: 111000100000000000000000000000000

现在这里总共有 32 位,如果需要,您可以计算它们。现在,如果您不熟悉 BAR 中的位是如何布局的,请看这里 -> http://wiki.osdev.org/PCI,特别是“基地址寄存器”,更具体地说是读取“内存空间 BAR”的图像布局”。现在让我们开始从右端到左端读取位,并使用我在上面指向您的链接中的图像作为指南。

所以从右边开始的第一位(Bit 0)是0,表示这是一个内存地址BAR。Bits(1-2) 为 0,表示它是一个 32 位(注意这不是大小)内存条。第 3 位为 0,表示它不是 Prefetchable 内存。位 4-31 代表地址。

该页面记录了 PCI 批准的流程:

要确定 PCI 设备所需的地址空间量,您必须保存 BAR 的原始值,将全 1 的值写入寄存器,然后将其读回。然后可以通过屏蔽信息位、执行按位非(C 中的“~”)并将值增加 1 来确定内存量。然后应该恢复 BAR 的原始值。BAR 寄存器自然对齐,因此您只能修改设置的位。

另一种方法是使用设备管理器:开始->“设备管理器”->显示适配器->右键单击您的视频卡->属性->资源。每个标记为“内存范围”的资源类型都应该是一个内存条,正如您所见,它表示 [起始地址] 到 [结束地址]。例如,假设它读取 [00000000E2000000 - 00000000E2FFFFFF],以从 [结束地址] 获取 [起始地址] 的大小:00000000E2FFFFFF - 00000000E2000000 = FFFFFF,十进制的 FFFFFF = 16777215 = 16777215 字节 = 16。

于 2013-01-12T16:22:35.937 回答