| /** @file | |
| Instance of Runtime PCI Segment Library that support multi-segment PCI configuration access. | |
| PCI Segment Library that consumes segment information provided by PciSegmentInfoLib to | |
| support multi-segment PCI configuration access through enhanced configuration access mechanism. | |
| Copyright (c) 2017, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "PciSegmentLibCommon.h" | |
| #include <PiDxe.h> | |
| #include <Guid/EventGroup.h> | |
| #include <Library/UefiRuntimeLib.h> | |
| #include <Library/DxeServicesTableLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PciSegmentInfoLib.h> | |
| /// | |
| /// Define table for mapping PCI Segment MMIO physical addresses to virtual addresses at OS runtime | |
| /// | |
| typedef struct { | |
| UINTN PhysicalAddress; | |
| UINTN VirtualAddress; | |
| } PCI_SEGMENT_RUNTIME_REGISTRATION_TABLE; | |
| /// | |
| /// Set Virtual Address Map Event | |
| /// | |
| EFI_EVENT mDxeRuntimePciSegmentLibVirtualNotifyEvent = NULL; | |
| /// | |
| /// The number of PCI devices that have been registered for runtime access. | |
| /// | |
| UINTN mDxeRuntimePciSegmentLibNumberOfRuntimeRanges = 0; | |
| /// | |
| /// The table of PCI devices that have been registered for runtime access. | |
| /// | |
| PCI_SEGMENT_RUNTIME_REGISTRATION_TABLE *mDxeRuntimePciSegmentLibRegistrationTable = NULL; | |
| /// | |
| /// The table index of the most recent virtual address lookup. | |
| /// | |
| UINTN mDxeRuntimePciSegmentLibLastRuntimeRange = 0; | |
| /** | |
| Convert the physical PCI Express MMIO addresses for all registered PCI devices | |
| to virtual addresses. | |
| @param[in] Event The event that is being processed. | |
| @param[in] Context The Event Context. | |
| **/ | |
| VOID | |
| EFIAPI | |
| DxeRuntimePciSegmentLibVirtualNotify ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| UINTN Index; | |
| EFI_STATUS Status; | |
| // | |
| // If there have been no runtime registrations, then just return | |
| // | |
| if (mDxeRuntimePciSegmentLibRegistrationTable == NULL) { | |
| return; | |
| } | |
| // | |
| // Convert physical addresses associated with the set of registered PCI devices to | |
| // virtual addresses. | |
| // | |
| for (Index = 0; Index < mDxeRuntimePciSegmentLibNumberOfRuntimeRanges; Index++) { | |
| Status = EfiConvertPointer (0, (VOID **)&(mDxeRuntimePciSegmentLibRegistrationTable[Index].VirtualAddress)); | |
| ASSERT_EFI_ERROR (Status); | |
| } | |
| // | |
| // Convert table pointer that is allocated from EfiRuntimeServicesData to a virtual address. | |
| // | |
| Status = EfiConvertPointer (0, (VOID **)&mDxeRuntimePciSegmentLibRegistrationTable); | |
| ASSERT_EFI_ERROR (Status); | |
| } | |
| /** | |
| The constructor function caches the PCI Express Base Address and creates a | |
| Set Virtual Address Map event to convert physical address to virtual addresses. | |
| @param ImageHandle The firmware allocated handle for the EFI image. | |
| @param SystemTable A pointer to the EFI System Table. | |
| @retval EFI_SUCCESS The constructor completed successfully. | |
| @retval Other value The constructor did not complete successfully. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| DxeRuntimePciSegmentLibConstructor ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| // | |
| // Register SetVirtualAddressMap () notify function | |
| // | |
| Status = gBS->CreateEvent ( | |
| EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, | |
| TPL_NOTIFY, | |
| DxeRuntimePciSegmentLibVirtualNotify, | |
| NULL, | |
| &mDxeRuntimePciSegmentLibVirtualNotifyEvent | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } | |
| /** | |
| The destructor function frees any allocated buffers and closes the Set Virtual | |
| Address Map event. | |
| @param ImageHandle The firmware allocated handle for the EFI image. | |
| @param SystemTable A pointer to the EFI System Table. | |
| @retval EFI_SUCCESS The destructor completed successfully. | |
| @retval Other value The destructor did not complete successfully. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| DxeRuntimePciSegmentLibDestructor ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| // | |
| // If one or more PCI devices have been registered for runtime access, then | |
| // free the registration table. | |
| // | |
| if (mDxeRuntimePciSegmentLibRegistrationTable != NULL) { | |
| FreePool (mDxeRuntimePciSegmentLibRegistrationTable); | |
| } | |
| // | |
| // Close the Set Virtual Address Map event | |
| // | |
| Status = gBS->CloseEvent (mDxeRuntimePciSegmentLibVirtualNotifyEvent); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } | |
| /** | |
| Register a PCI device so PCI configuration registers may be accessed after | |
| SetVirtualAddressMap(). | |
| If any reserved bits in Address are set, then ASSERT(). | |
| @param Address The address that encodes the PCI Bus, Device, Function and | |
| Register. | |
| @retval RETURN_SUCCESS The PCI device was registered for runtime access. | |
| @retval RETURN_UNSUPPORTED An attempt was made to call this function | |
| after ExitBootServices(). | |
| @retval RETURN_UNSUPPORTED The resources required to access the PCI device | |
| at runtime could not be mapped. | |
| @retval RETURN_OUT_OF_RESOURCES There are not enough resources available to | |
| complete the registration. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| PciSegmentRegisterForRuntimeAccess ( | |
| IN UINTN Address | |
| ) | |
| { | |
| RETURN_STATUS Status; | |
| EFI_GCD_MEMORY_SPACE_DESCRIPTOR Descriptor; | |
| UINTN Index; | |
| VOID *NewTable; | |
| UINTN Count; | |
| PCI_SEGMENT_INFO *SegmentInfo; | |
| UINT64 EcamAddress; | |
| // | |
| // Convert Address to a ECAM address at the beginning of the PCI Configuration | |
| // header for the specified PCI Bus/Dev/Func | |
| // | |
| Address &= ~(UINTN)EFI_PAGE_MASK; | |
| SegmentInfo = GetPciSegmentInfo (&Count); | |
| EcamAddress = PciSegmentLibGetEcamAddress (Address, SegmentInfo, Count); | |
| // | |
| // Return an error if this function is called after ExitBootServices(). | |
| // | |
| if (EfiAtRuntime ()) { | |
| return RETURN_UNSUPPORTED; | |
| } | |
| if (sizeof (UINTN) == sizeof (UINT32)) { | |
| ASSERT (EcamAddress < BASE_4GB); | |
| } | |
| Address = (UINTN)EcamAddress; | |
| // | |
| // See if Address has already been registered for runtime access | |
| // | |
| for (Index = 0; Index < mDxeRuntimePciSegmentLibNumberOfRuntimeRanges; Index++) { | |
| if (mDxeRuntimePciSegmentLibRegistrationTable[Index].PhysicalAddress == Address) { | |
| return RETURN_SUCCESS; | |
| } | |
| } | |
| // | |
| // Get the GCD Memory Descriptor for the ECAM Address | |
| // | |
| Status = gDS->GetMemorySpaceDescriptor (Address, &Descriptor); | |
| if (EFI_ERROR (Status)) { | |
| return RETURN_UNSUPPORTED; | |
| } | |
| // | |
| // Mark the 4KB region for the PCI Express Bus/Dev/Func as EFI_RUNTIME_MEMORY so the OS | |
| // will allocate a virtual address range for the 4KB PCI Configuration Header. | |
| // | |
| Status = gDS->SetMemorySpaceAttributes (Address, EFI_PAGE_SIZE, Descriptor.Attributes | EFI_MEMORY_RUNTIME); | |
| if (EFI_ERROR (Status)) { | |
| return RETURN_UNSUPPORTED; | |
| } | |
| // | |
| // Grow the size of the registration table | |
| // | |
| NewTable = ReallocateRuntimePool ( | |
| (mDxeRuntimePciSegmentLibNumberOfRuntimeRanges + 0) * sizeof (PCI_SEGMENT_RUNTIME_REGISTRATION_TABLE), | |
| (mDxeRuntimePciSegmentLibNumberOfRuntimeRanges + 1) * sizeof (PCI_SEGMENT_RUNTIME_REGISTRATION_TABLE), | |
| mDxeRuntimePciSegmentLibRegistrationTable | |
| ); | |
| if (NewTable == NULL) { | |
| return RETURN_OUT_OF_RESOURCES; | |
| } | |
| mDxeRuntimePciSegmentLibRegistrationTable = NewTable; | |
| mDxeRuntimePciSegmentLibRegistrationTable[mDxeRuntimePciSegmentLibNumberOfRuntimeRanges].PhysicalAddress = Address; | |
| mDxeRuntimePciSegmentLibRegistrationTable[mDxeRuntimePciSegmentLibNumberOfRuntimeRanges].VirtualAddress = Address; | |
| mDxeRuntimePciSegmentLibNumberOfRuntimeRanges++; | |
| return RETURN_SUCCESS; | |
| } | |
| /** | |
| Return the linear address for the physical address. | |
| @param Address The physical address. | |
| @retval The linear address. | |
| **/ | |
| UINTN | |
| PciSegmentLibVirtualAddress ( | |
| IN UINTN Address | |
| ) | |
| { | |
| UINTN Index; | |
| // | |
| // If SetVirtualAddressMap() has not been called, then just return the physical address | |
| // | |
| if (!EfiGoneVirtual ()) { | |
| return Address; | |
| } | |
| // | |
| // See if there is a physical address match at the exact same index as the last address match | |
| // | |
| if (mDxeRuntimePciSegmentLibRegistrationTable[mDxeRuntimePciSegmentLibLastRuntimeRange].PhysicalAddress == (Address & (~(UINTN)EFI_PAGE_MASK))) { | |
| // | |
| // Convert the physical address to a virtual address and return the virtual address | |
| // | |
| return (Address & EFI_PAGE_MASK) + mDxeRuntimePciSegmentLibRegistrationTable[mDxeRuntimePciSegmentLibLastRuntimeRange].VirtualAddress; | |
| } | |
| // | |
| // Search the entire table for a physical address match | |
| // | |
| for (Index = 0; Index < mDxeRuntimePciSegmentLibNumberOfRuntimeRanges; Index++) { | |
| if (mDxeRuntimePciSegmentLibRegistrationTable[Index].PhysicalAddress == (Address & (~(UINTN)EFI_PAGE_MASK))) { | |
| // | |
| // Cache the matching index value | |
| // | |
| mDxeRuntimePciSegmentLibLastRuntimeRange = Index; | |
| // | |
| // Convert the physical address to a virtual address and return the virtual address | |
| // | |
| return (Address & EFI_PAGE_MASK) + mDxeRuntimePciSegmentLibRegistrationTable[Index].VirtualAddress; | |
| } | |
| } | |
| // | |
| // No match was found. This is a critical error at OS runtime, so ASSERT() and force a breakpoint. | |
| // | |
| ASSERT (FALSE); | |
| CpuBreakpoint (); | |
| // | |
| // Return the physical address | |
| // | |
| return Address; | |
| } |