schedule tasks across all cores
Per-core GDT/TSS and AP scheduler entry; fix AP SSE + single_threaded.
This commit is contained in:
@@ -9,14 +9,14 @@
|
||||
//!
|
||||
//! Cores are brought up **one at a time**: a single trampoline page and parameter
|
||||
//! block are reused, so the BSP patches, wakes, and waits for one AP before the
|
||||
//! next. The mechanism-vs-policy split matches the rest of the kernel — the generic
|
||||
//! scheduler decides *what* runs where; this just gets a core executing 64-bit code.
|
||||
//!
|
||||
//! This is step 3a: an AP climbs to long mode, publishes its per-CPU pointer, marks
|
||||
//! itself alive, and parks. Entering the scheduler (its own TSS, LAPIC timer, and
|
||||
//! the run loop) is the next step.
|
||||
//! next. That also lets `apEntry` pick up its dense CPU index from a plain global.
|
||||
//! Once a core has its own descriptor tables, LAPIC, and timer, it calls the generic
|
||||
//! scheduler entry and joins the run loop — mechanism here, policy there.
|
||||
|
||||
const io = @import("io.zig");
|
||||
const gdt = @import("gdt.zig");
|
||||
const tss = @import("tss.zig");
|
||||
const idt = @import("idt.zig");
|
||||
const apic = @import("apic.zig");
|
||||
|
||||
/// IA32_GS_BASE — the per-CPU data pointer (see cpu.zig; kept in sync here so the AP
|
||||
@@ -32,6 +32,19 @@ var tramp_phys: u64 = 0;
|
||||
/// one-at-a-time handshake (only one AP is being started at any moment).
|
||||
var ap_alive: u32 = 0;
|
||||
|
||||
/// The dense CPU index of the AP currently being started. Set by the BSP before the
|
||||
/// wake, read by `apEntry` (safe because bring-up is strictly one core at a time).
|
||||
var boot_index: usize = 0;
|
||||
|
||||
/// The generic scheduler entry a woken core jumps to once its arch state is up. Set
|
||||
/// by the kernel via `setSecondaryEntry`; never returns.
|
||||
var secondary_entry: ?*const fn () callconv(.c) noreturn = null;
|
||||
|
||||
/// Register the generic entry an AP calls once its per-CPU tables/LAPIC/timer are up.
|
||||
pub fn setSecondaryEntry(entry: *const fn () callconv(.c) noreturn) void {
|
||||
secondary_entry = entry;
|
||||
}
|
||||
|
||||
/// Copy the trampoline blob to its low page. Call once, after the page has been
|
||||
/// allocated and made executable, before waking any AP.
|
||||
pub fn prepare(phys: u64) void {
|
||||
@@ -53,11 +66,13 @@ fn param(comptime name: []const u8) *align(1) volatile u64 {
|
||||
return @ptrFromInt(tramp_phys + (sym - start));
|
||||
}
|
||||
|
||||
/// Wake the core with Local APIC id `apic_id`, hand it `stack_top` and `percpu` (its
|
||||
/// per-CPU pointer), and wait for it to come alive. Returns false if it doesn't
|
||||
/// report in within the timeout (left parked, no harm to the running system).
|
||||
/// `cr3` is the kernel page tables the AP adopts. Precondition: `prepare` has run.
|
||||
pub fn startAp(apic_id: u32, stack_top: usize, percpu: usize, cr3: u64) bool {
|
||||
/// Wake the core with Local APIC id `apic_id` as dense CPU `index`, hand it
|
||||
/// `stack_top` and its per-CPU pointer `percpu`, and wait for it to come alive.
|
||||
/// Returns false if it doesn't report in within the timeout (left parked, no harm to
|
||||
/// the running system). `cr3` is the kernel page tables the AP adopts. Precondition:
|
||||
/// `prepare` has run.
|
||||
pub fn startAp(apic_id: u32, stack_top: usize, percpu: usize, index: usize, cr3: u64) bool {
|
||||
boot_index = index;
|
||||
param("ap_tramp_cr3").* = cr3;
|
||||
param("ap_tramp_stack").* = stack_top;
|
||||
param("ap_tramp_entry").* = @intFromPtr(&apEntry);
|
||||
@@ -89,13 +104,20 @@ fn delayMicros(us: u64) void {
|
||||
}
|
||||
|
||||
/// The 64-bit entry every AP lands on, called from the trampoline with its per-CPU
|
||||
/// pointer in RDI. Adopts the shared descriptor tables, publishes its per-CPU
|
||||
/// pointer, signals the BSP it's alive, and (for now) parks. Never returns.
|
||||
/// pointer in RDI. Brings up this core's own descriptor tables, LAPIC and timer,
|
||||
/// signals the BSP, then jumps to the generic scheduler entry. Never returns.
|
||||
fn apEntry(percpu: usize) callconv(.c) noreturn {
|
||||
// Step 3a: minimal. The core keeps the trampoline's descriptor tables, publishes
|
||||
// its per-CPU pointer, signals the BSP, and parks with interrupts off. Loading
|
||||
// this core's own kernel GDT/IDT/TSS and entering the scheduler is step 3b.
|
||||
io.wrmsr(ia32_gs_base, percpu); // publish per-CPU pointer (GS base)
|
||||
@atomicStore(u32, &ap_alive, 1, .release); // "I'm up" — BSP is polling this
|
||||
while (true) asm volatile ("hlt"); // parked (3b enters the scheduler here)
|
||||
const cpu = boot_index;
|
||||
gdt.loadOnThisCpu(cpu); // this core's GDT (with its own TSS slot)
|
||||
tss.setupThisCpu(cpu); // this core's TSS + IST stack, loaded into TR
|
||||
idt.loadOnThisCpu(); // the shared IDT
|
||||
io.wrmsr(ia32_gs_base, percpu); // per-CPU pointer — *after* the GDT reload
|
||||
|
||||
apic.initSecondary(); // software-enable this core's LAPIC
|
||||
apic.initTimer(apic.frequencyHz()); // arm its timer (still masked: interrupts off)
|
||||
|
||||
@atomicStore(u32, &ap_alive, 1, .release); // "arch state up" — BSP is polling this
|
||||
|
||||
if (secondary_entry) |enterScheduler| enterScheduler(); // joins the run loop
|
||||
while (true) asm volatile ("hlt"); // (only if no entry was registered)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user