--- /dev/null
+/** @file\r
+ Map positions of extra PCI root buses to bus numbers.\r
+\r
+ Copyright (C) 2015, Red Hat, Inc.\r
+\r
+ This program and the accompanying materials are licensed and made available\r
+ under the terms and conditions of the BSD License which accompanies this\r
+ distribution. The full text of the license may be found at\r
+ http://opensource.org/licenses/bsd-license.php\r
+\r
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+ WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+**/\r
+\r
+#include <Library/DebugLib.h>\r
+#include <Library/DevicePathLib.h>\r
+#include <Library/MemoryAllocationLib.h>\r
+#include <Library/OrderedCollectionLib.h>\r
+#include <Library/UefiBootServicesTableLib.h>\r
+#include <Protocol/DevicePath.h>\r
+#include <Protocol/PciRootBridgeIo.h>\r
+\r
+#include "ExtraRootBusMap.h"\r
+\r
+//\r
+// The BusNumbers field is an array with Count elements. The elements increase\r
+// strictry monotonically. Zero is not an element (because the zero bus number\r
+// belongs to the "main" root bus, never to an extra root bus). Offset N in the\r
+// array maps the extra root bus with position (N+1) to its bus number (because\r
+// the root bus with position 0 is always the main root bus, therefore we don't\r
+// store it).\r
+//\r
+// If there are no extra root buses in the system, then Count is 0, and\r
+// BusNumbers is NULL.\r
+//\r
+struct EXTRA_ROOT_BUS_MAP_STRUCT {\r
+ UINT32 *BusNumbers;\r
+ UINTN Count;\r
+};\r
+\r
+\r
+/**\r
+ An ORDERED_COLLECTION_USER_COMPARE function that compares root bridge\r
+ protocol device paths based on UID.\r
+\r
+ @param[in] UserStruct1 Pointer to the first ACPI_HID_DEVICE_PATH.\r
+\r
+ @param[in] UserStruct2 Pointer to the second ACPI_HID_DEVICE_PATH.\r
+\r
+ @retval <0 If UserStruct1 compares less than UserStruct2.\r
+\r
+ @retval 0 If UserStruct1 compares equal to UserStruct2.\r
+\r
+ @retval >0 If UserStruct1 compares greater than UserStruct2.\r
+**/\r
+STATIC\r
+INTN\r
+EFIAPI\r
+RootBridgePathCompare (\r
+ IN CONST VOID *UserStruct1,\r
+ IN CONST VOID *UserStruct2\r
+ )\r
+{\r
+ CONST ACPI_HID_DEVICE_PATH *Acpi1;\r
+ CONST ACPI_HID_DEVICE_PATH *Acpi2;\r
+\r
+ Acpi1 = UserStruct1;\r
+ Acpi2 = UserStruct2;\r
+\r
+ return Acpi1->UID < Acpi2->UID ? -1 :\r
+ Acpi1->UID > Acpi2->UID ? 1 :\r
+ 0;\r
+}\r
+\r
+\r
+/**\r
+ An ORDERED_COLLECTION_KEY_COMPARE function that compares a root bridge\r
+ protocol device path against a UID.\r
+\r
+ @param[in] StandaloneKey Pointer to the bare UINT32 UID.\r
+\r
+ @param[in] UserStruct Pointer to the ACPI_HID_DEVICE_PATH with the\r
+ embedded UINT32 UID.\r
+\r
+ @retval <0 If StandaloneKey compares less than UserStruct's key.\r
+\r
+ @retval 0 If StandaloneKey compares equal to UserStruct's key.\r
+\r
+ @retval >0 If StandaloneKey compares greater than UserStruct's key.\r
+**/\r
+STATIC\r
+INTN\r
+EFIAPI\r
+RootBridgePathKeyCompare (\r
+ IN CONST VOID *StandaloneKey,\r
+ IN CONST VOID *UserStruct\r
+ )\r
+{\r
+ CONST UINT32 *Uid;\r
+ CONST ACPI_HID_DEVICE_PATH *Acpi;\r
+\r
+ Uid = StandaloneKey;\r
+ Acpi = UserStruct;\r
+\r
+ return *Uid < Acpi->UID ? -1 :\r
+ *Uid > Acpi->UID ? 1 :\r
+ 0;\r
+}\r
+\r
+\r
+/**\r
+ Create a structure that maps the relative positions of PCI root buses to bus\r
+ numbers.\r
+\r
+ In the "bootorder" fw_cfg file, QEMU refers to extra PCI root buses by their\r
+ positions, in relative root bus number order, not by their actual PCI bus\r
+ numbers. The ACPI HID device path nodes however that are associated with\r
+ PciRootBridgeIo protocol instances in the system have their UID fields set to\r
+ the bus numbers. Create a map that gives, for each extra PCI root bus's\r
+ position (ie. "serial number") its actual PCI bus number.\r
+\r
+ @param[out] ExtraRootBusMap The data structure implementing the map.\r
+\r
+ @retval EFI_SUCCESS ExtraRootBusMap has been populated.\r
+\r
+ @retval EFI_OUT_OF_RESOURCES Memory allocation failed.\r
+\r
+ @retval EFI_ALREADY_STARTED A duplicate root bus number has been found in\r
+ the system. (This should never happen.)\r
+\r
+ @return Error codes returned by\r
+ gBS->LocateHandleBuffer() and\r
+ gBS->HandleProtocol().\r
+\r
+**/\r
+EFI_STATUS\r
+CreateExtraRootBusMap (\r
+ OUT EXTRA_ROOT_BUS_MAP **ExtraRootBusMap\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ UINTN NumHandles;\r
+ EFI_HANDLE *Handles;\r
+ ORDERED_COLLECTION *Collection;\r
+ EXTRA_ROOT_BUS_MAP *Map;\r
+ UINTN Idx;\r
+ ORDERED_COLLECTION_ENTRY *Entry, *Entry2;\r
+\r
+ //\r
+ // Handles and Collection are temporary / helper variables, while in Map we\r
+ // build the return value.\r
+ //\r
+\r
+ Status = gBS->LocateHandleBuffer (ByProtocol,\r
+ &gEfiPciRootBridgeIoProtocolGuid, NULL /* SearchKey */,\r
+ &NumHandles, &Handles);\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+\r
+ Collection = OrderedCollectionInit (RootBridgePathCompare,\r
+ RootBridgePathKeyCompare);\r
+ if (Collection == NULL) {\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto FreeHandles;\r
+ }\r
+\r
+ Map = AllocateZeroPool (sizeof *Map);\r
+ if (Map == NULL) {\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto FreeCollection;\r
+ }\r
+\r
+ //\r
+ // Collect the ACPI device path protocols of the root bridges.\r
+ //\r
+ for (Idx = 0; Idx < NumHandles; ++Idx) {\r
+ EFI_DEVICE_PATH_PROTOCOL *DevicePath;\r
+\r
+ Status = gBS->HandleProtocol (Handles[Idx], &gEfiDevicePathProtocolGuid,\r
+ (VOID**)&DevicePath);\r
+ if (EFI_ERROR (Status)) {\r
+ goto FreeMap;\r
+ }\r
+\r
+ //\r
+ // Examine if the device path is an ACPI HID one, and if so, if UID is\r
+ // nonzero (ie. the root bridge that the bus number belongs to is "extra",\r
+ // not the main one). In that case, link the device path into Collection.\r
+ //\r
+ if (DevicePathType (DevicePath) == ACPI_DEVICE_PATH &&\r
+ DevicePathSubType (DevicePath) == ACPI_DP &&\r
+ ((ACPI_HID_DEVICE_PATH *)DevicePath)->HID == EISA_PNP_ID(0x0A03) &&\r
+ ((ACPI_HID_DEVICE_PATH *)DevicePath)->UID > 0) {\r
+ Status = OrderedCollectionInsert (Collection, NULL, DevicePath);\r
+ if (EFI_ERROR (Status)) {\r
+ goto FreeMap;\r
+ }\r
+ ++Map->Count;\r
+ }\r
+ }\r
+\r
+ if (Map->Count > 0) {\r
+ //\r
+ // At least one extra PCI root bus exists.\r
+ //\r
+ Map->BusNumbers = AllocatePool (Map->Count * sizeof *Map->BusNumbers);\r
+ if (Map->BusNumbers == NULL) {\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto FreeMap;\r
+ }\r
+ }\r
+\r
+ //\r
+ // Now collect the bus numbers of the extra PCI root buses into Map.\r
+ //\r
+ Idx = 0;\r
+ Entry = OrderedCollectionMin (Collection);\r
+ while (Idx < Map->Count) {\r
+ ACPI_HID_DEVICE_PATH *Acpi;\r
+\r
+ ASSERT (Entry != NULL);\r
+ Acpi = OrderedCollectionUserStruct (Entry);\r
+ Map->BusNumbers[Idx] = Acpi->UID;\r
+ DEBUG ((EFI_D_VERBOSE,\r
+ "%a: extra bus position 0x%Lx maps to bus number (UID) 0x%x\n",\r
+ __FUNCTION__, (UINT64)(Idx + 1), Acpi->UID));\r
+ ++Idx;\r
+ Entry = OrderedCollectionNext (Entry);\r
+ }\r
+ ASSERT (Entry == NULL);\r
+\r
+ *ExtraRootBusMap = Map;\r
+ Status = EFI_SUCCESS;\r
+\r
+ //\r
+ // Fall through in order to release temporaries.\r
+ //\r
+\r
+FreeMap:\r
+ if (EFI_ERROR (Status)) {\r
+ if (Map->BusNumbers != NULL) {\r
+ FreePool (Map->BusNumbers);\r
+ }\r
+ FreePool (Map);\r
+ }\r
+\r
+FreeCollection:\r
+ for (Entry = OrderedCollectionMin (Collection); Entry != NULL;\r
+ Entry = Entry2) {\r
+ Entry2 = OrderedCollectionNext (Entry);\r
+ OrderedCollectionDelete (Collection, Entry, NULL);\r
+ }\r
+ OrderedCollectionUninit (Collection);\r
+\r
+FreeHandles:\r
+ FreePool (Handles);\r
+\r
+ return Status;\r
+}\r
+\r
+\r
+/**\r
+ Release a map created with CreateExtraRootBusMap().\r
+\r
+ @param[in] ExtraRootBusMap The map to release.\r
+*/\r
+VOID\r
+DestroyExtraRootBusMap (\r
+ IN EXTRA_ROOT_BUS_MAP *ExtraRootBusMap\r
+ )\r
+{\r
+ if (ExtraRootBusMap->BusNumbers != NULL) {\r
+ FreePool (ExtraRootBusMap->BusNumbers);\r
+ }\r
+ FreePool (ExtraRootBusMap);\r
+}\r
+\r
+/**\r
+ Map the position (serial number) of an extra PCI root bus to its bus number.\r
+\r
+ @param[in] ExtraRootBusMap The map created with CreateExtraRootBusMap();\r
+\r
+ @param[in] RootBusPos The extra PCI root bus position to map.\r
+\r
+ @param[out] RootBusNr The bus number belonging to the extra PCI root\r
+ bus identified by RootBusPos.\r
+\r
+ @retval EFI_INVALID_PARAMETER RootBusPos is zero. The zero position\r
+ identifies the main root bus, whose bus number\r
+ is always zero, and is therefore never\r
+ maintained in ExtraRootBusMap.\r
+\r
+ @retval EFI_NOT_FOUND RootBusPos is not found in ExtraRootBusMap.\r
+\r
+ @retval EFI_SUCCESS Mapping successful.\r
+**/\r
+EFI_STATUS\r
+MapRootBusPosToBusNr (\r
+ IN CONST EXTRA_ROOT_BUS_MAP *ExtraRootBusMap,\r
+ IN UINT64 RootBusPos,\r
+ OUT UINT32 *RootBusNr\r
+ )\r
+{\r
+ if (RootBusPos == 0) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ if (RootBusPos > ExtraRootBusMap->Count) {\r
+ return EFI_NOT_FOUND;\r
+ }\r
+ *RootBusNr = ExtraRootBusMap->BusNumbers[RootBusPos - 1];\r
+ return EFI_SUCCESS;\r
+}\r