blob: 29bc868ffc9ef022104c062a7cbe6e8ada2dc3f9 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/acpi_lite.h>
#include <inttypes.h>
#include <zircon/compiler.h>
#include <trace.h>
#include <vm/physmap.h>
#define LOCAL_TRACE 1
// global state of the acpi lite library
struct acpi_lite_state {
const acpi_rsdp *rsdp;
const acpi_rsdt_xsdt *sdt;
size_t num_tables; // number of top level tables
bool xsdt; // are the pointers 64 or 32bit?
} acpi;
static inline const void *phys_to_ptr(uintptr_t pa) {
if (!is_physmap_phys_addr(pa)) {
return nullptr;
}
// consider 0 to be invalid
if (pa == 0) {
return nullptr;
}
return static_cast<const void *>(paddr_to_physmap(pa));
}
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);
for (size_t i = 0; i < len; i++) {
c = (uint8_t)(c + buf[i]);
}
return c;
}
static bool validate_rsdp(const acpi_rsdp *rsdp) {
// check the signature
if (memcmp(ACPI_RSDP_SIG, rsdp->sig, 8)) {
return false;
}
// validate the v1 checksum on the first 20 bytes of the table
uint8_t c = acpi_checksum(rsdp, 20);
if (c) {
return false;
}
// is it v2?
if (rsdp->revision >= 2) {
if (rsdp->length < 36 || rsdp->length > 4096) {
// keep the table length within reason
return false;
}
c = acpi_checksum(rsdp, rsdp->length);
if (c) {
return false;
}
}
// seems okay
return true;
}
static zx_paddr_t find_rsdp_pc() {
// search for it in the BIOS EBDA area (0xe0000..0xfffff) on 16 byte boundaries
for (zx_paddr_t ptr = 0xe0000; ptr <= 0xfffff; ptr += 16) {
const auto rsdp = static_cast<const acpi_rsdp *>(phys_to_ptr(ptr));
if (validate_rsdp(rsdp)) {
return ptr;
}
}
return 0;
}
static bool validate_sdt(const acpi_rsdt_xsdt *sdt, size_t *num_tables, bool *xsdt) {
LTRACEF("%p\n", sdt);
// check the signature and see if it's a rsdt or xsdt
if (!memcmp(sdt->header.sig, "XSDT", 4)) {
*xsdt = true;
} else if (!memcmp(sdt->header.sig, "RSDT", 4)) {
*xsdt = false;
} else {
return false;
}
// is the length sane?
if (sdt->header.length < 36 || sdt->header.length > 4096) {
return false;
}
// is it a revision we understand?
if (sdt->header.revision != 1) {
return false;
}
// checksum the entire table
uint8_t c = acpi_checksum(sdt, sdt->header.length);
if (c) {
return false;
}
// compute the number of pointers to tables we have
*num_tables = (sdt->header.length - 36u) / (*xsdt ? 8u : 4u);
// looks okay
return true;
}
const acpi_sdt_header *acpi_get_table_at_index(size_t index) {
if (index >= acpi.num_tables) {
return nullptr;
}
zx_paddr_t pa;
if (acpi.xsdt) {
pa = acpi.sdt->addr64[index];
} else {
pa = acpi.sdt->addr32[index];
}
return static_cast<const acpi_sdt_header *>(phys_to_ptr(pa));
}
const acpi_sdt_header *acpi_get_table_by_sig(const char *sig) {
// walk the list of tables
for (size_t i = 0; i < acpi.num_tables; i++) {
const auto header = acpi_get_table_at_index(i);
if (!header) {
continue;
}
if (!memcmp(sig, header->sig, 4)) {
// validate the checksum
uint8_t c = acpi_checksum(header, header->length);
if (c == 0) {
return header;
}
}
}
return nullptr;
}
zx_status_t acpi_lite_init(zx_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 PLATFORM_PC
rsdp_pa = find_rsdp_pc();
if (rsdp_pa == 0) {
dprintf(INFO, "ACPI LITE: couldn't find ACPI RSDP in BIOS area\n");
}
#endif
if (rsdp_pa == 0) {
return ZX_ERR_NOT_FOUND;
}
}
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);
return ZX_ERR_NOT_FOUND;
}
// see if the RSDP is there
acpi.rsdp = static_cast<const acpi_rsdp *>(ptr);
if (!validate_rsdp(acpi.rsdp)) {
dprintf(INFO, "ACPI LITE: RSDP structure does not check out\n");
return ZX_ERR_NOT_FOUND;
}
dprintf(SPEW, "ACPI LITE: RSDP checks out\n");
// find the pointer to either the RSDT or XSDT
acpi.sdt = nullptr;
if (acpi.rsdp->revision < 2) {
// v1 RSDP, pointing at a RSDT
acpi.sdt = static_cast<const acpi_rsdt_xsdt *>(phys_to_ptr(acpi.rsdp->rsdt_address));
} else {
// v2+ RSDP, pointing at a XSDT
// try to use the 64bit address first
acpi.sdt = static_cast<const acpi_rsdt_xsdt *>(phys_to_ptr(acpi.rsdp->xsdt_address));
if (!acpi.sdt) {
acpi.sdt = static_cast<const acpi_rsdt_xsdt *>(phys_to_ptr(acpi.rsdp->rsdt_address));
}
}
if (!validate_sdt(acpi.sdt, &acpi.num_tables, &acpi.xsdt)) {
dprintf(INFO, "ACPI LITE: RSDT/XSDT structure does not check out\n");
return ZX_ERR_NOT_FOUND;
}
dprintf(SPEW, "ACPI LITE: RSDT/XSDT checks out, %zu tables\n", acpi.num_tables);
acpi_lite_dump_tables();
return ZX_OK;
}
void acpi_lite_dump_tables() {
if (!acpi.sdt) {
return;
}
//printf("root table:\n");
//hexdump(acpi.sdt, acpi.sdt->header.length);
// walk the table list
for (size_t i = 0; i < acpi.num_tables; i++) {
const auto header = acpi_get_table_at_index(i);
if (!header) {
continue;
}
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);
}
}
zx_status_t acpi_process_madt_entries_etc(const uint8_t search_type, const MadtEntryCallback& callback) {
const acpi_madt_table *madt = reinterpret_cast<const acpi_madt_table *>(acpi_get_table_by_sig(ACPI_MADT_SIG));
if (!madt) {
return ZX_ERR_NOT_FOUND;
}
// bytewise array of the same table
const uint8_t *madt_array = reinterpret_cast<const uint8_t *>(madt);
// walk the table off the end of the header, looking for the requested type
size_t off = sizeof(*madt);
while (off < madt->header.length) {
uint8_t type = madt_array[off];
uint8_t length = madt_array[off + 1];
if (type == search_type) {
callback(static_cast<const void *>(&madt_array[off]) , length);
}
off += length;
}
return ZX_OK;
}