diff --git a/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c new file mode 100644 index 0000000000..ec422145c4 --- /dev/null +++ b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c @@ -0,0 +1,313 @@ +/** @file + Map positions of extra PCI root buses to bus numbers. + + Copyright (C) 2015, Red Hat, Inc. + + 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 +#include +#include +#include +#include +#include +#include + +#include "ExtraRootBusMap.h" + +// +// The BusNumbers field is an array with Count elements. The elements increase +// strictry monotonically. Zero is not an element (because the zero bus number +// belongs to the "main" root bus, never to an extra root bus). Offset N in the +// array maps the extra root bus with position (N+1) to its bus number (because +// the root bus with position 0 is always the main root bus, therefore we don't +// store it). +// +// If there are no extra root buses in the system, then Count is 0, and +// BusNumbers is NULL. +// +struct EXTRA_ROOT_BUS_MAP_STRUCT { + UINT32 *BusNumbers; + UINTN Count; +}; + + +/** + An ORDERED_COLLECTION_USER_COMPARE function that compares root bridge + protocol device paths based on UID. + + @param[in] UserStruct1 Pointer to the first ACPI_HID_DEVICE_PATH. + + @param[in] UserStruct2 Pointer to the second ACPI_HID_DEVICE_PATH. + + @retval <0 If UserStruct1 compares less than UserStruct2. + + @retval 0 If UserStruct1 compares equal to UserStruct2. + + @retval >0 If UserStruct1 compares greater than UserStruct2. +**/ +STATIC +INTN +EFIAPI +RootBridgePathCompare ( + IN CONST VOID *UserStruct1, + IN CONST VOID *UserStruct2 + ) +{ + CONST ACPI_HID_DEVICE_PATH *Acpi1; + CONST ACPI_HID_DEVICE_PATH *Acpi2; + + Acpi1 = UserStruct1; + Acpi2 = UserStruct2; + + return Acpi1->UID < Acpi2->UID ? -1 : + Acpi1->UID > Acpi2->UID ? 1 : + 0; +} + + +/** + An ORDERED_COLLECTION_KEY_COMPARE function that compares a root bridge + protocol device path against a UID. + + @param[in] StandaloneKey Pointer to the bare UINT32 UID. + + @param[in] UserStruct Pointer to the ACPI_HID_DEVICE_PATH with the + embedded UINT32 UID. + + @retval <0 If StandaloneKey compares less than UserStruct's key. + + @retval 0 If StandaloneKey compares equal to UserStruct's key. + + @retval >0 If StandaloneKey compares greater than UserStruct's key. +**/ +STATIC +INTN +EFIAPI +RootBridgePathKeyCompare ( + IN CONST VOID *StandaloneKey, + IN CONST VOID *UserStruct + ) +{ + CONST UINT32 *Uid; + CONST ACPI_HID_DEVICE_PATH *Acpi; + + Uid = StandaloneKey; + Acpi = UserStruct; + + return *Uid < Acpi->UID ? -1 : + *Uid > Acpi->UID ? 1 : + 0; +} + + +/** + Create a structure that maps the relative positions of PCI root buses to bus + numbers. + + In the "bootorder" fw_cfg file, QEMU refers to extra PCI root buses by their + positions, in relative root bus number order, not by their actual PCI bus + numbers. The ACPI HID device path nodes however that are associated with + PciRootBridgeIo protocol instances in the system have their UID fields set to + the bus numbers. Create a map that gives, for each extra PCI root bus's + position (ie. "serial number") its actual PCI bus number. + + @param[out] ExtraRootBusMap The data structure implementing the map. + + @retval EFI_SUCCESS ExtraRootBusMap has been populated. + + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + + @retval EFI_ALREADY_STARTED A duplicate root bus number has been found in + the system. (This should never happen.) + + @return Error codes returned by + gBS->LocateHandleBuffer() and + gBS->HandleProtocol(). + +**/ +EFI_STATUS +CreateExtraRootBusMap ( + OUT EXTRA_ROOT_BUS_MAP **ExtraRootBusMap + ) +{ + EFI_STATUS Status; + UINTN NumHandles; + EFI_HANDLE *Handles; + ORDERED_COLLECTION *Collection; + EXTRA_ROOT_BUS_MAP *Map; + UINTN Idx; + ORDERED_COLLECTION_ENTRY *Entry, *Entry2; + + // + // Handles and Collection are temporary / helper variables, while in Map we + // build the return value. + // + + Status = gBS->LocateHandleBuffer (ByProtocol, + &gEfiPciRootBridgeIoProtocolGuid, NULL /* SearchKey */, + &NumHandles, &Handles); + if (EFI_ERROR (Status)) { + return Status; + } + + Collection = OrderedCollectionInit (RootBridgePathCompare, + RootBridgePathKeyCompare); + if (Collection == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeHandles; + } + + Map = AllocateZeroPool (sizeof *Map); + if (Map == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeCollection; + } + + // + // Collect the ACPI device path protocols of the root bridges. + // + for (Idx = 0; Idx < NumHandles; ++Idx) { + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + + Status = gBS->HandleProtocol (Handles[Idx], &gEfiDevicePathProtocolGuid, + (VOID**)&DevicePath); + if (EFI_ERROR (Status)) { + goto FreeMap; + } + + // + // Examine if the device path is an ACPI HID one, and if so, if UID is + // nonzero (ie. the root bridge that the bus number belongs to is "extra", + // not the main one). In that case, link the device path into Collection. + // + if (DevicePathType (DevicePath) == ACPI_DEVICE_PATH && + DevicePathSubType (DevicePath) == ACPI_DP && + ((ACPI_HID_DEVICE_PATH *)DevicePath)->HID == EISA_PNP_ID(0x0A03) && + ((ACPI_HID_DEVICE_PATH *)DevicePath)->UID > 0) { + Status = OrderedCollectionInsert (Collection, NULL, DevicePath); + if (EFI_ERROR (Status)) { + goto FreeMap; + } + ++Map->Count; + } + } + + if (Map->Count > 0) { + // + // At least one extra PCI root bus exists. + // + Map->BusNumbers = AllocatePool (Map->Count * sizeof *Map->BusNumbers); + if (Map->BusNumbers == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto FreeMap; + } + } + + // + // Now collect the bus numbers of the extra PCI root buses into Map. + // + Idx = 0; + Entry = OrderedCollectionMin (Collection); + while (Idx < Map->Count) { + ACPI_HID_DEVICE_PATH *Acpi; + + ASSERT (Entry != NULL); + Acpi = OrderedCollectionUserStruct (Entry); + Map->BusNumbers[Idx] = Acpi->UID; + DEBUG ((EFI_D_VERBOSE, + "%a: extra bus position 0x%Lx maps to bus number (UID) 0x%x\n", + __FUNCTION__, (UINT64)(Idx + 1), Acpi->UID)); + ++Idx; + Entry = OrderedCollectionNext (Entry); + } + ASSERT (Entry == NULL); + + *ExtraRootBusMap = Map; + Status = EFI_SUCCESS; + + // + // Fall through in order to release temporaries. + // + +FreeMap: + if (EFI_ERROR (Status)) { + if (Map->BusNumbers != NULL) { + FreePool (Map->BusNumbers); + } + FreePool (Map); + } + +FreeCollection: + for (Entry = OrderedCollectionMin (Collection); Entry != NULL; + Entry = Entry2) { + Entry2 = OrderedCollectionNext (Entry); + OrderedCollectionDelete (Collection, Entry, NULL); + } + OrderedCollectionUninit (Collection); + +FreeHandles: + FreePool (Handles); + + return Status; +} + + +/** + Release a map created with CreateExtraRootBusMap(). + + @param[in] ExtraRootBusMap The map to release. +*/ +VOID +DestroyExtraRootBusMap ( + IN EXTRA_ROOT_BUS_MAP *ExtraRootBusMap + ) +{ + if (ExtraRootBusMap->BusNumbers != NULL) { + FreePool (ExtraRootBusMap->BusNumbers); + } + FreePool (ExtraRootBusMap); +} + +/** + Map the position (serial number) of an extra PCI root bus to its bus number. + + @param[in] ExtraRootBusMap The map created with CreateExtraRootBusMap(); + + @param[in] RootBusPos The extra PCI root bus position to map. + + @param[out] RootBusNr The bus number belonging to the extra PCI root + bus identified by RootBusPos. + + @retval EFI_INVALID_PARAMETER RootBusPos is zero. The zero position + identifies the main root bus, whose bus number + is always zero, and is therefore never + maintained in ExtraRootBusMap. + + @retval EFI_NOT_FOUND RootBusPos is not found in ExtraRootBusMap. + + @retval EFI_SUCCESS Mapping successful. +**/ +EFI_STATUS +MapRootBusPosToBusNr ( + IN CONST EXTRA_ROOT_BUS_MAP *ExtraRootBusMap, + IN UINT64 RootBusPos, + OUT UINT32 *RootBusNr + ) +{ + if (RootBusPos == 0) { + return EFI_INVALID_PARAMETER; + } + if (RootBusPos > ExtraRootBusMap->Count) { + return EFI_NOT_FOUND; + } + *RootBusNr = ExtraRootBusMap->BusNumbers[RootBusPos - 1]; + return EFI_SUCCESS; +} diff --git a/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.h b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.h new file mode 100644 index 0000000000..e2dbc38ed8 --- /dev/null +++ b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.h @@ -0,0 +1,40 @@ +/** @file + Map positions of extra PCI root buses to bus numbers. + + Copyright (C) 2015, Red Hat, Inc. + + 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. +**/ + +#ifndef __EXTRA_ROOT_BUS_MAP_H__ +#define __EXTRA_ROOT_BUS_MAP_H__ + +/** + Incomplete ("opaque") data type implementing the map. +**/ +typedef struct EXTRA_ROOT_BUS_MAP_STRUCT EXTRA_ROOT_BUS_MAP; + +EFI_STATUS +CreateExtraRootBusMap ( + OUT EXTRA_ROOT_BUS_MAP **ExtraRootBusMap + ); + +VOID +DestroyExtraRootBusMap ( + IN EXTRA_ROOT_BUS_MAP *ExtraRootBusMap + ); + +EFI_STATUS +MapRootBusPosToBusNr ( + IN CONST EXTRA_ROOT_BUS_MAP *ExtraRootBusMap, + IN UINT64 RootBusPos, + OUT UINT32 *RootBusNr + ); + +#endif diff --git a/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf b/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf index e972ae9923..102432848b 100644 --- a/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf +++ b/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf @@ -32,6 +32,7 @@ [Sources] QemuBootOrderLib.c + ExtraRootBusMap.c [Packages] MdePkg/MdePkg.dec @@ -49,6 +50,7 @@ PrintLib DevicePathLib BaseMemoryLib + OrderedCollectionLib [Guids] gEfiGlobalVariableGuid @@ -60,3 +62,7 @@ [Pcd] gEfiMdePkgTokenSpaceGuid.PcdPlatformBootTimeOut + +[Protocols] + gEfiDevicePathProtocolGuid ## CONSUMES + gEfiPciRootBridgeIoProtocolGuid ## CONSUMES