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

时间:2012-11-07 21:58:03

标签: c windows size regions pci

我需要扫描我的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.我预计,至少有一个循环从循环中显示一个大小&gt; 0

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

您如何建议我获取此信息?如果有替代pciutils for Windows并提供此信息,我很乐意获得它们的链接。

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

2 个答案:

答案 0 :(得分:4)

这是如何工作的非常复杂。 PCI设备使用Base Address Registers让BIOS和操作系统决定在哪里找到他们的内存区域。允许每个PCI设备指定它想要的几个内存或IO区域,并让BIOS / OS决定放置它的位置。使问题复杂化的是,只有一个寄存器用于指定大小和地址。这是如何工作的?

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

换句话说,卡告诉操作系统内存需要什么尺寸/对齐,然后操作系统用内存地址覆盖相同的寄存器/变量。完成此操作后,如果不重置地址,则无法从寄存器中取出大小。

这意味着没有便携式的方式来询问卡片的区域有多大,所有你可以问的是区域在哪里。

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

return flags & ~PCI_FILL_SIZES;

这基本上意味着“我要归还你要求的所有东西,但尺寸。”

但是,无论如何这可能无关紧要。如果你所做的只是想读/写I2C寄存器,它们通常(总是?)在控制/配置区域的第一个4K中。您可以只映射4K(一页)并忽略可能有更多的事实。另外要注意的是,您可能需要采取其他措施来阻止此卡的真正驱动程序进行读/写操作。如果您手动 bit-banging I2C总线,并且驱动程序同时尝试,则可能会导致总线混乱。

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

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

答案 1 :(得分:2)

好的whamma给出了一个非常好的答案[但]有一件事他错了,这是区域大小。区域大小很容易找到,在这里我将展示两种方式,第一种方法是从条形码的地址解密,第二种方式是通过Windows用户界面。

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

现在总共有32位,如果必须,可以计算它们。现在,如果你不熟悉这些位 BAR被打好了,看这里 - &gt; http://wiki.osdev.org/PCI,特别是“基地址寄存器”,更具体地说 读取“Memory Space BAR Layout”的图像。现在让我们开始读取从右端到左端的位并使用 链接中的图像我在上面指出了你作为指南。

因此从右边开始的第一位(位0)为0,表示这是一个存储器地址BAR。 位(1-2)为0,表示它是32位(注意这不是大小)内存BAR。 位3为0,表示它不是Prefetchable内存。 现在我们消除了无用的位,让我们继续保持良好的位置,位4-31。如果你看看位的4 -31,你会 请注意,第一位是“1”是位24.这是一些数学发挥作用的地方。首先我们必须找到 Bit 24的二进制加权值,即16777216,也是16777216字节,即16 MB,告诉我们 BAR分配的内存大小为16 MB。如果你想知道我如何得到第24位的二进制加权值,它就像这样:2(二进制是基数2)乘以 本身24(24位),或2到24的幂,或2 ^ 24。

另一种方法是使用设备管理器: 开始 - &gt;“设备管理器” - &gt;显示适配器 - &gt;右键单击您的视频卡 - >属性 - >资源。标记的每种资源类型 “Memory Range”应该是一个内存BAR,你可以看到[start address]到[end address]。例如,让我们说 它读取[00000000E2000000 - 00000000E2FFFFFF],以获取[end address]中[起始地址]的大小: 00000000E2FFFFFF - 00000000E2000000 = FFFFFF,十进制FFFFFF = 16777215 = 16777215字节= 16MB。