aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons/Lesson_29/README.md
blob: 1b613e73dbfd646da0ae01bba5e4338f2677e6d5 (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
In the last lesson we've discovered that our system has BGRT ACPI table.

According to the ACPI specification:
```
The Boot Graphics Resource Table (BGRT) is an optional table that provides a mechanism to indicate that
an image was drawn on the screen during boot, and some information about the image.
The table is written when the image is drawn on the screen. This should be done after it is expected that
any firmware components that may write to the screen are done doing so and it is known that the image
is the only thing on the screen. If the boot path is interrupted (e.g., by a key press), the valid bit within the
status field should be changed to 0 to indicate to the OS that the current image is invalidated
```
This table actually have a pointer to image data, check structure definition under https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi63.h:
```
///
/// Boot Graphics Resource Table definition.
///
typedef struct {
  EFI_ACPI_DESCRIPTION_HEADER Header;
  ///
  /// 2-bytes (16 bit) version ID. This value must be 1.
  ///
  UINT16                      Version;
  ///
  /// 1-byte status field indicating current status about the table.
  ///     Bits[7:1] = Reserved (must be zero)
  ///     Bit [0] = Valid. A one indicates the boot image graphic is valid.
  ///
  UINT8                       Status;
  ///
  /// 1-byte enumerated type field indicating format of the image.
  ///     0 = Bitmap
  ///     1 - 255  Reserved (for future use)
  ///
  UINT8                       ImageType;
  ///
  /// 8-byte (64 bit) physical address pointing to the firmware's in-memory copy
  /// of the image bitmap.
  ///
  UINT64                      ImageAddress;
  ///
  /// A 4-byte (32-bit) unsigned long describing the display X-offset of the boot image.
  /// (X, Y) display offset of the top left corner of the boot image.
  /// The top left corner of the display is at offset (0, 0).
  ///
  UINT32                      ImageOffsetX;
  ///
  /// A 4-byte (32-bit) unsigned long describing the display Y-offset of the boot image.
  /// (X, Y) display offset of the top left corner of the boot image.
  /// The top left corner of the display is at offset (0, 0).
  ///
  UINT32                      ImageOffsetY;
} EFI_ACPI_6_3_BOOT_GRAPHICS_RESOURCE_TABLE;
```


Let's create an app that would save an image from BGRT.

This time to get BGRT table we would utilize `EFI_ACPI_SDT_PROTOCOL` protocol.

To get ACPI table data we would use `GetAcpiTable()` function from this protocol:
```
EFI_ACPI_SDT_PROTOCOL.GetAcpiTable()

Summary:
Returns a requested ACPI table.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_ACPI_GET_ACPI_TABLE) (
 IN UINTN Index,
 OUT EFI_ACPI_SDT_HEADER **Table,
 OUT EFI_ACPI_TABLE_VERSION *Version,
 OUT UINTN *TableKey
 );

Parameters:
Index		The zero-based index of the table to retrieve.
Table		Pointer for returning the table buffer.
Version		On return, updated with the ACPI versions to which this table belongs.
TableKey	On return, points to the table key for the specified ACPI system definition table.

Description:
The GetAcpiTable() function returns a pointer to a buffer containing the ACPI table associated with the Index that was input. The following structures are not considered elements in the list of ACPI tables:
- Root System Description Pointer (RSD_PTR)
- Root System Description Table (RSDT)
- Extended System Description Table (XSDT)
```
In edk2 it is defined here: https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/AcpiSystemDescriptionTable.h

To get all tables we need to call `GetAcpiTable` with incrementing values for `Index` starting with 0, while function returns `EFI_SUCCESS`.

On every success call we would get a pointer to a common header for a ACPI table:
```
typedef struct {
 UINT32 Signature;
 UINT32 Length;
 UINT8 Revision;
 UINT8 Checksum;
 CHAR8 OemId[6];
 CHAR8 OemTableId[8];
 UINT32 OemRevision;
 UINT32 CreatorId;
 UINT32 CreatorRevision;
} EFI_ACPI_SDT_HEADER;
```

To use `EFI_ACPI_SDT_PROTOCOL` we need to add include to our file:
```
#include <Protocol/AcpiSystemDescriptionTable.h>
```
And add protocol to the *.inf file:
```
[Protocols]
  gEfiAcpiSdtProtocolGuid
```

Here is a code finding BGRT ACPI table:
```
EFI_ACPI_SDT_PROTOCOL* AcpiSdtProtocol;
EFI_STATUS Status = gBS->LocateProtocol (
                &gEfiAcpiSdtProtocolGuid,
                NULL,
                (VOID**)&AcpiSdtProtocol
                );
if (EFI_ERROR (Status)) {
  return Status;
}

BOOLEAN BGRT_found = FALSE;
UINTN Index = 0;
EFI_ACPI_SDT_HEADER* Table;
EFI_ACPI_TABLE_VERSION Version;
UINTN TableKey;
while (TRUE) {
  Status = AcpiSdtProtocol->GetAcpiTable(Index,
    &Table,
    &Version,
    &TableKey
  );
  if (EFI_ERROR(Status)) {
    break;
  }
  if (((CHAR8)((Table->Signature >>  0) & 0xFF) == 'B') &&
      ((CHAR8)((Table->Signature >>  8) & 0xFF) == 'G') &&
      ((CHAR8)((Table->Signature >> 16) & 0xFF) == 'R') &&
      ((CHAR8)((Table->Signature >> 24) & 0xFF) == 'T')) {
    BGRT_found = TRUE;
    break;
  }
  Index++;
}
if (!BGRT_found) {
  Print(L"BGRT table is not present in the system\n");
  return EFI_UNSUPPORTED;
}
```

Now we need to save an image from BGRT table.

Currently ACPI specification support only BMP image type https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#image-type

So first we check if the type is actually BMP:
```
EFI_ACPI_6_3_BOOT_GRAPHICS_RESOURCE_TABLE* BGRT = (EFI_ACPI_6_3_BOOT_GRAPHICS_RESOURCE_TABLE*)Table;
if (BGRT->ImageType == 0) {
  ...
}
```

Now we need to actually save a BMP image. BGRT doesn't contain any size for an image, only offset to data: `ImageAddress`.

To get image size we need to look at BMP header. 

In edk2 it is defined under https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Bmp.h:
```
typedef struct {
  CHAR8         CharB;
  CHAR8         CharM;
  UINT32        Size;
  UINT16        Reserved[2];
  UINT32        ImageOffset;
  UINT32        HeaderSize;
  UINT32        PixelWidth;
  UINT32        PixelHeight;
  UINT16        Planes;          ///< Must be 1
  UINT16        BitPerPixel;     ///< 1, 4, 8, or 24
  UINT32        CompressionType;
  UINT32        ImageSize;       ///< Compressed image size in bytes
  UINT32        XPixelsPerMeter;
  UINT32        YPixelsPerMeter;
  UINT32        NumberOfColors;
  UINT32        ImportantColors;
} BMP_IMAGE_HEADER;
```

Don't forget to include this file in our program:
```
#include <IndustryStandard/Bmp.h>
```

When we know that the image is BMP, we can check its signature (`BM`), parse its size and actually write its data to a file. Here we use `EFI_STATUS WriteFile(CHAR16* FileName, VOID* Data, UINTN* Size)` function to write data to a file, we will define it in a minute:
```
BMP_IMAGE_HEADER* BMP = (BMP_IMAGE_HEADER*)(BGRT->ImageAddress);

if ((BMP->CharB != 'B') || (BMP->CharM != 'M')) {
  Print(L"BMP image has wrong signature!\n");
  return EFI_UNSUPPORTED;
}
Print(L"BGRT conatins BMP image with %dx%d resolution\n", BMP->PixelWidth, BMP->PixelHeight);
UINTN Size = BMP->Size;
Status = WriteFile(L"BGRT.bmp", BMP, &Size);
if (EFI_ERROR(Status)) {
  Print(L"Error! Can't write BGRT.bmp file\n");
}
```

Last time we've used `EFI_SHELL_PROTOCOL` to create a file and write data to it. This time we will try to utilize ShelLib:

https://github.com/tianocore/edk2/blob/master/ShellPkg/Include/Library/ShellLib.h

https://github.com/tianocore/edk2/blob/master/ShellPkg/Library/UefiShellLib/UefiShellLib.c

Again we will need 3 functions: for file open, write and close:
```
/**
  This function will open a file or directory referenced by filename.
  If return is EFI_SUCCESS, the Filehandle is the opened file's handle;
  otherwise, the Filehandle is NULL. Attributes is valid only for
  EFI_FILE_MODE_CREATE.
  @param[in] FileName           The pointer to file name.
  @param[out] FileHandle        The pointer to the file handle.
  @param[in] OpenMode           The mode to open the file with.
  @param[in] Attributes         The file's file attributes.
  ...
**/

EFI_STATUS
EFIAPI
ShellOpenFileByName(
  IN CONST CHAR16               *FileName,
  OUT SHELL_FILE_HANDLE         *FileHandle,
  IN UINT64                     OpenMode,
  IN UINT64                     Attributes
  );
```
```
/**
  Write data to a file.
  This function writes the specified number of bytes to the file at the current
  file position. The current file position is advanced the actual number of bytes
  written, which is returned in BufferSize. Partial writes only occur when there
  has been a data error during the write attempt (such as "volume space full").
  The file is automatically grown to hold the data if required. Direct writes to
  opened directories are not supported.
  @param[in] FileHandle          The opened file for writing.
  @param[in, out] BufferSize     On input the number of bytes in Buffer.  On output
                                 the number of bytes written.
  @param[in] Buffer              The buffer containing data to write is stored.
  ...
**/

EFI_STATUS
EFIAPI
ShellWriteFile(
  IN SHELL_FILE_HANDLE          FileHandle,
  IN OUT UINTN                  *BufferSize,
  IN VOID                       *Buffer
  );
```
```
/**
  Close an open file handle.
  This function closes a specified file handle. All "dirty" cached file data is
  flushed to the device, and the file is closed. In all cases the handle is
  closed.
  @param[in] FileHandle           The file handle to close.
**/

EFI_STATUS
EFIAPI
ShellCloseFile (
  IN SHELL_FILE_HANDLE          *FileHandle
  );
```

Advantage of using `ShellLib` is that now we don't need to find `EFI_SHELL_PROTOCOL` and work with it manually.


Our `WriteFile` function would look like this:
```
EFI_STATUS WriteFile(CHAR16* FileName, VOID* Data, UINTN* Size)
{
  SHELL_FILE_HANDLE FileHandle;
  EFI_STATUS Status = ShellOpenFileByName(
    FileName,
    &FileHandle,
    EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
    0
  );
  if (!EFI_ERROR(Status)) {
    Print(L"Save it to %s\n", FileName);
    UINTN ToWrite = *Size;
    Status = ShellWriteFile(
      FileHandle,
      Size,
      Data
    );
    if (EFI_ERROR(Status)) {
      Print(L"Can't write file: %r\n", Status);
    }
    if (*Size != ToWrite) {
      Print(L"Error! Not all data was written\n");
    }
    Status = ShellCloseFile(
      &FileHandle
    );
    if (EFI_ERROR(Status)) {
      Print(L"Can't close file: %r\n", Status);
    }
  } else {
    Print(L"Can't open file: %r\n", Status);
  }
  return Status;
}
```

To use ShellLib we need to include a header in our program:
```
#include <Library/ShellLib.h>
```

Also we need to add `ShellPkg.dec` to our packages and add `ShellLib` to our library classes:
```
[Packages]
  MdePkg/MdePkg.dec
+  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
+  ShellLib
```
Besides that our package `*.dsc` file needs to include a `ShellLib` library class:
```
[LibraryClasses]
  ...
  ShellLib|ShellPkg/Library/UefiShellLib/UefiShellLib.inf
```

Unfortunately this is not enough, our current build would fail with a message, because `ShellLib` by itself needs another library:
```
build.py...
/home/kostr/tiano/edk2/UefiLessonsPkg/UefiLessonsPkg.dsc(...): error 4000: Instance of library class [FileHandleLib] is not found
```

To find it use our standard tactic:
```
$ grep FileHandleLib -r ./ --include=*.inf | grep LIBRARY_CLASS
```

In the end we had to add several more LibraryClasses to make our build succeed:
```
[LibraryClasses]
  ...
  FileHandleLib|MdePkg/Library/UefiFileHandleLib/UefiFileHandleLib.inf
  HiiLib|MdeModulePkg/Library/UefiHiiLib/UefiHiiLib.inf
  SortLib|MdeModulePkg/Library/UefiSortLib/UefiSortLib.inf
  UefiHiiServicesLib|MdeModulePkg/Library/UefiHiiServicesLib/UefiHiiServicesLib.inf
```

Build our app and execute it under OVMF:
```
FS0:\> SaveBGRT.efi
BGRT conatins BMP image with 193x58 resolution
Save it to BGRT.bmp7
FS0:\>
```

If you look at the BGRT.bmp picture that are app have produced, it would have the same content as https://raw.githubusercontent.com/tianocore/edk2/master/MdeModulePkg/Logo/Logo.bmp

The file itself wouldn't be the same since BGRT driver don't use an image from flash, but actually grabs a boot screen and transforms it to a BMP image. For the proof checkout how https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/Acpi/BootGraphicsResourceTableDxe/BootGraphicsResourceTableDxe.c uses `TranslateGopBltToBmp` function from the https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/BaseBmpSupportLib/BmpSupportLib.c library.
If you find it strange that BGRT grabs a screen instead of using an image from flash, remember how BGRT is defined in ACPI specification:
```
The Boot Graphics Resource Table (BGRT) is an optional table that provides a mechanism to indicate that an image was drawn on the screen during boot
```

The file GUID for binary boot logo image is defined in the file https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Logo/Logo.inf
```
FILE_GUID                      = 7BB28B99-61BB-11D5-9A5D-0090273FC14D
```
It is a GUID that is usually used for the Logo image in BIOS. It is even hardcoded to https://github.com/tianocore/edk2/blob/master/BaseTools/Source/Python/Eot/Report.py
```
## GenerateFfs() method
#
#  Generate FFS information
#
#  @param self: The object pointer
#  @param FfsObj: FFS object after FV image is parsed
#
def GenerateFfs(self, FfsObj):
    self.FfsIndex = self.FfsIndex + 1
    if FfsObj is not None and FfsObj.Type in [0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xA]:
        FfsGuid = FfsObj.Guid
        FfsOffset = FfsObj._OFF_
        FfsName = 'Unknown-Module'
        FfsPath = FfsGuid
        FfsType = FfsObj._TypeName[FfsObj.Type]

        # Hard code for Binary INF
        if FfsGuid.upper() == '7BB28B99-61BB-11D5-9A5D-0090273FC14D':
            FfsName = 'Logo'

        if FfsGuid.upper() == '7E374E25-8E01-4FEE-87F2-390C23C606CD':
            FfsName = 'AcpiTables'

        if FfsGuid.upper() == '961578FE-B6B7-44C3-AF35-6BC705CD2B1F':
            FfsName = 'Fat'
        ...
```

If you want to know how Logo and BGRT are work in edk2, checkout these drivers:
- https://github.com/tianocore/edk2/tree/master/MdeModulePkg/Library/BootLogoLib/
- https://github.com/tianocore/edk2/tree/master/MdeModulePkg/Logo/
- https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/Acpi/BootGraphicsResourceTableDxe/
- https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/BaseBmpSupportLib/