In this lesson we try to show all PCI devices available in a system.
For this task we'll need to utilize EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
from the UEFI specification. This protocol is installed to every PCI Root bridge in the system.
It provides various functions to access PCI devices under this root bridge. For example with its help it is possible to read PCI device memory, I/O and configuration spaces for every PCI device:
You can look at a protocol structure to get a hint on what it can do:
typedef struct _EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL {
EFI_HANDLE ParentHandle;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollMem;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollIo;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Mem;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Io;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Pci;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_COPY_MEM CopyMem;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_MAP Map;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_UNMAP Unmap;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FREE_BUFFER FreeBuffer;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FLUSH Flush;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GET_ATTRIBUTES GetAttributes;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_SET_ATTRIBUTES SetAttributes;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_CONFIGURATION Configuration;
UINT32 SegmentNumber;
} EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL;
As in the system can be many PCI root bridges and therefore many EFI_PCI_ROOT_BRIDGE_IO_PROTOCOLs
, we need to use LocateHandleBuffer
to get all handles that have this protocol and then loop through these handles using OpenProtocol
on every one of them.
EFI_BOOT_SERVICES.LocateHandleBuffer()
Summary:
Returns an array of handles that support the requested protocol in a buffer allocated from pool.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_HANDLE_BUFFER) (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
OUT UINTN *NoHandles,
OUT EFI_HANDLE **Buffer
);
Parameters:
SearchType Specifies which handle(s) are to be returned.
Protocol Provides the protocol to search by. This parameter is only valid for a SearchType of ByProtocol.
SearchKey Supplies the search key depending on the SearchType.
NoHandles The number of handles returned in Buffer.
Buffer A pointer to the buffer to return the requested array of handles that support Protocol.
This buffer is allocated with a call to the Boot Service EFI_BOOT_SERVICES.AllocatePool().
It is the caller's responsibility to call the Boot Service EFI_BOOT_SERVICES.FreePool() when the caller no longer
requires the contents of Buffer.
Description:
The LocateHandleBuffer() function returns one or more handles that match the SearchType request. Buffer is allocated from pool, and the number of entries in Buffer is returned in NoHandles. Each
SearchType is described below:
AllHandles Protocol and SearchKey are ignored and the function returns an array of every handle in the system.
ByRegisterNotify SearchKey supplies the Registration returned by EFI_BOOT_SERVICES.RegisterProtocolNotify().
The function returns the next handle that is new for the Registration.
Only one handle is returned at a time, and the caller must loop until
no more handles are returned. Protocol is ignored for this search type.
ByProtocol All handles that support Protocol are returned. SearchKey is ignored for this search type.
EFI_STATUS Status;
UINTN HandleCount;
EFI_HANDLE *HandleBuffer;
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiPciRootBridgeIoProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (EFI_ERROR (Status)) {
Print(L"Can't locate EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL: %r\n", Status);
return Status;
}
Print(L"Number of PCI root bridges in the system: %d\n", HandleCount);
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo;
for (UINTN Index = 0; Index < HandleCount; Index++) {
...
}
FreePool(HandleBuffer);
Don't forget to include <Protocol/PciRootBridgeIo.h>
for the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
and <Library/MemoryAllocationLib.h>
for the FreePool
. And offcourse protocol should be included in the app *.inf
file:
[Protocols]
gEfiPciRootBridgeIoProtocolGuid
To get a protocol for particaular handle you can use OpenProtocol
function:
EFI_BOOT_SERVICES.OpenProtocol()
Summary:
Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the
handle, it opens the protocol on behalf of the calling agent. This is an extended version of the EFI boot
service EFI_BOOT_SERVICES.HandleProtocol().
Prototype
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface OPTIONAL,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle,
IN UINT32 Attributes
);
Parameters:
Handle The handle for the protocol interface that is being opened.
Protocol The published unique identifier of the protocol.
Interface Supplies the address where a pointer to the corresponding Protocol Interface is returned. NULL will be returned in *Interface if a
structure is not associated with Protocol. This parameter is optional, and will be ignored if Attributes is EFI_OPEN_PROTOCOL_TEST_PROTOCOL.
AgentHandle The handle of the agent that is opening the protocol interface specified by Protocol and Interface. For agents that follow the UEFI
Driver Model, this parameter is the handle that contains the EFI_DRIVER_BINDING_PROTOCOL instance that is produced by
the UEFI driver that is opening the protocol interface. For UEFI applications, this is the image handle of the UEFI application that is
opening the protocol interface. For applications that use HandleProtocol() to open a protocol interface, this parameter is
the image handle of the EFI firmware.
ControllerHandle If the agent that is opening a protocol is a driver that follows the
UEFI Driver Model, then this parameter is the controller handle that
requires the protocol interface. If the agent does not follow the UEFI
Driver Model, then this parameter is optional and may be NULL.
Attributes The open mode of the protocol interface specified by Handle and
Protocol.
Description:
This function opens a protocol interface on the handle specified by Handle for the protocol specified by Protocol.
The first three parameters are the same as EFI_BOOT_SERVICES.HandleProtocol(). The only difference is that the agent that is opening a protocol interface is tracked in an EFI's internal handle
database
There are various Attributes
(last parameter):
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
We will need EFI_OPEN_PROTOCOL_GET_PROTOCOL
:
GET_PROTOCOL - Used by a driver to get a protocol interface from a handle
You can read more about other values in the UEFI specification.
Use OpenProtocol
call in our loop, and call our custom EFI_STATUS PrintRootBridge(EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo)
function for every found protocol:
for (UINTN Index = 0; Index < HandleCount; Index++) {
Status = gBS->OpenProtocol (
HandleBuffer[Index],
&gEfiPciRootBridgeIoProtocolGuid,
(VOID **)&PciRootBridgeIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR(Status)) {
Print(L"Can't open protocol: %r\n", Status);
return Status;
}
Print(L"\nPCI Root Bridge %d\n", Index);
Status = PrintRootBridge(PciRootBridgeIo);
if (EFI_ERROR(Status)) {
Print(L"Error in PCI Root Bridge printing\n");
}
}
Now let's write this PrintRootBridge
function.
First we need to get all available buses for the PCI Root Bridge. To do this we can use Configuration()
function from the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
:
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Configuration()
Summary:
Retrieves the current resource settings of this PCI root bridge in the form of a set of ACPI resource descriptors.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_CONFIGURATION) (
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
OUT VOID **Resources
);
Parameters:
This A pointer to the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
Resources A pointer to the resource descriptors that describe the current configuration of this PCI root bridge.
The storage for the resource descriptors is allocated by this function. The caller must treat the return
buffer as read-only data, and the buffer must not be freed by the caller.
Description:
The Configuration() function retrieves a set of resource descriptors that contains the current
configuration of this PCI root bridge.
Also here is important information about ACPI resource descriptors
- the data that we would get from excuting this function:
There are only two resource descriptor types from the ACPI Specification that may be used to describe
the current resources allocated to a PCI root bridge. These are the QWORD Address Space Descriptor,
and the End Tag. The QWORD Address Space Descriptor can describe memory, I/O, and bus number
ranges for dynamic or fixed resources. The configuration of a PCI root bridge is described with one or
more QWORD Address Space Descriptors followed by an End Tag
So we need to check ACPI specification about 2 types of ACPI resource descriptors:
- QWORD Address Space Descriptor
- End Tag Descriptor
The QWORD address space descriptor is defined here in ACPI specification https://uefi.org/specs/ACPI/6.4/06_Device_Configuration/Device_Configuration.html?#qword-address-space-descriptor
In edk2 its structure is placed in a file https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi10.h
///
/// The common definition of QWORD, DWORD, and WORD
/// Address Space Descriptors.
///
typedef PACKED struct {
UINT8 Desc;
UINT16 Len;
UINT8 ResType;
UINT8 GenFlag;
UINT8 SpecificFlag;
UINT64 AddrSpaceGranularity;
UINT64 AddrRangeMin;
UINT64 AddrRangeMax;
UINT64 AddrTranslationOffset;
UINT64 AddrLen;
} EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR;
The end tag descriptor is defined in ACPI spec under https://uefi.org/specs/ACPI/6.4/06_Device_Configuration/Device_Configuration.html#end-tag
Define for it in edk2 is here https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi10.h:
#define ACPI_END_TAG_DESCRIPTOR 0x79
So after we get an array of EFI_ACPI_ADDRESS_SPACE_DESCRIPTORs
from our PciRootBridgeIo->Configuration
call, we need to loop through it until we would encounter descriptor ACPI_END_TAG_DESCRIPTOR
.
QWORD address space descriptor can have one of the several resource types:
//
// Resource Type
//
#define ACPI_ADDRESS_SPACE_TYPE_MEM 0x00
#define ACPI_ADDRESS_SPACE_TYPE_IO 0x01
#define ACPI_ADDRESS_SPACE_TYPE_BUS 0x02
Right now we are interested in ACPI_ADDRESS_SPACE_TYPE_BUS
type. We need to know, how many PCI buses has this PCI root bridge.
So the code for our function would look like this:
EFI_STATUS PrintRootBridge(EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo)
{
EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR* AddressDescriptor;
EFI_STATUS Status = PciRootBridgeIo->Configuration(
PciRootBridgeIo,
(VOID**)&AddressDescriptor
);
if (EFI_ERROR(Status)) {
Print(L"\tError! Can't get EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR: %r\n", Status);
return Status;
}
while (AddressDescriptor->Desc != ACPI_END_TAG_DESCRIPTOR) {
if (AddressDescriptor->ResType == ACPI_ADDRESS_SPACE_TYPE_BUS) {
...
}
AddressDescriptor++;
}
}
return Status;
When we know all available buses for the PCI root bridge we can try to read PCI configuration space for its devices with a help of EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read()
function:
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read()
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Write()
Summary:
Enables a PCI driver to access PCI controller registers in a PCI root bridge’s configuration space.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM) (
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH Width,
IN UINT64 Address,
IN UINTN Count,
IN OUT VOID *Buffer
);
Parameters:
This A pointer to the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
Width Signifies the width of the memory operations.
Address The address within the PCI configuration space for the PCI controller.
Count The number of PCI configuration operations to perform. Bytes moved is Width size * Count, starting at Address.
Buffer For read operations, the destination buffer to store the results.
For write operations, the source buffer to write data from.
Description:
The Pci.Read() and Pci.Write() functions enable a driver to access PCI configuration registers for a
PCI controller.
All the PCI transactions generated by this function are guaranteed to be completed before this function
returns.
The address in this function is defined as follows:
So we write a simple function to create an Address variable from the Bus/Device/Function/Register value:
UINT64 PciConfigurationAddress(UINT8 Bus,
UINT8 Device,
UINT8 Function,
UINT32 Register)
{
UINT64 Address = (((UINT64)Bus) << 24) + (((UINT64)Device) << 16) + (((UINT64)Function) << 8);
if (Register & 0xFFFFFF00) {
Address += (((UINT64)Register) << 32);
} else {
Address += (((UINT64)Register) << 0);
}
return Address;
}
Let's try to loop through all possible PCI functions and for every one of them read its header from PCI cofiguration space.
Maximum values for PCI bus, device and function are determined by PCI specification.
In edk2 they are defined in github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci22.h:
#define PCI_MAX_BUS 255
#define PCI_MAX_DEVICE 31
#define PCI_MAX_FUNC 7
As with ACPI newer PCI specifications include the older ones:
Pci.h > PciExpress50.h > PciExpress40.h > PciExpress31.h > PciExpress30.h > PciExpress21.h > Pci30.h > Pci23.h > Pci22.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress50.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress40.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress31.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress30.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress21.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci30.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci23.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci22.h
For every possible PCI function we would try to read its common PCI configuration space header:
///
/// Common header region in PCI Configuration Space
/// Section 6.1, PCI Local Bus Specification, 2.2
///
typedef struct {
UINT16 VendorId;
UINT16 DeviceId;
UINT16 Command;
UINT16 Status;
UINT8 RevisionID;
UINT8 ClassCode[3];
UINT8 CacheLineSize;
UINT8 LatencyTimer;
UINT8 HeaderType;
UINT8 BIST;
} PCI_DEVICE_INDEPENDENT_REGION;
After getting the data we would check if a VendorId
field is valid. If it is not equal to 0xffff
it is an actual PCI function. In this case we would print some information about it.
Here is a code for this Bus/Device/Func loop:
for (UINT8 Bus = AddressDescriptor->AddrRangeMin; Bus <= AddressDescriptor->AddrRangeMax; Bus++) {
for (UINT8 Device = 0; Device <= PCI_MAX_DEVICE; Device++) {
for (UINT8 Func = 0; Func <= PCI_MAX_FUNC; Func++) {
UINT64 Address = PciConfigurationAddress(Bus, Device, Func, 0);
PCI_DEVICE_INDEPENDENT_REGION PCIConfHdr;
Status = PciRootBridgeIo->Pci.Read(
PciRootBridgeIo,
EfiPciWidthUint8,
Address,
sizeof(PCI_DEVICE_INDEPENDENT_REGION),
&PCIConfHdr
);
if (!EFI_ERROR(Status)) {
if (PCIConfHdr.VendorId != 0xffff) {
Print(L"\tBus: %02x, Dev: %02x, Func: %02x - Vendor:%04x, Device:%04x\n",
Bus,
Device,
Func,
PCIConfHdr.VendorId,
PCIConfHdr.DeviceId);
}
} else {
Print(L"\tError in PCI read: %r\n", Status);
}
}
}
}
If we build and execute our app under OVMF we would get:
FS0:\> ListPCI.efi
Number of PCI root bridges in the system: 1
PCI Root Bridge 0
Bus: 00, Dev: 00, Func: 00 - Vendor:8086, Device:1237
Bus: 00, Dev: 01, Func: 00 - Vendor:8086, Device:7000
Bus: 00, Dev: 01, Func: 01 - Vendor:8086, Device:7010
Bus: 00, Dev: 01, Func: 03 - Vendor:8086, Device:7113
Bus: 00, Dev: 02, Func: 00 - Vendor:1234, Device:1111
You can verify that our output is correct if you execute UEFI shell pci
command:
FS0:\> pci
Seg Bus Dev Func
--- --- --- ----
00 00 00 00 ==> Bridge Device - Host/PCI bridge
Vendor 8086 Device 1237 Prog Interface 0
00 00 01 00 ==> Bridge Device - PCI/ISA bridge
Vendor 8086 Device 7000 Prog Interface 0
00 00 01 01 ==> Mass Storage Controller - IDE controller
Vendor 8086 Device 7010 Prog Interface 80
00 00 01 03 ==> Bridge Device - Other bridge type
Vendor 8086 Device 7113 Prog Interface 0
00 00 02 00 ==> Display Controller - VGA/8514 controller
Vendor 1234 Device 1111 Prog Interface 0
One more thing to end this lesson, you can utilize PciLib
to access PCI Configuration Space registers. Check out its interface at https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PciLib.h