wake application processors to long mode
INIT-SIPI-SIPI plus a self-relocating real-mode trampoline.
This commit is contained in:
@@ -30,6 +30,11 @@ const cp_running = 0x70;
|
||||
const cp_exception = 0xE0;
|
||||
const cp_panic = 0xEE;
|
||||
|
||||
/// Physical address of the low page reserved at boot for the AP trampoline (0 = none
|
||||
/// was available). Claimed right after the frame allocator comes up, before paging
|
||||
/// and the heap consume the scarce sub-1 MiB frames.
|
||||
var ap_trampoline_page: u64 = 0;
|
||||
|
||||
/// Kernel entry point. The bootloader jumps here after `ExitBootServices` with a
|
||||
/// pointer to the handoff data. There is no runtime, no stack unwinding, and no
|
||||
/// caller to return to, so this never returns.
|
||||
@@ -98,6 +103,10 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
||||
// Bring up the physical frame allocator over that map, and prove it works:
|
||||
// allocate three frames, then hand them back.
|
||||
pmm.init(boot_info.memory_map);
|
||||
// Claim the AP trampoline's low (<1 MiB) page *now*, before paging and the heap
|
||||
// draw down sub-1 MiB frames (the allocator scans upward from frame 0). Held
|
||||
// until SMP bring-up; 0 means none was available (we stay uniprocessor).
|
||||
ap_trampoline_page = pmm.allocBelow(0x100000) orelse 0;
|
||||
const s1 = pmm.stats();
|
||||
log.print("\ndanos: frame allocator online\n", .{});
|
||||
log.print(" free frames: {d} ({d} MiB)\n", .{ s1.free_frames, mib(s1.free_frames) });
|
||||
@@ -221,6 +230,10 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
||||
log.checkpoint(cp_timer);
|
||||
log.print("danos: timer online ({d} Hz tick; LAPIC {d} MHz, TSC {d} MHz; calibrated via {s})\n", .{ arch.timer_hz, arch.lapicHz() / 1_000_000, arch.tscHz() / 1_000_000, arch.timerCalibrationSource() });
|
||||
|
||||
// Wake the other cores (application processors). A no-op on a single-core
|
||||
// machine; on SMP each AP climbs to long mode and reports in (docs/smp.md).
|
||||
bringUpSecondaries();
|
||||
|
||||
// In a test build (`zig build -Dtest-case=<name>`), run that case and stop.
|
||||
// Normal builds fall through to the idle halt.
|
||||
if (build_options.test_case) |case| {
|
||||
@@ -238,6 +251,41 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
||||
arch.halt();
|
||||
}
|
||||
|
||||
/// Wake the application processors the firmware left parked. Allocates the low
|
||||
/// trampoline page (and makes it executable), then wakes each non-boot core in turn,
|
||||
/// handing it a fresh kernel stack and its per-CPU slot. Cores that don't report in
|
||||
/// are left parked — the running system is unaffected. See docs/smp.md.
|
||||
fn bringUpSecondaries() void {
|
||||
const cores = platform.cpus();
|
||||
if (cores.len <= 1) return;
|
||||
|
||||
// The trampoline page was reserved below 1 MiB at boot (a real-mode SIPI vector
|
||||
// addresses it). Make it executable — the blanket RAM mapping is NX (W^X).
|
||||
if (ap_trampoline_page == 0) {
|
||||
log.write("danos: smp: no low page for the AP trampoline; staying uniprocessor\n");
|
||||
return;
|
||||
}
|
||||
arch.setPageExecutable(ap_trampoline_page);
|
||||
arch.prepareSecondaries(ap_trampoline_page);
|
||||
|
||||
log.print("\ndanos: bringing up {d} application processor(s)\n", .{cores.len - 1});
|
||||
for (cores[1..], 1..) |core, index| {
|
||||
const stack = heap.allocator().alloc(u8, 16 * 1024) catch {
|
||||
log.print(" cpu apic_id {d}: no stack; skipped\n", .{core.apic_id});
|
||||
continue;
|
||||
};
|
||||
const stack_top = (@intFromPtr(stack.ptr) + stack.len) & ~@as(usize, 15);
|
||||
const pc = scheduler.prepareSecondary(index, core.apic_id);
|
||||
if (arch.startSecondary(core.apic_id, stack_top, @intFromPtr(pc))) {
|
||||
pc.online = true;
|
||||
log.print(" cpu apic_id {d}: online\n", .{core.apic_id});
|
||||
} else {
|
||||
log.print(" cpu apic_id {d}: no response (parked)\n", .{core.apic_id});
|
||||
}
|
||||
}
|
||||
log.print("danos: {d}/{d} cores online\n", .{ scheduler.onlineCount(), cores.len });
|
||||
}
|
||||
|
||||
/// A user-facing status line: to the diagnostic `log` *and* the on-screen console
|
||||
/// (if a framebuffer is present). The verbose log uses `log.*` directly and never
|
||||
/// touches the framebuffer.
|
||||
|
||||
Reference in New Issue
Block a user