Files
lk/arch/x86/mp.c
Travis Geiselbrecht b7d69d8804 [arch][x86] handle the local apic of the boot cpu not being 0
I have a bulldozer machine here that curiously starts the APIC IDs for
the cpus at 16 and counts up.

This is a problem since the current code assumes that the boot cpu is 0,
and would try to start itself (apic id 16) later because it thought it
was the first secondary. Fix this by re-reading the APIC id on the boot
cpu and patching the percpu structure a bit into boot. Kinda a hack but
avoids having to detect the APIC, find the type of ID to read, etc.

Also means that practically speaking the system is using the full 32bit
APIC IDs if that feature is present, since now the local apic id is
entirely read from the local apic as it should be (if present).

Fixes #475
2025-09-22 20:57:30 -07:00

124 lines
3.4 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 <arch/x86/mp.h>
#include <assert.h>
#include <lk/trace.h>
#include <lk/err.h>
#include <lk/init.h>
#include <lk/main.h>
#include <arch/mp.h>
#include <string.h>
#include <arch/x86.h>
#include <arch/x86/descriptor.h>
#include <arch/arch_ops.h>
#include <sys/types.h>
#include <arch/x86/lapic.h>
#include <arch/x86/feature.h>
#define LOCAL_TRACE 0
#if WITH_SMP
// the boot cpu's percpu struct
static x86_percpu_t x86_boot_percpu;
// pointer to an array of percpu structs for each of the secondary cpus
static x86_percpu_t *x86_ap_percpus;
x86_percpu_t *x86_get_percpu_for_cpu(uint cpu_num) {
DEBUG_ASSERT(cpu_num < SMP_MAX_CPUS);
if (cpu_num == 0) {
return &x86_boot_percpu;
}
DEBUG_ASSERT(x86_ap_percpus);
return &x86_ap_percpus[cpu_num - 1];
}
void x86_configure_percpu_early(uint cpu_num, uint apic_id) {
x86_percpu_t *percpu = x86_get_percpu_for_cpu(cpu_num);
// initialize the percpu structure for this cpu
percpu->self = percpu;
percpu->cpu_num = cpu_num;
percpu->apic_id = apic_id;
#if ARCH_X86_64
// use the 64-bit gs base msr to set up a pointer to the percpu struct
write_msr(X86_MSR_IA32_KERNEL_GS_BASE, 0);
write_msr(X86_MSR_IA32_GS_BASE, (uint64_t)percpu);
#else
// set up a gs descriptor for this cpu
uint16_t selector = PERCPU_SELECTOR_BASE + cpu_num * 8;
x86_set_gdt_descriptor(selector, percpu, sizeof(*percpu), 1, 0, 1, SEG_TYPE_DATA_RW, 0, 1);
x86_set_gs(selector);
#endif
}
status_t arch_mp_send_ipi(mp_cpu_mask_t target, mp_ipi_t ipi) {
LTRACEF("cpu %u target 0x%x, ipi 0x%x\n", arch_curr_cpu_num(), target, ipi);
DEBUG_ASSERT(arch_ints_disabled());
uint curr_cpu_num = arch_curr_cpu_num();
// translate the target bitmap to apic id
while (target) {
uint cpu_num = __builtin_ctz(target);
target &= ~(1u << cpu_num);
// skip the current cpu
if (cpu_num == curr_cpu_num) {
continue;
}
x86_percpu_t *percpu = x86_get_percpu_for_cpu(cpu_num);
uint32_t apic_id = percpu->apic_id;
// send the ipi to the target cpu
lapic_send_ipi(apic_id, ipi);
}
return NO_ERROR;
}
void x86_secondary_entry(uint cpu_num) {
// Read the local apic id from the local apic.
// NOTE: assumes a local apic is present but since this is a secondary cpu,
// it should be a safe assumption.
lapic_enable_on_local_cpu();
uint32_t apic_id = lapic_get_apic_id();
x86_configure_percpu_early(cpu_num, apic_id);
x86_early_init_percpu();
// run early secondary cpu init routines up to the threading level
lk_init_level(LK_INIT_FLAG_SECONDARY_CPUS, LK_INIT_LEVEL_EARLIEST, LK_INIT_LEVEL_THREADING - 1);
dprintf(INFO, "SMP: secondary cpu %u started, apic id %u\n", arch_curr_cpu_num(), apic_id);
lk_secondary_cpu_entry();
// should never get here except for an error condition
for (;;);
}
status_t x86_allocate_percpu_array(uint num_cpus) {
x86_ap_percpus = memalign(_Alignof(x86_percpu_t), num_cpus * sizeof(x86_percpu_t));
if (!x86_ap_percpus) {
return ERR_NO_MEMORY;
}
memset(x86_ap_percpus, 0, num_cpus * sizeof(x86_percpu_t));
return NO_ERROR;
}
#else
void x86_configure_percpu_early(uint cpu_num, uint apic_id) {}
#endif