| /** @file | |
| PCI Host Bridge Library instance for pci-ecam-generic DT nodes | |
| Copyright (c) 2016, Linaro Ltd. All rights reserved.<BR> | |
| This program and the accompanying materials are licensed and made available | |
| under the terms and conditions of the BSD License which accompanies this | |
| distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php. | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR | |
| IMPLIED. | |
| **/ | |
| #include <PiDxe.h> | |
| #include <Library/PciHostBridgeLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/DevicePathLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PcdLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Protocol/FdtClient.h> | |
| #include <Protocol/PciRootBridgeIo.h> | |
| #include <Protocol/PciHostBridgeResourceAllocation.h> | |
| #pragma pack(1) | |
| typedef struct { | |
| ACPI_HID_DEVICE_PATH AcpiDevicePath; | |
| EFI_DEVICE_PATH_PROTOCOL EndDevicePath; | |
| } EFI_PCI_ROOT_BRIDGE_DEVICE_PATH; | |
| #pragma pack () | |
| STATIC EFI_PCI_ROOT_BRIDGE_DEVICE_PATH mEfiPciRootBridgeDevicePath = { | |
| { | |
| { | |
| ACPI_DEVICE_PATH, | |
| ACPI_DP, | |
| { | |
| (UINT8) (sizeof(ACPI_HID_DEVICE_PATH)), | |
| (UINT8) ((sizeof(ACPI_HID_DEVICE_PATH)) >> 8) | |
| } | |
| }, | |
| EISA_PNP_ID(0x0A03), | |
| 0 | |
| }, | |
| { | |
| END_DEVICE_PATH_TYPE, | |
| END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
| { | |
| END_DEVICE_PATH_LENGTH, | |
| 0 | |
| } | |
| } | |
| }; | |
| GLOBAL_REMOVE_IF_UNREFERENCED | |
| CHAR16 *mPciHostBridgeLibAcpiAddressSpaceTypeStr[] = { | |
| L"Mem", L"I/O", L"Bus" | |
| }; | |
| // | |
| // We expect the "ranges" property of "pci-host-ecam-generic" to consist of | |
| // records like this. | |
| // | |
| #pragma pack (1) | |
| typedef struct { | |
| UINT32 Type; | |
| UINT64 ChildBase; | |
| UINT64 CpuBase; | |
| UINT64 Size; | |
| } DTB_PCI_HOST_RANGE_RECORD; | |
| #pragma pack () | |
| #define DTB_PCI_HOST_RANGE_RELOCATABLE BIT31 | |
| #define DTB_PCI_HOST_RANGE_PREFETCHABLE BIT30 | |
| #define DTB_PCI_HOST_RANGE_ALIASED BIT29 | |
| #define DTB_PCI_HOST_RANGE_MMIO32 BIT25 | |
| #define DTB_PCI_HOST_RANGE_MMIO64 (BIT25 | BIT24) | |
| #define DTB_PCI_HOST_RANGE_IO BIT24 | |
| #define DTB_PCI_HOST_RANGE_TYPEMASK (BIT31 | BIT30 | BIT29 | BIT25 | BIT24) | |
| STATIC | |
| EFI_STATUS | |
| ProcessPciHost ( | |
| OUT UINT64 *IoBase, | |
| OUT UINT64 *IoSize, | |
| OUT UINT64 *Mmio32Base, | |
| OUT UINT64 *Mmio32Size, | |
| OUT UINT64 *Mmio64Base, | |
| OUT UINT64 *Mmio64Size, | |
| OUT UINT32 *BusMin, | |
| OUT UINT32 *BusMax | |
| ) | |
| { | |
| FDT_CLIENT_PROTOCOL *FdtClient; | |
| INT32 Node; | |
| UINT64 ConfigBase, ConfigSize; | |
| CONST VOID *Prop; | |
| UINT32 Len; | |
| UINT32 RecordIdx; | |
| EFI_STATUS Status; | |
| UINT64 IoTranslation; | |
| UINT64 Mmio32Translation; | |
| UINT64 Mmio64Translation; | |
| // | |
| // The following output arguments are initialized only in | |
| // order to suppress '-Werror=maybe-uninitialized' warnings | |
| // *incorrectly* emitted by some gcc versions. | |
| // | |
| *IoBase = 0; | |
| *Mmio32Base = 0; | |
| *Mmio64Base = MAX_UINT64; | |
| *BusMin = 0; | |
| *BusMax = 0; | |
| // | |
| // *IoSize, *Mmio##Size and IoTranslation are initialized to zero because the | |
| // logic below requires it. However, since they are also affected by the issue | |
| // reported above, they are initialized early. | |
| // | |
| *IoSize = 0; | |
| *Mmio32Size = 0; | |
| *Mmio64Size = 0; | |
| IoTranslation = 0; | |
| Status = gBS->LocateProtocol (&gFdtClientProtocolGuid, NULL, | |
| (VOID **)&FdtClient); | |
| ASSERT_EFI_ERROR (Status); | |
| Status = FdtClient->FindCompatibleNode (FdtClient, "pci-host-ecam-generic", | |
| &Node); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_INFO, | |
| "%a: No 'pci-host-ecam-generic' compatible DT node found\n", | |
| __FUNCTION__)); | |
| return EFI_NOT_FOUND; | |
| } | |
| DEBUG_CODE ( | |
| INT32 Tmp; | |
| // | |
| // A DT can legally describe multiple PCI host bridges, but we are not | |
| // equipped to deal with that. So assert that there is only one. | |
| // | |
| Status = FdtClient->FindNextCompatibleNode (FdtClient, | |
| "pci-host-ecam-generic", Node, &Tmp); | |
| ASSERT (Status == EFI_NOT_FOUND); | |
| ); | |
| Status = FdtClient->GetNodeProperty (FdtClient, Node, "reg", &Prop, &Len); | |
| if (EFI_ERROR (Status) || Len != 2 * sizeof (UINT64)) { | |
| DEBUG ((EFI_D_ERROR, "%a: 'reg' property not found or invalid\n", | |
| __FUNCTION__)); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| // | |
| // Fetch the ECAM window. | |
| // | |
| ConfigBase = SwapBytes64 (((CONST UINT64 *)Prop)[0]); | |
| ConfigSize = SwapBytes64 (((CONST UINT64 *)Prop)[1]); | |
| // | |
| // Fetch the bus range (note: inclusive). | |
| // | |
| Status = FdtClient->GetNodeProperty (FdtClient, Node, "bus-range", &Prop, | |
| &Len); | |
| if (EFI_ERROR (Status) || Len != 2 * sizeof (UINT32)) { | |
| DEBUG ((EFI_D_ERROR, "%a: 'bus-range' not found or invalid\n", | |
| __FUNCTION__)); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| *BusMin = SwapBytes32 (((CONST UINT32 *)Prop)[0]); | |
| *BusMax = SwapBytes32 (((CONST UINT32 *)Prop)[1]); | |
| // | |
| // Sanity check: the config space must accommodate all 4K register bytes of | |
| // all 8 functions of all 32 devices of all buses. | |
| // | |
| if (*BusMax < *BusMin || *BusMax - *BusMin == MAX_UINT32 || | |
| DivU64x32 (ConfigSize, SIZE_4KB * 8 * 32) < *BusMax - *BusMin + 1) { | |
| DEBUG ((EFI_D_ERROR, "%a: invalid 'bus-range' and/or 'reg'\n", | |
| __FUNCTION__)); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| // | |
| // Iterate over "ranges". | |
| // | |
| Status = FdtClient->GetNodeProperty (FdtClient, Node, "ranges", &Prop, &Len); | |
| if (EFI_ERROR (Status) || Len == 0 || | |
| Len % sizeof (DTB_PCI_HOST_RANGE_RECORD) != 0) { | |
| DEBUG ((EFI_D_ERROR, "%a: 'ranges' not found or invalid\n", __FUNCTION__)); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| for (RecordIdx = 0; RecordIdx < Len / sizeof (DTB_PCI_HOST_RANGE_RECORD); | |
| ++RecordIdx) { | |
| CONST DTB_PCI_HOST_RANGE_RECORD *Record; | |
| Record = (CONST DTB_PCI_HOST_RANGE_RECORD *)Prop + RecordIdx; | |
| switch (SwapBytes32 (Record->Type) & DTB_PCI_HOST_RANGE_TYPEMASK) { | |
| case DTB_PCI_HOST_RANGE_IO: | |
| *IoBase = SwapBytes64 (Record->ChildBase); | |
| *IoSize = SwapBytes64 (Record->Size); | |
| IoTranslation = SwapBytes64 (Record->CpuBase) - *IoBase; | |
| ASSERT (PcdGet64 (PcdPciIoTranslation) == IoTranslation); | |
| break; | |
| case DTB_PCI_HOST_RANGE_MMIO32: | |
| *Mmio32Base = SwapBytes64 (Record->ChildBase); | |
| *Mmio32Size = SwapBytes64 (Record->Size); | |
| Mmio32Translation = SwapBytes64 (Record->CpuBase) - *Mmio32Base; | |
| if (*Mmio32Base > MAX_UINT32 || *Mmio32Size > MAX_UINT32 || | |
| *Mmio32Base + *Mmio32Size > SIZE_4GB) { | |
| DEBUG ((EFI_D_ERROR, "%a: MMIO32 space invalid\n", __FUNCTION__)); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| ASSERT (PcdGet64 (PcdPciMmio32Translation) == Mmio32Translation); | |
| if (Mmio32Translation != 0) { | |
| DEBUG ((EFI_D_ERROR, "%a: unsupported nonzero MMIO32 translation " | |
| "0x%Lx\n", __FUNCTION__, Mmio32Translation)); | |
| return EFI_UNSUPPORTED; | |
| } | |
| break; | |
| case DTB_PCI_HOST_RANGE_MMIO64: | |
| *Mmio64Base = SwapBytes64 (Record->ChildBase); | |
| *Mmio64Size = SwapBytes64 (Record->Size); | |
| Mmio64Translation = SwapBytes64 (Record->CpuBase) - *Mmio64Base; | |
| ASSERT (PcdGet64 (PcdPciMmio64Translation) == Mmio64Translation); | |
| if (Mmio64Translation != 0) { | |
| DEBUG ((EFI_D_ERROR, "%a: unsupported nonzero MMIO64 translation " | |
| "0x%Lx\n", __FUNCTION__, Mmio64Translation)); | |
| return EFI_UNSUPPORTED; | |
| } | |
| break; | |
| } | |
| } | |
| if (*IoSize == 0 || *Mmio32Size == 0) { | |
| DEBUG ((EFI_D_ERROR, "%a: %a space empty\n", __FUNCTION__, | |
| (*IoSize == 0) ? "IO" : "MMIO32")); | |
| return EFI_PROTOCOL_ERROR; | |
| } | |
| // | |
| // The dynamic PCD PcdPciExpressBaseAddress should have already been set, | |
| // and should match the value we found in the DT node. | |
| // | |
| ASSERT (PcdGet64 (PcdPciExpressBaseAddress) == ConfigBase); | |
| DEBUG ((EFI_D_INFO, "%a: Config[0x%Lx+0x%Lx) Bus[0x%x..0x%x] " | |
| "Io[0x%Lx+0x%Lx)@0x%Lx Mem32[0x%Lx+0x%Lx)@0x0 Mem64[0x%Lx+0x%Lx)@0x0\n", | |
| __FUNCTION__, ConfigBase, ConfigSize, *BusMin, *BusMax, *IoBase, *IoSize, | |
| IoTranslation, *Mmio32Base, *Mmio32Size, *Mmio64Base, *Mmio64Size)); | |
| return EFI_SUCCESS; | |
| } | |
| STATIC PCI_ROOT_BRIDGE mRootBridge; | |
| /** | |
| Return all the root bridge instances in an array. | |
| @param Count Return the count of root bridge instances. | |
| @return All the root bridge instances in an array. | |
| The array should be passed into PciHostBridgeFreeRootBridges() | |
| when it's not used. | |
| **/ | |
| PCI_ROOT_BRIDGE * | |
| EFIAPI | |
| PciHostBridgeGetRootBridges ( | |
| UINTN *Count | |
| ) | |
| { | |
| UINT64 IoBase, IoSize; | |
| UINT64 Mmio32Base, Mmio32Size; | |
| UINT64 Mmio64Base, Mmio64Size; | |
| UINT32 BusMin, BusMax; | |
| EFI_STATUS Status; | |
| if (PcdGet64 (PcdPciExpressBaseAddress) == 0) { | |
| DEBUG ((EFI_D_INFO, "%a: PCI host bridge not present\n", __FUNCTION__)); | |
| *Count = 0; | |
| return NULL; | |
| } | |
| Status = ProcessPciHost (&IoBase, &IoSize, &Mmio32Base, &Mmio32Size, | |
| &Mmio64Base, &Mmio64Size, &BusMin, &BusMax); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "%a: failed to discover PCI host bridge: %r\n", | |
| __FUNCTION__, Status)); | |
| *Count = 0; | |
| return NULL; | |
| } | |
| *Count = 1; | |
| mRootBridge.Segment = 0; | |
| mRootBridge.Supports = EFI_PCI_ATTRIBUTE_ISA_IO_16 | | |
| EFI_PCI_ATTRIBUTE_ISA_MOTHERBOARD_IO | | |
| EFI_PCI_ATTRIBUTE_VGA_IO_16 | | |
| EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO_16; | |
| mRootBridge.Attributes = mRootBridge.Supports; | |
| mRootBridge.DmaAbove4G = TRUE; | |
| mRootBridge.NoExtendedConfigSpace = FALSE; | |
| mRootBridge.ResourceAssigned = FALSE; | |
| mRootBridge.AllocationAttributes = EFI_PCI_HOST_BRIDGE_COMBINE_MEM_PMEM; | |
| mRootBridge.Bus.Base = BusMin; | |
| mRootBridge.Bus.Limit = BusMax; | |
| mRootBridge.Io.Base = IoBase; | |
| mRootBridge.Io.Limit = IoBase + IoSize - 1; | |
| mRootBridge.Mem.Base = Mmio32Base; | |
| mRootBridge.Mem.Limit = Mmio32Base + Mmio32Size - 1; | |
| if (sizeof (UINTN) == sizeof (UINT64)) { | |
| mRootBridge.MemAbove4G.Base = Mmio64Base; | |
| mRootBridge.MemAbove4G.Limit = Mmio64Base + Mmio64Size - 1; | |
| if (Mmio64Size > 0) { | |
| mRootBridge.AllocationAttributes |= EFI_PCI_HOST_BRIDGE_MEM64_DECODE; | |
| } | |
| } else { | |
| // | |
| // UEFI mandates a 1:1 virtual-to-physical mapping, so on a 32-bit | |
| // architecture such as ARM, we will not be able to access 64-bit MMIO | |
| // BARs unless they are allocated below 4 GB. So ignore the range above | |
| // 4 GB in this case. | |
| // | |
| mRootBridge.MemAbove4G.Base = MAX_UINT64; | |
| mRootBridge.MemAbove4G.Limit = 0; | |
| } | |
| // | |
| // No separate ranges for prefetchable and non-prefetchable BARs | |
| // | |
| mRootBridge.PMem.Base = MAX_UINT64; | |
| mRootBridge.PMem.Limit = 0; | |
| mRootBridge.PMemAbove4G.Base = MAX_UINT64; | |
| mRootBridge.PMemAbove4G.Limit = 0; | |
| mRootBridge.DevicePath = (EFI_DEVICE_PATH_PROTOCOL *)&mEfiPciRootBridgeDevicePath; | |
| return &mRootBridge; | |
| } | |
| /** | |
| Free the root bridge instances array returned from | |
| PciHostBridgeGetRootBridges(). | |
| @param Bridges The root bridge instances array. | |
| @param Count The count of the array. | |
| **/ | |
| VOID | |
| EFIAPI | |
| PciHostBridgeFreeRootBridges ( | |
| PCI_ROOT_BRIDGE *Bridges, | |
| UINTN Count | |
| ) | |
| { | |
| ASSERT (Count == 1); | |
| } | |
| /** | |
| Inform the platform that the resource conflict happens. | |
| @param HostBridgeHandle Handle of the Host Bridge. | |
| @param Configuration Pointer to PCI I/O and PCI memory resource | |
| descriptors. The Configuration contains the resources | |
| for all the root bridges. The resource for each root | |
| bridge is terminated with END descriptor and an | |
| additional END is appended indicating the end of the | |
| entire resources. The resource descriptor field | |
| values follow the description in | |
| EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL | |
| .SubmitResources(). | |
| **/ | |
| VOID | |
| EFIAPI | |
| PciHostBridgeResourceConflict ( | |
| EFI_HANDLE HostBridgeHandle, | |
| VOID *Configuration | |
| ) | |
| { | |
| EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Descriptor; | |
| UINTN RootBridgeIndex; | |
| DEBUG ((EFI_D_ERROR, "PciHostBridge: Resource conflict happens!\n")); | |
| RootBridgeIndex = 0; | |
| Descriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *) Configuration; | |
| while (Descriptor->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR) { | |
| DEBUG ((EFI_D_ERROR, "RootBridge[%d]:\n", RootBridgeIndex++)); | |
| for (; Descriptor->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR; Descriptor++) { | |
| ASSERT (Descriptor->ResType < | |
| (sizeof (mPciHostBridgeLibAcpiAddressSpaceTypeStr) / | |
| sizeof (mPciHostBridgeLibAcpiAddressSpaceTypeStr[0]) | |
| ) | |
| ); | |
| DEBUG ((EFI_D_ERROR, " %s: Length/Alignment = 0x%lx / 0x%lx\n", | |
| mPciHostBridgeLibAcpiAddressSpaceTypeStr[Descriptor->ResType], | |
| Descriptor->AddrLen, Descriptor->AddrRangeMax | |
| )); | |
| if (Descriptor->ResType == ACPI_ADDRESS_SPACE_TYPE_MEM) { | |
| DEBUG ((EFI_D_ERROR, " Granularity/SpecificFlag = %ld / %02x%s\n", | |
| Descriptor->AddrSpaceGranularity, Descriptor->SpecificFlag, | |
| ((Descriptor->SpecificFlag & | |
| EFI_ACPI_MEMORY_RESOURCE_SPECIFIC_FLAG_CACHEABLE_PREFETCHABLE | |
| ) != 0) ? L" (Prefetchable)" : L"" | |
| )); | |
| } | |
| } | |
| // | |
| // Skip the END descriptor for root bridge | |
| // | |
| ASSERT (Descriptor->Desc == ACPI_END_TAG_DESCRIPTOR); | |
| Descriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *)( | |
| (EFI_ACPI_END_TAG_DESCRIPTOR *)Descriptor + 1 | |
| ); | |
| } | |
| } |