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>
242 lines
8.7 KiB
C
242 lines
8.7 KiB
C
/*
|
||
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;
|
||
}
|