blob: c04f1e8a2c966197dc8515966733a97ab4ae4346 [file] [log] [blame]
// Copyright 2019 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 <lib/acpi_tables.h>
#include <assert.h>
#include <err.h>
#include <lk/init.h>
#include <trace.h>
#define LOCAL_TRACE 0
namespace {
constexpr uint32_t kAcpiMaxInitTables = 32;
ACPI_TABLE_DESC acpi_tables[kAcpiMaxInitTables];
} // namespace
bool AcpiTables::initialized_ = false;
void AcpiTables::Initialize(uint32_t) {
DEBUG_ASSERT(!initialized_);
const auto status = AcpiInitializeTables(acpi_tables, kAcpiMaxInitTables, FALSE);
if (status == AE_NOT_FOUND) {
TRACEF("WARNING: could not find ACPI tables\n");
return;
} else if (status == AE_NO_MEMORY) {
TRACEF("WARNING: could not initialize ACPI tables, no memory\n");
return;
} else if (status != AE_OK) {
TRACEF("WARNING: could not initialize ACPI tables for unknown reason\n");
return;
}
initialized_ = true;
LTRACEF("ACPI tables initialized\n");
}
/* initialize ACPI tables as soon as we have a working VM */
LK_INIT_HOOK(acpi_tables, &AcpiTables::Initialize, LK_INIT_LEVEL_VM + 1);
zx_status_t AcpiTables::cpu_count(uint32_t* cpu_count) const {
uint32_t count = 0;
auto visitor = [&count](ACPI_SUBTABLE_HEADER* record) {
ACPI_MADT_LOCAL_APIC* lapic = (ACPI_MADT_LOCAL_APIC*)record;
if (!(lapic->LapicFlags & ACPI_MADT_ENABLED)) {
LTRACEF("Skipping disabled processor %02x\n", lapic->Id);
return ZX_OK;
}
count++;
return ZX_OK;
};
auto status = ForEachInMadt(ACPI_MADT_TYPE_LOCAL_APIC, visitor);
if (status != ZX_OK) {
return status;
}
*cpu_count = count;
return ZX_OK;
}
zx_status_t AcpiTables::cpu_apic_ids(uint32_t* apic_ids, uint32_t array_size,
uint32_t* apic_id_count) const {
uint32_t count = 0;
auto visitor = [apic_ids, array_size, &count](ACPI_SUBTABLE_HEADER* record) {
ACPI_MADT_LOCAL_APIC* lapic = (ACPI_MADT_LOCAL_APIC*)record;
if (!(lapic->LapicFlags & ACPI_MADT_ENABLED)) {
LTRACEF("Skipping disabled processor %02x\n", lapic->Id);
return ZX_OK;
}
if (count >= array_size) {
return ZX_ERR_INVALID_ARGS;
}
apic_ids[count++] = lapic->Id;
return ZX_OK;
};
auto status = ForEachInMadt(ACPI_MADT_TYPE_LOCAL_APIC, visitor);
if (status != ZX_OK) {
return status;
}
*apic_id_count = count;
return ZX_OK;
}
zx_status_t AcpiTables::io_apic_count(uint32_t* io_apics_count) const {
return NumInMadt(ACPI_MADT_TYPE_IO_APIC, io_apics_count);
}
zx_status_t AcpiTables::io_apics(io_apic_descriptor* io_apics, uint32_t array_size,
uint32_t* io_apics_count) const {
uint32_t count = 0;
auto visitor = [io_apics, array_size, &count](ACPI_SUBTABLE_HEADER* record) {
ACPI_MADT_IO_APIC* io_apic = (ACPI_MADT_IO_APIC*)record;
if (count >= array_size) {
return ZX_ERR_INVALID_ARGS;
}
io_apics[count].apic_id = io_apic->Id;
io_apics[count].paddr = io_apic->Address;
io_apics[count].global_irq_base = io_apic->GlobalIrqBase;
count++;
return ZX_OK;
};
auto status = ForEachInMadt(ACPI_MADT_TYPE_IO_APIC, visitor);
if (status != ZX_OK) {
return status;
}
*io_apics_count = count;
return ZX_OK;
}
zx_status_t AcpiTables::interrupt_source_overrides_count(uint32_t* overrides_count) const {
return NumInMadt(ACPI_MADT_TYPE_INTERRUPT_OVERRIDE, overrides_count);
}
zx_status_t AcpiTables::interrupt_source_overrides(
io_apic_isa_override* overrides, uint32_t array_size, uint32_t* overrides_count) const {
uint32_t count = 0;
auto visitor = [overrides, array_size, &count](ACPI_SUBTABLE_HEADER* record) {
if (count >= array_size) {
return ZX_ERR_INVALID_ARGS;
}
ACPI_MADT_INTERRUPT_OVERRIDE* iso = (ACPI_MADT_INTERRUPT_OVERRIDE*)record;
ASSERT(iso->Bus == 0); // 0 means ISA, ISOs are only ever for ISA IRQs
overrides[count].isa_irq = iso->SourceIrq;
overrides[count].remapped = true;
overrides[count].global_irq = iso->GlobalIrq;
uint32_t flags = iso->IntiFlags;
uint32_t polarity = flags & ACPI_MADT_POLARITY_MASK;
uint32_t trigger = flags & ACPI_MADT_TRIGGER_MASK;
// Conforms below means conforms to the bus spec. ISA is
// edge triggered and active high.
switch (polarity) {
case ACPI_MADT_POLARITY_CONFORMS:
case ACPI_MADT_POLARITY_ACTIVE_HIGH:
overrides[count].pol = IRQ_POLARITY_ACTIVE_HIGH;
break;
case ACPI_MADT_POLARITY_ACTIVE_LOW:
overrides[count].pol = IRQ_POLARITY_ACTIVE_LOW;
break;
default:
panic("Unknown IRQ polarity in override: %u\n",
polarity);
}
switch (trigger) {
case ACPI_MADT_TRIGGER_CONFORMS:
case ACPI_MADT_TRIGGER_EDGE:
overrides[count].tm = IRQ_TRIGGER_MODE_EDGE;
break;
case ACPI_MADT_TRIGGER_LEVEL:
overrides[count].tm = IRQ_TRIGGER_MODE_LEVEL;
break;
default:
panic("Unknown IRQ trigger in override: %u\n",
trigger);
}
count++;
return ZX_OK;
};
auto status = ForEachInMadt(ACPI_MADT_TYPE_INTERRUPT_OVERRIDE, visitor);
if (status != ZX_OK) {
return status;
}
*overrides_count = count;
return ZX_OK;
}
zx_status_t AcpiTables::NumInMadt(uint8_t type, uint32_t* element_count) const {
uint32_t count = 0;
auto visitor = [&count](ACPI_SUBTABLE_HEADER* record) {
count++;
return ZX_OK;
};
auto status = ForEachInMadt(type, visitor);
if (status != ZX_OK) {
return status;
}
*element_count = count;
return ZX_OK;
}
template <typename V>
zx_status_t AcpiTables::ForEachInMadt(uint8_t type, V visitor) const {
uintptr_t records_start, records_end;
zx_status_t status = GetMadtRecordLimits(&records_start, &records_end);
if (status != ZX_OK) {
return status;
}
uintptr_t addr;
ACPI_SUBTABLE_HEADER* record_hdr;
for (addr = records_start; addr < records_end; addr += record_hdr->Length) {
record_hdr = (ACPI_SUBTABLE_HEADER*)addr;
if (record_hdr->Type == type) {
status = visitor(record_hdr);
if (status != ZX_OK) {
return status;
}
}
}
if (addr != records_end) {
TRACEF("malformed MADT\n");
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t AcpiTables::GetMadtRecordLimits(uintptr_t* start, uintptr_t* end) const {
ACPI_TABLE_HEADER* table = nullptr;
ACPI_STATUS status = tables_->GetTable((char*)ACPI_SIG_MADT, 1, &table);
if (status != AE_OK) {
TRACEF("could not find MADT\n");
return ZX_ERR_NOT_FOUND;
}
ACPI_TABLE_MADT* madt = (ACPI_TABLE_MADT*)table;
uintptr_t records_start = ((uintptr_t)madt) + sizeof(*madt);
uintptr_t records_end = ((uintptr_t)madt) + madt->Header.Length;
if (records_start >= records_end) {
TRACEF("MADT wraps around address space\n");
return ZX_ERR_INTERNAL;
}
// Shouldn't be too many records
if (madt->Header.Length > 4096) {
TRACEF("MADT suspiciously long: %u\n", madt->Header.Length);
return ZX_ERR_INTERNAL;
}
*start = records_start;
*end = records_end;
return ZX_OK;
}
zx_status_t AcpiTables::hpet(acpi_hpet_descriptor* hpet) const {
ACPI_TABLE_HEADER* table = NULL;
ACPI_STATUS status = tables_->GetTable((char*)ACPI_SIG_HPET, 1, &table);
if (status != AE_OK) {
TRACEF("could not find HPET\n");
return ZX_ERR_NOT_FOUND;
}
ACPI_TABLE_HPET* hpet_tbl = (ACPI_TABLE_HPET*)table;
if (hpet_tbl->Header.Length != sizeof(ACPI_TABLE_HPET)) {
TRACEF("Unexpected HPET table length\n");
return ZX_ERR_NOT_FOUND;
}
hpet->minimum_tick = hpet_tbl->MinimumTick;
hpet->sequence = hpet_tbl->Sequence;
hpet->address = hpet_tbl->Address.Address;
switch (hpet_tbl->Address.SpaceId) {
case ACPI_ADR_SPACE_SYSTEM_IO:
hpet->port_io = true;
break;
case ACPI_ADR_SPACE_SYSTEM_MEMORY:
hpet->port_io = false;
break;
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}