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 `` for the `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL` and `` 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: ![PCI_Configuration_Address](PCI_Configuration_Address.png?raw=true "PCI_Configuration_Address") 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