Files
LithosAnanake/src/starkernel/arch/amd64/smp.c
T
Robert Allan JamesandClaude Opus 5.5 71e0be96b0 FABRIC-3.10.8 MC-3b: waking a worker core
Each worker core sets up its own IPI receiver before going online (amd64
LAPIC + vector 0x22 with its own stub, aarch64 banked GIC SGI 0, riscv64
sie.SSIE); core 0 sends by ICR / GICD_SGIR / SBI IPI. Park lets exactly
one interrupt in without a lost-wake window. Boot self-test: IPI round
trips 3/3 and 'woke x3, IPIs x3' on all three ISAs; worker vCPU threads
0.0% on the host. 3-ISA acceptance clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:31:01 -04:00

242 lines
8.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
*/
/**
* smp.c (amd64) - secondary-core bring-up, FABRIC-3.10.8 MC-1a.
*
* Cores come from the MADT's enabled Local APIC entries. Each is started
* with INIT, 10 ms, STARTUP, 200 us, STARTUP into ap_trampoline.S, copied
* into a conventional-memory page below 1 MiB taken from the UEFI memory
* map. The trampoline enters long mode on the boot core's own control
* state and calls sk_smp_secondary_main() with its GS base already set.
*
* Limit (recorded for SER5): the trampoline loads CR3 in 32-bit mode, so
* the page tables must lie below 4 GiB; a start is refused otherwise.
*/
#include "starkernel/smp.h"
#include "starkernel/apic.h"
#include "starkernel/timer.h"
#include "starkernel/console.h"
#include "starkernel/acpi.h" /* MC-1b: the MADT lookup, shared with aarch64 */
#include "uefi.h"
extern char sk_ap_tramp_start[], sk_ap_tramp_end[];
extern char sk_ap_tr_pm_jmp[], sk_ap_tr_lm_jmp[], sk_ap_tr_pm32[], sk_ap_tr_lm64[];
extern char sk_ap_tr_gdt[], sk_ap_tr_gdtr[], sk_ap_tr_data[];
/* The trampoline's data block (ap_trampoline.S, D_* offsets). */
typedef struct __attribute__((packed)) {
uint64_t cr0, cr3, cr4_boot, cr4, efer, pat, xcr0, xcr0_valid;
uint64_t percpu, stack, entry;
uint16_t kgdt_limit; uint64_t kgdt_base;
uint16_t kidt_limit; uint64_t kidt_base;
} ap_tramp_data_t;
#define CR4_PCIDE (1ull << 17)
#define CR4_OSXSAVE (1ull << 18)
#define ICR_INIT 0x00004500u /* INIT, level assert */
#define ICR_STARTUP 0x00004600u /* STARTUP; low byte = vector (page number) */
/* ---- MADT ---------------------------------------------------------------- */
static int id_seen(const uint64_t *ids, uint32_t n, uint64_t id)
{
uint32_t i;
for (i = 0; i < n; i++) if (ids[i] == id) return 1;
return 0;
}
/* Enabled type-0 (Local APIC) entries, then type-9 (x2APIC) entries whose
* id fits xAPIC (<= 255) and was not already listed. */
uint32_t arch_smp_enumerate(BootInfo *boot_info, uint64_t *hw_ids, uint32_t cap)
{
const uint8_t *madt = acpi_find_table(boot_info ? boot_info->acpi_table : (void *)0, "APIC");
const uint8_t *p, *end;
uint32_t n = 0, pass;
if (!madt) {
if (cap) hw_ids[0] = apic_id();
return 1;
}
end = madt + acpi_table_length(madt);
for (pass = 0; pass < 2; pass++) {
for (p = madt + 44; p + 2 <= end; p += p[1]) {
uint64_t id;
uint32_t flags;
if (p[1] == 0 || p + p[1] > end) break;
if (pass == 0 && p[0] == 0 && p[1] >= 8) {
id = p[3];
flags = (uint32_t)p[4] | ((uint32_t)p[5] << 8) | ((uint32_t)p[6] << 16) | ((uint32_t)p[7] << 24);
} else if (pass == 1 && p[0] == 9 && p[1] >= 16) {
id = (uint64_t)p[4] | ((uint64_t)p[5] << 8) | ((uint64_t)p[6] << 16) | ((uint64_t)p[7] << 24);
flags = (uint32_t)p[8] | ((uint32_t)p[9] << 8) | ((uint32_t)p[10] << 16) | ((uint32_t)p[11] << 24);
if (id > 255u) continue;
} else {
continue;
}
if (!(flags & 1u)) continue; /* not enabled */
if (cap) {
if (id_seen(hw_ids, n < cap ? n : cap, id)) continue;
if (n < cap) hw_ids[n] = id;
}
n++;
}
}
if (n == 0) {
if (cap) hw_ids[0] = apic_id();
return 1;
}
return n;
}
uint64_t arch_smp_boot_hw_id(void) { return apic_id(); }
/* MC-3b: exactly one interrupt may end the halt -- `sti`'s one-instruction
* shadow covers the `hlt`, so a wake IPI can't slip in between and be lost. */
void arch_smp_park_once(void)
{
__asm__ volatile ("sti; hlt; cli" ::: "memory");
}
void arch_smp_secondary_init(sk_percpu_t *pc)
{
apic_secondary_enable();
pc->ipi_target = pc->hw_id; /* the APIC id */
}
void arch_smp_send_ipi(const sk_percpu_t *target)
{
(void)apic_send_ipi((uint8_t)target->ipi_target, 0x00004000u | SK_IPI_VECTOR); /* fixed, assert */
}
const char *arch_smp_arrival(const sk_percpu_t *pc)
{
(void)pc;
return "long mode"; /* the trampoline's only way to reach C */
}
/* ---- the trampoline page ------------------------------------------------- */
static uint64_t g_tramp_page; /* 0 = not chosen yet */
/* The lowest conventional page in [0x1000, 1 MiB) whose whole 4 KiB is
* free in the UEFI memory map. */
static uint64_t pick_tramp_page(BootInfo *bi)
{
uint64_t best = 0;
UINTN n, i;
if (!bi || !bi->memory_map || !bi->memory_map_descriptor_size) return 0;
n = bi->memory_map_size / bi->memory_map_descriptor_size;
for (i = 0; i < n; i++) {
const EFI_MEMORY_DESCRIPTOR *d = (const EFI_MEMORY_DESCRIPTOR *)
((const uint8_t *)bi->memory_map + i * bi->memory_map_descriptor_size);
uint64_t start, end;
if (d->Type != EfiConventionalMemory) continue;
start = d->PhysicalStart;
end = start + d->NumberOfPages * 4096ull;
if (start < 0x1000ull) start = 0x1000ull;
start = (start + 0xFFFull) & ~0xFFFull;
if (start + 4096ull > end || start + 4096ull > 0x100000ull) continue;
if (!best || start < best) best = start;
}
return best;
}
static uint64_t rdmsr64(uint32_t msr)
{
uint32_t lo, hi;
__asm__ volatile ("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
return ((uint64_t)hi << 32) | lo;
}
static void wait_us(uint64_t us)
{
uint64_t hz = timer_tsc_hz(), t0 = heartbeat_read_counter();
uint64_t budget = hz ? (hz / 1000000u) * us : 0;
uint64_t spin = us * 1000u; /* only if the counter's rate is unknown */
while (hz ? (heartbeat_read_counter() - t0 < budget) : (spin-- > 0)) {
__asm__ volatile ("pause");
}
}
int arch_smp_start(BootInfo *boot_info, sk_percpu_t *pc, void *stack_top)
{
uint8_t *page;
size_t len = (size_t)(sk_ap_tramp_end - sk_ap_tramp_start), k;
ap_tramp_data_t *d;
uint64_t cr0, cr3, cr4;
struct __attribute__((packed)) { uint16_t limit; uint64_t base; } gdtr, idtr;
uint32_t T;
if (pc->hw_id > 255u || len > 4096u) return -1;
if (!g_tramp_page) g_tramp_page = pick_tramp_page(boot_info);
if (!g_tramp_page) {
console_println("SMP: no conventional page below 1 MiB for the trampoline");
return -1;
}
__asm__ volatile ("mov %%cr0, %0" : "=r"(cr0));
__asm__ volatile ("mov %%cr3, %0" : "=r"(cr3));
__asm__ volatile ("mov %%cr4, %0" : "=r"(cr4));
if (cr3 >> 32) {
console_println("SMP: page tables above 4 GiB -- the trampoline cannot load CR3");
return -1;
}
__asm__ volatile ("sgdt %0" : "=m"(gdtr));
__asm__ volatile ("sidt %0" : "=m"(idtr));
T = (uint32_t)g_tramp_page;
page = (uint8_t *)(uintptr_t)g_tramp_page;
for (k = 0; k < len; k++) page[k] = (uint8_t)sk_ap_tramp_start[k];
*(uint32_t *)(void *)(page + (sk_ap_tr_pm_jmp - sk_ap_tramp_start)) =
T + (uint32_t)(sk_ap_tr_pm32 - sk_ap_tramp_start);
*(uint32_t *)(void *)(page + (sk_ap_tr_lm_jmp - sk_ap_tramp_start)) =
T + (uint32_t)(sk_ap_tr_lm64 - sk_ap_tramp_start);
*(uint32_t *)(void *)(page + (sk_ap_tr_gdtr - sk_ap_tramp_start) + 2) =
T + (uint32_t)(sk_ap_tr_gdt - sk_ap_tramp_start);
d = (ap_tramp_data_t *)(void *)(page + (sk_ap_tr_data - sk_ap_tramp_start));
d->cr0 = cr0;
d->cr3 = cr3;
d->cr4 = cr4;
d->cr4_boot = cr4 & ~CR4_PCIDE;
d->efer = rdmsr64(0xC0000080u) & ~(1ull << 10); /* LMA is the CPU's to set */
d->pat = rdmsr64(0x277u);
d->xcr0_valid = (cr4 & CR4_OSXSAVE) ? 1u : 0u;
d->xcr0 = 0;
if (d->xcr0_valid) {
uint32_t lo, hi;
__asm__ volatile ("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0));
d->xcr0 = ((uint64_t)hi << 32) | lo;
}
d->percpu = (uint64_t)(uintptr_t)pc;
d->stack = (uint64_t)(uintptr_t)stack_top;
d->entry = (uint64_t)(uintptr_t)&sk_smp_secondary_main;
d->kgdt_limit = gdtr.limit; d->kgdt_base = gdtr.base;
d->kidt_limit = idtr.limit; d->kidt_base = idtr.base;
__asm__ volatile ("mfence" ::: "memory");
if (apic_send_ipi((uint8_t)pc->hw_id, ICR_INIT) != 0) {
console_println("SMP: INIT IPI not delivered");
return -1;
}
wait_us(10000u);
(void)apic_send_ipi((uint8_t)pc->hw_id, ICR_STARTUP | (T >> 12));
wait_us(200u);
if (!__atomic_load_n(&pc->online, __ATOMIC_ACQUIRE)) {
(void)apic_send_ipi((uint8_t)pc->hw_id, ICR_STARTUP | (T >> 12));
}
return 0;
}