How do I obtain PCI Region size in Windows?
Asked Answered
Z

2

8

I needed to scan my PCI bus and obtain information for specific devices from specific vendors. My goal is to find the PCI Region size for the AMD Graphics card, in order to map the PCI memory of that card to userspace in order to do i2c transfers and view information from various sensors.

For scanning the PCI bus I downloaded and compiled pciutils 3.1.7 for Windows x64 around a year ago. It supposedly uses DirectIO.

This is my code.

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;
}

As you see in my printf line, I try to print some data, I am successfully printing device_id and base_addr however size which should contain the PCI region size for this device is always 0. I expected, at least one of the cycles from the loop to display a size > 0.

My code is based on a Linux application which uses the same code, though it uses the pci.h headers that come with Linux(pciutils apparenltly has the same APIs). Apparently, Windows(that is Windows 7 x64 in my case) does not show this information or the at the very least is not exposed to PCIUtils.

How do you propose I obtain this information? If there are alternatives to pciutils for Windows and provide this information, I'd be glad to obtain a link to them.

EDIT:I have still found no solution. If there are any solutions to my problem and also work for 32-bit Windows, It would be deeply appreciated.

Zaidazailer answered 7/11, 2012 at 21:58 Comment(5)
This is a bad idea - these resources belong to the driver object that owns the device - tickling them in the way you describe can have severe consequences (not the least of which are security related). That said, what sort of information are you attempting to extract?Worser
Why do you need to be running Windows to get this hardware-specific information?Invar
I map physical memory to userspace in order to access the i2c bus of the graphics card, from there the voltage controller and obtain information about voltage,current and temperature. Currently I am using hardcoded values which is wrong, the correct way is to programatically obtain the PCI region size and map the correct memory.Zaidazailer
Touching frequency and voltage regulators through i2c is scary because there is no locking mechanism, so you don't know that your commands won't interact badly with commands sent by the power management portion of the GPU's driver. Some piece of code should own the device and be solely responsible for interacting with it. Why do you need this sensor programmatically? Doesn't GPU-Z just provide what you want to accomplish?Freeswimming
GPU-Z is just a monitoring tool, I am developing a tool that in addition to the stuff GPU-z can do, will have other more special features.Zaidazailer
F
3

Well whamma gave a very good answer [but] there's one thing he was wrong about, which is region sizes. Region sizes are pretty easy to find, here i will show two ways, the first by deciphering it from the address of the bar, the second through Windows user interface.

Let's assume that E2000000 is the address of the Base Register. If we convert that to binary we get: 11100010000000000000000000000000

Now there are 32 bits here in total, you can count them if you must. Now if you are not familiar with how the bits in a BAR are layed out, look here -> http://wiki.osdev.org/PCI , specifically "Base Address Registers" and more specifically the image that reads "Memory Space BAR Layout". Now lets start reading the bits from the right end to the left end and use the image in the link i pointed to you above as a guide.

So the first bit(Bit 0) starting from the right is 0, indicating that this is a memory address BAR. Bits(1-2) are 0, indicating that it's a 32-bit(note this is not the size) memory BAR. Bit 3 is 0, indicating that it's not Prefetchable memory. Bits 4-31 represent the address.

The page documents the PCI approved process:

To determine the amount of address space needed by a PCI device, you must save the original value of the BAR, write a value of all 1's to the register, then read it back. The amount of memory can then be determined by masking the information bits, performing a bitwise NOT ('~' in C), and incrementing the value by 1. The original value of the BAR should then be restored. The BAR register is naturally aligned and as such you can only modify the bits that are set.

The other way is using Device Manager: Start->"Device Manager"->Display Adapters->Right Click your video card->Properties->Resources. Each resource type marked "Memory Range" should be a memory BAR and as you can see it says [start address] to [end address]. For example lets say it read [00000000E2000000 - 00000000E2FFFFFF], to get the size you would take [start address] from [end address]: 00000000E2FFFFFF - 00000000E2000000 = FFFFFF, FFFFFF in decimal = 16777215 = 16777215 bytes = 16MB.

Frankfort answered 12/1, 2013 at 16:22 Comment(2)
this first bit that is a "1" isn't bit 25?Hanukkah
Updated to reflect the proper process, confirmed via PCIE sig spec 4.0, write 1 to all bits then read it back, inverse, add 1Vocalism
F
4

How this works is pretty complicated. PCI devices use Base Address Registers to let the BIOS and Operating System decide where to locate their memory regions. Each PCI device is allowed to specify several memory or IO regions it wants, and lets the BIOS/OS decide where to put it. Complicating matters, there's only one register that is used both to specify the size AND the address. How does this work?

