summaryrefslogtreecommitdiff
path: root/arch/i386/machine/mps.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/i386/machine/mps.c')
-rw-r--r--arch/i386/machine/mps.c446
1 files changed, 446 insertions, 0 deletions
diff --git a/arch/i386/machine/mps.c b/arch/i386/machine/mps.c
new file mode 100644
index 00000000..8a4a899d
--- /dev/null
+++ b/arch/i386/machine/mps.c
@@ -0,0 +1,446 @@
+/*
+ * 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/biosmem.h>
+#include <machine/cpu.h>
+#include <machine/io.h>
+#include <machine/lapic.h>
+#include <machine/mps.h>
+#include <vm/vm_kmem.h>
+
+/*
+ * Alignment of the FPS.
+ */
+#define MPS_FPS_ALIGN 16
+
+/*
+ * Signature of the floating pointer structure.
+ */
+#define MPS_FPS_SIG "_MP_"
+
+/*
+ * Flag of the feature2 byte indicating the presence of the interrupt mode
+ * configuration register (IMCR).
+ */
+#define MPS_FPS_IMCR_PRESENT 0x80
+
+/*
+ * IMCR ports and values.
+ */
+#define MPS_IMCR_PORT_ADDR 0x22
+#define MPS_IMCR_PORT_DATA 0x23
+#define MPS_IMCR_SELECT 0x70
+#define MPS_IMCR_APIC_MODE 0x01
+
+struct mps_fps {
+ uint8_t signature[4];
+ uint32_t phys_addr;
+ uint8_t length;
+ uint8_t spec_rev;
+ uint8_t checksum;
+ uint8_t conf_type;
+ uint8_t feature2;
+ uint8_t feature3;
+ uint8_t feature4;
+ uint8_t feature5;
+} __packed;
+
+/*
+ * Processor entry flags.
+ */
+#define MPS_PROC_EN 0x1
+#define MPS_PROC_BP 0x2
+
+struct mps_proc {
+ uint8_t type;
+ uint8_t lapic_id;
+ uint8_t lapic_version;
+ uint8_t cpu_flags;
+ uint32_t cpu_signature;
+ uint32_t feature_flags;
+ uint32_t reserved1;
+ uint32_t reserved2;
+} __packed;
+
+struct mps_bus {
+ uint8_t type;
+ uint8_t bus_id;
+ uint8_t bus_type[6];
+} __packed;
+
+struct mps_ioapic {
+ uint8_t type;
+ uint8_t id;
+ uint8_t version;
+ uint8_t flags;
+ uint32_t addr;
+} __packed;
+
+struct mps_intr {
+ uint8_t type;
+ uint8_t intr_type;
+ uint16_t intr_flags;
+ uint8_t src_bus_id;
+ uint8_t src_bus_irq;
+ uint8_t dst_apic_id;
+ uint8_t dst_apic_intr;
+} __packed;
+
+union mps_entry {
+ uint8_t type;
+ struct mps_proc proc;
+ struct mps_bus bus;
+ struct mps_ioapic ioapic;
+ struct mps_intr intr;
+} __packed;
+
+/*
+ * Signature of the MPS table.
+ */
+#define MPS_TABLE_SIG "PCMP"
+
+struct mps_cth {
+ uint8_t signature[4];
+ uint16_t base_table_length;
+ uint8_t spec_rev;
+ uint8_t checksum;
+ uint8_t oem_id[8];
+ uint8_t prod_id[12];
+ uint32_t oem_table_ptr;
+ uint16_t oem_table_size;
+ uint16_t entry_count;
+ uint32_t lapic_addr;
+ uint16_t ext_table_length;
+ uint8_t ext_table_checksum;
+ uint8_t reserved;
+ union mps_entry entries[0];
+} __packed;
+
+struct mps_iter {
+ const union mps_entry *entry;
+ uint16_t index;
+ uint16_t size;
+};
+
+#define mps_foreach(table, iter) \
+for (mps_iter_init(iter, table); mps_iter_valid(iter); mps_iter_next(iter))
+
+/*
+ * MPS table entry type codes.
+ */
+#define MPS_ENTRY_PROC 0
+#define MPS_ENTRY_BUS 1
+#define MPS_ENTRY_IOAPIC 2
+#define MPS_ENTRY_IOINTR 3
+#define MPS_ENTRY_LOCAL_INTR 4
+
+/*
+ * Array of entry type sizes.
+ *
+ * The entry type codes must match the indexes of their associated size.
+ */
+static const size_t mps_entry_sizes[] __initdata = {
+ sizeof(struct mps_proc),
+ sizeof(struct mps_bus),
+ sizeof(struct mps_ioapic),
+ sizeof(struct mps_intr),
+ sizeof(struct mps_intr)
+};
+
+static unsigned int __init
+mps_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
+mps_check_fps(const struct mps_fps *fps)
+{
+ unsigned int checksum;
+
+ if (memcmp(fps->signature, MPS_FPS_SIG, sizeof(fps->signature)) != 0)
+ return -1;
+
+ checksum = mps_checksum(fps, sizeof(*fps));
+
+ if (checksum != 0)
+ return -1;
+
+ return 0;
+}
+
+static int __init
+mps_get_fps(vm_phys_t start, size_t size, struct mps_fps *fps)
+{
+ const struct mps_fps *src;
+ unsigned long addr, end, map_addr;
+ size_t map_size;
+ int error;
+
+ assert(size > 0);
+ assert(P2ALIGNED(size, MPS_FPS_ALIGN));
+
+ if (!P2ALIGNED(start, MPS_FPS_ALIGN))
+ return -1;
+
+ addr = (unsigned long)vm_kmem_map_pa(start, size, &map_addr, &map_size);
+
+ if (addr == 0)
+ panic("mps: unable to map bios memory in kernel map");
+
+ for (end = addr + size; addr < end; addr += MPS_FPS_ALIGN) {
+ src = (const struct mps_fps *)addr;
+ error = mps_check_fps(src);
+
+ if (!error)
+ break;
+ }
+
+ if (!(addr < end)) {
+ error = -1;
+ goto out;
+ }
+
+ memcpy(fps, src, sizeof(*fps));
+ error = 0;
+
+out:
+ vm_kmem_unmap_pa(map_addr, map_size);
+ return error;
+}
+
+static int __init
+mps_find_fps(struct mps_fps *fps)
+{
+ 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("mps: 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;
+ else
+ base = BIOSMEM_BASE_END - 1024;
+
+ error = mps_get_fps(base, 1024, fps);
+
+ if (!error)
+ return 0;
+
+ error = mps_get_fps(BIOSMEM_ROM, BIOSMEM_END - BIOSMEM_ROM, fps);
+
+ if (!error)
+ return 0;
+
+ printk("mps: unable to find floating pointer structure\n");
+ return -1;
+}
+
+static struct mps_cth * __init
+mps_copy_table(const struct mps_fps *fps)
+{
+ const struct mps_cth *table;
+ struct mps_cth *copy;
+ unsigned long map_addr;
+ size_t size, map_size;
+ unsigned int checksum;
+
+ if (fps->phys_addr == 0) {
+ printk("mps: table doesn't exist");
+ return NULL;
+ }
+
+ table = vm_kmem_map_pa(fps->phys_addr, sizeof(*table),
+ &map_addr, &map_size);
+
+ if (table == NULL)
+ panic("mps: unable to map table header in kernel map");
+
+ size = ((const volatile struct mps_cth *)table)->base_table_length;
+ vm_kmem_unmap_pa(map_addr, map_size);
+
+ table = vm_kmem_map_pa(fps->phys_addr, size, &map_addr, &map_size);
+
+ if (table == NULL)
+ panic("mps: unable to map table in kernel map");
+
+ if (memcmp(table->signature, MPS_TABLE_SIG, sizeof(table->signature))
+ != 0) {
+ printk("mps: invalid table signature\n");
+ copy = NULL;
+ goto error;
+ }
+
+ checksum = mps_checksum(table, size);
+
+ if (checksum != 0) {
+ printk("mps: table checksum failed\n");
+ copy = NULL;
+ goto error;
+ }
+
+ copy = kmem_alloc(size);
+
+ if (copy == NULL)
+ panic("mps: unable to allocate memory for table copy");
+
+ memcpy(copy, table, size);
+
+error:
+ vm_kmem_unmap_pa(map_addr, map_size);
+ return copy;
+}
+
+static void __init
+mps_info(const struct mps_cth *table)
+{
+ printk("mps: spec revision: 1.%u, %.*s %.*s\n",
+ (unsigned int)table->spec_rev,
+ (int)sizeof(table->oem_id), table->oem_id,
+ (int)sizeof(table->prod_id), table->prod_id);
+}
+
+static void __init
+mps_set_intr_mode(const struct mps_fps *fps)
+{
+ uint8_t byte;
+
+ if (!(fps->feature2 & MPS_FPS_IMCR_PRESENT))
+ return;
+
+ /* Switch to symmetric I/O mode */
+ io_write_byte(MPS_IMCR_PORT_ADDR, MPS_IMCR_SELECT);
+ byte = io_read_byte(MPS_IMCR_PORT_DATA);
+ byte |= MPS_IMCR_APIC_MODE;
+ io_write_byte(MPS_IMCR_PORT_DATA, byte);
+}
+
+static void __init
+mps_iter_init(struct mps_iter *iter, const struct mps_cth *table)
+{
+ iter->entry = table->entries;
+ iter->index = 0;
+ iter->size = table->entry_count;
+}
+
+static int __init
+mps_iter_valid(const struct mps_iter *iter)
+{
+ return iter->index < iter->size;
+}
+
+static void __init
+mps_iter_next(struct mps_iter *iter)
+{
+ assert(iter->entry->type < ARRAY_SIZE(mps_entry_sizes));
+ iter->entry = (void *)iter->entry + mps_entry_sizes[iter->entry->type];
+ iter->index++;
+}
+
+static void __init
+mps_load_proc(const struct mps_proc *proc)
+{
+ if (!(proc->cpu_flags & MPS_PROC_EN))
+ return;
+
+ cpu_mp_register_lapic(proc->lapic_id, proc->cpu_flags & MPS_PROC_BP);
+}
+
+static void __init
+mps_load_table(const struct mps_cth *table)
+{
+ struct mps_iter iter;
+
+ lapic_setup(table->lapic_addr);
+
+ mps_foreach(table, &iter) {
+ switch (iter.entry->type) {
+ case MPS_ENTRY_PROC:
+ mps_load_proc(&iter.entry->proc);
+ break;
+ case MPS_ENTRY_BUS:
+ case MPS_ENTRY_IOAPIC:
+ case MPS_ENTRY_IOINTR:
+ case MPS_ENTRY_LOCAL_INTR:
+ break;
+ default:
+ panic("mps: invalid table entry type");
+ }
+ }
+}
+
+static void __init
+mps_free_table(struct mps_cth *table)
+{
+ kmem_free(table, table->base_table_length);
+}
+
+int __init
+mps_setup(void)
+{
+ struct mps_cth *table;
+ struct mps_fps fps;
+ int error;
+
+ error = mps_find_fps(&fps);
+
+ if (error)
+ return error;
+
+ if (fps.conf_type != 0)
+ panic("mps: default tables not implemented");
+ else {
+ table = mps_copy_table(&fps);
+
+ if (table == NULL)
+ return -1;
+ }
+
+ mps_info(table);
+ mps_set_intr_mode(&fps);
+ mps_load_table(table);
+ mps_free_table(table);
+ return 0;
+}