/**@file Hardware info library with types and accessors to parse information about PCI host bridges. Copyright 2021 - 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include "HardwareInfoPciHostBridgeLib.h" #define IS_RANGE_INVALID(Start, Size, MaxValue) (Start >= MaxValue || Size == 0) /** Calculate the last (inclusive) address of a range. @param[in] Start First address of the range @param[in] Size Size of the range @return Last address of the range **/ STATIC UINT64 GetRangeEnd ( IN UINT64 Start, IN UINT64 Size, IN UINT64 MaxValue ) { if (IS_RANGE_INVALID (Start, Size, MaxValue)) { return 0; } return Start + Size - 1; } /** Internal helper to update LastAddress if the Limit address of the Mem aperture is higher than the provided value. @param[in] Mem Pointer to aperture whose limit is to be compared against accumulative last address. @param[out] LastAddress Pointer to accumulative last address to be updated if Limit is higher **/ STATIC VOID UpdateLastAddressIfHigher ( IN PCI_ROOT_BRIDGE_APERTURE *Mem, OUT UINT64 *LastAddress ) { if (Mem->Limit > *LastAddress) { *LastAddress = Mem->Limit; } } EFI_STATUS HardwareInfoPciHostBridgeLastMmioAddress ( IN CONST HOST_BRIDGE_INFO *HostBridge, IN UINTN DataSize, IN BOOLEAN HighMem, OUT UINT64 *LastMmioAddress ) { EFI_STATUS Status; PCI_ROOT_BRIDGE_APERTURE Mem; PCI_ROOT_BRIDGE_APERTURE MemAbove4G; PCI_ROOT_BRIDGE_APERTURE PMem; PCI_ROOT_BRIDGE_APERTURE PMemAbove4G; if (LastMmioAddress == NULL) { return EFI_INVALID_PARAMETER; } // // Set the output to the lowest possible value so that if some step fails // the overall outcome reflects no information found // *LastMmioAddress = 0; Status = HardwareInfoPciHostBridgeGetApertures ( HostBridge, DataSize, NULL, &Mem, &MemAbove4G, &PMem, &PMemAbove4G, NULL ); // // Forward error to caller but ignore warnings given that, very likely, // the host bridge will have a PIO aperture we are explicitly // ignoring here since we care only about MMIO resources. // if (EFI_ERROR (Status)) { return Status; } if (HighMem) { UpdateLastAddressIfHigher (&MemAbove4G, LastMmioAddress); UpdateLastAddressIfHigher (&PMemAbove4G, LastMmioAddress); } else { UpdateLastAddressIfHigher (&Mem, LastMmioAddress); UpdateLastAddressIfHigher (&PMem, LastMmioAddress); } return EFI_SUCCESS; } /** Set the boundaries of an aperture to invalid values having size zero and start MaxValue (yields Start > Limit which depicts an invalid range) @param[in] MaxValue Max value of the aperture's range (depends on the data type) @param[out] Aperture Aperture object to invalidate **/ STATIC VOID InvalidateRootBridgeAperture ( OUT PCI_ROOT_BRIDGE_APERTURE *Aperture ) { if (Aperture == NULL) { return; } Aperture->Base = MAX_UINT64; Aperture->Limit = 0; } /** Fill a PCI ROOT BRIDGE APERTURE with the proper values calculated from the provided start and size. @param[in] Start Start address of the aperture @param[in] Size Size, in bytes, of the aperture @param[in] MaxValue Max value a valid address could take and which represents an invalid start address. @param[out] Aperture Pointer to the aperture to be filled @retval EFI_SUCCESS Aperture was filled successfully @retval EFI_INVALID_PARAMETER Range depicted by Start and Size is valid but ignored because aperture pointer is NULL @retval EFI_WARN_BUFFER_TOO_SMALL Aperture pointer is invalid but the range also is so no harm. **/ STATIC EFI_STATUS FillHostBridgeAperture ( IN UINT64 Start, IN UINT64 Size, IN UINT64 MaxValue, OUT PCI_ROOT_BRIDGE_APERTURE *Aperture ) { UINT64 End; End = GetRangeEnd (Start, Size, MaxValue); if (Aperture == NULL) { if (!IS_RANGE_INVALID (Start, Size, MaxValue)) { // // Report an error to the caller since the range specified in // the host bridge's resources is non-empty but the provided // aperture pointer is null, thus the valid range is ignored. // return EFI_INVALID_PARAMETER; } return EFI_WARN_BUFFER_TOO_SMALL; } if (IS_RANGE_INVALID (Start, Size, MaxValue)) { // // Fill Aperture with invalid range values to signal the // absence of an address space (empty range) // InvalidateRootBridgeAperture (Aperture); } else { Aperture->Base = Start; Aperture->Limit = End; } return EFI_SUCCESS; } /** Merge 2 ranges (normal and prefetchable) into a single aperture comprehending the addresses encompassed by both of them. If both ranges are not empty they must be contiguous for correctness. @param[in] Start Range start address @param[in] Size Range size in bytes @param[in] PStart Prefetchable range start address @param[in] PSize Prefetchable range size in bytes @param[in] MaxValue Max value a valid address could take and which represents an invalid start address. @param[out] Aperture Pointer to the aperture to be filled @retval EFI_SUCCESS Aperture was filled successfully @retval EFI_INVALID_PARAMETER Either range depicted by Start, Size or PStart, PSize or both are valid but ignored because aperture pointer is NULL @retval EFI_WARN_BUFFER_TOO_SMALL Aperture pointer is invalid but both ranges are too so no harm. **/ STATIC EFI_STATUS MergeHostBridgeApertures ( IN UINT64 Start, IN UINT64 Size, IN UINT64 PStart, IN UINT64 PSize, IN UINT64 MaxValue, OUT PCI_ROOT_BRIDGE_APERTURE *Aperture ) { UINT64 PEnd; if (Aperture == NULL) { if (!IS_RANGE_INVALID (Start, Size, MaxValue) || !IS_RANGE_INVALID (PStart, PSize, MaxValue)) { // // Report an error to the caller since the range specified in // the host bridge's resources is non-empty but the provided // aperture pointer is null, thus the valid range is ignored. // return EFI_INVALID_PARAMETER; } return EFI_WARN_BUFFER_TOO_SMALL; } // // Start from an empty range (Limit < Base) // InvalidateRootBridgeAperture (Aperture); if (!IS_RANGE_INVALID (Start, Size, MaxValue)) { Aperture->Base = Start; Aperture->Limit = Start + Size - 1; } if (!IS_RANGE_INVALID (PStart, PSize, MaxValue)) { PEnd = PStart + PSize - 1; if (PStart < Aperture->Base) { Aperture->Base = PStart; } if (PEnd > Aperture->Limit) { Aperture->Limit = PEnd; } } return EFI_SUCCESS; } EFI_STATUS HardwareInfoPciHostBridgeGetBusNrRange ( IN CONST HOST_BRIDGE_INFO *HostBridge, IN UINTN DataSize, OUT UINTN *BusNrStart, OUT UINTN *BusNrLast ) { if ((HostBridge == NULL) || (DataSize == 0) || (BusNrStart == NULL) || (BusNrLast == NULL)) { return EFI_INVALID_PARAMETER; } // // For now only version 0 is supported // if (HostBridge->Version != 0) { return EFI_INCOMPATIBLE_VERSION; } *BusNrStart = HostBridge->BusNrStart; *BusNrLast = HostBridge->BusNrLast; return EFI_SUCCESS; } EFI_STATUS HardwareInfoPciHostBridgeGetApertures ( IN CONST HOST_BRIDGE_INFO *HostBridge, IN UINTN DataSize, OUT PCI_ROOT_BRIDGE_APERTURE *Io, OUT PCI_ROOT_BRIDGE_APERTURE *Mem, OUT PCI_ROOT_BRIDGE_APERTURE *MemAbove4G, OUT PCI_ROOT_BRIDGE_APERTURE *PMem, OUT PCI_ROOT_BRIDGE_APERTURE *PMemAbove4G, OUT PCI_ROOT_BRIDGE_APERTURE *PcieConfig ) { EFI_STATUS Status; BOOLEAN StickyError; StickyError = FALSE; if ((HostBridge == NULL) || (DataSize == 0)) { return EFI_INVALID_PARAMETER; } // // For now only version 0 is supported // if (HostBridge->Version != 0) { return EFI_INCOMPATIBLE_VERSION; } Status = FillHostBridgeAperture ( HostBridge->IoStart, HostBridge->IoSize, MAX_UINT32, Io ); StickyError |= EFI_ERROR (Status); Status = FillHostBridgeAperture ( HostBridge->PcieConfigStart, HostBridge->PcieConfigSize, MAX_UINT64, PcieConfig ); StickyError |= EFI_ERROR (Status); if (HostBridge->Flags.Bits.CombineMemPMem) { Status = MergeHostBridgeApertures ( HostBridge->MemStart, HostBridge->MemSize, HostBridge->PMemStart, HostBridge->PMemSize, MAX_UINT32, Mem ); StickyError |= EFI_ERROR (Status); Status = MergeHostBridgeApertures ( HostBridge->MemAbove4GStart, HostBridge->MemAbove4GSize, HostBridge->PMemAbove4GStart, HostBridge->PMemAbove4GSize, MAX_UINT64, MemAbove4G ); StickyError |= EFI_ERROR (Status); // // Invalidate unused apertures // InvalidateRootBridgeAperture (PMem); InvalidateRootBridgeAperture (PMemAbove4G); } else { Status = FillHostBridgeAperture ( HostBridge->MemStart, HostBridge->MemSize, MAX_UINT32, Mem ); StickyError |= EFI_ERROR (Status); Status = FillHostBridgeAperture ( HostBridge->PMemStart, HostBridge->PMemSize, MAX_UINT32, PMem ); StickyError |= EFI_ERROR (Status); Status = FillHostBridgeAperture ( HostBridge->MemAbove4GStart, HostBridge->MemAbove4GSize, MAX_UINT64, MemAbove4G ); StickyError |= EFI_ERROR (Status); Status = FillHostBridgeAperture ( HostBridge->PMemAbove4GStart, HostBridge->PMemAbove4GSize, MAX_UINT64, PMem ); StickyError |= EFI_ERROR (Status); } if (StickyError) { // // If any function returned an error it is due to a valid range // specified in the host bridge that was ignored due to a NULL // pointer. Translate it to a warning to allow for calling with // only a subset of the apertures. // return EFI_WARN_STALE_DATA; } return EFI_SUCCESS; } EFI_STATUS HardwareInfoPciHostBridgeGetFlags ( IN CONST HOST_BRIDGE_INFO *HostBridge, IN UINTN DataSize, OUT UINT64 *Attributes OPTIONAL, OUT BOOLEAN *DmaAbove4G OPTIONAL, OUT BOOLEAN *NoExtendedConfigSpace OPTIONAL, OUT BOOLEAN *CombineMemPMem OPTIONAL ) { if ((HostBridge == NULL) || (DataSize == 0)) { return EFI_INVALID_PARAMETER; } // // For now only version 0 is supported // if (HostBridge->Version != 0) { return EFI_INCOMPATIBLE_VERSION; } if (Attributes) { *Attributes = HostBridge->Attributes; } if (DmaAbove4G) { *DmaAbove4G = !!HostBridge->Flags.Bits.DmaAbove4G; } if (NoExtendedConfigSpace) { *NoExtendedConfigSpace = !!HostBridge->Flags.Bits.NoExtendedConfigSpace; } if (CombineMemPMem) { *CombineMemPMem = !!HostBridge->Flags.Bits.CombineMemPMem; } return EFI_SUCCESS; } EFI_STATUS HardwareInfoPciHostBridgeGet ( IN CONST HOST_BRIDGE_INFO *HostBridge, IN UINTN DataSize, OUT UINTN *BusNrStart, OUT UINTN *BusNrLast, OUT UINT64 *Attributes OPTIONAL, OUT BOOLEAN *DmaAbove4G OPTIONAL, OUT BOOLEAN *NoExtendedConfigSpace OPTIONAL, OUT BOOLEAN *CombineMemPMem OPTIONAL, OUT PCI_ROOT_BRIDGE_APERTURE *Io OPTIONAL, OUT PCI_ROOT_BRIDGE_APERTURE *Mem OPTIONAL, OUT PCI_ROOT_BRIDGE_APERTURE *MemAbove4G OPTIONAL, OUT PCI_ROOT_BRIDGE_APERTURE *PMem OPTIONAL, OUT PCI_ROOT_BRIDGE_APERTURE *PMemAbove4G OPTIONAL, OUT PCI_ROOT_BRIDGE_APERTURE *PcieConfig OPTIONAL ) { EFI_STATUS Status; Status = HardwareInfoPciHostBridgeGetBusNrRange ( HostBridge, DataSize, BusNrStart, BusNrLast ); if (EFI_ERROR (Status)) { return Status; } Status = HardwareInfoPciHostBridgeGetFlags ( HostBridge, DataSize, Attributes, DmaAbove4G, NoExtendedConfigSpace, CombineMemPMem ); if (EFI_ERROR (Status)) { return Status; } Status = HardwareInfoPciHostBridgeGetApertures ( HostBridge, DataSize, Io, Mem, MemAbove4G, PMem, PMemAbove4G, PcieConfig ); return Status; }