blob: 3003d3abba88fc57068cd62be070df0838b6cb2a [file] [log] [blame]
// Copyright 2016 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 <zircon/compiler.h>
#include <acpica/acpi.h>
#include <assert.h>
#include <err.h>
#include <trace.h>
#include <lk/init.h>
#include <arch/x86/apic.h>
#include <platform/pc/acpi.h>
#define LOCAL_TRACE 0
#define ACPI_MAX_INIT_TABLES 32
static ACPI_TABLE_DESC acpi_tables[ACPI_MAX_INIT_TABLES];
static bool acpi_initialized = false;
static ACPI_STATUS acpi_set_apic_irq_mode(void);
/**
* @brief Initialize early-access ACPI tables
*
* This function enables *only* the ACPICA Table Manager subsystem.
* The rest of the ACPI subsystem will remain uninitialized.
*/
void platform_init_acpi_tables(uint level)
{
DEBUG_ASSERT(!acpi_initialized);
ACPI_STATUS status;
status = AcpiInitializeTables(acpi_tables, ACPI_MAX_INIT_TABLES, 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\n");
return;
} else if (status != AE_OK) {
TRACEF("WARNING: could not initialize ACPI tables for unknown reason\n");
return;
}
acpi_initialized = true;
LTRACEF("ACPI tables initialized\n");
}
/* initialize ACPI tables as soon as we have a working VM */
LK_INIT_HOOK(acpi_tables, &platform_init_acpi_tables, LK_INIT_LEVEL_VM + 1);
static status_t acpi_get_madt_record_limits(uintptr_t *start, uintptr_t *end)
{
ACPI_TABLE_HEADER *table = NULL;
ACPI_STATUS status = AcpiGetTable((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;
}
/* @brief Enumerate all functioning CPUs and their APIC IDs
*
* If apic_ids is NULL, just returns the number of logical processors
* via num_cpus.
*
* @param apic_ids Array to write found APIC ids into.
* @param len Length of apic_ids.
* @param num_cpus Output for the number of logical processors detected.
*
* @return ZX_OK on success. Note that if len < *num_cpus, not all
* logical apic_ids will be returned.
*/
status_t platform_enumerate_cpus(
uint32_t *apic_ids,
uint32_t len,
uint32_t *num_cpus)
{
if (num_cpus == NULL) {
return ZX_ERR_INVALID_ARGS;
}
uintptr_t records_start, records_end;
status_t status = acpi_get_madt_record_limits(&records_start, &records_end);
if (status != AE_OK) {
return status;
}
uint32_t count = 0;
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;
switch (record_hdr->Type) {
case ACPI_MADT_TYPE_LOCAL_APIC: {
ACPI_MADT_LOCAL_APIC *lapic = (ACPI_MADT_LOCAL_APIC *)record_hdr;
if (!(lapic->LapicFlags & ACPI_MADT_ENABLED)) {
TRACEF("Skipping disabled processor %02x\n", lapic->Id);
continue;
}
if (apic_ids != NULL && count < len) {
apic_ids[count] = lapic->Id;
}
count++;
break;
}
}
}
if (addr != records_end) {
TRACEF("malformed MADT\n");
return ZX_ERR_INTERNAL;
}
*num_cpus = count;
return ZX_OK;
}
/* @brief Enumerate all IO APICs
*
* If io_apics is NULL, just returns the number of IO APICs
* via num_io_apics.
*
* @param io_apics Array to populate descriptors into.
* @param len Length of io_apics.
* @param num_io_apics Number of IO apics found
*
* @return ZX_OK on success. Note that if len < *num_io_apics, not all
* IO APICs will be returned.
*/
status_t platform_enumerate_io_apics(
struct io_apic_descriptor *io_apics,
uint32_t len,
uint32_t *num_io_apics)
{
if (num_io_apics == NULL) {
return ZX_ERR_INVALID_ARGS;
}
uintptr_t records_start, records_end;
status_t status = acpi_get_madt_record_limits(&records_start, &records_end);
if (status != AE_OK) {
return status;
}
uint32_t count = 0;
uintptr_t addr;
for (addr = records_start; addr < records_end;) {
ACPI_SUBTABLE_HEADER *record_hdr = (ACPI_SUBTABLE_HEADER *)addr;
switch (record_hdr->Type) {
case ACPI_MADT_TYPE_IO_APIC: {
ACPI_MADT_IO_APIC *io_apic = (ACPI_MADT_IO_APIC *)record_hdr;
if (io_apics != NULL && count < len) {
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++;
break;
}
}
addr += record_hdr->Length;
}
if (addr != records_end) {
TRACEF("malformed MADT\n");
return ZX_ERR_INVALID_ARGS;
}
*num_io_apics = count;
return ZX_OK;
}
/* @brief Enumerate all interrupt source overrides
*
* If isos is NULL, just returns the number of ISOs via num_isos.
*
* @param isos Array to populate overrides into.
* @param len Length of isos.
* @param num_isos Number of ISOs found
*
* @return ZX_OK on success. Note that if len < *num_isos, not all
* ISOs will be returned.
*/
status_t platform_enumerate_interrupt_source_overrides(
struct io_apic_isa_override *isos,
uint32_t len,
uint32_t *num_isos)
{
if (num_isos == NULL) {
return ZX_ERR_INVALID_ARGS;
}
uintptr_t records_start, records_end;
status_t status = acpi_get_madt_record_limits(&records_start, &records_end);
if (status != AE_OK) {
return status;
}
uint32_t count = 0;
uintptr_t addr;
for (addr = records_start; addr < records_end;) {
ACPI_SUBTABLE_HEADER *record_hdr = (ACPI_SUBTABLE_HEADER *)addr;
switch (record_hdr->Type) {
case ACPI_MADT_TYPE_INTERRUPT_OVERRIDE: {
ACPI_MADT_INTERRUPT_OVERRIDE *iso =
(ACPI_MADT_INTERRUPT_OVERRIDE *)record_hdr;
if (isos != NULL && count < len) {
ASSERT(iso->Bus == 0); // 0 means ISA, ISOs are only ever for ISA IRQs
isos[count].isa_irq = iso->SourceIrq;
isos[count].remapped = true;
isos[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:
isos[count].pol = IRQ_POLARITY_ACTIVE_HIGH;
break;
case ACPI_MADT_POLARITY_ACTIVE_LOW:
isos[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:
isos[count].tm = IRQ_TRIGGER_MODE_EDGE;
break;
case ACPI_MADT_TRIGGER_LEVEL:
isos[count].tm = IRQ_TRIGGER_MODE_LEVEL;
break;
default:
panic("Unknown IRQ trigger in override: %u\n",
trigger);
}
}
count++;
break;
}
}
addr += record_hdr->Length;
}
if (addr != records_end) {
TRACEF("malformed MADT\n");
return ZX_ERR_INVALID_ARGS;
}
*num_isos = count;
return ZX_OK;
}
/* @brief Return information about the High Precision Event Timer, if present.
*
* @param hpet Descriptor to populate
*
* @return ZX_OK on success.
*/
status_t platform_find_hpet(struct acpi_hpet_descriptor *hpet)
{
ACPI_TABLE_HEADER *table = NULL;
ACPI_STATUS status = AcpiGetTable((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;
}