From 6064c1e48b622f53538f4df9bdd402c607a87d51 Mon Sep 17 00:00:00 2001 From: Konstantin Aladyshev Date: Sat, 10 Jul 2021 00:04:40 +0300 Subject: Move lessons to separate folder Signed-off-by: Konstantin Aladyshev --- Lessons/Lesson_30/README.md | 458 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 Lessons/Lesson_30/README.md (limited to 'Lessons/Lesson_30/README.md') diff --git a/Lessons/Lesson_30/README.md b/Lessons/Lesson_30/README.md new file mode 100644 index 0000000..756583f --- /dev/null +++ b/Lessons/Lesson_30/README.md @@ -0,0 +1,458 @@ + +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 + -- cgit v1.2.3-18-g5258