blob: 7eb108691c115a33cee52ae3709957921335cba8 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/virtualization/bin/vmm/arch/x64/acpi.h"
#include <fcntl.h>
#include <lib/syslog/cpp/macros.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <acpica/acpi.h>
#include <fbl/unique_fd.h>
#include "src/virtualization/bin/vmm/arch/x64/io_port.h"
static uint8_t acpi_checksum(void* table, uint32_t length) {
uint8_t sum = 0;
uint8_t* start = reinterpret_cast<uint8_t*>(table);
uint8_t* end = start + length;
for (; start != end; ++start) {
sum = static_cast<uint8_t>(sum + *start);
}
return static_cast<uint8_t>(UINT8_MAX - sum + 1);
}
static void acpi_header(ACPI_TABLE_HEADER* header, const char* table_id, const char* signature,
uint8_t revision, uint32_t length) {
memset(header, 0, sizeof(*header));
memcpy(header->Signature, signature, ACPI_NAMESEG_SIZE);
header->Revision = revision;
header->Length = length;
memcpy(header->OemId, "ZX", 2);
memcpy(header->OemTableId, table_id, ACPI_OEM_TABLE_ID_SIZE);
header->OemRevision = 0;
header->Checksum = acpi_checksum(header, header->Length);
}
static zx_status_t load_file(const char* path, const PhysMem& phys_mem, uint32_t off,
uint32_t* actual) {
fbl::unique_fd fd(open(path, O_RDONLY));
if (!fd) {
FX_LOGS(ERROR) << "Failed to open ACPI table " << path;
return ZX_ERR_IO;
}
struct stat stat;
ssize_t ret = fstat(fd.get(), &stat);
if (ret < 0) {
FX_LOGS(ERROR) << "Failed to stat ACPI table " << path;
return ZX_ERR_IO;
}
ret = read(fd.get(), phys_mem.as<void>(off, stat.st_size), stat.st_size);
if (ret != stat.st_size) {
FX_LOGS(ERROR) << "Failed to read ACPI table " << path;
return ZX_ERR_IO;
}
*actual = static_cast<uint32_t>(stat.st_size);
return ZX_OK;
}
template <typename T>
static T* madt_subtable(void* base, uint32_t off, uint8_t type) {
auto subtable = reinterpret_cast<T*>(static_cast<uint8_t*>(base) + off);
subtable->Header.Type = type;
subtable->Header.Length = sizeof(T);
return subtable;
}
static zx_status_t create_madt(ACPI_TABLE_MADT* madt, zx_vaddr_t io_apic_addr, uint8_t num_cpus,
uint32_t* actual) {
uint32_t table_size =
static_cast<uint32_t>(sizeof(ACPI_TABLE_MADT) + (num_cpus * sizeof(ACPI_MADT_LOCAL_APIC)) +
sizeof(ACPI_MADT_IO_APIC));
uint32_t offset = sizeof(ACPI_TABLE_MADT);
for (uint8_t id = 0; id < num_cpus; ++id) {
auto local_apic = madt_subtable<ACPI_MADT_LOCAL_APIC>(madt, offset, ACPI_MADT_TYPE_LOCAL_APIC);
local_apic->ProcessorId = id;
local_apic->Id = id;
local_apic->LapicFlags = ACPI_MADT_ENABLED;
offset += static_cast<uint32_t>(sizeof(ACPI_MADT_LOCAL_APIC));
}
auto io_apic = madt_subtable<ACPI_MADT_IO_APIC>(madt, offset, ACPI_MADT_TYPE_IO_APIC);
io_apic->Reserved = 0;
io_apic->Address = static_cast<uint32_t>(io_apic_addr);
io_apic->GlobalIrqBase = 0;
// add header, computing checksum for the entire table
acpi_header(&madt->Header, "ZX MADT", ACPI_SIG_MADT, 4, table_size);
*actual = table_size;
return ZX_OK;
}
zx_status_t create_acpi_table(const AcpiConfig& cfg, const PhysMem& phys_mem) {
if (phys_mem.size() < kAcpiOffset + PAGE_SIZE) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
const uint32_t rsdt_entries = 3;
const uint32_t rsdt_length = sizeof(ACPI_TABLE_RSDT) + (rsdt_entries - 1) * sizeof(uint32_t);
// RSDP. ACPI 1.0.
auto rsdp = phys_mem.as<ACPI_RSDP_COMMON>(kAcpiOffset);
ACPI_MAKE_RSDP_SIG(rsdp->Signature);
memcpy(rsdp->OemId, "ZX", 2);
rsdp->RsdtPhysicalAddress = static_cast<uint32_t>(kAcpiOffset + sizeof(ACPI_RSDP_COMMON));
rsdp->Checksum = acpi_checksum(rsdp, ACPI_RSDP_CHECKSUM_LENGTH);
// FADT.
const uint32_t fadt_off = rsdp->RsdtPhysicalAddress + rsdt_length;
auto fadt = phys_mem.as<ACPI_TABLE_FADT>(fadt_off);
const uint32_t dsdt_off = static_cast<uint32_t>(fadt_off + sizeof(ACPI_TABLE_FADT));
fadt->Dsdt = dsdt_off;
fadt->Pm1aEventBlock = kPm1EventPort;
fadt->Pm1EventLength = (ACPI_PM1_REGISTER_WIDTH / 8) * 2 /* enable and status registers */;
fadt->Pm1aControlBlock = kPm1ControlPort;
fadt->Pm1ControlLength = ACPI_PM1_REGISTER_WIDTH / 8;
// Table ID must match RSDT.
acpi_header(&fadt->Header, "ZX ACPI", ACPI_SIG_FADT, 6, sizeof(ACPI_TABLE_FADT));
// DSDT.
uint32_t actual;
zx_status_t status = load_file(cfg.dsdt_path, phys_mem, dsdt_off, &actual);
if (status != ZX_OK) {
return status;
}
// MADT.
const uint32_t madt_off = dsdt_off + actual;
status = create_madt(phys_mem.as<ACPI_TABLE_MADT>(madt_off), cfg.io_apic_addr, cfg.cpus, &actual);
if (status != ZX_OK) {
return status;
}
// MCFG.
const uint32_t mcfg_off = madt_off + actual;
status = load_file(cfg.mcfg_path, phys_mem, mcfg_off, &actual);
if (status != ZX_OK) {
return status;
}
// RSDT.
auto rsdt = phys_mem.as<ACPI_TABLE_RSDT>(rsdp->RsdtPhysicalAddress);
rsdt->TableOffsetEntry[0] = fadt_off;
rsdt->TableOffsetEntry[1] = madt_off;
rsdt->TableOffsetEntry[2] = mcfg_off;
// Table ID must match FADT.
acpi_header(&rsdt->Header, "ZX ACPI", ACPI_SIG_RSDT, 1, rsdt_length);
return ZX_OK;
}