summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c
blob: 25d5494047abc60aacf3d2a6d25c4a504b9727e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/** @file
  Map positions of extra PCI root buses to bus numbers.

  Copyright (C) 2015, Red Hat, Inc.

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OrderedCollectionLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/DevicePath.h>
#include <Protocol/PciRootBridgeIo.h>

#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 ((DEBUG_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[(UINTN)RootBusPos - 1];
  return EFI_SUCCESS;
}