blob: a7f1a7e897c96b4dc522fec0cd6ed83e4347914e [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 <assert.h>
#include <stdint.h>
#include <string.h>
#include <err.h>
#include <trace.h>
#include <arch/mmu.h>
#include <arch/x86.h>
#include <arch/x86/bootstrap16.h>
#include <arch/x86/apic.h>
#include <arch/x86/descriptor.h>
#include <arch/x86/mmu_mem_types.h>
#include <arch/x86/mp.h>
#include <lk/main.h>
#include <kernel/mp.h>
#include <kernel/thread.h>
#include <vm/vm_aspace.h>
void x86_init_smp(uint32_t *apic_ids, uint32_t num_cpus)
{
DEBUG_ASSERT(num_cpus <= UINT8_MAX);
status_t status = x86_allocate_ap_structures(apic_ids, (uint8_t)num_cpus);
if (status != ZX_OK) {
TRACEF("Failed to allocate structures for APs");
return;
}
lk_init_secondary_cpus(num_cpus - 1);
}
status_t x86_bringup_aps(uint32_t *apic_ids, uint32_t count)
{
volatile int aps_still_booting = 0;
status_t status = ZX_ERR_INTERNAL;
// if being asked to bring up 0 cpus, move on
if (count == 0) {
return ZX_OK;
}
// Sanity check the given ids
for (uint i = 0; i < count; ++i) {
int cpu = x86_apic_id_to_cpu_num(apic_ids[i]);
DEBUG_ASSERT(cpu > 0);
if (cpu <= 0) {
return ZX_ERR_INVALID_ARGS;
}
if (mp_is_cpu_online(cpu)) {
return ZX_ERR_BAD_STATE;
}
aps_still_booting |= 1U << cpu;
}
struct x86_ap_bootstrap_data *bootstrap_data = NULL;
fbl::RefPtr<VmAspace> bootstrap_aspace;
status = x86_bootstrap16_prep(
PHYS_BOOTSTRAP_PAGE,
(uintptr_t)_x86_secondary_cpu_long_mode_entry,
&bootstrap_aspace,
(void **)&bootstrap_data);
if (status != ZX_OK) {
return status;
}
bootstrap_data->cpu_id_counter = 0;
bootstrap_data->cpu_waiting_mask = &aps_still_booting;
// Zero the kstack list so if we have to bail, we can safely free the
// resources.
memset(&bootstrap_data->per_cpu, 0, sizeof(bootstrap_data->per_cpu));
// Allocate kstacks and threads for all processors
for (unsigned int i = 0; i < count; ++i) {
thread_t *thread = (thread_t *)memalign(16,
ROUNDUP(sizeof(thread_t), 16) +
#if __has_feature(safe_stack)
PAGE_SIZE +
#endif
PAGE_SIZE);
if (!thread) {
status = ZX_ERR_NO_MEMORY;
goto cleanup_allocations;
}
uintptr_t kstack_base =
(uint64_t)thread + ROUNDUP(sizeof(thread_t), 16);
bootstrap_data->per_cpu[i].kstack_base = kstack_base;
bootstrap_data->per_cpu[i].thread = (uint64_t)thread;
#if __has_feature(safe_stack)
thread->unsafe_stack = (void *) (kstack_base + PAGE_SIZE);
thread->stack_size = PAGE_SIZE;
#endif
}
// Memory fence to ensure all writes to the bootstrap region are
// visible on the APs when they come up
smp_mb();
dprintf(INFO, "booting apic ids: ");
for (unsigned int i = 0; i < count; ++i) {
uint32_t apic_id = apic_ids[i];
dprintf(INFO, "%#x ", apic_id);
apic_send_ipi(0, apic_id, DELIVERY_MODE_INIT);
}
dprintf(INFO, "\n");
// Wait 10 ms and then send the startup signals
thread_sleep_relative(LK_MSEC(10));
// Actually send the startups
ASSERT(PHYS_BOOTSTRAP_PAGE < 1 * MB);
uint8_t vec;
vec = PHYS_BOOTSTRAP_PAGE >> PAGE_SIZE_SHIFT;
// Try up to two times per CPU, as Intel 3A recommends.
for (int tries = 0; tries < 2; ++tries) {
for (unsigned int i = 0; i < count; ++i) {
uint32_t apic_id = apic_ids[i];
// This will cause the APs to begin executing at PHYS_BOOTSTRAP_PAGE in
// physical memory.
apic_send_ipi(vec, apic_id, DELIVERY_MODE_STARTUP);
}
if (aps_still_booting == 0) {
break;
}
// Wait 1ms for cores to boot. The docs recommend 200us between STARTUP
// IPIs.
thread_sleep_relative(LK_MSEC(1));
}
// The docs recommend waiting 200us for cores to boot. We do a bit more
// work before the cores report in, so wait longer (up to 1 second).
for (int tries_left = 200;
aps_still_booting != 0 && tries_left > 0;
--tries_left) {
thread_sleep_relative(LK_MSEC(5));
}
uint failed_aps;
failed_aps = (uint)atomic_swap(&aps_still_booting, 0);
if (failed_aps != 0) {
printf("Failed to boot CPUs: mask %x\n", failed_aps);
for (uint i = 0; i < count; ++i) {
int cpu = x86_apic_id_to_cpu_num(apic_ids[i]);
uint mask = 1U << cpu;
if ((failed_aps & mask) == 0) {
continue;
}
// Shut the failed AP down
apic_send_ipi(0, apic_ids[i], DELIVERY_MODE_INIT);
// It shouldn't have been possible for it to have been in the
// scheduler...
ASSERT(!mp_is_cpu_active(cpu));
// Make sure the CPU is not marked online
atomic_and((volatile int *)&mp.online_cpus, ~mask);
// Free the failed AP's thread, it was cancelled before it could use
// it.
free((void *)bootstrap_data->per_cpu[i].thread);
failed_aps &= ~mask;
}
DEBUG_ASSERT(failed_aps == 0);
status = ZX_ERR_TIMED_OUT;
goto finish;
}
// Now that everything is booted, cleanup all temporary structures (e.g.
// everything except the kstacks).
goto cleanup_aspace;
cleanup_allocations:
for (unsigned int i = 0; i < count; ++i) {
if (bootstrap_data->per_cpu[i].thread) {
free((void *)bootstrap_data->per_cpu[i].thread);
}
}
cleanup_aspace:
bootstrap_aspace->Destroy();
VmAspace::kernel_aspace()->FreeRegion(reinterpret_cast<vaddr_t>(bootstrap_data));
finish:
return status;
}