commit - 86eeeaadbb2bf02bbf0095e1e915e69bea6db6e0
commit + 2935cd12356e32581616d26dedee999d49dd98ba
blob - /dev/null
blob + 616847cd6cf6f9ae96e38bb6f81ec01bd2fc033d (mode 644)
--- /dev/null
+++ src/idt.rs
+// 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
+// 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
+// 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);
+ }
+}