aboutsummaryrefslogtreecommitdiffstats
path: root/Lesson_30/README.md
blob: 7061b5b8136d7570241dfa0ebee0622ae8cf332b (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
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