1 /** @file
2 Map positions of extra PCI root buses to bus numbers.
3
4 Copyright (C) 2015, Red Hat, Inc.
5
6 This program and the accompanying materials are licensed and made available
7 under the terms and conditions of the BSD License which accompanies this
8 distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
10
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
12 WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13 **/
14
15 #include <Library/DebugLib.h>
16 #include <Library/DevicePathLib.h>
17 #include <Library/MemoryAllocationLib.h>
18 #include <Library/OrderedCollectionLib.h>
19 #include <Library/UefiBootServicesTableLib.h>
20 #include <Protocol/DevicePath.h>
21 #include <Protocol/PciRootBridgeIo.h>
22
23 #include "ExtraRootBusMap.h"
24
25 //
26 // The BusNumbers field is an array with Count elements. The elements increase
27 // strictry monotonically. Zero is not an element (because the zero bus number
28 // belongs to the "main" root bus, never to an extra root bus). Offset N in the
29 // array maps the extra root bus with position (N+1) to its bus number (because
30 // the root bus with position 0 is always the main root bus, therefore we don't
31 // store it).
32 //
33 // If there are no extra root buses in the system, then Count is 0, and
34 // BusNumbers is NULL.
35 //
36 struct EXTRA_ROOT_BUS_MAP_STRUCT {
37 UINT32 *BusNumbers;
38 UINTN Count;
39 };
40
41
42 /**
43 An ORDERED_COLLECTION_USER_COMPARE function that compares root bridge
44 protocol device paths based on UID.
45
46 @param[in] UserStruct1 Pointer to the first ACPI_HID_DEVICE_PATH.
47
48 @param[in] UserStruct2 Pointer to the second ACPI_HID_DEVICE_PATH.
49
50 @retval <0 If UserStruct1 compares less than UserStruct2.
51
52 @retval 0 If UserStruct1 compares equal to UserStruct2.
53
54 @retval >0 If UserStruct1 compares greater than UserStruct2.
55 **/
56 STATIC
57 INTN
58 EFIAPI
RootBridgePathCompare(IN CONST VOID * UserStruct1,IN CONST VOID * UserStruct2)59 RootBridgePathCompare (
60 IN CONST VOID *UserStruct1,
61 IN CONST VOID *UserStruct2
62 )
63 {
64 CONST ACPI_HID_DEVICE_PATH *Acpi1;
65 CONST ACPI_HID_DEVICE_PATH *Acpi2;
66
67 Acpi1 = UserStruct1;
68 Acpi2 = UserStruct2;
69
70 return Acpi1->UID < Acpi2->UID ? -1 :
71 Acpi1->UID > Acpi2->UID ? 1 :
72 0;
73 }
74
75
76 /**
77 An ORDERED_COLLECTION_KEY_COMPARE function that compares a root bridge
78 protocol device path against a UID.
79
80 @param[in] StandaloneKey Pointer to the bare UINT32 UID.
81
82 @param[in] UserStruct Pointer to the ACPI_HID_DEVICE_PATH with the
83 embedded UINT32 UID.
84
85 @retval <0 If StandaloneKey compares less than UserStruct's key.
86
87 @retval 0 If StandaloneKey compares equal to UserStruct's key.
88
89 @retval >0 If StandaloneKey compares greater than UserStruct's key.
90 **/
91 STATIC
92 INTN
93 EFIAPI
RootBridgePathKeyCompare(IN CONST VOID * StandaloneKey,IN CONST VOID * UserStruct)94 RootBridgePathKeyCompare (
95 IN CONST VOID *StandaloneKey,
96 IN CONST VOID *UserStruct
97 )
98 {
99 CONST UINT32 *Uid;
100 CONST ACPI_HID_DEVICE_PATH *Acpi;
101
102 Uid = StandaloneKey;
103 Acpi = UserStruct;
104
105 return *Uid < Acpi->UID ? -1 :
106 *Uid > Acpi->UID ? 1 :
107 0;
108 }
109
110
111 /**
112 Create a structure that maps the relative positions of PCI root buses to bus
113 numbers.
114
115 In the "bootorder" fw_cfg file, QEMU refers to extra PCI root buses by their
116 positions, in relative root bus number order, not by their actual PCI bus
117 numbers. The ACPI HID device path nodes however that are associated with
118 PciRootBridgeIo protocol instances in the system have their UID fields set to
119 the bus numbers. Create a map that gives, for each extra PCI root bus's
120 position (ie. "serial number") its actual PCI bus number.
121
122 @param[out] ExtraRootBusMap The data structure implementing the map.
123
124 @retval EFI_SUCCESS ExtraRootBusMap has been populated.
125
126 @retval EFI_OUT_OF_RESOURCES Memory allocation failed.
127
128 @retval EFI_ALREADY_STARTED A duplicate root bus number has been found in
129 the system. (This should never happen.)
130
131 @return Error codes returned by
132 gBS->LocateHandleBuffer() and
133 gBS->HandleProtocol().
134
135 **/
136 EFI_STATUS
CreateExtraRootBusMap(OUT EXTRA_ROOT_BUS_MAP ** ExtraRootBusMap)137 CreateExtraRootBusMap (
138 OUT EXTRA_ROOT_BUS_MAP **ExtraRootBusMap
139 )
140 {
141 EFI_STATUS Status;
142 UINTN NumHandles;
143 EFI_HANDLE *Handles;
144 ORDERED_COLLECTION *Collection;
145 EXTRA_ROOT_BUS_MAP *Map;
146 UINTN Idx;
147 ORDERED_COLLECTION_ENTRY *Entry, *Entry2;
148
149 //
150 // Handles and Collection are temporary / helper variables, while in Map we
151 // build the return value.
152 //
153
154 Status = gBS->LocateHandleBuffer (ByProtocol,
155 &gEfiPciRootBridgeIoProtocolGuid, NULL /* SearchKey */,
156 &NumHandles, &Handles);
157 if (EFI_ERROR (Status)) {
158 return Status;
159 }
160
161 Collection = OrderedCollectionInit (RootBridgePathCompare,
162 RootBridgePathKeyCompare);
163 if (Collection == NULL) {
164 Status = EFI_OUT_OF_RESOURCES;
165 goto FreeHandles;
166 }
167
168 Map = AllocateZeroPool (sizeof *Map);
169 if (Map == NULL) {
170 Status = EFI_OUT_OF_RESOURCES;
171 goto FreeCollection;
172 }
173
174 //
175 // Collect the ACPI device path protocols of the root bridges.
176 //
177 for (Idx = 0; Idx < NumHandles; ++Idx) {
178 EFI_DEVICE_PATH_PROTOCOL *DevicePath;
179
180 Status = gBS->HandleProtocol (Handles[Idx], &gEfiDevicePathProtocolGuid,
181 (VOID**)&DevicePath);
182 if (EFI_ERROR (Status)) {
183 goto FreeMap;
184 }
185
186 //
187 // Examine if the device path is an ACPI HID one, and if so, if UID is
188 // nonzero (ie. the root bridge that the bus number belongs to is "extra",
189 // not the main one). In that case, link the device path into Collection.
190 //
191 if (DevicePathType (DevicePath) == ACPI_DEVICE_PATH &&
192 DevicePathSubType (DevicePath) == ACPI_DP &&
193 ((ACPI_HID_DEVICE_PATH *)DevicePath)->HID == EISA_PNP_ID(0x0A03) &&
194 ((ACPI_HID_DEVICE_PATH *)DevicePath)->UID > 0) {
195 Status = OrderedCollectionInsert (Collection, NULL, DevicePath);
196 if (EFI_ERROR (Status)) {
197 goto FreeMap;
198 }
199 ++Map->Count;
200 }
201 }
202
203 if (Map->Count > 0) {
204 //
205 // At least one extra PCI root bus exists.
206 //
207 Map->BusNumbers = AllocatePool (Map->Count * sizeof *Map->BusNumbers);
208 if (Map->BusNumbers == NULL) {
209 Status = EFI_OUT_OF_RESOURCES;
210 goto FreeMap;
211 }
212 }
213
214 //
215 // Now collect the bus numbers of the extra PCI root buses into Map.
216 //
217 Idx = 0;
218 Entry = OrderedCollectionMin (Collection);
219 while (Idx < Map->Count) {
220 ACPI_HID_DEVICE_PATH *Acpi;
221
222 ASSERT (Entry != NULL);
223 Acpi = OrderedCollectionUserStruct (Entry);
224 Map->BusNumbers[Idx] = Acpi->UID;
225 DEBUG ((EFI_D_VERBOSE,
226 "%a: extra bus position 0x%Lx maps to bus number (UID) 0x%x\n",
227 __FUNCTION__, (UINT64)(Idx + 1), Acpi->UID));
228 ++Idx;
229 Entry = OrderedCollectionNext (Entry);
230 }
231 ASSERT (Entry == NULL);
232
233 *ExtraRootBusMap = Map;
234 Status = EFI_SUCCESS;
235
236 //
237 // Fall through in order to release temporaries.
238 //
239
240 FreeMap:
241 if (EFI_ERROR (Status)) {
242 if (Map->BusNumbers != NULL) {
243 FreePool (Map->BusNumbers);
244 }
245 FreePool (Map);
246 }
247
248 FreeCollection:
249 for (Entry = OrderedCollectionMin (Collection); Entry != NULL;
250 Entry = Entry2) {
251 Entry2 = OrderedCollectionNext (Entry);
252 OrderedCollectionDelete (Collection, Entry, NULL);
253 }
254 OrderedCollectionUninit (Collection);
255
256 FreeHandles:
257 FreePool (Handles);
258
259 return Status;
260 }
261
262
263 /**
264 Release a map created with CreateExtraRootBusMap().
265
266 @param[in] ExtraRootBusMap The map to release.
267 */
268 VOID
DestroyExtraRootBusMap(IN EXTRA_ROOT_BUS_MAP * ExtraRootBusMap)269 DestroyExtraRootBusMap (
270 IN EXTRA_ROOT_BUS_MAP *ExtraRootBusMap
271 )
272 {
273 if (ExtraRootBusMap->BusNumbers != NULL) {
274 FreePool (ExtraRootBusMap->BusNumbers);
275 }
276 FreePool (ExtraRootBusMap);
277 }
278
279 /**
280 Map the position (serial number) of an extra PCI root bus to its bus number.
281
282 @param[in] ExtraRootBusMap The map created with CreateExtraRootBusMap();
283
284 @param[in] RootBusPos The extra PCI root bus position to map.
285
286 @param[out] RootBusNr The bus number belonging to the extra PCI root
287 bus identified by RootBusPos.
288
289 @retval EFI_INVALID_PARAMETER RootBusPos is zero. The zero position
290 identifies the main root bus, whose bus number
291 is always zero, and is therefore never
292 maintained in ExtraRootBusMap.
293
294 @retval EFI_NOT_FOUND RootBusPos is not found in ExtraRootBusMap.
295
296 @retval EFI_SUCCESS Mapping successful.
297 **/
298 EFI_STATUS
MapRootBusPosToBusNr(IN CONST EXTRA_ROOT_BUS_MAP * ExtraRootBusMap,IN UINT64 RootBusPos,OUT UINT32 * RootBusNr)299 MapRootBusPosToBusNr (
300 IN CONST EXTRA_ROOT_BUS_MAP *ExtraRootBusMap,
301 IN UINT64 RootBusPos,
302 OUT UINT32 *RootBusNr
303 )
304 {
305 if (RootBusPos == 0) {
306 return EFI_INVALID_PARAMETER;
307 }
308 if (RootBusPos > ExtraRootBusMap->Count) {
309 return EFI_NOT_FOUND;
310 }
311 *RootBusNr = ExtraRootBusMap->BusNumbers[RootBusPos - 1];
312 return EFI_SUCCESS;
313 }
314