| // 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; |
| } |
| |
| |