/* * Copyright (c) 2012-2017 Richard Braun. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Priority of the shutdown operations. * * Higher than all other methods. */ #define ACPI_SHUTDOWN_PRIORITY 10 #define ACPI_GAS_ASID_SYSIO 1 struct acpi_gas { uint8_t asid; uint8_t reg_width; uint8_t reg_offset; uint8_t access_size; uint64_t addr; } __packed; #define ACPI_RSDP_ALIGN 16 #define ACPI_RSDP_SIG "RSD PTR " struct acpi_rsdp { uint8_t signature[8]; uint8_t checksum; uint8_t oem_id[6]; uint8_t reserved; uint32_t rsdt_address; } __packed; #define ACPI_SIG_SIZE 5 struct acpi_sdth { uint8_t signature[4]; uint32_t length; uint8_t revision; uint8_t checksum; uint8_t oem_id[6]; uint8_t oem_table_id[8]; uint32_t oem_revision; uint8_t creator_id[4]; uint32_t creator_revision; } __packed; struct acpi_rsdt { struct acpi_sdth header; uint32_t entries[0]; } __packed; #define ACPI_MADT_ENTRY_LAPIC 0 #define ACPI_MADT_ENTRY_IOAPIC 1 #define ACPI_MADT_ENTRY_ISO 2 struct acpi_madt_entry_hdr { uint8_t type; uint8_t length; } __packed; #define ACPI_MADT_LAPIC_ENABLED 0x1 struct acpi_madt_entry_lapic { struct acpi_madt_entry_hdr header; uint8_t processor_id; uint8_t apic_id; uint32_t flags; } __packed; struct acpi_madt_entry_ioapic { struct acpi_madt_entry_hdr header; uint8_t id; uint8_t _reserved; uint32_t addr; uint32_t base; } __packed; #define ACPI_MADT_ISO_POL_DEFAULT 0x00 #define ACPI_MADT_ISO_POL_HIGH 0x01 #define ACPI_MADT_ISO_POL_LOW 0x03 #define ACPI_MADT_ISO_POL_MASK 0x03 #define ACPI_MADT_ISO_TRIG_DEFAULT 0x00 #define ACPI_MADT_ISO_TRIG_EDGE 0x04 #define ACPI_MADT_ISO_TRIG_LEVEL 0x0c #define ACPI_MADT_ISO_TRIG_MASK 0x0c struct acpi_madt_entry_iso { struct acpi_madt_entry_hdr header; uint8_t bus; uint8_t source; uint32_t gsi; int16_t flags; } __packed; union acpi_madt_entry { uint8_t type; struct acpi_madt_entry_hdr header; struct acpi_madt_entry_lapic lapic; struct acpi_madt_entry_ioapic ioapic; struct acpi_madt_entry_iso iso; } __packed; #define ACPI_MADT_PC_COMPAT 0x1 struct acpi_madt { struct acpi_sdth header; uint32_t lapic_addr; uint32_t flags; union acpi_madt_entry entries[0]; } __packed; struct acpi_madt_iter { const union acpi_madt_entry *entry; const union acpi_madt_entry *end; }; #define acpi_madt_foreach(madt, iter) \ for (acpi_madt_iter_init(iter, madt); \ acpi_madt_iter_valid(iter); \ acpi_madt_iter_next(iter)) #define ACPI_FADT_FL_RESET_REG_SUP 0x400 struct acpi_fadt { union { struct acpi_sdth header; char unused[112]; }; uint32_t flags; struct acpi_gas reset_reg; uint8_t reset_value; } __packed; struct acpi_table_addr { const char *sig; struct acpi_sdth *table; }; static struct acpi_table_addr acpi_table_addrs[] __initdata = { { "RSDT", NULL }, { "APIC", NULL }, { "FACP", NULL }, }; static struct acpi_gas acpi_reset_reg; static uint8_t acpi_reset_value; static void __init acpi_table_sig(const struct acpi_sdth *table, char *sig) { memcpy(sig, table->signature, sizeof(table->signature)); sig[4] = '\0'; } static int __init acpi_table_required(const struct acpi_sdth *table) { char sig[ACPI_SIG_SIZE]; size_t i; acpi_table_sig(table, sig); for (i = 0; i < ARRAY_SIZE(acpi_table_addrs); i++) { if (strcmp(sig, acpi_table_addrs[i].sig) == 0) { return 1; } } return 0; } static void __init acpi_register_table(struct acpi_sdth *table) { char sig[ACPI_SIG_SIZE]; size_t i; acpi_table_sig(table, sig); for (i = 0; i < ARRAY_SIZE(acpi_table_addrs); i++) { if (strcmp(sig, acpi_table_addrs[i].sig) == 0) { if (acpi_table_addrs[i].table != NULL) { log_warning("acpi: table %s ignored: already registered", sig); return; } acpi_table_addrs[i].table = table; return; } } log_warning("acpi: table '%s' ignored: unknown table", sig); } static struct acpi_sdth * __init acpi_lookup_table(const char *sig) { size_t i; for (i = 0; i < ARRAY_SIZE(acpi_table_addrs); i++) { if (strcmp(sig, acpi_table_addrs[i].sig) == 0) { return acpi_table_addrs[i].table; } } return NULL; } static int __init acpi_check_tables(void) { size_t i; for (i = 0; i < ARRAY_SIZE(acpi_table_addrs); i++) { if (acpi_table_addrs[i].table == NULL) { log_err("acpi: table %s missing", acpi_table_addrs[i].sig); return -1; } } return 0; } static void __init acpi_free_tables(void) { struct acpi_sdth *table; size_t i; for (i = 0; i < ARRAY_SIZE(acpi_table_addrs); i++) { table = acpi_table_addrs[i].table; if (table != NULL) { kmem_free(table, table->length); } } } static unsigned int __init acpi_checksum(const void *ptr, size_t size) { const uint8_t *bytes; uint8_t checksum; size_t i; bytes = ptr; checksum = 0; for (i = 0; i < size; i++) { checksum += bytes[i]; } return checksum; } static int __init acpi_check_rsdp(const struct acpi_rsdp *rsdp) { unsigned int checksum; if (memcmp(rsdp->signature, ACPI_RSDP_SIG, sizeof(rsdp->signature)) != 0) { return -1; } checksum = acpi_checksum(rsdp, sizeof(*rsdp)); if (checksum != 0) { return -1; } return 0; } static int __init acpi_get_rsdp(phys_addr_t start, size_t size, struct acpi_rsdp *rsdp) { const struct acpi_rsdp *src; uintptr_t addr, end, map_addr; size_t map_size; int error; assert(size > 0); assert(P2ALIGNED(size, ACPI_RSDP_ALIGN)); if (!P2ALIGNED(start, ACPI_RSDP_ALIGN)) { return -1; } addr = (uintptr_t)vm_kmem_map_pa(start, size, &map_addr, &map_size); if (addr == 0) { panic("acpi: unable to map bios memory in kernel map"); } for (end = addr + size; addr < end; addr += ACPI_RSDP_ALIGN) { src = (const struct acpi_rsdp *)addr; error = acpi_check_rsdp(src); if (!error) { break; } } if (!(addr < end)) { error = -1; goto out; } memcpy(rsdp, src, sizeof(*rsdp)); error = 0; out: vm_kmem_unmap_pa(map_addr, map_size); return error; } static int __init acpi_find_rsdp(struct acpi_rsdp *rsdp) { const uint16_t *ptr; uintptr_t base, map_addr; size_t map_size; int error; ptr = vm_kmem_map_pa(BIOSMEM_EBDA_PTR, sizeof(*ptr), &map_addr, &map_size); if (ptr == NULL) { panic("acpi: unable to map ebda pointer in kernel map"); } base = *((const volatile uint16_t *)ptr); vm_kmem_unmap_pa(map_addr, map_size); if (base != 0) { base <<= 4; error = acpi_get_rsdp(base, 1024, rsdp); if (!error) { return 0; } } error = acpi_get_rsdp(BIOSMEM_EXT_ROM, BIOSMEM_END - BIOSMEM_EXT_ROM, rsdp); if (!error) { return 0; } log_debug("acpi: unable to find root system description pointer"); return -1; } static void __init acpi_info(void) { const struct acpi_sdth *rsdt; rsdt = acpi_lookup_table("RSDT"); assert(rsdt != NULL); log_debug("acpi: revision: %u, oem: %.*s", rsdt->revision, (int)sizeof(rsdt->oem_id), rsdt->oem_id); } static struct acpi_sdth * __init acpi_copy_table(uint32_t addr) { const struct acpi_sdth *table; struct acpi_sdth *copy; uintptr_t map_addr; size_t size, map_size; unsigned int checksum; table = vm_kmem_map_pa(addr, sizeof(*table), &map_addr, &map_size); if (table == NULL) { panic("acpi: unable to map acpi data in kernel map"); } if (!acpi_table_required(table)) { copy = NULL; goto out; } size = ((const volatile struct acpi_sdth *)table)->length; vm_kmem_unmap_pa(map_addr, map_size); table = vm_kmem_map_pa(addr, size, &map_addr, &map_size); if (table == NULL) { panic("acpi: unable to map acpi data in kernel map"); } checksum = acpi_checksum(table, size); if (checksum != 0) { char sig[ACPI_SIG_SIZE]; acpi_table_sig(table, sig); log_err("acpi: table %s: invalid checksum", sig); copy = NULL; goto out; } copy = kmem_alloc(size); if (copy == NULL) { panic("acpi: unable to allocate memory for acpi data copy"); } memcpy(copy, table, size); out: vm_kmem_unmap_pa(map_addr, map_size); return copy; } static int __init acpi_copy_tables(const struct acpi_rsdp *rsdp) { struct acpi_rsdt *rsdt; struct acpi_sdth *table; uint32_t *addr, *end; int error; table = acpi_copy_table(rsdp->rsdt_address); if (table == NULL) { return -1; } acpi_register_table(table); rsdt = structof(table, struct acpi_rsdt, header); end = (void *)rsdt + rsdt->header.length; for (addr = rsdt->entries; addr < end; addr++) { table = acpi_copy_table(*addr); if (table == NULL) { continue; } acpi_register_table(table); } error = acpi_check_tables(); if (error) { goto error; } return 0; error: acpi_free_tables(); return -1; } static void __init acpi_madt_iter_init(struct acpi_madt_iter *iter, const struct acpi_madt *madt) { iter->entry = madt->entries; iter->end = (void *)madt + madt->header.length; } static int __init acpi_madt_iter_valid(const struct acpi_madt_iter *iter) { return iter->entry < iter->end; } static void __init acpi_madt_iter_next(struct acpi_madt_iter *iter) { iter->entry = (void *)iter->entry + iter->entry->header.length; } static void __init acpi_load_lapic(const struct acpi_madt_entry_lapic *lapic, int *is_bsp) { if (!(lapic->flags & ACPI_MADT_LAPIC_ENABLED)) { return; } cpu_mp_register_lapic(lapic->apic_id, *is_bsp); *is_bsp = 0; } static void __init acpi_load_ioapic(const struct acpi_madt_entry_ioapic *ioapic) { ioapic_register(ioapic->id, ioapic->addr, ioapic->base); } static void __init acpi_load_iso(const struct acpi_madt_entry_iso *iso) { bool active_high, edge_triggered; if (iso->bus != 0) { log_err("acpi: invalid interrupt source override bus"); return; } switch (iso->flags & ACPI_MADT_ISO_POL_MASK) { case ACPI_MADT_ISO_POL_DEFAULT: case ACPI_MADT_ISO_POL_HIGH: active_high = true; break; case ACPI_MADT_ISO_POL_LOW: active_high = false; break; default: log_err("acpi: invalid polarity"); return; } switch (iso->flags & ACPI_MADT_ISO_TRIG_MASK) { case ACPI_MADT_ISO_TRIG_DEFAULT: case ACPI_MADT_ISO_TRIG_EDGE: edge_triggered = true; break; case ACPI_MADT_ISO_TRIG_LEVEL: edge_triggered = false; break; default: log_err("acpi: invalid trigger mode"); return; } ioapic_override(iso->source, iso->gsi, active_high, edge_triggered); } static void __init acpi_load_madt(void) { const struct acpi_sdth *table; const struct acpi_madt *madt; struct acpi_madt_iter iter; int is_bsp; table = acpi_lookup_table("APIC"); assert(table != NULL); madt = structof(table, struct acpi_madt, header); lapic_setup(madt->lapic_addr); is_bsp = 1; acpi_madt_foreach(madt, &iter) { switch (iter.entry->type) { case ACPI_MADT_ENTRY_LAPIC: acpi_load_lapic(&iter.entry->lapic, &is_bsp); break; case ACPI_MADT_ENTRY_IOAPIC: acpi_load_ioapic(&iter.entry->ioapic); break; case ACPI_MADT_ENTRY_ISO: acpi_load_iso(&iter.entry->iso); break; } } if (madt->flags & ACPI_MADT_PC_COMPAT) { pic_setup_disabled(); } } static void acpi_shutdown_reset_sysio(uint64_t addr) { if (addr > UINT16_MAX) { log_warning("acpi: invalid sysio address"); return; } io_write_byte((uint16_t)addr, acpi_reset_value); } static void acpi_shutdown_reset(void) { if ((acpi_reset_reg.reg_width != 8) || (acpi_reset_reg.reg_offset != 0)) { log_warning("acpi: invalid reset register"); return; } switch (acpi_reset_reg.asid) { case ACPI_GAS_ASID_SYSIO: acpi_shutdown_reset_sysio(acpi_reset_reg.addr); break; default: log_warning("acpi: unsupported reset register type"); } } static struct shutdown_ops acpi_shutdown_ops = { .reset = acpi_shutdown_reset, }; static void __init acpi_load_fadt(void) { const struct acpi_sdth *table; const struct acpi_fadt *fadt; table = acpi_lookup_table("FACP"); if (table == NULL) { log_debug("acpi: unable to find FADT table"); return; } fadt = structof(table, struct acpi_fadt, header); if (!(fadt->flags & ACPI_FADT_FL_RESET_REG_SUP)) { log_debug("acpi: reset register not supported"); return; } acpi_reset_reg = fadt->reset_reg; acpi_reset_value = fadt->reset_value; shutdown_register(&acpi_shutdown_ops, ACPI_SHUTDOWN_PRIORITY); } static int __init acpi_setup(void) { struct acpi_rsdp rsdp; int error; error = acpi_find_rsdp(&rsdp); if (error) { goto error; } error = acpi_copy_tables(&rsdp); if (error) { goto error; } acpi_info(); acpi_load_madt(); acpi_load_fadt(); acpi_free_tables(); return 0; error: /* * For the sake of simplicity, it has been decided to ignore legacy * specifications such as the multiprocessor specification, and use * ACPI only. If ACPI is unavailable, consider the APIC system to * be missing and fall back to using the legacy XT-PIC and PIT. */ pic_setup(); pit_setup(); return 0; } INIT_OP_DEFINE(acpi_setup, INIT_OP_DEP(cpu_setup, true), INIT_OP_DEP(intr_bootstrap, true), INIT_OP_DEP(kmem_setup, true), INIT_OP_DEP(log_setup, true), INIT_OP_DEP(percpu_setup, true), INIT_OP_DEP(shutdown_bootstrap, true), INIT_OP_DEP(trap_setup, true), INIT_OP_DEP(vm_kmem_setup, true));