Root ACPI table contains non canonical addresses.

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
CycloPentane
Posts: 2
Joined: Sat Oct 05, 2024 12:40 am

Root ACPI table contains non canonical addresses.

Post by CycloPentane »

Hello everyone!

I encountered a problem while trying to parse ACPI tables from the root ACPI table in my OS.

Following the ACPI 6.5 spec (https://uefi.org/sites/default/files/re ... _Aug29.pdf) and the tutorial on OS dev wiki I came up with the following code for the definitions of ACPI Root System Description Pointer, ACPI System Description Table Header and ACPI Extended System Description Table:

Code: Select all

//ACPI.h file

struct __attribute__((packed)) ACPI_RSDP {
    int8_t m_signature[8];
    uint8_t m_checksum;
    int8_t m_OEMID[6];
    uint8_t m_revision;
    uint32_t m_rsdt_address;
    uint32_t m_length;
    uint64_t m_xsdt_address;
    uint8_t m_extended_checksum;
    int8_t m_reserved[3];
  };
  
struct __attribute__((packed)) ACPI_SDTH {
    uint8_t m_signature[4];
    uint32_t m_length;
    uint8_t m_revision;
    uint8_t m_checksum;
    int8_t m_OEMID[6];
    uint64_t m_OEM_Table_ID;
    uint32_t m_OEM_revision;
    uint32_t m_creator_id;
    uint32_t m_creator_revision;
};
  
struct __attribute__((packed)) ACPI_XSDT {
    ACPI_SDTH m_header;
    uint64_t* m_entries;
};

inline ACPI_RSDP* g_rsdp = nullptr;
inline ACPI_XSDT* g_xsdt = nullptr;


Since I am using an UEFI bootloader, I did the following to get the address of the XSDT.

Code: Select all

// ACPI.cpp file

// Global variables for RSDP and XSDT are defined in ACPI.h file

bool initialize_ACPI(EFI_SYSTEM_TABLE* system_table) noexcept {

    for (uint64_t i = 0; i < system_table->NumberOfTableEntries; i++) {
      const auto& table = system_table->ConfigurationTable[i];
      if (table.VendorGuid == ACPI_2_RSDP_GUID) {
        g_rsdp = static_cast<ACPI_RSDP*>(table.VendorTable);
        g_xsdt = reinterpret_cast<ACPI_XSDT*>(g_rsdp->m_xsdt_address);
        break;
      }
    }
    
    if (!g_acpi_rsdt || !g_acpi_xsdt) {
        return false;
    }    
    
    size_t number_of_xsdt_entries = (g_xsdt->m_header.m_length - sizeof(ACPI_SDTH)) / 8;
    
    for (size_t i = 0; i < number_of_xsdt_entries; i++) {
        ACPI_SDTH* entry = reinterpret_cast<ACPI_SDTH*>(g_xsdt->m_entries[i]);
        // Check if entry is 'MADT' or 'FADT' or something else...
        // The rest of the code is omitted for brevity....
    }
}
Running the function above crashed the code with a page fault exception.

Using a debugger for a further inspection, I noticed that entries in the XSDT contain non canonical address. What I mean by that is if I place a breakpoint at the start of the last for loop and inspect the contents of the entries array in XSDT I get the following:

Code: Select all

(gdb) p  *g_rsdp

$1 = {m_signature = "RSD PTR ", m_checksum = 54 '6', m_OEMID = "BOCHS ", m_revision = 2 '\002', m_rsdt_address = 532140148, m_length = 36, m_xsdt_address = 532140264, m_extended_checksum = 78 'N', m_reserved = "\000\000"}
  
(gdb) p  *g_xsdt  

$2 = {m_header = {m_signature = "XSDT", m_length = 84, m_revision = 1 '\001', m_checksum = 35 '#', m_OEMID = "BOCHS ", m_OEM_Table_ID = 2314885531408816194, m_OEM_revision = 1, m_creator_id = 538976288, m_creator_revision = 16777235},  m_entries = 0x1fb79000}

(gdb) p (g_xsdt->m_header - sizeof(ACPI_SDTH)) / 8

$3 = 6

(gdb) x /8xg 0x1fb79000

0x1fb79000:     0x000000f450434146      0x205348434f421f03
0x1fb79010:     0x2020202043505842      0x4350584200000001
0x1fb79020:     0x1fbdd00000000001      0x000900011fb7a000
0x1fb79030:     0x00000302000000b2      0x0000000000000600

If I calculated the number of entries correctly, there is 6 of them. Why are there entries on addresses between canonical x86_64 address range? Namely entry number 2, 3, 4, 5 and 6?

I thought I parsed the table incorrectly or that I didn't send proper information to the OS when exiting a boot loader. Since I wrote my own boot loader (UEFI) I went back to it, commented out jumping to kernel and just printed out addresses in XSDT table. I get the same exact values as I did from using a debugger (given above).

Does anyone know what I'm doing wrong?

My host Linux kernel is: 6.11.1-arch1-1
My QEMU version is: 9.1.0.
I downloaded an OVMF file from EDK II nighlty build repository (I'm using a version from yesterday, October 4th, 2024) Link to nightly build repository: https://retrage.github.io/edk2-nightly/

I run QEMU with the following command:

Code: Select all

qemu-system-x86_64 -s -S --machine q35 -m 512 -cpu Skylake-Client-v3 -bios path_to_OVMF.fd_file -drive file=path_to_UEFI_file,if=ide -d cpu_reset
Octocontrabass
Member
Member
Posts: 5543
Joined: Mon Mar 25, 2013 7:01 pm

Re: Root ACPI table contains non canonical addresses.

Post by Octocontrabass »

CycloPentane wrote: Sat Oct 05, 2024 4:14 am

Code: Select all

struct __attribute__((packed)) ACPI_XSDT {
    ACPI_SDTH m_header;
    uint64_t* m_entries;
};
You've declared m_entries as a pointer when it's actually an array. It should be something like this:

Code: Select all

struct __attribute__((packed)) ACPI_XSDT {
    ACPI_SDTH m_header;
    uint64_t m_entries[];
};
CycloPentane
Posts: 2
Joined: Sat Oct 05, 2024 12:40 am

Re: Root ACPI table contains non canonical addresses.

Post by CycloPentane »

Octocontrabass wrote: Sat Oct 05, 2024 1:27 pm
You've declared m_entries as a pointer when it's actually an array. It should be something like this:

Code: Select all

struct __attribute__((packed)) ACPI_XSDT {
    ACPI_SDTH m_header;
    uint64_t m_entries[];
};
That did the trick, Thank you !!

Note:
For anyone else who encounters this thread. I made a mistake thinking that the following two structures are equivalent and that they have an equivalent size:

Code: Select all

struct __attribute__((packed)) Wrong_ACPI_XSDT {
    ACPI_SDTH m_header;
    uint64_t* m_entries;
};

struct __attribute__((packed)) Correct_ACPI_XSDT {
    ACPI_SDTH m_header;
    uint64_t m_entries[]
};

sizeof(Wrong_ACPI_XSDT) is 44 bytes.
sizeof(Correct_ACPI_XSDT) is 36 bytes.

Obviously, casting the address of XSDT table into a pointer to XSDT structure will have different result in these two scenarios.
Post Reply