diff options
author | Konstantin Aladyshev <aladyshev22@gmail.com> | 2021-07-03 00:47:16 +0300 |
---|---|---|
committer | Konstantin Aladyshev <aladyshev22@gmail.com> | 2021-07-03 16:18:55 +0300 |
commit | 0872ca11d741d039846c8b301eed1d720d640ed0 (patch) | |
tree | 67c57919a56aba4b4ab5bed9b462d847078c587e /Lesson_29 | |
parent | 560f0303beaac1bfa8966de3fe636657825724da (diff) | |
download | UEFI-Lessons-0872ca11d741d039846c8b301eed1d720d640ed0.tar.gz UEFI-Lessons-0872ca11d741d039846c8b301eed1d720d640ed0.tar.bz2 UEFI-Lessons-0872ca11d741d039846c8b301eed1d720d640ed0.zip |
Add lesson 28
Signed-off-by: Konstantin Aladyshev <aladyshev22@gmail.com>
Diffstat (limited to 'Lesson_29')
-rw-r--r-- | Lesson_29/README.md | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/Lesson_29/README.md b/Lesson_29/README.md new file mode 100644 index 0000000..1b613e7 --- /dev/null +++ b/Lesson_29/README.md @@ -0,0 +1,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/ + |