Doesn't do much but provided the detection path for it and ability to hold initialized state. The higher level platform code is going to need to use it directly so will mostly just provide an api for access to it. Moved ACPI sniffing back to just after the VM is initialized instead of all the way into platform_init(). This should try to ensure that all drivers that come up afterwards will have ioapics discovered in case future development tries to enable and use them, kicking the machine out of virtual-wire-mode.
194 lines
5.5 KiB
C
194 lines
5.5 KiB
C
/*
|
|
* Copyright (c) 2024 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 "platform_p.h"
|
|
|
|
#include <kernel/thread.h>
|
|
#include <kernel/vm.h>
|
|
#include <lk/err.h>
|
|
#include <lk/main.h>
|
|
#include <lk/trace.h>
|
|
#include <string.h>
|
|
#include <arch/x86/apic.h>
|
|
|
|
#if WITH_SMP
|
|
|
|
#include <lib/acpi_lite.h>
|
|
|
|
#define TRAMPOLINE_ADDRESS 0x4000
|
|
|
|
#define LOCAL_TRACE 1
|
|
|
|
extern void mp_boot_start(void);
|
|
extern void mp_boot_end(void);
|
|
|
|
struct bootstrap_args {
|
|
// referenced in mp-boot.S, do not move without updating assembly
|
|
uintptr_t trampoline_cr3;
|
|
uintptr_t stack_top;
|
|
|
|
// referenced in C, okay to move
|
|
uintptr_t cpu_num;
|
|
volatile uint32_t *boot_completed_ptr; // set by the secondary cpu when it's done
|
|
};
|
|
|
|
// called from assembly code in mp-boot.S
|
|
__NO_RETURN void secondary_entry(struct bootstrap_args *args) {
|
|
volatile uint32_t *boot_completed = args->boot_completed_ptr;
|
|
uint cpu_num = args->cpu_num;
|
|
|
|
// context switch to the kernels cr3
|
|
x86_set_cr3(vmm_get_kernel_aspace()->arch_aspace.cr3_phys);
|
|
// from now on out the boot args structure is not visible
|
|
|
|
// we're done, let the primary cpu know so it can reuse the args
|
|
*boot_completed = 1;
|
|
|
|
x86_secondary_entry(cpu_num);
|
|
}
|
|
|
|
static status_t start_cpu(uint cpu_num, uint32_t apic_id, struct bootstrap_args *args) {
|
|
LTRACEF("cpu_num %u, apic_id %u\n", cpu_num, apic_id);
|
|
|
|
// assert that this thread is pinned to the current cpu
|
|
DEBUG_ASSERT(thread_pinned_cpu(get_current_thread()) == (int)arch_curr_cpu_num());
|
|
|
|
volatile uint32_t boot_completed = 0;
|
|
args->boot_completed_ptr = &boot_completed;
|
|
|
|
// start x86 secondary cpu
|
|
|
|
// send INIT IPI
|
|
lapic_send_init_ipi(apic_id, true);
|
|
thread_sleep(10);
|
|
|
|
// deassert INIT
|
|
lapic_send_init_ipi(apic_id, false);
|
|
thread_sleep(10);
|
|
|
|
// send Startup IPI up to 2 times as recommended by Intel
|
|
for (int i = 0; i < 2; i++) {
|
|
lapic_send_startup_ipi(apic_id, TRAMPOLINE_ADDRESS);
|
|
|
|
// Wait a little bit for the cpu to start before trying a second time
|
|
thread_sleep(10);
|
|
if (boot_completed) {
|
|
goto booted;
|
|
}
|
|
}
|
|
|
|
// Wait up to a second for the cpu to finish starting
|
|
for (int i = 0; i < 1000; i++) {
|
|
if (boot_completed) {
|
|
goto booted;
|
|
}
|
|
thread_sleep(10);
|
|
}
|
|
|
|
// we have failed to start this core
|
|
// TODO: handle trying to shut the core down before moving on.
|
|
printf("PC: failed to start cpu %u\n", cpu_num);
|
|
return ERR_TIMED_OUT;
|
|
|
|
booted:
|
|
LTRACEF("cpu %u booted\n", cpu_num);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
struct detected_cpus {
|
|
uint32_t num_detected;
|
|
uint32_t apic_ids[SMP_MAX_CPUS];
|
|
};
|
|
|
|
static void local_apic_callback(const void *_entry, size_t entry_len, void *cookie) {
|
|
const struct acpi_madt_local_apic_entry *entry = _entry;
|
|
struct detected_cpus *cpus = cookie;
|
|
|
|
if ((entry->flags & ACPI_MADT_FLAG_ENABLED) == 0) {
|
|
return;
|
|
}
|
|
|
|
if (entry->apic_id == x86_get_apic_id()) {
|
|
// skip the boot cpu
|
|
return;
|
|
}
|
|
if (cpus->num_detected < SMP_MAX_CPUS) {
|
|
cpus->apic_ids[cpus->num_detected++] = entry->apic_id;
|
|
}
|
|
}
|
|
|
|
void platform_start_secondary_cpus(void) {
|
|
struct detected_cpus cpus;
|
|
cpus.num_detected = 1;
|
|
cpus.apic_ids[0] = 0; // the boot cpu
|
|
|
|
acpi_process_madt_entries_etc(ACPI_MADT_TYPE_LOCAL_APIC, &local_apic_callback, &cpus);
|
|
|
|
// TODO: fall back to legacy methods if ACPI fails
|
|
// TODO: deal with cpu topology
|
|
|
|
// start up the secondary cpus
|
|
if (cpus.num_detected < 2) {
|
|
dprintf(INFO, "PC: no secondary cpus detected\n");
|
|
return;
|
|
}
|
|
|
|
// create a new aspace to build an identity map in
|
|
vmm_aspace_t *aspace;
|
|
status_t err = vmm_create_aspace(&aspace, "identity map", 0);
|
|
if (err < 0) {
|
|
panic("failed to create identity map aspace\n");
|
|
}
|
|
|
|
// set up an identity map for the trampoline code
|
|
|
|
void *ptr = (void *)TRAMPOLINE_ADDRESS;
|
|
err = vmm_alloc_physical(aspace, "trampoline", 0x10000, &ptr, 0,
|
|
TRAMPOLINE_ADDRESS, VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_CACHED);
|
|
if (err < 0) {
|
|
panic("failed to allocate trampoline memory\n");
|
|
}
|
|
|
|
vmm_aspace_t *old_aspace = vmm_set_active_aspace(aspace);
|
|
|
|
// set up bootstrap code page at TRAMPOLINE_ADDRESS for secondary cpu
|
|
memcpy(ptr, mp_boot_start, mp_boot_end - mp_boot_start);
|
|
|
|
// next page has args in it
|
|
struct bootstrap_args *args = (struct bootstrap_args *)((uintptr_t)ptr + 0x1000);
|
|
args->trampoline_cr3 = aspace->arch_aspace.cr3_phys;
|
|
|
|
dprintf(INFO, "PC: detected %u cpus\n", cpus.num_detected);
|
|
|
|
lk_init_secondary_cpus(cpus.num_detected - 1);
|
|
err = x86_allocate_percpu_array(cpus.num_detected - 1);
|
|
if (err < 0) {
|
|
panic("failed to allocate percpu array\n");
|
|
}
|
|
|
|
for (uint i = 1; i < cpus.num_detected; i++) {
|
|
dprintf(INFO, "PC: starting cpu %u\n", cpus.apic_ids[i]);
|
|
|
|
args->cpu_num = i;
|
|
|
|
x86_percpu_t *percpu = x86_get_percpu_for_cpu(i);
|
|
args->stack_top = (uintptr_t)percpu->bootstrap_stack + sizeof(percpu->bootstrap_stack);
|
|
|
|
LTRACEF("args for cpu %lu: trampoline_cr3 %#lx, stack_top 0x%lx\n", args->cpu_num, args->trampoline_cr3, args->stack_top);
|
|
|
|
start_cpu(i, cpus.apic_ids[i], args);
|
|
}
|
|
|
|
// restore old aspace
|
|
vmm_set_active_aspace(old_aspace);
|
|
|
|
// free the trampoline aspace
|
|
vmm_free_aspace(aspace);
|
|
}
|
|
|
|
#endif // WITH_SMP
|