Translate

Image of RHCE Red Hat Certified Engineer Linux Study Guide (Exam RH302) (Certification Press)
Image of Beginning Google Maps API 3
Image of Linux Kernel Development (3rd Edition)
Image of Android Wireless Application Development

List ACPI Tables From UEFI Shell

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.

5 comments to List ACPI Tables From UEFI Shell

  • Icarus

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

  • Icarus

    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.

  • Tim

    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.