diff options
Diffstat (limited to 'arch/i386/machine/acpimp.c')
-rw-r--r-- | arch/i386/machine/acpimp.c | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/arch/i386/machine/acpimp.c b/arch/i386/machine/acpimp.c new file mode 100644 index 00000000..e2fc6eb3 --- /dev/null +++ b/arch/i386/machine/acpimp.c @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2012 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 <http://www.gnu.org/licenses/>. + */ + +#include <kern/init.h> +#include <kern/kmem.h> +#include <kern/panic.h> +#include <kern/printk.h> +#include <kern/types.h> +#include <lib/assert.h> +#include <lib/macros.h> +#include <lib/stddef.h> +#include <lib/stdint.h> +#include <lib/string.h> +#include <machine/acpimp.h> +#include <machine/biosmem.h> +#include <machine/cpu.h> +#include <machine/io.h> +#include <machine/lapic.h> +#include <vm/vm_kmem.h> + +/* + * Alignment of the RSDP. + */ +#define ACPIMP_RSDP_ALIGN 16 + +/* + * Signature of the root system description pointer. + */ +#define ACPIMP_RSDP_SIG "RSD PTR " + +struct acpimp_rsdp { + uint8_t signature[8]; + uint8_t checksum; + uint8_t oem_id[6]; + uint8_t reserved; + uint32_t rsdt_address; +} __packed; + +/* + * Size of a buffer which can store a table signature as a string. + */ +#define ACPIMP_SIG_SIZE 5 + +struct acpimp_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 acpimp_rsdt { + struct acpimp_sdth header; + uint32_t entries[0]; +} __packed; + +/* + * MADT entry type codes. + */ +#define ACPIMP_MADT_ENTRY_LAPIC 0 + +struct acpimp_madt_entry_hdr { + uint8_t type; + uint8_t length; +} __packed; + +#define ACPIMP_MADT_LAPIC_ENABLED 0x1 + +struct acpimp_madt_entry_lapic { + struct acpimp_madt_entry_hdr header; + uint8_t processor_id; + uint8_t apic_id; + uint32_t flags; +} __packed; + +union acpimp_madt_entry { + uint8_t type; + struct acpimp_madt_entry_hdr header; + struct acpimp_madt_entry_lapic lapic; +} __packed; + +struct acpimp_madt { + struct acpimp_sdth header; + uint32_t lapic_addr; + uint32_t flags; + union acpimp_madt_entry entries[0]; +} __packed; + +struct acpimp_madt_iter { + const union acpimp_madt_entry *entry; + const union acpimp_madt_entry *end; +}; + +#define acpimp_madt_foreach(madt, iter) \ +for (acpimp_madt_iter_init(iter, madt); \ + acpimp_madt_iter_valid(iter); \ + acpimp_madt_iter_next(iter)) + +struct acpimp_table_addr { + const char *sig; + struct acpimp_sdth *table; +}; + +static struct acpimp_table_addr acpimp_table_addrs[] __initdata = { + { "RSDT", NULL }, + { "APIC", NULL } +}; + +static void __init +acpimp_table_sig(const struct acpimp_sdth *table, char sig[ACPIMP_SIG_SIZE]) +{ + memcpy(sig, table->signature, sizeof(table->signature)); + sig[4] = '\0'; +} + +static int __init +acpimp_table_required(const struct acpimp_sdth *table) +{ + char sig[ACPIMP_SIG_SIZE]; + size_t i; + + acpimp_table_sig(table, sig); + + for (i = 0; i < ARRAY_SIZE(acpimp_table_addrs); i++) + if (strcmp(sig, acpimp_table_addrs[i].sig) == 0) + return 1; + + return 0; +} + +static int __init +acpimp_register_table(struct acpimp_sdth *table) +{ + char sig[ACPIMP_SIG_SIZE]; + size_t i; + + acpimp_table_sig(table, sig); + + for (i = 0; i < ARRAY_SIZE(acpimp_table_addrs); i++) + if (strcmp(sig, acpimp_table_addrs[i].sig) == 0) { + if (acpimp_table_addrs[i].table != NULL) { + printk("acpimp: table %s already registered, aborting\n", sig); + return -1; + } + + acpimp_table_addrs[i].table = table; + return 0; + } + + panic("acpimp: attempting to register unknown table '%s'", sig); +} + +static struct acpimp_sdth * __init +acpimp_lookup_table(const char *sig) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(acpimp_table_addrs); i++) + if (strcmp(sig, acpimp_table_addrs[i].sig) == 0) + return acpimp_table_addrs[i].table; + + return NULL; +} + +static int __init +acpimp_check_tables(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(acpimp_table_addrs); i++) + if (acpimp_table_addrs[i].table == NULL) { + printk("acpimp: table %s missing, aborting\n", + acpimp_table_addrs[i].sig); + return -1; + } + + return 0; +} + +static void __init +acpimp_free_tables(void) +{ + struct acpimp_sdth *table; + size_t i; + + for (i = 0; i < ARRAY_SIZE(acpimp_table_addrs); i++) { + table = acpimp_table_addrs[i].table; + + if (table != NULL) + kmem_free(table, table->length); + } +} + +static unsigned int __init +acpimp_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 +acpimp_check_rsdp(const struct acpimp_rsdp *rsdp) +{ + unsigned int checksum; + + if (memcmp(rsdp->signature, ACPIMP_RSDP_SIG, sizeof(rsdp->signature)) != 0) + return -1; + + checksum = acpimp_checksum(rsdp, sizeof(*rsdp)); + + if (checksum != 0) + return -1; + + return 0; +} + +static int __init +acpimp_get_rsdp(vm_phys_t start, size_t size, struct acpimp_rsdp *rsdp) +{ + const struct acpimp_rsdp *src; + unsigned long addr, end, map_addr; + size_t map_size; + int error; + + assert(size > 0); + assert(P2ALIGNED(size, ACPIMP_RSDP_ALIGN)); + + if (!P2ALIGNED(start, ACPIMP_RSDP_ALIGN)) + return -1; + + addr = (unsigned long)vm_kmem_map_pa(start, size, &map_addr, &map_size); + + if (addr == 0) + panic("acpimp: unable to map bios memory in kernel map"); + + for (end = addr + size; addr < end; addr += ACPIMP_RSDP_ALIGN) { + src = (const struct acpimp_rsdp *)addr; + error = acpimp_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 +acpimp_find_rsdp(struct acpimp_rsdp *rsdp) +{ + const uint16_t *ptr; + unsigned long 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("acpimp: 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 = acpimp_get_rsdp(base, 1024, rsdp); + + if (!error) + return 0; + } + + error = acpimp_get_rsdp(BIOSMEM_EXT_ROM, BIOSMEM_END - BIOSMEM_EXT_ROM, + rsdp); + + if (!error) + return 0; + + printk("acpimp: unable to find root system description pointer\n"); + return -1; +} + +static void __init +acpimp_info(void) +{ + const struct acpimp_sdth *rsdt; + + rsdt = acpimp_lookup_table("RSDT"); + assert(rsdt != NULL); + printk("acpimp: revision: %u, oem: %.*s\n", rsdt->revision, + (int)sizeof(rsdt->oem_id), rsdt->oem_id); +} + +static struct acpimp_sdth * __init +acpimp_copy_table(uint32_t addr) +{ + const struct acpimp_sdth *table; + struct acpimp_sdth *copy; + unsigned long 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("unable to map acpi data in kernel map"); + + if (!acpimp_table_required(table)) { + copy = NULL; + goto out; + } + + size = ((const volatile struct acpimp_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("unable to map acpi data in kernel map"); + + checksum = acpimp_checksum(table, size); + + if (checksum != 0) { + char sig[ACPIMP_SIG_SIZE]; + + acpimp_table_sig(table, sig); + printk("acpimp: table %s: invalid checksum\n", sig); + copy = NULL; + goto out; + } + + copy = kmem_alloc(size); + + if (copy == NULL) + panic("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 +acpimp_copy_tables(const struct acpimp_rsdp *rsdp) +{ + struct acpimp_rsdt *rsdt; + struct acpimp_sdth *table; + uint32_t *addr, *end; + int error; + + table = acpimp_copy_table(rsdp->rsdt_address); + + if (table == NULL) + return -1; + + error = acpimp_register_table(table); + assert(!error); + + rsdt = structof(table, struct acpimp_rsdt, header); + end = (void *)rsdt + rsdt->header.length; + + for (addr = rsdt->entries; addr < end; addr++) { + table = acpimp_copy_table(*addr); + + if (table == NULL) + continue; + + error = acpimp_register_table(table); + + if (error) + goto error; + } + + error = acpimp_check_tables(); + + if (error) + goto error; + + return 0; + +error: + acpimp_free_tables(); + return -1; +} + +static void __init +acpimp_madt_iter_init(struct acpimp_madt_iter *iter, + const struct acpimp_madt *madt) +{ + iter->entry = madt->entries; + iter->end = (void *)madt + madt->header.length; +} + +static int __init +acpimp_madt_iter_valid(const struct acpimp_madt_iter *iter) +{ + return iter->entry < iter->end; +} + +static void __init +acpimp_madt_iter_next(struct acpimp_madt_iter *iter) +{ + iter->entry = (void *)iter->entry + iter->entry->header.length; +} + +static void __init +acpimp_load_lapic(const struct acpimp_madt_entry_lapic *lapic, int *is_bsp) +{ + if (!(lapic->flags & ACPIMP_MADT_LAPIC_ENABLED)) + return; + + cpu_mp_register_lapic(lapic->apic_id, *is_bsp); + *is_bsp = 0; +} + +static void __init +acpimp_load_madt(void) +{ + const struct acpimp_sdth *table; + const struct acpimp_madt *madt; + struct acpimp_madt_iter iter; + int is_bsp; + + table = acpimp_lookup_table("APIC"); + assert(table != NULL); + madt = structof(table, struct acpimp_madt, header); + lapic_setup(madt->lapic_addr); + is_bsp = 1; + + acpimp_madt_foreach(madt, &iter) + switch (iter.entry->type) { + case ACPIMP_MADT_ENTRY_LAPIC: + acpimp_load_lapic(&iter.entry->lapic, &is_bsp); + break; + } +} + +int __init +acpimp_setup(void) +{ + struct acpimp_rsdp rsdp; + int error; + + error = acpimp_find_rsdp(&rsdp); + + if (error) + return error; + + error = acpimp_copy_tables(&rsdp); + + if (error) + return error; + + acpimp_info(); + acpimp_load_madt(); + acpimp_free_tables(); + return 0; +} |