blob: edea7db4ba46ff8c80c0146599566362a4ee6942 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// 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 <lib/console.h>
#include <lib/debuglog.h>
#include <platform.h>
#include <stdio.h>
#include <string.h>
#include <arch/mp.h>
#include <arch/x86.h>
#include <arch/x86/apic.h>
#include <arch/x86/feature.h>
#include <arch/x86/mp.h>
#include <ktl/atomic.h>
#include <platform/keyboard.h>
// The I/O port to write to for QEMU debug exit.
const uint16_t kQEMUDebugExitPort = 0xf4;
// The return code that we should propagate to QEMU on isa-debug-exit.
// This number must be non-zero and odd, since QEMU calculates the return
// code as (val << 1) | 1 where "val" is the value written to 0xf4.
const uint8_t kQEMUExitCode = 0x1f;
static_assert(kQEMUExitCode != 0 && kQEMUExitCode % 2 != 0,
"QEMU exit code must be non-zero and odd.");
static void reboot(void) {
// select the default reboot reason
x86_reboot_reason_func_t reboot_reason = x86_get_microarch_config()->reboot_reason;
if (reboot_reason)
reboot_reason(0u);
// We fell through. Try normal reboot.
x86_get_microarch_config()->reboot_system();
// We fell through. Try rebooting via keyboard controller.
pc_keyboard_reboot();
}
static void reboot_recovery() {
x86_reboot_reason_func_t reboot_reason = x86_get_microarch_config()->reboot_reason;
if (reboot_reason)
reboot_reason(2u);
}
static void reboot_bootloader() {
x86_reboot_reason_func_t reboot_reason = x86_get_microarch_config()->reboot_reason;
if (reboot_reason)
reboot_reason(4u);
}
static ktl::atomic<cpu_mask_t> halted_cpus(0);
static void halt_other_cpus(void) {
static ktl::atomic<int> halted(0);
if (halted.exchange(1) == 0) {
// This function may have been called early in the boot process, before the
// mp subsystem has been initialized or secondary CPUs have been brought
// online. To avoid calling into the mp subsystem before it has been
// initialized, check the online mask. If this CPU is the only one online,
// then simply return.
cpu_mask_t targets = mp_get_online_mask() & ~cpu_num_to_mask(arch_curr_cpu_num());
if (targets == 0) {
return;
}
// 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++) {
if (halted_cpus.load() == targets) {
break;
}
__asm volatile("nop");
}
// Don't send an INIT IPI to the BSP, since that may cause the system to
// reboot
x86_force_halt_all_but_local_and_bsp();
}
}
void platform_halt_cpu(void) {
// Signal that this CPU is in its halt loop
halted_cpus.fetch_or(cpu_num_to_mask(arch_curr_cpu_num()));
}
void platform_panic_start(void) {
platform_debug_panic_start();
arch_disable_ints();
static ktl::atomic<int> panic_started(0);
if (panic_started.exchange(1) == 0) {
dlog_bluescreen_init();
}
halt_other_cpus();
}
extern const char* manufacturer;
void platform_specific_halt(platform_halt_action suggested_action, zircon_crash_reason_t reason,
bool halt_on_panic) {
printf("platform_halt suggested_action %d reason %d\n", suggested_action,
static_cast<int>(reason));
arch_disable_ints();
switch (suggested_action) {
case HALT_ACTION_SHUTDOWN:
if (strcmp("QEMU", manufacturer) == 0) {
outp(kQEMUDebugExitPort, (kQEMUExitCode >> 1));
}
printf("Power off failed, halting\n");
break;
case HALT_ACTION_REBOOT:
printf("Rebooting...\n");
reboot();
printf("Reboot failed, halting\n");
break;
case HALT_ACTION_HALT:
printf("Halting...\n");
halt_other_cpus();
break;
case HALT_ACTION_REBOOT_BOOTLOADER:
printf("Rebooting ... To Boot Loader\n");
reboot_bootloader();
// We fell through.
printf("platform_halt: Unsupported halt reason %d\n", suggested_action);
break;
case HALT_ACTION_REBOOT_RECOVERY:
printf("Rebooting ... To Recovery\n");
reboot_recovery();
// We fell through.
printf("platform_halt: Unsupported halt reason %d\n", suggested_action);
break;
}
if (reason == ZirconCrashReason::Panic) {
Thread::Current::PrintBacktrace();
}
if (!halt_on_panic) {
printf("Rebooting...\n");
reboot();
}
printf("Halted\n");
#if ENABLE_PANIC_SHELL
panic_shell_start();
#endif
for (;;) {
x86_hlt();
}
}