Translate

Image of RHCE Red Hat Certified Engineer Linux Study Guide (Exam RH302) (Certification Press)
Image of Linux Kernel Development (3rd Edition)
Image of Advanced Programming in the UNIX Environment, Second Edition (Addison-Wesley Professional Computing Series)
Image of XSLT 2.0 and XPath 2.0 Programmer's Reference (Programmer to Programmer)

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.