blob: e71122e66582b0b7c1ed9d96419c98fd3591b5fc [file] [log] [blame]
// Copyright 2020 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/assert.h>
#include <zircon/compiler.h>
#include <pretty/hexdump.h>
#include "debug.h"
#define LOCAL_TRACE 0
namespace acpi_lite {
namespace {
// Map a variable-length structure into memory.
//
// Perform a two-phase PhysToPtr conversion:
//
// 1. We first read a fixed-sized header.
// 2. We next determine the length of the structure by reading the fields.
// 3. We finally map in the full size of the structure.
//
// This allows us to handle the common use-case where the number of bytes that need
// to be accessed at a particular address cannot be determined until we first read
// a header at that address.
template <typename T>
zx::status<const T*> MapStructure(PhysMemReader& reader, zx_paddr_t phys) {
// Try and read the header.
zx::status<const void*> result = reader.PhysToPtr(phys, sizeof(T));
if (result.is_error()) {
return result.take_error();
}
const T* header = static_cast<const T*>(result.value());
// Ensure that the length looks reasonable.
if (header->size() < sizeof(T)) {
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
// Get the number of bytes the full structure needs, as determined by its header.
result = reader.PhysToPtr(phys, header->size());
if (result.is_error()) {
return result.take_error();
}
return zx::success(static_cast<const T*>(result.value()));
}
bool ValidateRsdp(const AcpiRsdp* rsdp) {
// Verify the RSDP signature.
if (rsdp->sig1 != AcpiRsdp::kSignature1 || rsdp->sig2 != AcpiRsdp::kSignature2) {
return false;
}
// Validate the checksum on the V1 header.
if (!AcpiChecksumValid(rsdp, sizeof(AcpiRsdp))) {
return false;
}
return true;
}
struct RootSystemTableDetails {
uint64_t rsdp_address;
uint32_t rsdt_address;
uint64_t xsdt_address;
};
zx::status<RootSystemTableDetails> ParseRsdp(PhysMemReader& reader, zx_paddr_t rsdp_pa) {
// Read the header.
zx::status<const void*> maybe_rsdp_v1 = reader.PhysToPtr(rsdp_pa, sizeof(AcpiRsdp));
if (maybe_rsdp_v1.is_error()) {
return maybe_rsdp_v1.take_error();
}
auto* rsdp_v1 = static_cast<const AcpiRsdp*>(maybe_rsdp_v1.value());
// Verify the V1 header details.
if (!ValidateRsdp(rsdp_v1)) {
return zx::error(ZX_ERR_NOT_FOUND);
}
// If this is just a V1 RSDP, parse it and finish up.
if (rsdp_v1->revision < 2) {
return zx::success(RootSystemTableDetails{
.rsdp_address = rsdp_pa,
.rsdt_address = rsdp_v1->rsdt_address,
.xsdt_address = 0,
});
}
// Try and map the larger V2 structure.
zx::status<const AcpiRsdpV2*> rsdp_v2 = MapStructure<AcpiRsdpV2>(reader, rsdp_pa);
if (rsdp_v2.is_error()) {
return rsdp_v2.take_error();
}
// Validate the checksum of the larger structure.
if (!AcpiChecksumValid(rsdp_v2.value(), rsdp_v2.value()->length)) {
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::success(RootSystemTableDetails{
.rsdp_address = rsdp_pa,
.rsdt_address = rsdp_v2.value()->v1.rsdt_address,
.xsdt_address = rsdp_v2.value()->xsdt_address,
});
}
#if defined(__x86_64__) || defined(__i386__)
// Search for a valid RSDP in the BIOS read-only memory space in [0xe0000..0xfffff],
// on 16 byte boundaries.
//
// Return 0 if no RSDP found.
//
// Reference: ACPI v6.3, Section 5.2.5.1
zx::status<zx_paddr_t> FindRsdpPc(PhysMemReader& reader) {
// Get a virtual address for the read-only BIOS range.
zx::status<const void*> maybe_bios_section =
reader.PhysToPtr(kBiosReadOnlyAreaStart, kBiosReadOnlyAreaLength);
if (maybe_bios_section.is_error()) {
return maybe_bios_section.take_error();
}
auto* bios_section = static_cast<const uint8_t*>(maybe_bios_section.value());
// Try every 16-byte offset from 0xe0'0000 to 0xff'ffff, until we have no room left for an
// AcpiRsdp struct.
for (size_t offset = 0x0; offset <= kBiosReadOnlyAreaLength - sizeof(AcpiRsdp); offset += 16) {
const auto rsdp = reinterpret_cast<const AcpiRsdp*>(bios_section + offset);
if (ValidateRsdp(rsdp)) {
return zx::success(kBiosReadOnlyAreaStart + offset);
}
}
return zx::error(ZX_ERR_NOT_FOUND);
}
#endif
zx::status<RootSystemTableDetails> FindRootTables(PhysMemReader& physmem_reader,
zx_paddr_t rsdp_pa) {
// If the user gave us an explicit RSDP, just use that directly.
if (rsdp_pa != 0) {
return ParseRsdp(physmem_reader, rsdp_pa);
}
// Otherwise, attempt to find it in a platform-specific way.
#if defined(__x86_64__) || defined(__i386__)
{
zx::status<zx_paddr_t> result = FindRsdpPc(physmem_reader);
if (result.is_ok()) {
LOG_DEBUG("ACPI LITE: Found RSDP at physical address %#" PRIxPTR ".\n", result.value());
return ParseRsdp(physmem_reader, result.value());
}
LOG_INFO("ACPI LITE: Couldn't find ACPI RSDP in BIOS area\n");
}
#endif
return zx::error(ZX_ERR_NOT_FOUND);
}
} // namespace
bool AcpiChecksumValid(const void* buf, size_t len) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
// If we are fuzzing, calculate by don't verify checksums.
AcpiChecksum(buf, len);
return true;
#else
return AcpiChecksum(buf, len) == 0;
#endif
}
uint8_t AcpiChecksum(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 = static_cast<uint8_t>(c + buf[i]);
}
// The checksum is valid if the sum of bytes mod 256 == 0.
//
// We return "-c" here. This doesn't change a valid checksum, and allows
// code calculating checksums to use to code:
//
// foo.checksum = AcpiChecksum(&foo, sizeof(foo));
//
return -c;
}
zx::status<const AcpiRsdt*> ValidateRsdt(PhysMemReader& reader, uint32_t rsdt_pa,
size_t* num_tables) {
// Map in the RSDT.
zx::status<const AcpiRsdt*> rsdt = MapStructure<AcpiRsdt>(reader, rsdt_pa);
if (rsdt.is_error()) {
return rsdt.take_error();
}
// Ensure we have an RSDT signature.
if (rsdt.value()->header.sig != AcpiRsdt::kSignature) {
return zx::error(ZX_ERR_NOT_FOUND);
}
// Validate checksum.
if (!AcpiChecksumValid(rsdt.value(), rsdt.value()->header.length)) {
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
// Ensure this is a revision we understand.
if (rsdt.value()->header.revision != 1) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// Compute the number of tables we have.
*num_tables = (rsdt.value()->header.length - sizeof(AcpiSdtHeader)) / sizeof(uint32_t);
return rsdt;
}
zx::status<const AcpiXsdt*> ValidateXsdt(PhysMemReader& reader, uint64_t xsdt_pa,
size_t* num_tables) {
// Map in the XSDT.
zx::status<const AcpiXsdt*> xsdt =
MapStructure<AcpiXsdt>(reader, static_cast<zx_paddr_t>(xsdt_pa));
if (xsdt.is_error()) {
return xsdt.take_error();
}
// Ensure we have an XSDT signature.
if (xsdt.value()->header.sig != AcpiXsdt::kSignature) {
return zx::error(ZX_ERR_NOT_FOUND);
}
// Validate checksum.
if (!AcpiChecksumValid(xsdt.value(), xsdt.value()->header.length)) {
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
// Ensure this is a revision we understand.
if (xsdt.value()->header.revision != 1) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// Compute the number of tables we have.
*num_tables = (xsdt.value()->header.length - sizeof(AcpiSdtHeader)) / sizeof(uint64_t);
return xsdt;
}
zx_paddr_t AcpiParser::GetTablePhysAddr(size_t index) const {
if (index >= num_tables_) {
return 0;
}
// Get the physical address for the index'th table.
return xsdt_ != nullptr ? static_cast<zx_paddr_t>(xsdt_->addr64[index]) : rsdt_->addr32[index];
}
const AcpiSdtHeader* AcpiParser::GetTableAtIndex(size_t index) const {
zx_paddr_t paddr = GetTablePhysAddr(index);
if (paddr == 0) {
return nullptr;
}
// Map it in.
return MapStructure<AcpiSdtHeader>(*reader_, paddr).value_or(nullptr);
}
const AcpiSdtHeader* GetTableBySignature(const AcpiParserInterface& parser, AcpiSignature sig) {
size_t num_tables = parser.num_tables();
for (size_t i = 0; i < num_tables; i++) {
const AcpiSdtHeader* header = parser.GetTableAtIndex(i);
if (!header) {
continue;
}
// Continue searching if the header doesn't match.
if (sig != header->sig) {
continue;
}
// If the checksum is invalid, keep searching.
if (!AcpiChecksumValid(header, header->length)) {
continue;
}
return header;
}
return nullptr;
}
zx::status<AcpiParser> AcpiParser::Init(PhysMemReader& physmem_reader, zx_paddr_t rsdp_pa) {
// Find the root tables.
zx::status<RootSystemTableDetails> root_tables = FindRootTables(physmem_reader, rsdp_pa);
if (root_tables.is_error()) {
LOG_INFO("ACPI LITE: Could not validate RSDP structure: %" PRId32 "\n",
root_tables.error_value());
return root_tables.take_error();
}
// Validate the tables.
auto parser = [&]() -> zx::status<AcpiParser> {
// If an XSDT table exists, try using it first.
if (root_tables.value().xsdt_address != 0) {
size_t num_tables = 0;
zx::status<const AcpiXsdt*> xsdt =
ValidateXsdt(physmem_reader, root_tables.value().xsdt_address, &num_tables);
if (xsdt.is_ok()) {
LOG_DEBUG("ACPI LITE: Found valid XSDT table at physical address %#" PRIx64 "\n",
root_tables.value().xsdt_address);
return zx::success(AcpiParser(physmem_reader,
static_cast<zx_paddr_t>(root_tables.value().rsdp_address),
/*rsdt=*/nullptr, xsdt.value(), num_tables,
static_cast<zx_paddr_t>(root_tables.value().xsdt_address)));
}
LOG_DEBUG("ACPI LITE: Invalid XSDT table at physical address %#" PRIx64 "\n",
root_tables.value().xsdt_address);
}
// Otherwise, try using the RSDT.
if (root_tables.value().rsdt_address != 0) {
size_t num_tables = 0;
zx::status<const AcpiRsdt*> rsdt =
ValidateRsdt(physmem_reader, root_tables.value().rsdt_address, &num_tables);
if (rsdt.is_ok()) {
LOG_DEBUG("ACPI LITE: Found valid RSDT table at physical address %#" PRIx32 "\n",
root_tables.value().rsdt_address);
return zx::success(AcpiParser(
physmem_reader, static_cast<zx_paddr_t>(root_tables.value().rsdp_address), rsdt.value(),
/*xsdt=*/nullptr, num_tables, root_tables.value().rsdt_address));
}
LOG_DEBUG("ACPI LITE: Invalid RSDT table at physical address %#" PRIx32 "\n",
root_tables.value().rsdt_address);
}
// Nothing found.
return zx::error(ZX_ERR_NOT_FOUND);
}();
if (LOCAL_TRACE && parser.is_ok()) {
parser.value().DumpTables();
}
return parser;
}
void AcpiParser::DumpTables() const {
printf("root table at paddr %#" PRIxPTR ":\n", root_table_addr_);
if (xsdt_ != nullptr) {
hexdump(xsdt_, xsdt_->header.length);
} else {
ZX_DEBUG_ASSERT(rsdt_ != nullptr);
hexdump(rsdt_, rsdt_->header.length);
}
// walk the table list
for (size_t i = 0; i < num_tables_; i++) {
const auto header = GetTableAtIndex(i);
if (!header) {
continue;
}
char name[AcpiSignature::kAsciiLength + 1];
header->sig.WriteToBuffer(name);
printf("table %zu: '%s' at paddr %#" PRIxPTR ", len %" PRIu32 "\n", i, name,
GetTablePhysAddr(i), header->length);
hexdump(header, header->length);
}
}
} // namespace acpi_lite