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 --- Lesson_30/README.md | 458 ---------------------------------------------------- 1 file changed, 458 deletions(-) delete mode 100644 Lesson_30/README.md (limited to 'Lesson_30/README.md') diff --git a/Lesson_30/README.md b/Lesson_30/README.md deleted file mode 100644 index 756583f..0000000 --- a/Lesson_30/README.md +++ /dev/null @@ -1,458 +0,0 @@ - -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