[lib][acpi_lite] update to use the VMM to map acpi tables in

The old method assumed that all of the tables were mapped within the
kmap area of the kernel. This basically works on 64bit machines but on a
32bit x86 its entirely likely the ACPI tables are at higher physical
addresses that can be reached, which is currently limited to 1GB.

By using the VM it means it can individually map the headers and each
individual table.
This commit is contained in:
Travis Geiselbrecht
2022-11-02 02:26:06 -07:00
parent 7ddde308a3
commit 0d0568612a
4 changed files with 196 additions and 46 deletions

View File

@@ -10,44 +10,69 @@
#include <inttypes.h>
#include <string.h>
#include <lk/compiler.h>
#include <lk/cpp.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <kernel/vm.h>
// uses the vm to map in ACPI tables as they are found
static_assert(WITH_KERNEL_VM, "");
#define LOCAL_TRACE 0
// global state of the acpi lite library
struct acpi_lite_state {
const acpi_rsdp* rsdp;
paddr_t rsdp_pa;
const acpi_rsdt_xsdt* sdt;
paddr_t sdt_pa;
bool xsdt; // are the pointers in the SDT 64 or 32bit?
size_t num_tables; // number of top level tables
bool xsdt; // are the pointers 64 or 32bit?
const void **tables; // array of pointers to detected tables
} acpi;
// given a physical address, return a pointer to it from the VM
static const void* phys_to_ptr(uintptr_t pa) {
void *ptr = paddr_to_kvaddr(pa);
return ptr;
// map a region around a physical address
static void *map_region(paddr_t pa, size_t len, const char *name) {
const auto pa_page_aligned = ROUNDDOWN(pa, PAGE_SIZE);
const size_t align_offset = pa - pa_page_aligned;
size_t map_len = ROUNDUP(len + align_offset, PAGE_SIZE);
uint perms = ARCH_MMU_FLAG_PERM_RO;
if (arch_mmu_supports_nx_mappings()) {
perms |= ARCH_MMU_FLAG_PERM_NO_EXECUTE;
}
void *ptr;
status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), name, map_len,
&ptr, 0, pa_page_aligned, 0, perms);
if (err < 0) {
return nullptr;
}
return (void *)((uintptr_t)ptr + align_offset);
}
static uint8_t acpi_checksum(const void* _buf, size_t len) {
uint8_t c = 0;
const uint8_t* buf = static_cast<const uint8_t*>(_buf);
uint8_t c = 0;
for (size_t i = 0; i < len; i++) {
c = (uint8_t)(c + buf[i]);
c += buf[i];
}
return c;
}
static bool validate_rsdp(const acpi_rsdp* rsdp) {
static bool validate_rsdp(const acpi_rsdp* rsdp, bool debug_output = true) {
// check the signature
if (memcmp(ACPI_RSDP_SIG, rsdp->sig, 8)) {
// Generates a huge pile of info as it scans for RSDP
//LTRACEF("acpi rsdp signature failed:\n");
//hexdump8(rsdp->sig, 8);
if (debug_output && LOCAL_TRACE) {
LTRACEF("acpi rsdp signature failed:\n");
hexdump8(rsdp->sig, 8);
}
return false;
}
@@ -78,13 +103,35 @@ static bool validate_rsdp(const acpi_rsdp* rsdp) {
return true;
}
// search the bios region on a PC for the Root System Description Pointer (RSDP)
static paddr_t find_rsdp_pc() {
// search for it in the BIOS EBDA area (0xe0000..0xfffff) on 16 byte boundaries
for (paddr_t ptr = 0xe0000; ptr <= 0xfffff; ptr += 16) {
const auto rsdp = static_cast<const acpi_rsdp*>(phys_to_ptr(ptr));
LTRACE_ENTRY;
if (validate_rsdp(rsdp)) {
return ptr;
const paddr_t range_start = 0xe0000;
const paddr_t range_end = 0x100000;
const size_t len = range_end - range_start;
// map all of the scannable area, 0xe0000...1MB
const uint8_t *bios_ptr;
status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), "acpi rsdp bios area", len,
(void **)&bios_ptr, 0, range_start, 0, ARCH_MMU_FLAG_PERM_RO);
if (err < 0) {
return 0;
}
LTRACEF("bios area mapping at %p\n", bios_ptr);
// free the region when we exit
auto ac = lk::make_auto_call([bios_ptr]() {
vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)bios_ptr);
});
// search for it in the BIOS EBDA area (0xe0000..0xfffff) on 16 byte boundaries
for (size_t i = 0; i < len; i += 16) {
const auto rsdp = reinterpret_cast<const acpi_rsdp*>(bios_ptr + i);
if (validate_rsdp(rsdp, false)) {
LTRACEF("found rsdp at vaddr %p, paddr %#lx\n", bios_ptr + i, range_start + i);
return range_start + i;
}
}
@@ -138,9 +185,9 @@ static bool validate_sdt(const acpi_rsdt_xsdt* sdt, size_t* num_tables, bool* xs
return true;
}
const acpi_sdt_header* acpi_get_table_at_index(size_t index) {
static paddr_t acpi_get_table_pa_at_index(size_t index) {
if (index >= acpi.num_tables) {
return nullptr;
return 0;
}
paddr_t pa;
@@ -149,8 +196,17 @@ const acpi_sdt_header* acpi_get_table_at_index(size_t index) {
} else {
pa = acpi.sdt->addr32[index];
}
LTRACEF("index %zu, pa %#lx\n", index, pa);
return static_cast<const acpi_sdt_header*>(phys_to_ptr(pa));
return pa;
}
static const acpi_sdt_header* acpi_get_table_at_index(size_t index) {
if (index >= acpi.num_tables) {
return nullptr;
}
return static_cast<const acpi_sdt_header *>(acpi.tables[index]);
}
const acpi_sdt_header* acpi_get_table_by_sig(const char* sig) {
@@ -162,24 +218,76 @@ const acpi_sdt_header* acpi_get_table_by_sig(const char* sig) {
}
if (!memcmp(sig, header->sig, 4)) {
// validate the checksum
uint8_t c = acpi_checksum(header, header->length);
if (c == 0) {
return header;
}
// checksum should already have been validated when the table was loaded
return header;
}
}
return nullptr;
}
static status_t initialize_table(size_t i) {
char name[64];
snprintf(name, sizeof(name), "acpi table %zu", i);
const size_t table_initial_len = PAGE_SIZE; // enough to read the header
auto pa = acpi_get_table_pa_at_index(i);
const acpi_sdt_header *header = (const acpi_sdt_header *)map_region(pa, table_initial_len, name);
if (!header) {
dprintf(INFO, "ACPI LITE: failed to map table %zu address %#" PRIxPTR "\n", i, pa);
return ERR_NOT_FOUND;
}
// cleanup the mapping that maps just the first page when we exit
auto cleanup_header_mapping = lk::make_auto_call([header]() {
vmm_free_region(vmm_get_kernel_aspace(), ROUNDDOWN((vaddr_t)header, PAGE_SIZE));
});
// check the header and determine the real size
if (header->length > 1024*1024) {
// probably bogus?
dprintf(INFO, "ACPI LITE: table %zu has length %u, too large\n", i, header->length);
return ERR_NOT_FOUND;
}
if (header->length < sizeof(*header)) {
return ERR_NOT_FOUND;
}
// try to map the real table
char sig[5] = {};
sig[0] = header->sig[0];
sig[1] = header->sig[1];
sig[2] = header->sig[2];
sig[3] = header->sig[3];
snprintf(name, sizeof(name), "acpi table %s", sig);
acpi.tables[i] = map_region(pa, header->length, name);
if (!acpi.tables[i]) {
dprintf(INFO, "ACPI LITE: failed to map table %zu address %#" PRIxPTR "\n", i, pa);
return ERR_NOT_FOUND;
}
LTRACEF("table %zu (%s) mapped at %p\n", i, sig, acpi.tables[i]);
// ODO compute checksum on table?
header = (const acpi_sdt_header *)acpi.tables[i];
uint8_t c = acpi_checksum(header, header->length);
if (c != 0) {
dprintf(INFO, "ACPI LITE: table %zu (%s) fails checksum\n", i, sig);
acpi.tables[i] = nullptr;
return ERR_NOT_FOUND;
}
return NO_ERROR;
}
status_t acpi_lite_init(paddr_t rsdp_pa) {
LTRACEF("passed in rsdp %#" PRIxPTR "\n", rsdp_pa);
// see if the rsdp pointer is valid
if (rsdp_pa == 0) {
// search around for it in a platform-specific way
#if ARCH_X86
#if PLATFORM_PC
rsdp_pa = find_rsdp_pc();
if (rsdp_pa == 0) {
dprintf(INFO, "ACPI LITE: couldn't find ACPI RSDP in BIOS area\n");
@@ -191,39 +299,61 @@ status_t acpi_lite_init(paddr_t rsdp_pa) {
}
}
const void* ptr = phys_to_ptr(rsdp_pa);
if (!ptr) {
dprintf(INFO, "ACPI LITE: failed to translate RSDP address %#" PRIxPTR " to virtual\n",
rsdp_pa);
const size_t rsdp_area_len = 0x1000; // 4K should cover it. TODO: see if it's specced
const void * const rsdp_ptr = map_region(rsdp_pa, rsdp_area_len, "acpi rsdp area");
if (!rsdp_ptr) {
dprintf(INFO, "ACPI LITE: failed to map RSDP address %#" PRIxPTR " to virtual\n", rsdp_pa);
return ERR_NOT_FOUND;
}
LTRACEF("rsdp mapped at %p\n", rsdp_ptr);
// free the region if we abort
auto cleanup_rsdp_mapping = lk::make_auto_call([rsdp_ptr]() {
vmm_free_region(vmm_get_kernel_aspace(), ROUNDDOWN((vaddr_t)rsdp_ptr, PAGE_SIZE));
acpi.rsdp_pa = 0;
acpi.rsdp = nullptr;
});
// see if the RSDP is there
acpi.rsdp = static_cast<const acpi_rsdp*>(ptr);
acpi.rsdp = static_cast<const acpi_rsdp*>(rsdp_ptr);
if (!validate_rsdp(acpi.rsdp)) {
dprintf(INFO, "ACPI LITE: RSDP structure does not check out\n");
return ERR_NOT_FOUND;
}
acpi.rsdp_pa = rsdp_pa;
dprintf(SPEW, "ACPI LITE: RSDP checks out, found at %#lx, revision %u\n", rsdp_pa, acpi.rsdp->revision);
dprintf(SPEW, "ACPI LITE: RSDP checks out, found at %#lx, revision %u\n",
acpi.rsdp_pa, acpi.rsdp->revision);
// find the pointer to either the RSDT or XSDT
acpi.sdt = nullptr;
if (acpi.rsdp->revision < 2) {
// v1 RSDP, pointing at a RSDT
LTRACEF("v1 RSDP, using 32 bit RSDT address %#x\n", acpi.rsdp->rsdt_address);
acpi.sdt = static_cast<const acpi_rsdt_xsdt*>(phys_to_ptr(acpi.rsdp->rsdt_address));
acpi.sdt_pa = acpi.rsdp->rsdt_address;
} else {
// v2+ RSDP, pointing at a XSDT
// try to use the 64bit address first
LTRACEF("v2+ RSDP, trying 64 bit XSDT address %#" PRIx64 "\n", acpi.rsdp->xsdt_address);
acpi.sdt = static_cast<const acpi_rsdt_xsdt*>(phys_to_ptr(acpi.rsdp->xsdt_address));
if (!acpi.sdt) {
LTRACEF("v2+ RSDP, falling back to 32 RSDT address %#" PRIx32 "\n", acpi.rsdp->rsdt_address);
acpi.sdt = static_cast<const acpi_rsdt_xsdt*>(phys_to_ptr(acpi.rsdp->rsdt_address));
}
LTRACEF("v2+ RSDP, usingying 64 bit XSDT address %#" PRIx64 "\n", acpi.rsdp->xsdt_address);
acpi.sdt_pa = acpi.rsdp->xsdt_address;
}
// map the *sdt somewhere
const size_t sdt_area_len = 0x1000; // 4K should cover it. TODO: see if it's specced
const void * const sdt_ptr = map_region(acpi.sdt_pa, sdt_area_len, "acpi sdt area");
if (!sdt_ptr) {
dprintf(INFO, "ACPI LITE: failed to map SDT address %#" PRIxPTR " to virtual\n", acpi.sdt_pa);
return ERR_NOT_FOUND;
}
LTRACEF("sdt mapped at %p\n", sdt_ptr);
auto cleanup_sdt_mapping = lk::make_auto_call([sdt_ptr]() {
vmm_free_region(vmm_get_kernel_aspace(), ROUNDDOWN((vaddr_t)sdt_ptr, PAGE_SIZE));
acpi.sdt_pa = 0;
acpi.sdt = nullptr;
});
acpi.sdt = static_cast<const acpi_rsdt_xsdt *>(sdt_ptr);
if (!validate_sdt(acpi.sdt, &acpi.num_tables, &acpi.xsdt)) {
dprintf(INFO, "ACPI LITE: RSDT/XSDT structure does not check out\n");
return ERR_NOT_FOUND;
@@ -231,20 +361,36 @@ status_t acpi_lite_init(paddr_t rsdp_pa) {
dprintf(SPEW, "ACPI LITE: RSDT/XSDT checks out, %zu tables\n", acpi.num_tables);
// map all of the tables in
acpi.tables = new const void *[acpi.num_tables];
for (size_t i = 0; i < acpi.num_tables; i++) {
status_t err = initialize_table(i);
if (err < 0) {
dprintf(INFO, "ACPI LITE: failed to initialize table %zu\n", i);
// for now, simply continue, the table entry should not be initialized
}
}
// we should be initialized at this point
cleanup_sdt_mapping.cancel();
cleanup_rsdp_mapping.cancel();
if (LOCAL_TRACE) {
acpi_lite_dump_tables();
acpi_lite_dump_tables(false);
}
return NO_ERROR;
}
void acpi_lite_dump_tables() {
void acpi_lite_dump_tables(bool full_dump) {
if (!acpi.sdt) {
return;
}
printf("root table:\n");
hexdump(acpi.sdt, acpi.sdt->header.length);
if (full_dump) {
hexdump(acpi.sdt, acpi.sdt->header.length);
}
// walk the table list
for (size_t i = 0; i < acpi.num_tables; i++) {
@@ -255,7 +401,9 @@ void acpi_lite_dump_tables() {
printf("table %zu: '%c%c%c%c' len %u\n", i, header->sig[0], header->sig[1], header->sig[2],
header->sig[3], header->length);
hexdump(header, header->length);
if (full_dump) {
hexdump(header, header->length);
}
}
}

View File

@@ -8,6 +8,7 @@
#include <lib/acpi_lite/structs.h>
#include <stdbool.h>
#include <stdint.h>
#include <lk/compiler.h>
#include <assert.h>
@@ -15,10 +16,9 @@
__BEGIN_CDECLS
status_t acpi_lite_init(paddr_t rsdt);
void acpi_lite_dump_tables(void);
void acpi_lite_dump_tables(bool full_dump);
const struct acpi_sdt_header* acpi_get_table_by_sig(const char* sig);
const struct acpi_sdt_header* acpi_get_table_at_index(size_t index);
// A routine to iterate over all the MADT entries of a particular type via a callback
//using MadtEntryCallback = fbl::Function<void(const void* entry, size_t entry_len)>;

View File

@@ -2,6 +2,8 @@ LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
MODULE_OPTIONS := extra_warnings
MODULE_DEPS := \
lib/libcpp