193 lines
6.2 KiB
Zig
193 lines
6.2 KiB
Zig
//! x86_64 CPU operations. This is the "arch" module: the generic kernel imports
|
|
//! it as `@import("arch")` and never names x86_64 directly, so a second
|
|
//! architecture is added by pointing that module at a different directory in
|
|
//! build.zig — no change to the generic code. Keep everything CPU-specific here
|
|
//! (halt, the descriptor tables, later paging), and nothing generic.
|
|
|
|
const danos = @import("danos");
|
|
const gdt = @import("gdt.zig");
|
|
const tss = @import("tss.zig");
|
|
const idt = @import("idt.zig");
|
|
const paging = @import("paging.zig");
|
|
const serial = @import("serial.zig");
|
|
const apic = @import("apic.zig");
|
|
|
|
/// The saved register/trap frame passed to a fault handler.
|
|
pub const CpuState = idt.CpuState;
|
|
|
|
/// Bring up the serial port (the kernel's machine-readable log). No dependencies,
|
|
/// so it can be the very first thing called.
|
|
pub fn serialInit() void {
|
|
serial.init();
|
|
}
|
|
|
|
/// Write bytes to the serial port.
|
|
pub fn serialWrite(bytes: []const u8) void {
|
|
serial.write(bytes);
|
|
}
|
|
|
|
/// Set up the CPU's descriptor tables: our own GDT, the TSS (with an interrupt
|
|
/// stack for double faults), then the IDT with exception handlers. After this a
|
|
/// CPU fault is reported instead of triple-faulting. Install the fault handler
|
|
/// (setFaultHandler) first so early faults are caught.
|
|
pub fn init() void {
|
|
gdt.init();
|
|
tss.init();
|
|
idt.init();
|
|
}
|
|
|
|
/// Build the kernel's own page tables (with real permissions) and switch onto
|
|
/// them. Needs the frame allocator and the boot info (for the memory map and the
|
|
/// kernel's segment layout). Call once the frame allocator is up.
|
|
pub fn enablePaging(allocFrame: *const fn () ?u64, boot_info: *const danos.BootInfo) void {
|
|
paging.init(allocFrame, boot_info);
|
|
}
|
|
|
|
/// Map a page into the kernel address space (non-executable). For the heap, etc.
|
|
pub fn mapPage(virt: u64, phys: u64, writable: bool) void {
|
|
paging.map(virt, phys, writable);
|
|
}
|
|
|
|
/// Remove a kernel mapping.
|
|
pub fn unmapPage(virt: u64) void {
|
|
paging.unmap(virt);
|
|
}
|
|
|
|
/// CR3 holds the physical address of the active top-level page table.
|
|
pub fn readCr3() u64 {
|
|
return asm volatile ("mov %%cr3, %[out]"
|
|
: [out] "=r" (-> u64),
|
|
);
|
|
}
|
|
|
|
/// Kernel tick rate: 1000 Hz (1 ms), the scheduler's time quantum.
|
|
pub const timer_hz = 1000;
|
|
|
|
/// Enable the Local APIC, calibrate its timer against the PIT, and start it firing
|
|
/// at `timer_hz` — the kernel's real-time heartbeat. Interrupts still have to be
|
|
/// unmasked with enableInterrupts() to be delivered.
|
|
pub fn startTimer() void {
|
|
apic.init();
|
|
apic.calibrate();
|
|
idt.setHandler(apic.timer_vector, apic.timerTick);
|
|
apic.initTimer(timer_hz);
|
|
}
|
|
|
|
/// Number of timer ticks since startTimer().
|
|
pub fn ticks() u64 {
|
|
return apic.ticks();
|
|
}
|
|
|
|
// Monotonic high-resolution clock (from the TSC), one function per resolution.
|
|
pub fn nanos() u64 {
|
|
return apic.nanos();
|
|
}
|
|
pub fn micros() u64 {
|
|
return apic.micros();
|
|
}
|
|
pub fn millis() u64 {
|
|
return apic.millis();
|
|
}
|
|
|
|
/// Measured LAPIC timer / TSC frequencies in Hz (from calibration).
|
|
pub fn lapicHz() u64 {
|
|
return apic.lapicHz();
|
|
}
|
|
pub fn tscHz() u64 {
|
|
return apic.tscHz();
|
|
}
|
|
|
|
/// Unmask maskable interrupts (`sti`) so device interrupts get delivered.
|
|
pub fn enableInterrupts() void {
|
|
asm volatile ("sti");
|
|
}
|
|
|
|
/// Mask maskable interrupts (`cli`).
|
|
pub fn disableInterrupts() void {
|
|
asm volatile ("cli");
|
|
}
|
|
|
|
/// Disable interrupts and return the previous flags, so a nested critical section
|
|
/// can restore the caller's state rather than blindly re-enabling. Pairs with
|
|
/// restoreInterrupts.
|
|
pub fn saveInterrupts() u64 {
|
|
var flags: u64 = undefined;
|
|
asm volatile (
|
|
\\pushfq
|
|
\\pop %[f]
|
|
\\cli
|
|
: [f] "=r" (flags),
|
|
:
|
|
: .{ .memory = true }
|
|
);
|
|
return flags;
|
|
}
|
|
|
|
/// Re-enable interrupts only if they were enabled when `flags` was captured.
|
|
pub fn restoreInterrupts(flags: u64) void {
|
|
if (flags & 0x200 != 0) asm volatile ("sti" ::: .{ .memory = true }); // bit 9 = IF
|
|
}
|
|
|
|
/// Register a callback the timer interrupt invokes each tick (e.g. the scheduler).
|
|
pub fn setTickHook(hook: *const fn () void) void {
|
|
apic.setTickHook(hook);
|
|
}
|
|
|
|
// --- context switching (for the scheduler) -------------------------------
|
|
|
|
/// Save the current task's registers/stack and resume `new_rsp`; the old stack
|
|
/// pointer is written to `old_rsp`. Defined in isr.s.
|
|
extern fn switch_context(old_rsp: *usize, new_rsp: usize) callconv(.c) void;
|
|
|
|
pub fn switchContext(old_rsp: *usize, new_rsp: usize) void {
|
|
switch_context(old_rsp, new_rsp);
|
|
}
|
|
|
|
/// Build the initial stack for a new task so that switching to it lands in
|
|
/// `task_trampoline`, which then calls `entry`. Returns the saved stack pointer.
|
|
/// The layout must match switch_context's push order (callee-saved, then the
|
|
/// return address on top); `entry` is smuggled in via the r15 slot.
|
|
pub fn initTaskStack(stack_top: usize, entry: usize) usize {
|
|
const trampoline = @extern(*const anyopaque, .{ .name = "task_trampoline" });
|
|
var sp = stack_top;
|
|
const push = struct {
|
|
fn f(p: *usize, value: usize) void {
|
|
p.* -= @sizeOf(usize);
|
|
@as(*usize, @ptrFromInt(p.*)).* = value;
|
|
}
|
|
}.f;
|
|
push(&sp, @intFromPtr(trampoline)); // return address for switch_context's `ret`
|
|
push(&sp, 0); // rbx
|
|
push(&sp, 0); // rbp
|
|
push(&sp, 0); // r12
|
|
push(&sp, 0); // r13
|
|
push(&sp, 0); // r14
|
|
push(&sp, entry); // r15 -> task entry, read by task_trampoline
|
|
return sp;
|
|
}
|
|
|
|
/// Route CPU exceptions to `handler`, which receives the trap frame and does not
|
|
/// return. Until set, faults just halt the core.
|
|
pub fn setFaultHandler(handler: *const fn (*const CpuState) noreturn) void {
|
|
idt.on_fault = handler;
|
|
}
|
|
|
|
/// A human-readable name for a CPU exception vector.
|
|
pub fn vectorName(vector: u64) []const u8 {
|
|
return idt.vectorName(vector);
|
|
}
|
|
|
|
/// CR2 holds the faulting linear address after a page fault (#PF, vector 14).
|
|
pub fn readCr2() u64 {
|
|
return asm volatile ("mov %%cr2, %[out]"
|
|
: [out] "=r" (-> u64),
|
|
);
|
|
}
|
|
|
|
/// Park the core forever. `hlt` drops it into a low-power idle until the next
|
|
/// interrupt; the loop re-halts on every wake so the stop is permanent. See
|
|
/// docs/halting.md for the full reasoning.
|
|
pub fn halt() noreturn {
|
|
while (true) asm volatile ("hlt");
|
|
}
|