/** @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.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "PciSegmentLibCommon.h" #include #include #include #include #include #include #include /// /// 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; }