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 ``` 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 ``` 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 ``` 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/