/** @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;
}