blob: afdd5cbf6bb17dc519ade0760c942b407f4e07e7 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2009 Corey Tabaka
// Copyright (c) 2015 Intel Corporation
// Copyright (c) 2016 Travis Geiselbrecht
//
// 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 <err.h>
#include <trace.h>
#include <arch/x86/apic.h>
#include <arch/x86/cpu_topology.h>
#include <arch/x86/mmu.h>
#include <platform.h>
#include "platform_p.h"
#include <platform/pc.h>
#include <platform/pc/acpi.h>
#include <platform/pc/bootloader.h>
#include <platform/pc/memory.h>
#include <platform/console.h>
#include <platform/keyboard.h>
#include <magenta/boot/bootdata.h>
#include <magenta/boot/multiboot.h>
#include <dev/uart.h>
#include <arch/mmu.h>
#include <arch/mp.h>
#include <arch/x86.h>
#include <malloc.h>
#include <string.h>
#include <assert.h>
#include <lk/init.h>
#include <kernel/cmdline.h>
#include <kernel/vm/initial_map.h>
#include <kernel/vm/pmm.h>
#include <kernel/vm/vm_aspace.h>
extern "C" {
#include <efi/runtime-services.h>
#include <efi/system-table.h>
};
#define LOCAL_TRACE 0
extern multiboot_info_t* _multiboot_info;
extern bootdata_t* _bootdata_base;
pc_bootloader_info_t bootloader;
struct mmu_initial_mapping mmu_initial_mappings[] = {
/* 64GB of memory mapped where the kernel lives */
{
.phys = MEMBASE,
.virt = KERNEL_ASPACE_BASE,
.size = 64ULL*GB, /* x86-64 maps first 64GB by default */
.flags = 0,
.name = "memory"
},
/* KERNEL_SIZE of memory mapped where the kernel lives.
* On x86-64, this only sticks around until the VM is brought up, after
* that this will be replaced with mappings of the correct privileges. */
{
.phys = MEMBASE,
.virt = KERNEL_BASE,
.size = KERNEL_SIZE, /* x86 maps first KERNEL_SIZE by default */
.flags = MMU_INITIAL_MAPPING_TEMPORARY,
.name = "kernel_temp"
},
/* null entry to terminate the list */
{}
};
static bool early_console_disabled;
static int process_bootitem(bootdata_t* bd, void* item) {
switch (bd->type) {
case BOOTDATA_ACPI_RSDP:
if (bd->length < sizeof(uint64_t)) {
break;
}
bootloader.acpi_rsdp = *((uint64_t*)item);
break;
case BOOTDATA_EFI_SYSTEM_TABLE:
if (bd->length < sizeof(uint64_t)) {
break;
}
bootloader.efi_system_table = (void*) *((uint64_t*)item);
break;
case BOOTDATA_FRAMEBUFFER: {
if (bd->length < sizeof(bootdata_swfb_t)) {
break;
}
bootdata_swfb_t* fb = static_cast<bootdata_swfb_t*>(item);
bootloader.fb_base = (uint32_t) fb->phys_base;
bootloader.fb_width = fb->width;
bootloader.fb_height = fb->height;
bootloader.fb_stride = fb->stride;
bootloader.fb_format = fb->format;
break;
}
case BOOTDATA_CMDLINE:
if (bd->length < 1) {
break;
}
((char*) item)[bd->length - 1] = 0;
cmdline_append((char*) item);
break;
case BOOTDATA_EFI_MEMORY_MAP:
bootloader.efi_mmap = item;
bootloader.efi_mmap_size = bd->length;
break;
case BOOTDATA_E820_TABLE:
bootloader.e820_table = item;
bootloader.e820_count = bd->length / sizeof(e820entry_t);
break;
case BOOTDATA_IGNORE:
break;
}
return 0;
}
extern "C" void *boot_alloc_mem(size_t len);
extern "C" void boot_alloc_reserve(uintptr_t phys, size_t _len);
static void process_bootdata(bootdata_t* hdr, uintptr_t phys) {
if ((hdr->type != BOOTDATA_CONTAINER) ||
(hdr->extra != BOOTDATA_MAGIC) ||
(hdr->flags != 0)) {
printf("bootdata: invalid %08x %08x %08x %08x\n",
hdr->type, hdr->length, hdr->extra, hdr->flags);
return;
}
size_t total_len = hdr->length + sizeof(*hdr);
printf("bootdata: @ %p (%zu bytes)\n", hdr, total_len);
bootdata_t* bd = hdr + 1;
size_t remain = hdr->length;
while (remain > sizeof(bootdata_t)) {
remain -= sizeof(bootdata_t);
uintptr_t item = reinterpret_cast<uintptr_t>(bd + 1);
size_t len = BOOTDATA_ALIGN(bd->length);
if (len > remain) {
printf("bootdata: truncated\n");
break;
}
if (process_bootitem(bd, reinterpret_cast<void*>(item))) {
break;
}
bd = reinterpret_cast<bootdata_t*>(item + len);
remain -= len;
}
boot_alloc_reserve(phys, total_len);
bootloader.ramdisk_base = phys;
bootloader.ramdisk_size = total_len;
}
extern bool halt_on_panic;
static void platform_save_bootloader_data(void) {
if (_multiboot_info != NULL) {
multiboot_info_t* mi = (multiboot_info_t*) X86_PHYS_TO_VIRT(_multiboot_info);
printf("multiboot: info @ %p\n", mi);
if ((mi->flags & MB_INFO_CMD_LINE) && mi->cmdline) {
const char* cmdline = (const char*) X86_PHYS_TO_VIRT(mi->cmdline);
printf("multiboot: cmdline @ %p\n", cmdline);
cmdline_append(cmdline);
// Look for framebuffer info in multiboot command line
bootloader.fb_base = cmdline_get_uint32("fb.base", 0);
bootloader.fb_width = cmdline_get_uint32("fb.width", 0);
bootloader.fb_height = cmdline_get_uint32("fb.height", 0);
bootloader.fb_stride = cmdline_get_uint32("fb.stride", 0);
bootloader.fb_format = cmdline_get_uint32("fb.format", 0);
}
if ((mi->flags & MB_INFO_MODS) && mi->mods_addr) {
module_t* mod = (module_t*) X86_PHYS_TO_VIRT(mi->mods_addr);
if (mi->mods_count > 0) {
printf("multiboot: ramdisk @ %08x..%08x\n", mod->mod_start, mod->mod_end);
process_bootdata(reinterpret_cast<bootdata_t*>(X86_PHYS_TO_VIRT(mod->mod_start)),
mod->mod_start);
}
}
}
if (_bootdata_base != NULL) {
bootdata_t* bd = (bootdata_t*) X86_PHYS_TO_VIRT(_bootdata_base);
process_bootdata(bd, (uintptr_t) _bootdata_base);
}
halt_on_panic = cmdline_get_bool("kernel.halt_on_panic", false);
}
static void* ramdisk_base;
static size_t ramdisk_size;
static void platform_preserve_ramdisk(void) {
if (bootloader.ramdisk_size == 0) {
return;
}
if (bootloader.ramdisk_base == 0) {
return;
}
struct list_node list = LIST_INITIAL_VALUE(list);
size_t pages = ROUNDUP_PAGE_SIZE(bootloader.ramdisk_size) / PAGE_SIZE;
size_t actual = pmm_alloc_range(bootloader.ramdisk_base, pages, &list);
if (actual != pages) {
panic("unable to reserve ramdisk memory range\n");
}
// mark all of the pages we allocated as WIRED
vm_page *p;
list_for_every_entry(&list, p, vm_page, queue_node) {
p->state = VM_PAGE_STATE_WIRED;
}
ramdisk_base = paddr_to_kvaddr(bootloader.ramdisk_base);
ramdisk_size = pages * PAGE_SIZE;
}
void* platform_get_ramdisk(size_t *size) {
if (ramdisk_base) {
*size = ramdisk_size;
return ramdisk_base;
} else {
*size = 0;
return NULL;
}
}
#include <dev/display.h>
#include <lib/gfxconsole.h>
status_t display_get_info(struct display_info *info) {
return gfxconsole_display_get_info(info);
}
static void platform_early_display_init(void) {
struct display_info info;
void *bits;
if (bootloader.fb_base == 0) {
return;
}
if (cmdline_get_bool("gfxconsole.early", false) == false) {
early_console_disabled = true;
return;
}
// allocate an offscreen buffer of worst-case size, page aligned
bits = boot_alloc_mem(8192 + bootloader.fb_height * bootloader.fb_stride * 4);
bits = (void*) ((((uintptr_t) bits) + 4095) & (~4095));
memset(&info, 0, sizeof(info));
info.format = bootloader.fb_format;
info.width = bootloader.fb_width;
info.height = bootloader.fb_height;
info.stride = bootloader.fb_stride;
info.flags = DISPLAY_FLAG_HW_FRAMEBUFFER;
info.framebuffer = (void*) X86_PHYS_TO_VIRT(bootloader.fb_base);
gfxconsole_bind_display(&info, bits);
}
/* Ensure the framebuffer is write-combining as soon as we have the VMM.
* Some system firmware has the MTRRs for the framebuffer set to Uncached.
* Since dealing with MTRRs is rather complicated, we wait for the VMM to
* come up so we can use PAT to manage the memory types. */
static void platform_ensure_display_memtype(uint level)
{
if (bootloader.fb_base == 0) {
return;
}
if (early_console_disabled) {
return;
}
struct display_info info;
memset(&info, 0, sizeof(info));
info.format = bootloader.fb_format;
info.width = bootloader.fb_width;
info.height = bootloader.fb_height;
info.stride = bootloader.fb_stride;
info.flags = DISPLAY_FLAG_HW_FRAMEBUFFER;
void *addr = NULL;
status_t status = VmAspace::kernel_aspace()->AllocPhysical(
"boot_fb",
ROUNDUP(info.stride * info.height * 4, PAGE_SIZE),
&addr,
PAGE_SIZE_SHIFT,
bootloader.fb_base,
0 /* vmm flags */,
ARCH_MMU_FLAG_WRITE_COMBINING | ARCH_MMU_FLAG_PERM_READ |
ARCH_MMU_FLAG_PERM_WRITE);
if (status != MX_OK) {
TRACEF("Failed to map boot_fb: %d\n", status);
return;
}
info.framebuffer = addr;
gfxconsole_bind_display(&info, NULL);
}
LK_INIT_HOOK(display_memtype, &platform_ensure_display_memtype, LK_INIT_LEVEL_VM + 1);
static efi_guid magenta_guid = MAGENTA_VENDOR_GUID;
static char16_t crashlog_name[] = MAGENTA_CRASHLOG_EFIVAR;
static mxtl::RefPtr<VmAspace> efi_aspace;
void platform_init_crashlog(void) {
if (bootloader.efi_system_table != NULL) {
// Create a linear mapping to use to call UEFI Runtime Services
efi_aspace = VmAspace::Create(VmAspace::TYPE_LOW_KERNEL, "uefi");
if (!efi_aspace) {
return;
}
//TODO: get more precise about this. This gets the job done on
// the platforms we're working on right now, but is probably
// not entirely correct.
void* ptr = (void*) 0;
mx_status_t r = efi_aspace->AllocPhysical("1:1", 16*1024*1024*1024UL, &ptr,
PAGE_SIZE_SHIFT, 0,
VmAspace::VMM_FLAG_VALLOC_SPECIFIC,
ARCH_MMU_FLAG_PERM_READ |
ARCH_MMU_FLAG_PERM_WRITE |
ARCH_MMU_FLAG_PERM_EXECUTE);
if (r != MX_OK) {
efi_aspace.reset();
}
}
}
// Something big enough for the panic log but not too enormous
// to avoid excessive pressure on efi variable storage
#define MAX_CRASHLOG_LEN 4096
size_t platform_stow_crashlog(void* log, size_t len) {
if (!efi_aspace) {
return 0;
}
if (log == NULL) {
return MAX_CRASHLOG_LEN;
}
if (len > MAX_CRASHLOG_LEN) {
len = MAX_CRASHLOG_LEN;
}
vmm_set_active_aspace(reinterpret_cast<vmm_aspace_t*>(efi_aspace.get()));
efi_system_table* sys = static_cast<efi_system_table*>(bootloader.efi_system_table);
efi_runtime_services* rs = sys->RuntimeServices;
if (rs->SetVariable(crashlog_name, &magenta_guid, MAGENTA_CRASHLOG_EFIATTR, len, log) == 0) {
return len;
} else {
return 0;
}
}
void platform_early_init(void)
{
/* get the debug output working */
platform_init_debug_early();
#if WITH_LEGACY_PC_CONSOLE
/* get the text console working */
platform_init_console();
#endif
/* extract bootloader data while still accessible */
platform_save_bootloader_data();
/* if the bootloader has framebuffer info, use it for early console */
platform_early_display_init();
/* initialize physical memory arenas */
platform_mem_init();
platform_preserve_ramdisk();
}
static void platform_init_smp(void)
{
uint32_t num_cpus = 0;
status_t status = platform_enumerate_cpus(NULL, 0, &num_cpus);
if (status != MX_OK) {
TRACEF("failed to enumerate CPUs, disabling SMP\n");
return;
}
// allocate 2x the table for temporary work
uint32_t *apic_ids = static_cast<uint32_t *>(malloc(sizeof(*apic_ids) * num_cpus * 2));
if (apic_ids == NULL) {
TRACEF("failed to allocate apic_ids table, disabling SMP\n");
return;
}
// a temporary list used before we filter out hyperthreaded pairs
uint32_t *apic_ids_temp = apic_ids + num_cpus;
// find the list of all cpu apic ids into a temporary list
uint32_t real_num_cpus;
status = platform_enumerate_cpus(apic_ids_temp, num_cpus, &real_num_cpus);
if (status != MX_OK || num_cpus != real_num_cpus) {
TRACEF("failed to enumerate CPUs, disabling SMP\n");
free(apic_ids);
return;
}
// Filter out hyperthreads if we've been told not to init them
bool use_ht = cmdline_get_bool("smp.ht", true);
// we're implicitly running on the BSP
uint32_t bsp_apic_id = apic_local_id();
// iterate over all the cores and optionally disable some of them
dprintf(INFO, "cpu topology:\n");
uint32_t using_count = 0;
for (uint32_t i = 0; i < num_cpus; ++i) {
x86_cpu_topology_t topo;
x86_cpu_topology_decode(apic_ids_temp[i], &topo);
// filter it out if it's a HT pair that we dont want to use
bool keep = true;
if (!use_ht && topo.smt_id != 0)
keep = false;
dprintf(INFO, "\t%u: apic id 0x%x package %u core %u smt %u%s%s\n",
i, apic_ids_temp[i], topo.package_id, topo.core_id, topo.smt_id,
(apic_ids_temp[i] == bsp_apic_id) ? " BSP" : "",
keep ? "" : " (not using)");
if (!keep)
continue;
// save this apic id into the primary list
apic_ids[using_count++] = apic_ids_temp[i];
}
num_cpus = using_count;
// Find the CPU count limit
uint32_t max_cpus = cmdline_get_uint32("smp.maxcpus", SMP_MAX_CPUS);
if (max_cpus > SMP_MAX_CPUS || max_cpus <= 0) {
printf("invalid smp.maxcpus value, defaulting to %d\n", SMP_MAX_CPUS);
max_cpus = SMP_MAX_CPUS;
}
dprintf(INFO, "Found %u cpus\n", num_cpus);
if (num_cpus > max_cpus) {
TRACEF("Clamping number of CPUs to %u\n", max_cpus);
num_cpus = max_cpus;
}
if (num_cpus == max_cpus || !use_ht) {
// If we are at the max number of CPUs, or have filtered out
// hyperthreads, sanity check that the bootstrap processor is in the set.
bool found_bp = false;
for (unsigned int i = 0; i < num_cpus; ++i) {
if (apic_ids[i] == bsp_apic_id) {
found_bp = true;
break;
}
}
ASSERT(found_bp);
}
x86_init_smp(apic_ids, num_cpus);
for (uint i = 0; i < num_cpus - 1; ++i) {
if (apic_ids[i] == bsp_apic_id) {
apic_ids[i] = apic_ids[num_cpus - 1];
apic_ids[num_cpus - 1] = bsp_apic_id;
break;
}
}
x86_bringup_aps(apic_ids, num_cpus - 1);
free(apic_ids);
}
status_t platform_mp_prep_cpu_unplug(uint cpu_id)
{
// TODO: Make sure the IOAPIC and PCI have nothing for this CPU
return arch_mp_prep_cpu_unplug(cpu_id);
}
void platform_init(void)
{
platform_init_debug();
platform_init_crashlog();
#if NO_USER_KEYBOARD
platform_init_keyboard(&console_input_buf);
#endif
platform_init_smp();
}