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/