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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
|
In this lesson we try to show all PCI devices available in a system.
For this task we'll need to utilize `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL` from the UEFI specification. This protocol is installed to every PCI Root bridge in the system.
It provides various functions to access PCI devices under this root bridge. For example with its help it is possible to read PCI device memory, I/O and configuration spaces for every PCI device:
You can look at a protocol structure to get a hint on what it can do:
```
typedef struct _EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL {
EFI_HANDLE ParentHandle;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollMem;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollIo;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Mem;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Io;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Pci;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_COPY_MEM CopyMem;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_MAP Map;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_UNMAP Unmap;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FREE_BUFFER FreeBuffer;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FLUSH Flush;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GET_ATTRIBUTES GetAttributes;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_SET_ATTRIBUTES SetAttributes;
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_CONFIGURATION Configuration;
UINT32 SegmentNumber;
} EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL;
```
As in the system can be many PCI root bridges and therefore many `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOLs`, we need to use `LocateHandleBuffer` to get all handles that have this protocol and then loop through these handles using `OpenProtocol` on every one of them.
```
EFI_BOOT_SERVICES.LocateHandleBuffer()
Summary:
Returns an array of handles that support the requested protocol in a buffer allocated from pool.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_HANDLE_BUFFER) (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
OUT UINTN *NoHandles,
OUT EFI_HANDLE **Buffer
);
Parameters:
SearchType Specifies which handle(s) are to be returned.
Protocol Provides the protocol to search by. This parameter is only valid for a SearchType of ByProtocol.
SearchKey Supplies the search key depending on the SearchType.
NoHandles The number of handles returned in Buffer.
Buffer A pointer to the buffer to return the requested array of handles that support Protocol.
This buffer is allocated with a call to the Boot Service EFI_BOOT_SERVICES.AllocatePool().
It is the caller's responsibility to call the Boot Service EFI_BOOT_SERVICES.FreePool() when the caller no longer
requires the contents of Buffer.
Description:
The LocateHandleBuffer() function returns one or more handles that match the SearchType request. Buffer is allocated from pool, and the number of entries in Buffer is returned in NoHandles. Each
SearchType is described below:
AllHandles Protocol and SearchKey are ignored and the function returns an array of every handle in the system.
ByRegisterNotify SearchKey supplies the Registration returned by EFI_BOOT_SERVICES.RegisterProtocolNotify().
The function returns the next handle that is new for the Registration.
Only one handle is returned at a time, and the caller must loop until
no more handles are returned. Protocol is ignored for this search type.
ByProtocol All handles that support Protocol are returned. SearchKey is ignored for this search type.
```
```
EFI_STATUS Status;
UINTN HandleCount;
EFI_HANDLE *HandleBuffer;
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiPciRootBridgeIoProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (EFI_ERROR (Status)) {
Print(L"Can't locate EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL: %r\n", Status);
return Status;
}
Print(L"Number of PCI root bridges in the system: %d\n", HandleCount);
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo;
for (UINTN Index = 0; Index < HandleCount; Index++) {
...
}
FreePool(HandleBuffer);
```
Don't forget to include `<Protocol/PciRootBridgeIo.h>` for the `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL` and `<Library/MemoryAllocationLib.h>` for the `FreePool`. And offcourse protocol should be included in the app `*.inf` file:
```
[Protocols]
gEfiPciRootBridgeIoProtocolGuid
```
To get a protocol for particaular handle you can use `OpenProtocol` function:
```
EFI_BOOT_SERVICES.OpenProtocol()
Summary:
Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the
handle, it opens the protocol on behalf of the calling agent. This is an extended version of the EFI boot
service EFI_BOOT_SERVICES.HandleProtocol().
Prototype
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface OPTIONAL,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle,
IN UINT32 Attributes
);
Parameters:
Handle The handle for the protocol interface that is being opened.
Protocol The published unique identifier of the protocol.
Interface Supplies the address where a pointer to the corresponding Protocol Interface is returned. NULL will be returned in *Interface if a
structure is not associated with Protocol. This parameter is optional, and will be ignored if Attributes is EFI_OPEN_PROTOCOL_TEST_PROTOCOL.
AgentHandle The handle of the agent that is opening the protocol interface specified by Protocol and Interface. For agents that follow the UEFI
Driver Model, this parameter is the handle that contains the EFI_DRIVER_BINDING_PROTOCOL instance that is produced by
the UEFI driver that is opening the protocol interface. For UEFI applications, this is the image handle of the UEFI application that is
opening the protocol interface. For applications that use HandleProtocol() to open a protocol interface, this parameter is
the image handle of the EFI firmware.
ControllerHandle If the agent that is opening a protocol is a driver that follows the
UEFI Driver Model, then this parameter is the controller handle that
requires the protocol interface. If the agent does not follow the UEFI
Driver Model, then this parameter is optional and may be NULL.
Attributes The open mode of the protocol interface specified by Handle and
Protocol.
Description:
This function opens a protocol interface on the handle specified by Handle for the protocol specified by Protocol.
The first three parameters are the same as EFI_BOOT_SERVICES.HandleProtocol(). The only difference is that the agent that is opening a protocol interface is tracked in an EFI's internal handle
database
```
There are various `Attributes` (last parameter):
```
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
```
We will need `EFI_OPEN_PROTOCOL_GET_PROTOCOL`:
```
GET_PROTOCOL - Used by a driver to get a protocol interface from a handle
```
You can read more about other values in the UEFI specification.
Use `OpenProtocol` call in our loop, and call our custom `EFI_STATUS PrintRootBridge(EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo)` function for every found protocol:
```
for (UINTN Index = 0; Index < HandleCount; Index++) {
Status = gBS->OpenProtocol (
HandleBuffer[Index],
&gEfiPciRootBridgeIoProtocolGuid,
(VOID **)&PciRootBridgeIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR(Status)) {
Print(L"Can't open protocol: %r\n", Status);
return Status;
}
Print(L"\nPCI Root Bridge %d\n", Index);
Status = PrintRootBridge(PciRootBridgeIo);
if (EFI_ERROR(Status)) {
Print(L"Error in PCI Root Bridge printing\n");
}
}
```
Now let's write this `PrintRootBridge` function.
First we need to get all available buses for the PCI Root Bridge. To do this we can use `Configuration()` function from the `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL`:
```
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Configuration()
Summary:
Retrieves the current resource settings of this PCI root bridge in the form of a set of ACPI resource descriptors.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_CONFIGURATION) (
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
OUT VOID **Resources
);
Parameters:
This A pointer to the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
Resources A pointer to the resource descriptors that describe the current configuration of this PCI root bridge.
The storage for the resource descriptors is allocated by this function. The caller must treat the return
buffer as read-only data, and the buffer must not be freed by the caller.
Description:
The Configuration() function retrieves a set of resource descriptors that contains the current
configuration of this PCI root bridge.
```
Also here is important information about `ACPI resource descriptors` - the data that we would get from excuting this function:
```
There are only two resource descriptor types from the ACPI Specification that may be used to describe
the current resources allocated to a PCI root bridge. These are the QWORD Address Space Descriptor,
and the End Tag. The QWORD Address Space Descriptor can describe memory, I/O, and bus number
ranges for dynamic or fixed resources. The configuration of a PCI root bridge is described with one or
more QWORD Address Space Descriptors followed by an End Tag
```
So we need to check ACPI specification about 2 types of ACPI resource descriptors:
- QWORD Address Space Descriptor
- End Tag Descriptor
The QWORD address space descriptor is defined here in ACPI specification https://uefi.org/specs/ACPI/6.4/06_Device_Configuration/Device_Configuration.html?#qword-address-space-descriptor
In edk2 its structure is placed in a file https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi10.h
```
///
/// The common definition of QWORD, DWORD, and WORD
/// Address Space Descriptors.
///
typedef PACKED struct {
UINT8 Desc;
UINT16 Len;
UINT8 ResType;
UINT8 GenFlag;
UINT8 SpecificFlag;
UINT64 AddrSpaceGranularity;
UINT64 AddrRangeMin;
UINT64 AddrRangeMax;
UINT64 AddrTranslationOffset;
UINT64 AddrLen;
} EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR;
```
The end tag descriptor is defined in ACPI spec under https://uefi.org/specs/ACPI/6.4/06_Device_Configuration/Device_Configuration.html#end-tag
Define for it in edk2 is here https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi10.h:
```
#define ACPI_END_TAG_DESCRIPTOR 0x79
```
So after we get an array of `EFI_ACPI_ADDRESS_SPACE_DESCRIPTORs` from our `PciRootBridgeIo->Configuration` call, we need to loop through it until we would encounter descriptor `ACPI_END_TAG_DESCRIPTOR`.
QWORD address space descriptor can have one of the several resource types:
```
//
// Resource Type
//
#define ACPI_ADDRESS_SPACE_TYPE_MEM 0x00
#define ACPI_ADDRESS_SPACE_TYPE_IO 0x01
#define ACPI_ADDRESS_SPACE_TYPE_BUS 0x02
```
Right now we are interested in `ACPI_ADDRESS_SPACE_TYPE_BUS` type. We need to know, how many PCI buses has this PCI root bridge.
So the code for our function would look like this:
```
EFI_STATUS PrintRootBridge(EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo)
{
EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR* AddressDescriptor;
EFI_STATUS Status = PciRootBridgeIo->Configuration(
PciRootBridgeIo,
(VOID**)&AddressDescriptor
);
if (EFI_ERROR(Status)) {
Print(L"\tError! Can't get EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR: %r\n", Status);
return Status;
}
while (AddressDescriptor->Desc != ACPI_END_TAG_DESCRIPTOR) {
if (AddressDescriptor->ResType == ACPI_ADDRESS_SPACE_TYPE_BUS) {
...
}
AddressDescriptor++;
}
}
return Status;
```
When we know all available buses for the PCI root bridge we can try to read PCI configuration space for its devices with a help of `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read()` function:
```
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read()
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Write()
Summary:
Enables a PCI driver to access PCI controller registers in a PCI root bridge’s configuration space.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM) (
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH Width,
IN UINT64 Address,
IN UINTN Count,
IN OUT VOID *Buffer
);
Parameters:
This A pointer to the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
Width Signifies the width of the memory operations.
Address The address within the PCI configuration space for the PCI controller.
Count The number of PCI configuration operations to perform. Bytes moved is Width size * Count, starting at Address.
Buffer For read operations, the destination buffer to store the results.
For write operations, the source buffer to write data from.
Description:
The Pci.Read() and Pci.Write() functions enable a driver to access PCI configuration registers for a
PCI controller.
All the PCI transactions generated by this function are guaranteed to be completed before this function
returns.
```
The address in this function is defined as follows:
![PCI_Configuration_Address](PCI_Configuration_Address.png?raw=true "PCI_Configuration_Address")
So we write a simple function to create an Address variable from the Bus/Device/Function/Register value:
```
UINT64 PciConfigurationAddress(UINT8 Bus,
UINT8 Device,
UINT8 Function,
UINT32 Register)
{
UINT64 Address = (((UINT64)Bus) << 24) + (((UINT64)Device) << 16) + (((UINT64)Function) << 8);
if (Register & 0xFFFFFF00) {
Address += (((UINT64)Register) << 32);
} else {
Address += (((UINT64)Register) << 0);
}
return Address;
}
```
Let's try to loop through all possible PCI functions and for every one of them read its header from PCI cofiguration space.
Maximum values for PCI bus, device and function are determined by PCI specification.
In edk2 they are defined in github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci22.h:
```
#define PCI_MAX_BUS 255
#define PCI_MAX_DEVICE 31
#define PCI_MAX_FUNC 7
```
As with ACPI newer PCI specifications include the older ones:
```
Pci.h > PciExpress50.h > PciExpress40.h > PciExpress31.h > PciExpress30.h > PciExpress21.h > Pci30.h > Pci23.h > Pci22.h
```
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress50.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress40.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress31.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress30.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/PciExpress21.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci30.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci23.h
- https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Pci22.h
For every possible PCI function we would try to read its common PCI configuration space header:
```
///
/// Common header region in PCI Configuration Space
/// Section 6.1, PCI Local Bus Specification, 2.2
///
typedef struct {
UINT16 VendorId;
UINT16 DeviceId;
UINT16 Command;
UINT16 Status;
UINT8 RevisionID;
UINT8 ClassCode[3];
UINT8 CacheLineSize;
UINT8 LatencyTimer;
UINT8 HeaderType;
UINT8 BIST;
} PCI_DEVICE_INDEPENDENT_REGION;
```
After getting the data we would check if a `VendorId` field is valid. If it is not equal to `0xffff` it is an actual PCI function. In this case we would print some information about it.
Here is a code for this Bus/Device/Func loop:
```
for (UINT8 Bus = AddressDescriptor->AddrRangeMin; Bus <= AddressDescriptor->AddrRangeMax; Bus++) {
for (UINT8 Device = 0; Device <= PCI_MAX_DEVICE; Device++) {
for (UINT8 Func = 0; Func <= PCI_MAX_FUNC; Func++) {
UINT64 Address = PciConfigurationAddress(Bus, Device, Func, 0);
PCI_DEVICE_INDEPENDENT_REGION PCIConfHdr;
Status = PciRootBridgeIo->Pci.Read(
PciRootBridgeIo,
EfiPciWidthUint8,
Address,
sizeof(PCI_DEVICE_INDEPENDENT_REGION),
&PCIConfHdr
);
if (!EFI_ERROR(Status)) {
if (PCIConfHdr.VendorId != 0xffff) {
Print(L"\tBus: %02x, Dev: %02x, Func: %02x - Vendor:%04x, Device:%04x\n",
Bus,
Device,
Func,
PCIConfHdr.VendorId,
PCIConfHdr.DeviceId);
}
} else {
Print(L"\tError in PCI read: %r\n", Status);
}
}
}
}
```
If we build and execute our app under OVMF we would get:
```
FS0:\> ListPCI.efi
Number of PCI root bridges in the system: 1
PCI Root Bridge 0
Bus: 00, Dev: 00, Func: 00 - Vendor:8086, Device:1237
Bus: 00, Dev: 01, Func: 00 - Vendor:8086, Device:7000
Bus: 00, Dev: 01, Func: 01 - Vendor:8086, Device:7010
Bus: 00, Dev: 01, Func: 03 - Vendor:8086, Device:7113
Bus: 00, Dev: 02, Func: 00 - Vendor:1234, Device:1111
```
You can verify that our output is correct if you execute UEFI shell `pci` command:
```
FS0:\> pci
Seg Bus Dev Func
--- --- --- ----
00 00 00 00 ==> Bridge Device - Host/PCI bridge
Vendor 8086 Device 1237 Prog Interface 0
00 00 01 00 ==> Bridge Device - PCI/ISA bridge
Vendor 8086 Device 7000 Prog Interface 0
00 00 01 01 ==> Mass Storage Controller - IDE controller
Vendor 8086 Device 7010 Prog Interface 80
00 00 01 03 ==> Bridge Device - Other bridge type
Vendor 8086 Device 7113 Prog Interface 0
00 00 02 00 ==> Display Controller - VGA/8514 controller
Vendor 1234 Device 1111 Prog Interface 0
```
One more thing to end this lesson, you can utilize `PciLib` to access PCI Configuration Space registers. Check out its interface at https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PciLib.h
|