88 lines
3.8 KiB
Zig
88 lines
3.8 KiB
Zig
//! Task State Segment and its interrupt stacks. In long mode the TSS has two
|
|
//! jobs. First, the Interrupt Stack Table: an IDT gate can name an IST entry,
|
|
//! and the CPU switches to that stack when the exception fires — no matter how
|
|
//! broken the interrupted stack was. We use IST1 for the double-fault handler,
|
|
//! so a fault that happens *because* the current stack is unusable still lands
|
|
//! on solid ground instead of triple-faulting. Second, rsp0: the kernel stack
|
|
//! the CPU switches to when an interrupt arrives from ring 3 (published by the
|
|
//! user-mode entry path via `rsp0Ptr`).
|
|
//!
|
|
//! Each core needs **its own TSS** (its own IST stack): two cores taking a fault at
|
|
//! once can't share one fault stack. So the TSS and its IST stack are per-core,
|
|
//! indexed by CPU number; slot 0 is the BSP.
|
|
|
|
const parameters = @import("parameters");
|
|
const gdt = @import("gdt.zig");
|
|
|
|
/// x86_64 TSS. `packed` because several 64-bit fields sit at 4-byte-unaligned
|
|
/// offsets (rsp0 at byte 4), which a normal struct would pad away.
|
|
const Tss = packed struct {
|
|
reserved0: u32 = 0,
|
|
rsp0: u64 = 0,
|
|
rsp1: u64 = 0,
|
|
rsp2: u64 = 0,
|
|
reserved1: u64 = 0,
|
|
ist1: u64 = 0,
|
|
ist2: u64 = 0,
|
|
ist3: u64 = 0,
|
|
ist4: u64 = 0,
|
|
ist5: u64 = 0,
|
|
ist6: u64 = 0,
|
|
ist7: u64 = 0,
|
|
reserved2: u64 = 0,
|
|
reserved3: u16 = 0,
|
|
iomap_base: u16 = 0,
|
|
};
|
|
|
|
/// The IST slot (1-based, as the IDT gate encodes it) used for critical faults.
|
|
pub const double_fault_ist = 1;
|
|
|
|
const maximum_cpus = parameters.maximum_cpus;
|
|
pub const ist_stack_size = parameters.ist_stack_size;
|
|
|
|
/// One TSS per core (small — kept static). The IST stacks are 16 KiB each, so only
|
|
/// the **BSP's** is static: it must exist before the frame allocator does, to catch a
|
|
/// fault during early boot. Each **AP** gets a heap-allocated IST stack at bring-up
|
|
/// (after the heap is up), the top of which the BSP records here before waking it —
|
|
/// so we reserve big stacks only for cores that actually come online.
|
|
var tss_table = [_]Tss{.{}} ** maximum_cpus;
|
|
var bsp_ist_stack: [ist_stack_size]u8 align(16) = undefined;
|
|
var ap_ist_top = [_]usize{0} ** maximum_cpus; // per-AP IST stack top (0 = BSP / not set)
|
|
|
|
/// Loads the task register with the TSS selector. Defined in isr.s.
|
|
extern fn load_tr(selector: u16) callconv(.c) void;
|
|
|
|
/// Address of core `cpu`'s rsp0 slot — the kernel stack the CPU switches to on a
|
|
/// ring-3 -> ring-0 interrupt. Computed as base + 4 (rsp0's architectural offset,
|
|
/// which is why the pointer is only 4-aligned) rather than `&t.rsp0`, which on a
|
|
/// packed struct would be an unaligned bit-pointer type. The ring-3 entry path
|
|
/// (enter_user in isr.s) writes the current kernel stack pointer through this
|
|
/// before dropping to user mode.
|
|
pub fn rsp0Ptr(cpu: usize) *align(4) u64 {
|
|
return @ptrFromInt(@intFromPtr(&tss_table[cpu]) + 4);
|
|
}
|
|
|
|
/// Record the top of the IST stack the kernel allocated for AP `cpu`. Called on the
|
|
/// BSP before waking that core; read by the core's own `setupThisCpu`.
|
|
pub fn setApIstStack(cpu: usize, top: usize) void {
|
|
ap_ist_top[cpu] = top;
|
|
}
|
|
|
|
/// Set up core `cpu`'s TSS: point IST1 at its stack (the BSP's static one for core 0,
|
|
/// the allocated one recorded via `setApIstStack` for an AP), install the TSS
|
|
/// descriptor into that core's GDT, and load it into the task register. Requires the
|
|
/// core's GDT to already be loaded (gdt.loadOnThisCpu first).
|
|
pub fn setupThisCpu(cpu: usize) void {
|
|
const t = &tss_table[cpu];
|
|
t.* = .{};
|
|
t.ist1 = if (cpu == 0) @intFromPtr(&bsp_ist_stack) + ist_stack_size else ap_ist_top[cpu];
|
|
t.iomap_base = @sizeOf(Tss); // == limit: no I/O permission bitmap
|
|
gdt.setTssFor(cpu, @intFromPtr(t), @sizeOf(Tss) - 1);
|
|
load_tr(gdt.tss_selector);
|
|
}
|
|
|
|
/// Set up the bootstrap processor's TSS (slot 0). Requires gdt.init first.
|
|
pub fn init() void {
|
|
setupThisCpu(0);
|
|
}
|