Commit Diff


commit - 86eeeaadbb2bf02bbf0095e1e915e69bea6db6e0
commit + 2935cd12356e32581616d26dedee999d49dd98ba
blob - /dev/null
blob + 616847cd6cf6f9ae96e38bb6f81ec01bd2fc033d (mode 644)
--- /dev/null
+++ src/idt.rs
@@ -0,0 +1,346 @@
+// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et :
+//
+// Copyright (c) 2025-2026 Murilo Ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+use core::arch::{asm, naked_asm};
+
+use crate::gdt;
+use crate::pic;
+use crate::sched;
+use crate::serial;
+
+const IDT_SIZE: usize = 256;
+const ATTR_INTERRUPT: u8 = 0x8E; // P=1, DPL=0, interrupt gate
+
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct IdtEntry {
+    offset_low: u16,
+    selector: u16,
+    ist: u8,
+    attributes: u8,
+    offset_mid: u16,
+    offset_high: u32,
+    reserved: u32,
+}
+
+impl IdtEntry {
+    const fn empty() -> Self {
+        IdtEntry {
+            offset_low: 0,
+            selector: 0,
+            ist: 0,
+            attributes: 0,
+            offset_mid: 0,
+            offset_high: 0,
+            reserved: 0,
+        }
+    }
+
+    fn new(handler: u64) -> Self {
+        IdtEntry {
+            offset_low: handler as u16,
+            selector: gdt::KERNEL_CODE,
+            ist: 0,
+            attributes: ATTR_INTERRUPT,
+            offset_mid: (handler >> 16) as u16,
+            offset_high: (handler >> 32) as u32,
+            reserved: 0,
+        }
+    }
+}
+
+#[repr(C, align(16))]
+struct Idt {
+    entries: [IdtEntry; IDT_SIZE],
+}
+
+static mut IDT: Idt = Idt {
+    entries: [IdtEntry::empty(); IDT_SIZE],
+};
+
+#[repr(C, packed)]
+struct IdtPointer {
+    limit: u16,
+    base: u64,
+}
+
+// Interrupt stack frame pushed by the CPU + our wrapper
+#[repr(C)]
+pub struct InterruptFrame {
+    // Pushed by our wrapper (reverse order)
+    pub r15: u64,
+    pub r14: u64,
+    pub r13: u64,
+    pub r12: u64,
+    pub r11: u64,
+    pub r10: u64,
+    pub r9: u64,
+    pub r8: u64,
+    pub rbp: u64,
+    pub rdi: u64,
+    pub rsi: u64,
+    pub rdx: u64,
+    pub rcx: u64,
+    pub rbx: u64,
+    pub rax: u64,
+    // Pushed by our wrapper
+    pub vector: u64,
+    pub error_code: u64,
+    // Pushed by CPU
+    pub rip: u64,
+    pub cs: u64,
+    pub rflags: u64,
+    pub rsp: u64,
+    pub ss: u64,
+}
+
+pub fn init() {
+    unsafe {
+        // Exceptions (vectors 0-31)
+        IDT.entries[0] = IdtEntry::new(isr_stub_0 as *const () as u64);
+        IDT.entries[3] = IdtEntry::new(isr_stub_3 as *const () as u64);
+        IDT.entries[6] = IdtEntry::new(isr_stub_6 as *const () as u64);
+        IDT.entries[8] = IdtEntry::new(isr_stub_8 as *const () as u64);
+        IDT.entries[13] = IdtEntry::new(isr_stub_13 as *const () as u64);
+        IDT.entries[14] = IdtEntry::new(isr_stub_14 as *const () as u64);
+
+        // IRQ 0 (timer) = vector 32
+        IDT.entries[32] = IdtEntry::new(irq_stub_0 as *const () as u64);
+
+        let ptr = IdtPointer {
+            limit: (size_of::<Idt>() - 1) as u16,
+            base: (&raw const IDT) as u64,
+        };
+
+        asm!(
+            "lidt [{}]",
+            in(reg) &ptr,
+            options(readonly, nostack),
+        );
+    }
+}
+
+// Common handler called by all stubs after saving state
+#[unsafe(no_mangle)]
+extern "C" fn interrupt_handler(frame: &InterruptFrame) {
+    match frame.vector {
+        0 => {
+            serial::print("EXCEPTION: #DE Divide Error\n");
+            hcf_handler();
+        }
+        3 => {
+            serial::print("EXCEPTION: #BP Breakpoint\n");
+        }
+        6 => {
+            serial::print("EXCEPTION: #UD Invalid Opcode\n");
+            hcf_handler();
+        }
+        8 => {
+            serial::print("EXCEPTION: #DF Double Fault\n");
+            hcf_handler();
+        }
+        13 => {
+            serial::print("EXCEPTION: #GP General Protection\n");
+            print_error_frame(frame);
+            hcf_handler();
+        }
+        14 => {
+            serial::print("EXCEPTION: #PF Page Fault\n");
+            print_error_frame(frame);
+            // Print the faulting address from CR2
+            let cr2: u64;
+            unsafe {
+                asm!("mov {}, cr2", out(reg) cr2, options(nostack));
+            }
+            serial::print("  cr2=0x");
+            print_hex(cr2);
+            serial::print("\n");
+            hcf_handler();
+        }
+        32 => {
+            // Timer tick (IRQ 0)
+            pic::eoi(0);
+            sched::timer_tick();
+        }
+        _ => {
+            serial::print("INTERRUPT: unknown vector\n");
+            pic::eoi((frame.vector - 32) as u8);
+        }
+    }
+}
+
+fn print_error_frame(frame: &InterruptFrame) {
+    serial::print("  error_code=0x");
+    print_hex(frame.error_code);
+    serial::print(" rip=0x");
+    print_hex(frame.rip);
+    serial::print("\n");
+}
+
+fn hcf_handler() -> ! {
+    loop {
+        unsafe {
+            asm!("cli", "hlt");
+        }
+    }
+}
+
+fn print_hex(val: u64) {
+    let hex = b"0123456789abcdef";
+    // Skip leading zeros but always print at least one digit
+    let mut started = false;
+    for i in (0..16).rev() {
+        let nibble = ((val >> (i * 4)) & 0xF) as usize;
+        if nibble != 0 || started || i == 0 {
+            serial::putc(hex[nibble]);
+            started = true;
+        }
+    }
+}
+
+fn print_u64(val: u64) {
+    if val == 0 {
+        serial::putc(b'0');
+        return;
+    }
+    let mut buf = [0u8; 20];
+    let mut n = val;
+    let mut i = 0;
+    while n > 0 {
+        buf[i] = b'0' + (n % 10) as u8;
+        n /= 10;
+        i += 1;
+    }
+    while i > 0 {
+        i -= 1;
+        serial::putc(buf[i]);
+    }
+}
+
+// --- ISR stubs (assembly wrappers) ---
+//
+// Exceptions without error code push a fake 0.
+// Exceptions with error code (8, 13, 14) skip the push.
+// All stubs push the vector number, save registers,
+// call interrupt_handler, restore, and iretq.
+
+macro_rules! isr_no_err {
+    ($name:ident, $vec:expr) => {
+        #[unsafe(no_mangle)]
+        #[unsafe(naked)]
+        unsafe extern "C" fn $name() {
+            naked_asm!(
+                "push 0",          // fake error code
+                "push {vec}",      // vector number
+                "push rax",
+                "push rbx",
+                "push rcx",
+                "push rdx",
+                "push rsi",
+                "push rdi",
+                "push rbp",
+                "push r8",
+                "push r9",
+                "push r10",
+                "push r11",
+                "push r12",
+                "push r13",
+                "push r14",
+                "push r15",
+                "mov rdi, rsp",
+                "call interrupt_handler",
+                "pop r15",
+                "pop r14",
+                "pop r13",
+                "pop r12",
+                "pop r11",
+                "pop r10",
+                "pop r9",
+                "pop r8",
+                "pop rbp",
+                "pop rdi",
+                "pop rsi",
+                "pop rdx",
+                "pop rcx",
+                "pop rbx",
+                "pop rax",
+                "add rsp, 16",     // pop vector + error code
+                "iretq",
+                vec = const $vec,
+            );
+        }
+    };
+}
+
+macro_rules! isr_with_err {
+    ($name:ident, $vec:expr) => {
+        #[unsafe(no_mangle)]
+        #[unsafe(naked)]
+        unsafe extern "C" fn $name() {
+            naked_asm!(
+                // error code already on stack from CPU
+                "push {vec}",      // vector number
+                "push rax",
+                "push rbx",
+                "push rcx",
+                "push rdx",
+                "push rsi",
+                "push rdi",
+                "push rbp",
+                "push r8",
+                "push r9",
+                "push r10",
+                "push r11",
+                "push r12",
+                "push r13",
+                "push r14",
+                "push r15",
+                "mov rdi, rsp",
+                "call interrupt_handler",
+                "pop r15",
+                "pop r14",
+                "pop r13",
+                "pop r12",
+                "pop r11",
+                "pop r10",
+                "pop r9",
+                "pop r8",
+                "pop rbp",
+                "pop rdi",
+                "pop rsi",
+                "pop rdx",
+                "pop rcx",
+                "pop rbx",
+                "pop rax",
+                "add rsp, 16",     // pop vector + error code
+                "iretq",
+                vec = const $vec,
+            );
+        }
+    };
+}
+
+// Exceptions
+isr_no_err!(isr_stub_0, 0);    // #DE Divide Error
+isr_no_err!(isr_stub_3, 3);    // #BP Breakpoint
+isr_no_err!(isr_stub_6, 6);    // #UD Invalid Opcode
+isr_with_err!(isr_stub_8, 8);  // #DF Double Fault
+isr_with_err!(isr_stub_13, 13); // #GP General Protection
+isr_with_err!(isr_stub_14, 14); // #PF Page Fault
+
+// IRQ 0 (Timer) = vector 32
+isr_no_err!(irq_stub_0, 32);
blob - /dev/null
blob + 2bfec386eca41ef48bf4760df4e40a39eac02d61 (mode 644)
--- /dev/null
+++ src/pic.rs
@@ -0,0 +1,59 @@
+// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et :
+//
+// Copyright (c) 2025-2026 Murilo Ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+use crate::port::outb;
+
+const PIC1_CMD: u16 = 0x20;
+const PIC1_DATA: u16 = 0x21;
+const PIC2_CMD: u16 = 0xA0;
+const PIC2_DATA: u16 = 0xA1;
+
+// Remap IRQs: master → vectors 32-39, slave → 40-47
+pub const IRQ_OFFSET: u8 = 32;
+
+pub fn init() {
+    unsafe {
+        // ICW1: start initialization, cascade mode, ICW4 needed
+        outb(PIC1_CMD, 0x11);
+        outb(PIC2_CMD, 0x11);
+
+        // ICW2: vector offsets
+        outb(PIC1_DATA, IRQ_OFFSET);
+        outb(PIC2_DATA, IRQ_OFFSET + 8);
+
+        // ICW3: cascade wiring
+        outb(PIC1_DATA, 0x04); // slave on IRQ 2
+        outb(PIC2_DATA, 0x02); // cascade identity
+
+        // ICW4: 8086 mode
+        outb(PIC1_DATA, 0x01);
+        outb(PIC2_DATA, 0x01);
+
+        // Mask all IRQs except IRQ 0 (timer)
+        outb(PIC1_DATA, 0xFE); // 1111_1110: only IRQ 0
+        outb(PIC2_DATA, 0xFF); // mask all slave IRQs
+    }
+}
+
+pub fn eoi(irq: u8) {
+    unsafe {
+        if irq >= 8 {
+            outb(PIC2_CMD, 0x20);
+        }
+        outb(PIC1_CMD, 0x20);
+    }
+}
blob - /dev/null
blob + 1ae93245ab0635c108821b0fbc9c69009df4dbfa (mode 644)
--- /dev/null
+++ src/pit.rs
@@ -0,0 +1,34 @@
+// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et :
+//
+// Copyright (c) 2025-2026 Murilo Ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+use crate::port::outb;
+
+const PIT_CMD: u16 = 0x43;
+const PIT_CH0: u16 = 0x40;
+const PIT_FREQ: u32 = 1193182;
+
+// Configure PIT channel 0 at approximately `hz` frequency.
+pub fn init(hz: u32) {
+    let divisor = PIT_FREQ / hz;
+
+    unsafe {
+        // Channel 0, lobyte/hibyte, mode 3 (square wave)
+        outb(PIT_CMD, 0x36);
+        outb(PIT_CH0, divisor as u8);
+        outb(PIT_CH0, (divisor >> 8) as u8);
+    }
+}