Accessing PCI config space from user space

I am using 0xCF8 and 0xCFC ports to access PCI configuration, but it works if there is no segment or domain.
i.e. A PCI device is identified by Domain:Bus:Device:Function, using 0xCFC/0xCF8 we can only specify B:D:F there is no option to provide segment/domain. Below is the layout of CONFIG_ADDR (0xCF8) register.

Bit 31 Bits 30-24 Bits 23-16 Bits 15-11 Bits 10-8 Bits 7-0
Enable Bit Reserved Bus Number Device Number Function Number Register Offset1

My question is how do I access PCI config space which has Segment:Bus:Device:Function.

Under Win10 and Win11 running VHCI, access to those ports has been walled off; they don’t exist anymore. From kernel mode you can access the type 0 PCI config space region by using HalGetBusDataByOffset passing in the appropriate values …

Thanks Craig.
I believe "HalGetBusDataByOffset " is a kernel call. Can I get access to PCI config space without writing a driver?

is there a generic PCI driver in windows to which an application can do IOCTL to access the PCI config space?

You were NEVER supposed to do this from user mode. The I/O protection map should have blocked all “in” or “out” instructions from ring 3.

You cannot get to PCI config space without writing a driver; it can be a filter driver (look at the ChipSec project for this [https://github.com/chipsec/chipsec]) or a KM driver calling HalGetBusDataByOffset but it has to be a driver, which under SecureBoot and VHCI need to be properly signed and such … there used to be a generic PCI driver in RWEverything that would allow access to the PCI config space (and pretty much everything else) but that capability was taken away with the advent of VHCI/ VBS in Win10

In case it’s not obvious, HalGetBusDataByOffset has been deprecated for many years and may not even work in some situations/topologies.

there used to be a generic PCI driver in RWEverything

One of my absolute favorite diagnostic utilities. Ah, those were the days!

MS actually revived it specifically for allowing read access to PCI config space (it’s in the doc’s that they issued on Jan 24th) so it’s not going away anytime soon … :slight_smile:

@craig_howard

That strikes me as very curious. And I can’t find any revision of that page later than 1/17/24… can you give me a pointer, please? The blame on GitHub for the page shows the “obsolete” warning in this function was created by Ted Hudek (who has long been the doc owner for this function) on 1/17.

Why would the GetBusData method of BUS_INTERFACE_STANDARD not be what should be used?

Also, a good friend of mine used to own the HALs and PCI bus driver, and he specifically told me that he broke this function. Further, I’ve had to fix multiple drivers for third parties that used this call, and failed.

So… I’m super confused, and would appreciate whatever background you can share.

Sure … as I’ve written here a few years ago regarding PCI bus ECAM space access under Win10/Win11, with VBS/VHCI MS has “cracked down” on folks who want to read the PCI config space that are not the bus driver owners of that config space … they contend that only bus drivers should access that region (even for read access) and so disabled the CFC/CF8 registers [https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/accessing-pci-device-configuration-space] because of some threat actors had abused poorly written PCIe firmware using that [https://resources.infosecinstitute.com/topics/hacking/pci-expansion-rom/]

That’s all well and good, except that some legitimate vendors actually use the PCI ECAM space for PCI bus segmentation information for their products [https://stackoverflow.com/questions/49050847/how-is-pci-segmentdomain-related-to-multiple-host-bridgesor-root-bridges] (near the middle) and [https://docs.xilinx.com/r/en-US/pg344-pcie-dma-versal/Enhanced-Configuration-Access-Memory-Map] … doing this broke their products, as the driver would be unable to tell that it was talking to hardware blithely updating the ECAM space without being able to, well, read the ECAM space

Most folks didn’t know about this until motherboard vendors starting shipping BIOS’s with ACPI “hello” support [https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/windows-hello-enhanced-sign-in-security] which was a requisite for full VBS/VHCI working … it was a “suggested” thing for awhile, then became a “required” thing recently and things started to crash with non maskable BSOD’s

MS stated there were two workarounds for this; use the BUS_INTERFACE_STANDARD method (which means you’re part of the PCI stack and it doesn’t have nice minidriver interfaces like the file system or WFD) or send IRP_MN_XX_CONFIG calls to pci.sys, neither of which will allow access to ECAM space (which lives at a layer below type-0 PCI config space) and remember that pci.sys is an inbox driver which doesn’t play well with unexpected IRP’s :slight_smile:

Much wailing and gnashing of teeth ensued, MS was firm that the days of IO port access and MMIO access (which used to be how you would read ECAM space in the “old days”, you found the address of the PCI config space from the ACPI MCFG table, MMIO’ed that into memory and started walking pointers) were gone and several companies with three and two letter initials complained that they couldn’t even check to see if ECAM was possible without a BSOD …

MS compromised; a function which (as you point out) had been deprecated well over two decades ago was resurrected [https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-halgetbusdatabyoffset] to allow kernel drivers to read the type-0 PCI config space; you still can’t map and read the ECAM space, but vendors with hardware that used PCI bus segmentation didn’t BSOD on driver load anymore and they could figure out some other way to get ECAM information to the driver, likely with a custom BAR which is simply reflecting the ECAM region or similar)

It’s very easy to test, write a driver with this code

UINT32 value = 0;
UINT32 address = 0;
PCI_SLOT_NUMBER slot = {0};
slot.u.bits.DeviceNumber = 0; // or whatever you want
slot.u.bits.FunctionNumber = 0; // or whatever you want

 ULONG data_len = HalGetBusDataByOffset (PCIConfiguration, 0, slot.u.AsULONG, &value, address, 4);

… it should compile and link nicely and you should get back the DID of that device back …