blob: 7405d0f8a70dd73933da5d908e106f7b9175047a [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2015 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 <debug.h>
#include <err.h>
#include <fbl/atomic.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_ptr.h>
#include <reg.h>
#include <trace.h>
#include <arch.h>
#include <dev/display.h>
#include <dev/hw_rng.h>
#include <dev/power.h>
#include <dev/psci.h>
#include <dev/uart.h>
#include <kernel/cmdline.h>
#include <kernel/spinlock.h>
#include <lk/init.h>
#include <vm/physmap.h>
#include <vm/vm.h>
#include <mexec.h>
#include <platform.h>
#include <target.h>
#include <arch/arm64.h>
#include <arch/arm64/mmu.h>
#include <arch/arm64/mp.h>
#include <arch/arm64/periphmap.h>
#include <arch/mp.h>
#include <vm/vm_aspace.h>
#include <vm/bootreserve.h>
#include <lib/console.h>
#include <lib/memory_limit.h>
#if WITH_LIB_DEBUGLOG
#include <lib/debuglog.h>
#endif
#if WITH_PANIC_BACKTRACE
#include <kernel/thread.h>
#endif
#include <pdev/pdev.h>
#include <zircon/boot/bootdata.h>
#include <zircon/types.h>
// Defined in start.S.
extern paddr_t kernel_entry_paddr;
extern paddr_t bootdata_paddr;
static void* ramdisk_base;
static size_t ramdisk_size;
static uint cpu_cluster_count = 0;
static uint cpu_cluster_cpus[SMP_CPU_MAX_CLUSTERS] = {0};
static bool halt_on_panic = false;
static bool uart_disabled = false;
// all of the configured memory arenas from the bootdata
// at the moment, only support 1 arena
static pmm_arena_info_t mem_arena = {
/* .name */ "sdram",
/* .flags */ PMM_ARENA_FLAG_KMAP,
/* .priority */ 0,
/* .base */ 0, // filled in by bootdata
/* .size */ 0, // filled in by bootdata
};
// bootdata to save for mexec
// TODO(voydanoff): more generic way of doing this that can be shared with PC platform
static uint8_t mexec_bootdata[4096];
static size_t mexec_bootdata_length = 0;
static volatile int panic_started;
static void halt_other_cpus(void) {
static volatile int halted = 0;
if (atomic_swap(&halted, 1) == 0) {
// stop the other cpus
printf("stopping other cpus\n");
arch_mp_send_ipi(MP_IPI_TARGET_ALL_BUT_LOCAL, 0, MP_IPI_HALT);
// spin for a while
// TODO: find a better way to spin at this low level
for (volatile int i = 0; i < 100000000; i++) {
__asm volatile("nop");
}
}
}
void platform_panic_start(void) {
arch_disable_ints();
halt_other_cpus();
if (atomic_swap(&panic_started, 1) == 0) {
#if WITH_LIB_DEBUGLOG
dlog_bluescreen_init();
#endif
}
}
void* platform_get_ramdisk(size_t* size) {
if (ramdisk_base) {
*size = ramdisk_size;
return ramdisk_base;
} else {
*size = 0;
return nullptr;
}
}
void platform_halt_cpu(void) {
psci_cpu_off();
}
// One of these threads is spun up per CPU and calls halt which does not return.
static int park_cpu_thread(void* arg) {
event_t* shutdown_cplt = (event_t*)arg;
mp_set_curr_cpu_online(false);
mp_set_curr_cpu_active(false);
arch_disable_ints();
// Let the thread on the boot CPU know that we're just about done shutting down.
event_signal(shutdown_cplt, true);
// This method will not return because the target cpu has halted.
platform_halt_cpu();
panic("control should never reach here");
return -1;
}
void platform_halt_secondary_cpus(void) {
// Make sure that the current thread is pinned to the boot cpu.
const thread_t* current_thread = get_current_thread();
DEBUG_ASSERT(current_thread->cpu_affinity == (1 << BOOT_CPU_ID));
// Threads responsible for parking the cores.
thread_t* park_thread[SMP_MAX_CPUS];
// These are signalled when the CPU has almost shutdown.
event_t shutdown_cplt[SMP_MAX_CPUS];
for (uint i = 0; i < arch_max_num_cpus(); i++) {
// The boot cpu is going to be performing the remainder of the mexec
// for us so we don't want to park that one.
if (i == BOOT_CPU_ID) {
continue;
}
event_init(&shutdown_cplt[i], false, 0);
char park_thread_name[20];
snprintf(park_thread_name, sizeof(park_thread_name), "park %u", i);
park_thread[i] = thread_create(park_thread_name, park_cpu_thread,
(void*)(&shutdown_cplt[i]), DEFAULT_PRIORITY,
DEFAULT_STACK_SIZE);
thread_set_cpu_affinity(park_thread[i], cpu_num_to_mask(i));
thread_resume(park_thread[i]);
}
// Wait for all CPUs to signal that they're shutting down.
for (uint i = 0; i < arch_max_num_cpus(); i++) {
if (i == BOOT_CPU_ID) {
continue;
}
event_wait(&shutdown_cplt[i]);
}
// TODO(gkalsi): Wait for the secondaries to shutdown rather than sleeping.
// After the shutdown thread shuts down the core, we never
// hear from it again, so we wait 1 second to allow each
// thread to shut down. This is somewhat of a hack.
thread_sleep_relative(ZX_SEC(1));
}
static void platform_start_cpu(uint cluster, uint cpu) {
uint32_t ret = psci_cpu_on(cluster, cpu, kernel_entry_paddr);
dprintf(INFO, "Trying to start cpu %u:%u returned: %d\n", cluster, cpu, (int)ret);
}
static void* allocate_one_stack(void) {
uint8_t* stack = static_cast<uint8_t*>(
pmm_alloc_kpages(ARCH_DEFAULT_STACK_SIZE / PAGE_SIZE, nullptr, nullptr));
return static_cast<void*>(stack + ARCH_DEFAULT_STACK_SIZE);
}
static void platform_cpu_init(void) {
for (uint cluster = 0; cluster < cpu_cluster_count; cluster++) {
for (uint cpu = 0; cpu < cpu_cluster_cpus[cluster]; cpu++) {
if (cluster != 0 || cpu != 0) {
void* sp = allocate_one_stack();
void* unsafe_sp = nullptr;
#if __has_feature(safe_stack)
unsafe_sp = allocate_one_stack();
#endif
arm64_set_secondary_sp(cluster, cpu, sp, unsafe_sp);
platform_start_cpu(cluster, cpu);
}
}
}
}
static inline bool is_bootdata_container(void* addr) {
DEBUG_ASSERT(addr);
bootdata_t* header = (bootdata_t*)addr;
return header->type == BOOTDATA_CONTAINER;
}
static void save_mexec_bootdata(bootdata_t* section) {
size_t length = BOOTDATA_ALIGN(section->length + sizeof(bootdata_t));
ASSERT(sizeof(mexec_bootdata) - mexec_bootdata_length >= length);
memcpy(&mexec_bootdata[mexec_bootdata_length], section, length);
mexec_bootdata_length += length;
}
static void process_mem_range(const bootdata_mem_range_t* mem_range) {
switch (mem_range->type) {
case BOOTDATA_MEM_RANGE_RAM:
if (mem_arena.size == 0) {
mem_arena.base = mem_range->paddr;
mem_arena.size = mem_range->length;
dprintf(INFO, "mem_arena.base %#" PRIx64 " size %#" PRIx64 "\n", mem_arena.base,
mem_arena.size);
} else {
// if mem_area.base is already set, then just update the size
mem_arena.size = mem_range->length;
dprintf(INFO, "overriding mem arena 0 size from FDT: %#zx\n", mem_arena.size);
}
break;
case BOOTDATA_MEM_RANGE_PERIPHERAL: {
auto status = add_periph_range(mem_range->paddr, mem_range->length);
ASSERT(status == ZX_OK);
break;
}
case BOOTDATA_MEM_RANGE_RESERVED:
dprintf(INFO, "boot reserve mem range: phys base %#" PRIx64 " length %#" PRIx64 "\n",
mem_range->paddr, mem_range->length);
boot_reserve_add_range(mem_range->paddr, mem_range->length);
break;
default:
panic("bad mem_range->type in process_mem_range\n");
break;
}
}
static void process_bootsection(bootdata_t* section) {
switch (section->type) {
case BOOTDATA_KERNEL_DRIVER:
case BOOTDATA_PLATFORM_ID:
// we don't process these here, but we need to save them for mexec
save_mexec_bootdata(section);
break;
case BOOTDATA_CMDLINE: {
if (section->length < 1) {
break;
}
char* contents = reinterpret_cast<char*>(section) + sizeof(bootdata_t);
contents[section->length - 1] = '\0';
cmdline_append(contents);
break;
}
case BOOTDATA_MEM_CONFIG: {
bootdata_mem_range_t* mem_range = reinterpret_cast<bootdata_mem_range_t*>(section + 1);
uint32_t count = section->length / (uint32_t)sizeof(bootdata_mem_range_t);
for (uint32_t i = 0; i < count; i++) {
process_mem_range(mem_range++);
}
save_mexec_bootdata(section);
break;
}
case BOOTDATA_CPU_CONFIG: {
bootdata_cpu_config_t* cpu_config = reinterpret_cast<bootdata_cpu_config_t*>(section + 1);
cpu_cluster_count = cpu_config->cluster_count;
for (uint32_t i = 0; i < cpu_cluster_count; i++) {
cpu_cluster_cpus[i] = cpu_config->clusters[i].cpu_count;
}
arch_init_cpu_map(cpu_cluster_count, cpu_cluster_cpus);
save_mexec_bootdata(section);
break;
}
}
}
static void process_bootdata(bootdata_t* root) {
DEBUG_ASSERT(root);
if (root->type != BOOTDATA_CONTAINER) {
printf("bootdata: invalid type = %08x\n", root->type);
return;
}
if (root->extra != BOOTDATA_MAGIC) {
printf("bootdata: invalid magic = %08x\n", root->extra);
return;
}
size_t offset = sizeof(bootdata_t);
const size_t length = (root->length);
if (!(root->flags & BOOTDATA_FLAG_V2)) {
printf("bootdata: v1 no longer supported\n");
}
while (offset < length) {
uintptr_t ptr = reinterpret_cast<const uintptr_t>(root);
bootdata_t* section = reinterpret_cast<bootdata_t*>(ptr + offset);
process_bootsection(section);
offset += BOOTDATA_ALIGN(sizeof(bootdata_t) + section->length);
}
}
void platform_early_init(void) {
// if the bootdata_paddr variable is -1, it was not set
// in start.S, so we are in a bad place.
if (bootdata_paddr == -1UL) {
panic("no bootdata_paddr!\n");
}
void* bootdata_vaddr = paddr_to_physmap(bootdata_paddr);
// initialize the boot memory reservation system
boot_reserve_init();
if (bootdata_vaddr && is_bootdata_container(bootdata_vaddr)) {
bootdata_t* header = (bootdata_t*)bootdata_vaddr;
ramdisk_base = header;
ramdisk_size = ROUNDUP(header->length + sizeof(*header), PAGE_SIZE);
} else {
panic("no bootdata!\n");
}
if (!ramdisk_base || !ramdisk_size) {
panic("no ramdisk!\n");
}
bootdata_t* bootdata = reinterpret_cast<bootdata_t*>(ramdisk_base);
// walk the bootdata structure and process all the entries
process_bootdata(bootdata);
// bring up kernel drivers after we have mapped our peripheral ranges
pdev_init(bootdata);
// Serial port should be active now
// Read cmdline after processing bootdata, which may contain cmdline data.
halt_on_panic = cmdline_get_bool("kernel.halt-on-panic", false);
// Check if serial should be enabled
const char* serial_mode = cmdline_get("kernel.serial");
uart_disabled = (serial_mode != NULL && !strcmp(serial_mode, "none"));
// add the ramdisk to the boot reserve memory list
paddr_t ramdisk_start_phys = physmap_to_paddr(ramdisk_base);
paddr_t ramdisk_end_phys = ramdisk_start_phys + ramdisk_size;
dprintf(INFO, "reserving ramdisk phys range [%#" PRIx64 ", %#" PRIx64 "]\n",
ramdisk_start_phys, ramdisk_end_phys - 1);
boot_reserve_add_range(ramdisk_start_phys, ramdisk_size);
// check if a memory limit was passed in via kernel.memory-limit-mb and
// find memory ranges to use if one is found.
mem_limit_ctx_t ctx;
zx_status_t status = mem_limit_init(&ctx);
if (status == ZX_OK) {
// For these ranges we're using the base physical values
ctx.kernel_base = get_kernel_base_phys();
ctx.kernel_size = get_kernel_size();
ctx.ramdisk_base = ramdisk_start_phys;
ctx.ramdisk_size = ramdisk_end_phys - ramdisk_start_phys;
// Figure out and add arenas based on the memory limit and our range of DRAM
status = mem_limit_add_arenas_from_range(&ctx, mem_arena.base, mem_arena.size, mem_arena);
}
// If no memory limit was found, or adding arenas from the range failed, then add
// the existing global arena.
if (status != ZX_OK) {
pmm_add_arena(&mem_arena);
}
// tell the boot allocator to mark ranges we've reserved as off limits
boot_reserve_wire();
}
void platform_init(void) {
platform_cpu_init();
}
// after the fact create a region to reserve the peripheral map(s)
static void platform_init_postvm(uint level) {
reserve_periph_ranges();
}
LK_INIT_HOOK(platform_postvm, platform_init_postvm, LK_INIT_LEVEL_VM);
void platform_dputs_thread(const char* str, size_t len) {
if (uart_disabled) {
return;
}
uart_puts(str, len, true, true);
}
void platform_dputs_irq(const char* str, size_t len) {
if (uart_disabled) {
return;
}
uart_puts(str, len, false, true);
}
int platform_dgetc(char* c, bool wait) {
if (uart_disabled) {
return -1;
}
int ret = uart_getc(wait);
if (ret == -1)
return -1;
*c = static_cast<char>(ret);
return 0;
}
void platform_pputc(char c) {
if (uart_disabled) {
return;
}
uart_pputc(c);
}
int platform_pgetc(char* c, bool wait) {
if (uart_disabled) {
return -1;
}
int r = uart_pgetc();
if (r == -1) {
return -1;
}
*c = static_cast<char>(r);
return 0;
}
/* stub out the hardware rng entropy generator, which doesn't exist on this platform */
size_t hw_rng_get_entropy(void* buf, size_t len, bool block) {
return 0;
}
/* no built in framebuffer */
zx_status_t display_get_info(struct display_info* info) {
return ZX_ERR_NOT_FOUND;
}
void platform_halt(platform_halt_action suggested_action, platform_halt_reason reason) {
if (suggested_action == HALT_ACTION_REBOOT) {
power_reboot(REBOOT_NORMAL);
printf("reboot failed\n");
} else if (suggested_action == HALT_ACTION_REBOOT_BOOTLOADER) {
power_reboot(REBOOT_BOOTLOADER);
printf("reboot-bootloader failed\n");
} else if (suggested_action == HALT_ACTION_SHUTDOWN) {
power_shutdown();
}
#if WITH_LIB_DEBUGLOG
thread_print_current_backtrace();
dlog_bluescreen_halt();
#endif
if (reason == HALT_REASON_SW_PANIC) {
if (!halt_on_panic) {
power_reboot(REBOOT_NORMAL);
printf("reboot failed\n");
}
#if ENABLE_PANIC_SHELL
dprintf(ALWAYS, "CRASH: starting debug shell... (reason = %d)\n", reason);
arch_disable_ints();
panic_shell_start();
#endif // ENABLE_PANIC_SHELL
}
dprintf(ALWAYS, "HALT: spinning forever... (reason = %d)\n", reason);
// catch all fallthrough cases
arch_disable_ints();
for (;;)
;
}
size_t platform_stow_crashlog(void* log, size_t len) {
return 0;
}
size_t platform_recover_crashlog(size_t len, void* cookie,
void (*func)(const void* data, size_t, size_t len, void* cookie)) {
return 0;
}
zx_status_t platform_mexec_patch_bootdata(uint8_t* bootdata, const size_t len) {
size_t offset = 0;
// copy certain bootdata sections provided by the bootloader or boot shim
// to the mexec bootdata
while (offset < mexec_bootdata_length) {
bootdata_t* section = reinterpret_cast<bootdata_t*>(mexec_bootdata + offset);
zx_status_t status;
status = bootdata_append_section(bootdata, len, reinterpret_cast<uint8_t*>(section + 1),
section->length, section->type, section->extra,
section->flags);
if (status != ZX_OK) return status;
offset += BOOTDATA_ALIGN(sizeof(bootdata_t) + section->length);
}
return ZX_OK;
}
void platform_mexec(mexec_asm_func mexec_assembly, memmov_ops_t* ops,
uintptr_t new_bootimage_addr, size_t new_bootimage_len,
uintptr_t entry64_addr) {
paddr_t kernel_src_phys = (paddr_t)ops[0].src;
paddr_t kernel_dst_phys = (paddr_t)ops[0].dst;
// check to see if the kernel is packaged as a bootdata container
bootdata_t* header = (bootdata_t *)paddr_to_physmap(kernel_src_phys);
if (header[0].type == BOOTDATA_CONTAINER && header[1].type == BOOTDATA_KERNEL) {
bootdata_kernel_t* kernel_header = (bootdata_kernel_t *)&header[2];
// add offset from kernel header to entry point
kernel_dst_phys += kernel_header->entry64;
}
// else just jump to beginning of kernel image
mexec_assembly((uintptr_t)new_bootimage_addr, 0, 0, arm64_get_boot_el(), ops,
(void *)kernel_dst_phys);
}
bool platform_serial_enabled(void) {
return !uart_disabled && uart_present();
}