//! 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"); }