如何在Windows中获取PCI区域大小?

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_id 和 base_addr,但是应该包含此设备的 PCI 区域大小的 size 总是为 0。我期望循环中的至少一个周期显示一个 size > 0。
我的代码基于一个 Linux 应用程序,它使用相同的代码,尽管它使用了随 Linux 一起提供的 pci.h 标头(显然 pciutils 具有相同的 API)。 显然,Windows(也就是我的情况下的 Windows 7 x64)不显示这个信息或者至少没有暴露给 PCIUtils。
您如何建议我获取这些信息? 如果有替代 PCIUtils 的 Windows 工具可以提供此信息,我将很高兴获得它们的链接。
编辑:我仍然没有找到解决方案。 如果有任何解决我的问题且适用于 32 位 Windows 的解决方案,将不胜感激。

3
这是一个坏主意 - 这些资源属于拥有设备的驱动程序对象 - 按照你描述的方式触碰它们可能会产生严重后果(其中最不利的是安全相关的)。 话虽如此,你想要提取什么样的信息? - Bukes
为什么需要在Windows上运行才能获取这些硬件特定的信息? - eh9
我将物理内存映射到用户空间,以便访问图形卡的i2c总线,从而获取有关电压、电流和温度的信息。目前我正在使用硬编码值,这是错误的。正确的方法是通过编程方式获取PCI区域大小并映射正确的内存。 - farmdve
通过i2c触摸频率和电压调节器很可怕,因为没有锁定机制,所以您不知道您的命令是否会与GPU驱动程序的电源管理部分发送的命令产生不良交互。某些代码应该拥有该设备,并且完全负责与其交互。您为什么需要编程方式使用此传感器? GPU-Z难道不提供您想要实现的功能吗? - Z.T.
GPU-Z只是一个监控工具,我正在开发一个工具,除了GPU-Z可以做的事情之外,还将拥有其他更特殊的功能。 - farmdve
2个回答

4
这个工作原理相当复杂。PCI设备使用“基地址寄存器”让BIOS和操作系统决定它们的内存区域位置。每个PCI设备允许指定多个它想要的内存或IO区域,然后让BIOS/操作系统决定放在哪里。更加复杂的是,只有一个寄存器用于同时规定大小和地址。这是如何工作的?
当卡首次上电时,它的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文档)

任何小于6000字节的内容都会导致我的应用程序崩溃。我需要知道大小,对于Radeon卡来说是131072字节或0x20000,以便知道要映射哪个基地址寄存器。通常,正确的BAR寄存器是BAR2,但谁知道这什么时候会改变以及为什么会改变,所以现在可能并不完全必要,但它可以在未来为我省去很多麻烦。 - farmdve
1
我相信你可以依赖这样一个事实,如果你要查找的寄存器在BAR2中,它们总是会在那里。翻阅了几个PCI驱动程序的源代码,它们都假定第一个内存BAR表示一件事,第二个表示另一件事,以此类推。我没有看到使用大小来确定正确的BAR的示例。所以我认为你在这里是安全的。如果尝试映射小于某个数量的大小会导致崩溃,那么你需要映射更多。至少映射到达到你需要访问的最远的寄存器的位置。 - whamma

3
很好,Whamma给出了一个非常好的答案,但有一件事他说错了,那就是区域大小。区域大小很容易找到,这里我将展示两种方法,第一种是通过从栏地址中解密它,第二种是通过Windows用户界面。

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

现在总共有32位,如果必须计算,您可以计数。现在,如果您不熟悉BAR中的位如何布置,请查看此处-> http://wiki.osdev.org/PCI ,具体来说是“基地址寄存器”,更具体地 读取标有“存储空间BAR布局”的图像。现在让我们从右端开始阅读位,并使用 我上面指向你的链接中的图像作为指南。

因此,从右侧开始的第一个位(位0)为0,表示这是一个内存地址BAR。 位(1-2)为0,表示它是32位(请注意,这不是大小)内存BAR。 位3为0,表示它不是可预取的内存。 位4-31表示地址。

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

要确定PCI设备所需的地址空间量,必须保存BAR的原始值, 将所有1的值写入寄存器,然后读取它。然后可以确定内存量 通过掩码信息位,执行按位NOT(C中的'~'),并将该值增加1。原始价值 BAR应恢复。BAR寄存器自然对齐,因此您只能修改设置的位。

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


1
这个第一个比特位是“1”,不是第25位吗? - Tony
更新以反映正确的过程,经由PCIE sig规范4.0确认,将1写入所有位,然后读取它,反转并加1。 - Kevin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接