commit 2935cd12356e32581616d26dedee999d49dd98ba from: Murilo Ijanc date: Mon Feb 16 21:21:36 2026 UTC add idt with exception handlers, pic remapping and pit timer 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' +// +// 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::() - 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' +// +// 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' +// +// 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); + } +}