As part of my UEFI Rescue DVD project, I decided that I wanted a small utility to list out the firmware Advanced Configuration and Power Interface (ACPI) tables. ACPI defines platform-independent interfaces for hardware discovery, configuration, power management and monitoring, and these tables contain lots of useful information for low-level programmers such as myself.
Here is the source code for the first version of my listacpi utility:
// // Copyright (c) 2015 Finnbarr P. Murphy. All rights reserved. // // Display list of ACPI tables // // License: BSD License // #include <efi.h> #include <efilib.h> typedef struct { CHAR8 Signature[8]; UINT8 Checksum; UINT8 OemId[6]; UINT8 Revision; UINT32 RsdtAddress; UINT32 Length; UINT64 XsdtAddress; UINT8 ExtendedChecksum; UINT8 Reserved[3]; } EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER; // XSDT is the main System Description Table. // There are many kinds of SDT. An SDT may be split into two parts - // A common header and a data section which is different for each table. typedef struct { CHAR8 Signature[4]; UINT32 Length; UINT8 Revision; UINT8 Checksum; CHAR8 OemId[6]; CHAR8 OemTableId[8]; UINT32 OemRevision; UINT32 CreatorId; UINT32 CreatorRevision; } EFI_ACPI_SDT_HEADER; #define EFI_ACPI_TABLE_GUID \ { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d }} #define EFI_ACPI_20_TABLE_GUID \ { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 }} #define EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION 0x02 UINTN myStrnCmpA( CHAR8 *s1, CHAR8 *s2, UINTN len) { while (*s1 && len) { if (*s1 != *s2) { break; } s1 += 1; s2 += 1; len -= 1; } return len ? *s1 - *s2 : 0; } VOID Ascii2UnicodeStr(CHAR8 *String, CHAR16 *UniString, UINT8 length) { int len = length; while (*String != '\0' && len > 0) { *(UniString++) = (CHAR16) *(String++); len--; } *UniString = '\0'; } static int ParseRSDP( EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp, CHAR16* GuidStr) { EFI_ACPI_SDT_HEADER *Xsdt, *Entry; CHAR16 SigStr[20], OemStr[20]; UINT32 EntryCount; UINT64 *EntryPtr; int Index; Print(L"\n\nACPI GUID: %s\n", GuidStr); Ascii2UnicodeStr((CHAR8 *)(Rsdp->OemId), OemStr, 6); Print(L"\nFound RSDP. Version: %d OEM ID: %s\n", (int)(Rsdp->Revision), OemStr); if (Rsdp->Revision >= EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION) { Xsdt = (EFI_ACPI_SDT_HEADER *)(Rsdp->XsdtAddress); } else { Print(L"ERROR: No XSDT table found.\n"); return 1; } if (myStrnCmpA("XSDT", (CHAR8 *)(VOID *)(Xsdt->Signature), 4)) { Print(L"ERROR: Invalid XSDT table found.\n"); return 1; } Ascii2UnicodeStr((CHAR8 *)(Xsdt->OemId), OemStr, 6); EntryCount = (Xsdt->Length - sizeof (EFI_ACPI_SDT_HEADER)) / sizeof(UINT64); Print(L"Found XSDT. OEM ID: %s Entry Count: %d\n\n", OemStr, EntryCount); EntryPtr = (UINT64 *)(Xsdt + 1); for (Index = 0; Index < EntryCount; Index++, EntryPtr++) { Entry = (EFI_ACPI_SDT_HEADER *)((UINTN)(*EntryPtr)); Ascii2UnicodeStr((CHAR8 *)(Entry->Signature), SigStr, 4); Ascii2UnicodeStr((CHAR8 *)(Entry->OemId), OemStr, 6); Print(L"Found ACPI table: %s Version: %d OEM ID: %s\n", SigStr, (int)(Entry->Revision), OemStr ); } return 0; } VOID Guid2String(CHAR16 *Buffer, EFI_GUID *Guid) { SPrint (Buffer, 0, L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", Guid->Data1, Guid->Data2, Guid->Data3, Guid->Data4[0], Guid->Data4[1], Guid->Data4[2], Guid->Data4[3], Guid->Data4[4], Guid->Data4[5], Guid->Data4[6], Guid->Data4[7]); } EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *systab) { EFI_CONFIGURATION_TABLE *ect = systab->ConfigurationTable; EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp = NULL; EFI_GUID AcpiTableGuid = ACPI_TABLE_GUID; EFI_GUID Acpi2TableGuid = ACPI_20_TABLE_GUID; CHAR16 Str[20], GuidStr[100]; int Index; InitializeLib(image_handle, systab); // locate RSDP (Root System Description Pointer) for (Index = 0; Index < systab->NumberOfTableEntries; Index++) { if ((CompareGuid (&(systab->ConfigurationTable[Index].VendorGuid), &AcpiTableGuid)) || (CompareGuid (&(systab->ConfigurationTable[Index].VendorGuid), &Acpi2TableGuid))) { if (!myStrnCmpA("RSD PTR ", (CHAR8 *)(ect->VendorTable), 8)) { Guid2String(GuidStr, &(systab->ConfigurationTable[Index].VendorGuid)); Rsdp = (EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *)ect->VendorTable; ParseRSDP(Rsdp, GuidStr); } } ect++; } if (Rsdp == NULL) { Print(L"ERROR: Could not find a RSDP.\n"); return 1; } return 0; }
For UEFI utility development, I generally use Nigel Croxon’s gnu-efi library. That is what I used for this utility. I am not going to explain the code; suffice to say that if you are reading this post, I assume you are somewhat familiar with ACPI and UEFI programming.
Here is the associated Makefile which I used to build the executable:
SRCDIR = . PREFIX := /usr HOSTARCH := $(shell uname -m | sed s,i[3456789]86,ia32,) ARCH := $(shell uname -m | sed s,i[3456789]86,ia32,) INCDIR = -I. CPPFLAGS = -DCONFIG_$(ARCH) CFLAGS = $(ARCH3264) -g -O0 -fpic -Wall -fshort-wchar -fno-strict-aliasing -fno-merge-constants --std=gnu99 -D_GNU_SOURCE ASFLAGS = $(ARCH3264) LDFLAGS = -nostdlib INSTALL = install CC = gcc AS = as LD = ld.bfd AR = ar RANLIB = ranlib OBJCOPY = objcopy ifeq ($(ARCH), ia32) LIBDIR := $(PREFIX)/lib ifeq ($(HOSTARCH), x86_64) ARCH3264 := -m32 endif endif ifeq ($(ARCH), x86_64) CFLAGS += -mno-red-zone LIBDIR := $(PREFIX)/lib64 ifeq ($(HOSTARCH), ia32) ARCH3264 := -m64 endif endif FORMAT=efi-app-$(HOSTARCH) LDFLAGS = -nostdlib -T $(LIBDIR)/gnuefi/elf_$(HOSTARCH)_efi.lds -shared -Bsymbolic $(LIBDIR)/gnuefi/crt0-efi-$(HOSTARCH).o -L$(LIBDIR) LIBS=-lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name) CCLDFLAGS = CFLAGS = -I/usr/include/efi/ -I/usr/include/efi/$(HOSTARCH)/ -I/usr/include/efi/protocol -fpic -fshort-wchar -fno-reorder-functions -fno-strict-aliasing -fno-merge-constants -mno-red-zone -Wimplicit-function-declaration TARGETS = listacpi.efi all : $(TARGETS) clean : @rm -rf *.o *.a *.so $(TARGETS) .PHONY: all clean install %.efi : %.so $(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel \ -j .rela -j .reloc --target=$(FORMAT) $*.so $@ %.so: %.o $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) %.o: %.c $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -c $< -o $@ %.S: %.c $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -S $< -o $@ %.E: %.c $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -E $< -o $@
The source code assumes a 64-bit build environment such as Fedora 21 and 64-bit UEFI firmware. Some changes will have to be made to the source code to support 32-bit firmware.
Here is the output from the utility when run on my build system:
FS0:> LISTACPI.EFI ACPI GUID: EB9D2D30-2D88-11D3-9A16-0090273FC14D Found RSDP. Version: 2 OEM ID: ALASKA Found XSDT. OEM ID: ALASKA Entry Count: 8 Found ACPI table: FACP Version: 4 OEM ID: ALASKA Found ACPI table: APIC Version: 3 OEM ID: ALASKA Found ACPI table: MCFG Version: 1 OEM ID: ALASKA Found ACPI table: HPET Version: 1 OEM ID: ALASKA Found ACPI table: SSDT Version: 1 OEM ID: SataRe Found ACPI table: SSDT Version: 1 OEM ID: PmRef Found ACPI table: SSDT Version: 1 OEM ID: PmRef Found ACPI table: BGRT Version: 0 OEM ID: ALASKA ACPI GUID: 8868E871-E4F1-11D3-BC22-0080C73C8881 Found RSDP. Version: 2 OEM ID: ALASKA Found XSDT. OEM ID: ALASKA Entry Count: 8 Found ACPI table: FACP Version: 4 OEM ID: ALASKA Found ACPI table: APIC Version: 3 OEM ID: ALASKA Found ACPI table: MCFG Version: 1 OEM ID: ALASKA Found ACPI table: HPET Version: 1 OEM ID: ALASKA Found ACPI table: SSDT Version: 1 OEM ID: SataRe Found ACPI table: SSDT Version: 1 OEM ID: PmRef Found ACPI table: SSDT Version: 1 OEM ID: PmRef Found ACPI table: BGRT Version: 0 OEM ID: ALASKA FS0:>
Note that the list of ACPI tables was outputted twice. This is because the system I executed the listapci on has two RSDPs – one for ACPI and one for ACPI 2.0 and later. Look at the two different ACPI-related GUIDS!
On this firmware, the ACPICA acpidump utility, of which an EFI version can be build using gnu-efi, outputs two addition tables – DSDT and FACS. I am not yet sure why this happens; some further investigation is needed.
To pick holes in these two lines
HOSTARCH = $(shell uname -m | sed s,i[3456789]86,ia32,)
ARCH := $(shell uname -m | sed s,i[3456789]86,ia32,)
The sed command should be protected by quotation. Otherwise if you happen to have a file named i486 then the sed command will become “sed ‘s,i486,ia32,'”. Even worse if you have both i386 and i486 the command will become “sed ‘s,i386 i486,ia32,'”
In general it is a bad idea to use $(shell …) without using :=, as you have done for HOSTARCH. It means that each time the variable is used you will spawn another shell (and uname and sed). You can see this for example by changing it to
HOSTARCH = $( shell echo HOSTARCH: `date` >&2 ; uname -m | sed ‘s,i[3456789]86,ia32,’)
and you should see an HOSTARCH line pop up for every C compile (because CFLAGS has a reference to HOSTARCH), the generation of the “.so”, because LDFLAGS has a reference, and the “.efi” as FORMAT has a reference. There will probably be another reference when reading the Makefile depending on the value of ${ARCH}.
As a matter of style, I wouldn’t define CFLAGS on line 7, just to replace it on line 37, but it might be that you have a standard header for your Makefiles.
You are correct in what you say regarding :=. Thank you for noting it. As regards sed being protected by quotes, I looked at a number of major source code repositories and came across very few usages of sed quoting.
The most common form of HOSTARCH that i found was:
HOSTARCH := $(shell uname -m | \
sed -e s/i.86/i386/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
-e s/sa110/arm/ \
-e s/powerpc/ppc/ \
-e s/Power\ Macintosh/ppc/ \
-e s/macppc/ppc/)
I stand by my general assertion that if you are going to have patterns that include glob characters i.e. [, * and ? then you should quote them.
However I did overstate the issue as the pattern s,i[3456789]86,ia32, has to match (not just i486) so the chances of you having such a filename are much smaller.
Hi Sir,
After I use your source code to build a Efi application, it will error on below code.
I used EADK environment to build the Efi application. (command line is “build -a X64 -p AppPkg\AppPkg.dsc”)
(http://tianocore.sourceforge.net/wiki/EDKII_EADK)
—————————————————————————————–
VOID
Guid2String(CHAR16 *Buffer, EFI_GUID *Guid)
{
SPrint (Buffer, 0, L”%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x”,
Guid->Data1,
Guid->Data2,
Guid->Data3,
Guid->Data4[0],
Guid->Data4[1],
Guid->Data4[2],
Guid->Data4[3],
Guid->Data4[4],
Guid->Data4[5],
Guid->Data4[6],
Guid->Data4[7]);
}
ERROR message as blow
——————————————————————————————
d:\src\eadk\20141115\AppPkg\Applications\ShowACPI\ShowACPI.c(139) : error C2220:
warning treated as error – no ‘object’ file generated
d:\src\eadk\20141115\AppPkg\Applications\ShowACPI\ShowACPI.c(139) : warning C401
3: ‘SPrint’ undefined; assuming extern returning int
NMAKE : fatal error U1077: ‘”C:\Program Files (x86)\Microsoft Visual Studio 12.0
\Vc\bin\x86_amd64\cl.exe”‘ : return code ‘0x2’
Stop.
build…
: error 7000: Failed to execute command
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Vc\bin\nmake.exe /no
logo tbuild [d:\src\eadk\20141115\Build\AppPkg\DEBUG_VS2012x86\X64\AppPkg\Applic
ations\ShowACPI\ShowACPI]
——————————————————————————-
Hi Tim,
The utility was not built to be compiled/built in the EDK/UDK frameworks. Install gnu-efi development libraries and use the supplied Makefile to build the utility.