diff options
-rw-r--r-- | Lesson_28/README.md | 368 | ||||
-rw-r--r-- | Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.c | 47 | ||||
-rw-r--r-- | Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.inf | 3 | ||||
-rw-r--r-- | Lesson_29/README.md | 427 | ||||
-rw-r--r-- | README.md | 3 |
5 files changed, 830 insertions, 18 deletions
diff --git a/Lesson_28/README.md b/Lesson_28/README.md index a10663a..3927045 100644 --- a/Lesson_28/README.md +++ b/Lesson_28/README.md @@ -3,7 +3,7 @@ The latest ACPI specification can be found under UEFI specifications page https: The current latest specification is "ACPI Specification Version 6.4 (released January 2021)" (https://uefi.org/specs/ACPI/6.4/) -Use the same tactic we used for SMBIOS tables: +Use the same tactic we used for SMBIOS tables to print ACPI entry point table address: ``` #include <Library/UefiBootServicesTableLib.h> #include <Library/UefiLib.h> @@ -26,7 +26,7 @@ UefiMain ( } ``` -dmem peak inside memory: +Use `dmem` to peak inside ACPI table memory: ``` FS0:\> AcpiInfo.efi ACPI table is placed at 7B7E014 @@ -39,19 +39,18 @@ Memory Address 0000000007B7E014 30 Bytes FS0:\> ``` -The signature `RSP PTR` stands for Root System Description Pointer (RSDP) Structure (https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#root-system-description-pointer-rsdp-structure). +The signature `RSP PTR` stands for `Root System Description Pointer (RSDP) Structure` (https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#root-system-description-pointer-rsdp-structure). -It contains addresses for RSDT and XSDT tables. If you calculate offsets, you'll get these addresses from our memory dump: +It contains addresses for `RSDT` and `XSDT` tables. If you calculate offsets, you'll get these addresses from our memory dump: ``` XSDT=0x07B7D0E8 RSDT=0x07B7D074 ``` These tables in turn would cointain pointers to other ACPI tables that actualy contain data useful to OS. -According to the spec "platforms provide the RSDT to enable compatibility with ACPI 1.0 operating systems. The XSDT, described in the next section, supersedes RSDT functionality". If you peak these addresses with `dmem` table contents would be pretty much the same except table signatures. +According to the spec "platforms provide the RSDT to enable compatibility with ACPI 1.0 operating systems. The XSDT supersedes RSDT functionality". So if you peak these addresses with `dmem`, table contents would be pretty much the same except table signatures. Therefore in our app code we would be parsing XSDT table data. - -Ok, it's time to write some code. ACPI structures are defined in following header files: +Ok, it's time to write some code. ACPI structures are defined in the following header files: ``` $ ls -1 MdePkg/Include/IndustryStandard/Acpi* MdePkg/Include/IndustryStandard/Acpi.h @@ -171,11 +170,14 @@ UINT64 offset = sizeof(EFI_ACPI_DESCRIPTION_HEADER); while (offset < XSDT->Length) { UINT64* table_address = (UINT64*)((UINT8*)XSDT + offset); EFI_ACPI_6_3_COMMON_HEADER* table = (EFI_ACPI_6_3_COMMON_HEADER*)(*table_address); - Print(L"\t%c%c%c%c table is placed at address %p with length 0x%x\n", - (CHAR8)((table->Signature>> 0)&0xFF), - (CHAR8)((table->Signature>> 8)&0xFF), - (CHAR8)((table->Signature>>16)&0xFF), - (CHAR8)((table->Signature>>24)&0xFF), + TableName[0] = (CHAR16)((table->Signature>> 0)&0xFF); + TableName[1] = (CHAR16)((table->Signature>> 8)&0xFF); + TableName[2] = (CHAR16)((table->Signature>>16)&0xFF); + TableName[3] = (CHAR16)((table->Signature>>24)&0xFF); + TableName[4] = 0; + + Print(L"\t%s table is placed at address %p with length 0x%x\n", + TableName, table, table->Length); offset += sizeof(UINT64); @@ -205,3 +207,345 @@ Pretty neat, our system has 4 ACPI data tables: - Boot Graphics Resource Table (`BGRT`) - https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#boot-graphics-resource-table-bgrt +Keep in mind that as with SMBIOS tables we could use a protocol to get the same data. `GetAcpiTable()` function of a `EFI_ACPI_SDT_PROTOCOL` can help to get the same information. This protocol also is defined by UEFI PI specification. + +In edk2 it is defined under https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/AcpiSystemDescriptionTable.h + +# Use `EFI_SHELL_PROTOCOL` to save table data + +Now let's try to save ACPI tables from memory to files. + +To do this we can utilize `EFI_SHELL_PROTOCOL` that is defined in UEFI Shell specification (https://uefi.org/sites/default/files/resources/UEFI_Shell_2_2.pdf). It has many functions for File I/O. + +The necessary header in edk2 is https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/Shell.h +``` +typedef struct _EFI_SHELL_PROTOCOL { + EFI_SHELL_EXECUTE Execute; + EFI_SHELL_GET_ENV GetEnv; + EFI_SHELL_SET_ENV SetEnv; + EFI_SHELL_GET_ALIAS GetAlias; + EFI_SHELL_SET_ALIAS SetAlias; + EFI_SHELL_GET_HELP_TEXT GetHelpText; + EFI_SHELL_GET_DEVICE_PATH_FROM_MAP GetDevicePathFromMap; + EFI_SHELL_GET_MAP_FROM_DEVICE_PATH GetMapFromDevicePath; + EFI_SHELL_GET_DEVICE_PATH_FROM_FILE_PATH GetDevicePathFromFilePath; + EFI_SHELL_GET_FILE_PATH_FROM_DEVICE_PATH GetFilePathFromDevicePath; + EFI_SHELL_SET_MAP SetMap; + EFI_SHELL_GET_CUR_DIR GetCurDir; + EFI_SHELL_SET_CUR_DIR SetCurDir; + EFI_SHELL_OPEN_FILE_LIST OpenFileList; + EFI_SHELL_FREE_FILE_LIST FreeFileList; + EFI_SHELL_REMOVE_DUP_IN_FILE_LIST RemoveDupInFileList; + EFI_SHELL_BATCH_IS_ACTIVE BatchIsActive; + EFI_SHELL_IS_ROOT_SHELL IsRootShell; + EFI_SHELL_ENABLE_PAGE_BREAK EnablePageBreak; + EFI_SHELL_DISABLE_PAGE_BREAK DisablePageBreak; + EFI_SHELL_GET_PAGE_BREAK GetPageBreak; + EFI_SHELL_GET_DEVICE_NAME GetDeviceName; + EFI_SHELL_GET_FILE_INFO GetFileInfo; + EFI_SHELL_SET_FILE_INFO SetFileInfo; + EFI_SHELL_OPEN_FILE_BY_NAME OpenFileByName; + EFI_SHELL_CLOSE_FILE CloseFile; + EFI_SHELL_CREATE_FILE CreateFile; + EFI_SHELL_READ_FILE ReadFile; + EFI_SHELL_WRITE_FILE WriteFile; + EFI_SHELL_DELETE_FILE DeleteFile; + EFI_SHELL_DELETE_FILE_BY_NAME DeleteFileByName; + EFI_SHELL_GET_FILE_POSITION GetFilePosition; + EFI_SHELL_SET_FILE_POSITION SetFilePosition; + EFI_SHELL_FLUSH_FILE FlushFile; + EFI_SHELL_FIND_FILES FindFiles; + EFI_SHELL_FIND_FILES_IN_DIR FindFilesInDir; + EFI_SHELL_GET_FILE_SIZE GetFileSize; + EFI_SHELL_OPEN_ROOT OpenRoot; + EFI_SHELL_OPEN_ROOT_BY_HANDLE OpenRootByHandle; + EFI_EVENT ExecutionBreak; + UINT32 MajorVersion; + UINT32 MinorVersion; + // Added for Shell 2.1 + EFI_SHELL_REGISTER_GUID_NAME RegisterGuidName; + EFI_SHELL_GET_GUID_NAME GetGuidName; + EFI_SHELL_GET_GUID_FROM_NAME GetGuidFromName; + EFI_SHELL_GET_ENV_EX GetEnvEx; +} EFI_SHELL_PROTOCOL; +``` + +We will use 3 functions from this protocol `OpenFileByName`/`WriteFile`/`CloseFile`: +``` +EFI_SHELL_PROTOCOL.OpenFileByName() + +Summary: +Opens a file or a directory by file name. + +Prototype: +typdef +EFI_STATUS +(EFIAPI *EFI_SHELL_OPEN_FILE_BY_NAME) ( + IN CONST CHAR16 *FileName, + OUT SHELL_FILE_HANDLE *FileHandle, + IN UINT64 OpenMode + ); + +Parameters: +FileName Points to the null-terminated UCS-2 encoded file name. +FileHandle On return, points to the file handle. +OpenMode File open mode. + +Description: +This function opens the specified file in the specified OpenMode and returns a file handle. +``` +``` +EFI_SHELL_PROTOCOL.WriteFile() + +Summary: +Writes data to the file. + +Prototype: +typedef +EFI_STATUS +(EFIAPI EFI_SHELL_WRITE_FILE)( + IN SHELL_FILE_HANDLE FileHandle, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer + ); + +Parameters: +FileHandle The opened file handle for writing. +BufferSize On input, size of Buffer. +Buffer The buffer in which data to write. + +Description: +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 automatically grows to hold the data, if required. +``` +``` +EFI_SHELL_PROTOCOL.CloseFile() + +Summary: +Closes the file handle. + +Prototype: +typedef +EFI_STATUS +(EFIAPI *EFI_SHELL_CLOSE_FILE)( + IN SHELL_FILE_HANDLE FileHandle + ); + +Parameters: +FileHandle The file handle to be closed +Description 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. + +``` + +Now let's start coding. Add necessary include to our *.c file: +``` +#include <Protocol/Shell.h> +``` +And necessary protocol guid to our *.inf file: +``` +[Protocols] + gEfiShellProtocolGuid +``` + +In our program we need to acquire `EFI_SHELL_PROTOCOL`, this can be done via `LocateProtocol` function from the BootServices: +``` +EFI_SHELL_PROTOCOL* ShellProtocol; +EFI_STATUS Status = gBS->LocateProtocol( + &gEfiShellProtocolGuid, + NULL, + (VOID **)&ShellProtocol +); + +if (EFI_ERROR(Status)) { + Print(L"Can't open EFI_SHELL_PROTOCOL: %r\n", Status); + return EFI_SUCCESS; +} +``` + +Then use `EFI_SHELL_PROTOCOL` functions in our while loop to create files with ACPI table data. For every table we will create a file "<signature>.aml". We use `.aml` extension for our files because in ACPI language source files usually have *.asl/*.dsl extension (ACPI Source Language), and compiled files have *.aml extension (ACPI Machine Language): +``` +CHAR16 FileName[9] = {0}; +StrCpyS(FileName, 9, TableName); +StrCatS(FileName, 9, L".aml"); +Print(L"%s\n", FileName); +SHELL_FILE_HANDLE FileHandle; +Status = ShellProtocol->OpenFileByName(FileName, + &FileHandle, + EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ); + +if (!EFI_ERROR(Status)) { + UINTN size = table->Length; + Status = ShellProtocol->WriteFile(FileHandle, &size, (VOID*)table); + if (EFI_ERROR(Status)) { + Print(L"Error in WriteFile: %r\n", Status); + } + Status = ShellProtocol->CloseFile(FileHandle); + if (EFI_ERROR(Status)) { + Print(L"Error in CloseFile: %r\n", Status); + } +} else { + Print(L"Error in OpenFileByName: %r\n", Status); +} +``` + +To create a string with a file name we use `StrCatS` and `StrCpyS` functions. They are safe versions of string concatention/string copy functions similar to their C++ analogs `strcat_s`/`strcpy_s`. You can check out them in a library https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseLib/SafeString.c + +With a help of `EFI_SHELL_PROTOCOL` file operation functions writing data to a file is pretty similar to standard system programming. We open handle, write data to it, and finally close handle. + +If you build our app and execute it under OVMF you would get 4 files in our `UEFI_disk` shared folder: +``` +$ ls -1 ~/UEFI_disk/*.aml +/home/kostr/UEFI_disk/apic.aml +/home/kostr/UEFI_disk/bgrt.aml +/home/kostr/UEFI_disk/facp.aml +/home/kostr/UEFI_disk/hpet.aml +``` + +You can use `iasl` compiler to disassemle ACPI table data: +``` +$ iasl -d ~/UEFI_disk/*.aml + +Intel ACPI Component Architecture +ASL+ Optimizing Compiler/Disassembler version 20190509 +Copyright (c) 2000 - 2019 Intel Corporation + +File appears to be binary: found 81 non-ASCII characters, disassembling +Binary file appears to be a valid ACPI table, disassembling +Input file /home/kostr/UEFI_disk/apic.aml, Length 0x78 (120) bytes +ACPI: APIC 0x0000000000000000 000078 (v01 BOCHS BXPCAPIC 00000001 BXPC 00000001) +Acpi Data Table [APIC] decoded +Formatted output: /home/kostr/UEFI_disk/apic.dsl - 4935 bytes +File appears to be binary: found 32 non-ASCII characters, disassembling +Binary file appears to be a valid ACPI table, disassembling +Input file /home/kostr/UEFI_disk/bgrt.aml, Length 0x38 (56) bytes +ACPI: BGRT 0x0000000000000000 000038 (v01 INTEL EDK2 00000002 01000013) +Acpi Data Table [BGRT] decoded +Formatted output: /home/kostr/UEFI_disk/bgrt.dsl - 1628 bytes +File appears to be binary: found 91 non-ASCII characters, disassembling +Binary file appears to be a valid ACPI table, disassembling +Input file /home/kostr/UEFI_disk/facp.aml, Length 0x74 (116) bytes +ACPI: FACP 0x0000000000000000 000074 (v01 BOCHS BXPCFACP 00000001 BXPC 00000001) +Acpi Data Table [FACP] decoded +Formatted output: /home/kostr/UEFI_disk/facp.dsl - 4892 bytes +File appears to be binary: found 33 non-ASCII characters, disassembling +Binary file appears to be a valid ACPI table, disassembling +Input file /home/kostr/UEFI_disk/hpet.aml, Length 0x38 (56) bytes +ACPI: HPET 0x0000000000000000 000038 (v01 BOCHS BXPCHPET 00000001 BXPC 00000001) +Acpi Data Table [HPET] decoded +Formatted output: /home/kostr/UEFI_disk/hpet.dsl - 1887 bytes +``` + +Now you have *.dsl files in the same `UEFI_disk` shared folder. + +For example here is a content for `APIC` table: +``` +$ cat ~/UEFI_disk/apic.dsl +/* + * Intel ACPI Component Architecture + * AML/ASL+ Disassembler version 20190509 (64-bit version) + * Copyright (c) 2000 - 2019 Intel Corporation + * + * Disassembly of /home/kostr/UEFI_disk/apic.aml, Sat Jul 3 00:09:16 2021 + * + * ACPI Data Table [APIC] + * + * Format: [HexOffset DecimalOffset ByteLength] FieldName : FieldValue + */ + +[000h 0000 4] Signature : "APIC" [Multiple APIC Description Table (MADT)] +[004h 0004 4] Table Length : 00000078 +[008h 0008 1] Revision : 01 +[009h 0009 1] Checksum : ED +[00Ah 0010 6] Oem ID : "BOCHS " +[010h 0016 8] Oem Table ID : "BXPCAPIC" +[018h 0024 4] Oem Revision : 00000001 +[01Ch 0028 4] Asl Compiler ID : "BXPC" +[020h 0032 4] Asl Compiler Revision : 00000001 + +[024h 0036 4] Local Apic Address : FEE00000 +[028h 0040 4] Flags (decoded below) : 00000001 + PC-AT Compatibility : 1 + +[02Ch 0044 1] Subtable Type : 00 [Processor Local APIC] +[02Dh 0045 1] Length : 08 +[02Eh 0046 1] Processor ID : 00 +[02Fh 0047 1] Local Apic ID : 00 +[030h 0048 4] Flags (decoded below) : 00000001 + Processor Enabled : 1 + Runtime Online Capable : 0 + +[034h 0052 1] Subtable Type : 01 [I/O APIC] +[035h 0053 1] Length : 0C +[036h 0054 1] I/O Apic ID : 00 +[037h 0055 1] Reserved : 00 +[038h 0056 4] Address : FEC00000 +[03Ch 0060 4] Interrupt : 00000000 + +[040h 0064 1] Subtable Type : 02 [Interrupt Source Override] +[041h 0065 1] Length : 0A +[042h 0066 1] Bus : 00 +[043h 0067 1] Source : 00 +[044h 0068 4] Interrupt : 00000002 +[048h 0072 2] Flags (decoded below) : 0000 + Polarity : 0 + Trigger Mode : 0 + +[04Ah 0074 1] Subtable Type : 02 [Interrupt Source Override] +[04Bh 0075 1] Length : 0A +[04Ch 0076 1] Bus : 00 +[04Dh 0077 1] Source : 05 +[04Eh 0078 4] Interrupt : 00000005 +[052h 0082 2] Flags (decoded below) : 000D + Polarity : 1 + Trigger Mode : 3 + +[054h 0084 1] Subtable Type : 02 [Interrupt Source Override] +[055h 0085 1] Length : 0A +[056h 0086 1] Bus : 00 +[057h 0087 1] Source : 09 +[058h 0088 4] Interrupt : 00000009 +[05Ch 0092 2] Flags (decoded below) : 000D + Polarity : 1 + Trigger Mode : 3 + +[05Eh 0094 1] Subtable Type : 02 [Interrupt Source Override] +[05Fh 0095 1] Length : 0A +[060h 0096 1] Bus : 00 +[061h 0097 1] Source : 0A +[062h 0098 4] Interrupt : 0000000A +[066h 0102 2] Flags (decoded below) : 000D + Polarity : 1 + Trigger Mode : 3 + +[068h 0104 1] Subtable Type : 02 [Interrupt Source Override] +[069h 0105 1] Length : 0A +[06Ah 0106 1] Bus : 00 +[06Bh 0107 1] Source : 0B +[06Ch 0108 4] Interrupt : 0000000B +[070h 0112 2] Flags (decoded below) : 000D + Polarity : 1 + Trigger Mode : 3 + +[072h 0114 1] Subtable Type : 04 [Local APIC NMI] +[073h 0115 1] Length : 06 +[074h 0116 1] Processor ID : FF +[075h 0117 2] Flags (decoded below) : 0000 + Polarity : 0 + Trigger Mode : 0 +[077h 0119 1] Interrupt Input LINT : 01 + +Raw Table Data: Length 120 (0x78) + + 0000: 41 50 49 43 78 00 00 00 01 ED 42 4F 43 48 53 20 // APICx.....BOCHS + 0010: 42 58 50 43 41 50 49 43 01 00 00 00 42 58 50 43 // BXPCAPIC....BXPC + 0020: 01 00 00 00 00 00 E0 FE 01 00 00 00 00 08 00 00 // ................ + 0030: 01 00 00 00 01 0C 00 00 00 00 C0 FE 00 00 00 00 // ................ + 0040: 02 0A 00 00 02 00 00 00 00 00 02 0A 00 05 05 00 // ................ + 0050: 00 00 0D 00 02 0A 00 09 09 00 00 00 0D 00 02 0A // ................ + 0060: 00 0A 0A 00 00 00 0D 00 02 0A 00 0B 0B 00 00 00 // ................ + 0070: 0D 00 04 06 FF 00 00 01 // ........ +``` + + diff --git a/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.c b/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.c index 88c78b6..0511f59 100644 --- a/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.c +++ b/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.c @@ -2,6 +2,7 @@ #include <Library/UefiLib.h> #include <Library/BaseMemoryLib.h> +#include <Protocol/Shell.h> EFI_STATUS EFIAPI @@ -10,6 +11,18 @@ UefiMain ( IN EFI_SYSTEM_TABLE *SystemTable ) { + EFI_SHELL_PROTOCOL* ShellProtocol; + EFI_STATUS Status = gBS->LocateProtocol( + &gEfiShellProtocolGuid, + NULL, + (VOID **)&ShellProtocol + ); + + if (EFI_ERROR(Status)) { + Print(L"Can't open EFI_SHELL_PROTOCOL: %r\n", Status); + return EFI_SUCCESS; + } + EFI_ACPI_6_3_ROOT_SYSTEM_DESCRIPTION_POINTER* RSDP = NULL; for (UINTN i=0; i<SystemTable->NumberOfTableEntries; i++) { @@ -55,13 +68,37 @@ UefiMain ( while (offset < XSDT->Length) { UINT64* table_address = (UINT64*)((UINT8*)XSDT + offset); EFI_ACPI_6_3_COMMON_HEADER* table = (EFI_ACPI_6_3_COMMON_HEADER*)(*table_address); - Print(L"\t%c%c%c%c table is placed at address %p with length 0x%x\n", - (CHAR8)((table->Signature>> 0)&0xFF), - (CHAR8)((table->Signature>> 8)&0xFF), - (CHAR8)((table->Signature>>16)&0xFF), - (CHAR8)((table->Signature>>24)&0xFF), + CHAR16 TableName[5]; + TableName[0] = (CHAR16)((table->Signature>> 0)&0xFF); + TableName[1] = (CHAR16)((table->Signature>> 8)&0xFF); + TableName[2] = (CHAR16)((table->Signature>>16)&0xFF); + TableName[3] = (CHAR16)((table->Signature>>24)&0xFF); + TableName[4] = 0; + + Print(L"\t%s table is placed at address %p with length 0x%x\n", + TableName, table, table->Length); + CHAR16 FileName[9] = {0}; + StrCpyS(FileName, 9, TableName); + StrCatS(FileName, 9, L".aml"); + SHELL_FILE_HANDLE FileHandle; + Status = ShellProtocol->OpenFileByName(FileName, + &FileHandle, + EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ); + if (!EFI_ERROR(Status)) { + UINTN size = table->Length; + Status = ShellProtocol->WriteFile(FileHandle, &size, (VOID*)table); + if (EFI_ERROR(Status)) { + Print(L"Error in WriteFile: %r\n", Status); + } + Status = ShellProtocol->CloseFile(FileHandle); + if (EFI_ERROR(Status)) { + Print(L"Error in CloseFile: %r\n", Status); + } + } else { + Print(L"Error in OpenFileByName: %r\n", Status); + } offset += sizeof(UINT64); } diff --git a/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.inf b/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.inf index 3884e76..53d0356 100644 --- a/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.inf +++ b/Lesson_28/UefiLessonsPkg/AcpiInfo/AcpiInfo.inf @@ -19,3 +19,6 @@ [Guids] gEfiAcpi20TableGuid +[Protocols] + gEfiShellProtocolGuid + 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/ + @@ -30,7 +30,8 @@ Lessons description: - Lesson 25: More on PCDs - Lesson 26: Tables referenced in `EFI_CONFIGURATION_TABLE` - Lesson 27: Get SMBIOS information with `dmem`/`EFI_SMBIOS_PROTOCOL`/`smbiosview` -- Lesson 28: Get ACPI tables +- Lesson 28: Get ACPI tables and save them to files with a help of `EFI_SHELL_PROTOCOL` +- Lesson 29: Use `EFI_ACPI_SDT_PROTOCOL` and `ShellLib` to save a BMP image from the ACPI BGRT table ______ |