When the card is first powered up, it's 32-bit address register will have something like 0xFFFF0000 in it. Any binary 1 means "the OS can change this", any binary 0 means "must stay zero". So this is telling the OS that any of the top 16 bits can be set to whatever the OS wants, but the bottom 16 bits have to stay zero. Which also means that this memory region takes up 16 bits of address space, or 64k. Because of this, memory regions have to be aligned to their size. If a card wants 64K of address space, the OS can only put it on memory addresses that are a multiple of 64K. When the OS has decided where it wants to locate this card's 64K memory space, it writes it back into this register, overwriting the initial 0xFFFF0000 that was in there.

In other words, the card tells the OS what size/alignment it needs for the memory, then the OS overwrites that same register/variable with the address for the memory. Once it's done this, you can't get the size back out of the register without resetting the address.

This means there's no portable way of asking a card how big its region is, all you can ask it is WHERE the region is.

So why does this work in Linux? Because it's asking the kernel for this information. The kernel has an API to provide this stuff, the same way that lspci works. I am not a Windows expert, but I'm unaware of any way for an application to ask the Windows kernel this information. There may be an API to do this somehow, or you may need to write something that runs on the kernel side to pass this information back to you. If you look in the libpci source, for windows it calls the "generic" version of pci_fill_info(), which returns:

return flags & ~PCI_FILL_SIZES;

which basically means "I'm returning everything you asked for, but the sizes."

BUT, this may not matter anyway. If all you're doing is wanting to read/write to the I2C registers, they're usually (always?) in the first 4K of the control/configuration region. You can probably just map 4K (one page) and ignore the fact that there might be more. Also be warned that you may need to take additional steps to stop the real driver for this card from reading/writing while you are. If you're bit-banging the I2C bus manually, and the driver tries to at the same time, it's likely to cause a mess on the bus.

There also may be an existing way to ask the radeon driver to do I2C requests for you, which might avoid all of this.

(also note I'm simplifying and glossing over a lot of details with how the BARs work, including 64 bit addresses, I/O space, etc, read PCI documentation if you want to learn more)

Fixing answered 22/11, 2012 at 23:42 Comment(2)
Anything less than 6000 bytes causes my application to crash. And I need to know the size, which for Radeon cards is 131072 bytes, or 0x20000 in order to know which Base Address Register to map. Now usually, the correct BAR register is BAR2, but who knows when this might change and for what reason, so it may not be fully necessary now, but it can save me a lot of trouble in the future.Zaidazailer
I believe you can rely on the fact that if the registers you are looking for are in BAR2, they'll always be there. Flipping through several PCI drivers that I do have the source to, they are all assuming that the first memory BAR means one thing, the second means another, etc. I don't see any examples of using the size to determine which the correct BAR is. So I think you're safe here. If it's crashing if you try mapping sizes less than a certain amount then you're correct that you need to map more. Map at least enough to reach the furthest register you need to touch.Fixing
F
3

Well whamma gave a very good answer [but] there's one thing he was wrong about, which is region sizes. Region sizes are pretty easy to find, here i will show two ways, the first by deciphering it from the address of the bar, the second through Windows user interface.

Let's assume that E2000000 is the address of the Base Register. If we convert that to binary we get: 11100010000000000000000000000000

Now there are 32 bits here in total, you can count them if you must. Now if you are not familiar with how the bits in a BAR are layed out, look here -> http://wiki.osdev.org/PCI , specifically "Base Address Registers" and more specifically the image that reads "Memory Space BAR Layout". Now lets start reading the bits from the right end to the left end and use the image in the link i pointed to you above as a guide.

So the first bit(Bit 0) starting from the right is 0, indicating that this is a memory address BAR. Bits(1-2) are 0, indicating that it's a 32-bit(note this is not the size) memory BAR. Bit 3 is 0, indicating that it's not Prefetchable memory. Bits 4-31 represent the address.

The page documents the PCI approved process:

To determine the amount of address space needed by a PCI device, you must save the original value of the BAR, write a value of all 1's to the register, then read it back. The amount of memory can then be determined by masking the information bits, performing a bitwise NOT ('~' in C), and incrementing the value by 1. The original value of the BAR should then be restored. The BAR register is naturally aligned and as such you can only modify the bits that are set.

The other way is using Device Manager: Start->"Device Manager"->Display Adapters->Right Click your video card->Properties->Resources. Each resource type marked "Memory Range" should be a memory BAR and as you can see it says [start address] to [end address]. For example lets say it read [00000000E2000000 - 00000000E2FFFFFF], to get the size you would take [start address] from [end address]: 00000000E2FFFFFF - 00000000E2000000 = FFFFFF, FFFFFF in decimal = 16777215 = 16777215 bytes = 16MB.

Frankfort answered 12/1, 2013 at 16:22 Comment(2)
this first bit that is a "1" isn't bit 25?Hanukkah
Updated to reflect the proper process, confirmed via PCIE sig spec 4.0, write 1 to all bits then read it back, inverse, add 1Vocalism

© 2022 - 2024 — McMap. All rights reserved.