RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / interrupts.rs
8011 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Interrupt handling: the IDT, CPU exception handlers and hardware IRQs.
//!
//! The IDT (Interrupt Descriptor Table) maps each interrupt vector number to
//! a handler function:
//!   * vectors 0..=31  are CPU exceptions (divide error, page fault, ...)
//!   * vectors 32..=47 are where we remap the 8259 PIC's 16 hardware IRQs
//!
//! Handlers use the special `extern "x86-interrupt"` calling convention: the
//! compiler saves/restores all registers and returns with `iretq`.

use crate::{gdt, print, println, serial_print, serial_println};
use core::sync::atomic::{AtomicU64, Ordering};
use lazy_static::lazy_static;
use pic8259::ChainedPics;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode};

// ---------------------------------------------------------------------------
// 8259 PIC (Programmable Interrupt Controller)
// ---------------------------------------------------------------------------

/// The PICs deliver IRQ 0..15 on vectors 0..15 by default, which clash with
/// CPU exceptions. We remap them to 32..47.
pub const PIC_1_OFFSET: u8 = 32;
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;

/// Two PICs: primary handles IRQ 0-7, secondary IRQ 8-15 (chained via IRQ 2).
pub static PICS: spin::Mutex<ChainedPics> =
    spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });

/// Vector numbers of the hardware interrupts we handle.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum InterruptIndex {
    Timer = PIC_1_OFFSET, // IRQ 0: Programmable Interval Timer, ~18.2 Hz by default
    Keyboard,             // IRQ 1: PS/2 keyboard
}

impl InterruptIndex {
    fn as_u8(self) -> u8 {
        self as u8
    }
}

// ---------------------------------------------------------------------------
// IDT setup
// ---------------------------------------------------------------------------

lazy_static! {
    /// The IDT must live forever (`'static`): the CPU keeps a pointer to it.
    static ref IDT: InterruptDescriptorTable = {
        let mut idt = InterruptDescriptorTable::new();
        idt.breakpoint.set_handler_fn(breakpoint_handler);
        idt.page_fault.set_handler_fn(page_fault_handler);
        unsafe {
            // Run the double-fault handler on its own known-good stack (IST).
            idt.double_fault
                .set_handler_fn(double_fault_handler)
                .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
        }
        idt[InterruptIndex::Timer.as_u8()].set_handler_fn(timer_interrupt_handler);
        idt[InterruptIndex::Keyboard.as_u8()].set_handler_fn(keyboard_interrupt_handler);
        idt
    };
}

/// Load the IDT into the CPU (`lidt`).
pub fn init_idt() {
    IDT.load();
}

// ---------------------------------------------------------------------------
// CPU exception handlers
// ---------------------------------------------------------------------------

/// `int3` (what debuggers use). Harmless: print and carry on.
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
    println!("EXCEPTION: BREAKPOINT");
    serial_println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}

/// Page fault: accessing unmapped/protected memory. CR2 holds the bad address.
/// We can't fix it yet (no paging code), so report and stop.
extern "x86-interrupt" fn page_fault_handler(
    stack_frame: InterruptStackFrame,
    error_code: PageFaultErrorCode,
) {
    use x86_64::registers::control::Cr2;
    println!("EXCEPTION: PAGE FAULT at {:?}", Cr2::read());
    serial_println!(
        "EXCEPTION: PAGE FAULT\nAccessed address: {:?}\nError code: {:?}\n{:#?}",
        Cr2::read(),
        error_code,
        stack_frame
    );
    crate::hlt_loop();
}

/// Double fault: an exception happened while handling another one. Must not
/// return (hence `-> !`). Runs on the IST stack set up in gdt.rs.
extern "x86-interrupt" fn double_fault_handler(
    stack_frame: InterruptStackFrame,
    _error_code: u64, // always 0 for double faults
) -> ! {
    serial_println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);

    // In the deliberate stack-overflow test, reaching here means success.
    #[cfg(all(feature = "headless", feature = "stack-overflow"))]
    {
        serial_println!("[ok] stack overflow was caught by the double fault handler");
        crate::exit_qemu(crate::QemuExitCode::Success);
    }

    panic!("EXCEPTION: DOUBLE FAULT");
}

// ---------------------------------------------------------------------------
// Hardware interrupt handlers
// ---------------------------------------------------------------------------

/// Timer ticks since boot. Atomic, so it's safe to touch from an interrupt.
pub static TICKS: AtomicU64 = AtomicU64::new(0);

/// IRQ 0. Fires ~18.2 times per second (the PIT's power-on default).
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
    let ticks = TICKS.fetch_add(1, Ordering::Relaxed) + 1;

    // Show uptime in the top-right corner of the screen, updated each second.
    if ticks % 18 == 0 {
        crate::vga_buffer::show_uptime(ticks / 18);
    }

    // Headless runs: print a dot per tick to serial (proof the timer fires),
    // then stop after ~3 seconds so scripts finish on their own. (Not done in
    // normal runs, so the dots don't get mixed into your keyboard echo.)
    #[cfg(feature = "headless")]
    {
        serial_print!(".");
        if ticks % 18 == 0 {
            serial_println!(" {} ticks", ticks);
        }
        #[cfg(not(feature = "stack-overflow"))]
        if ticks >= 54 {
            serial_println!("[ok] timer works, exiting");
            crate::exit_qemu(crate::QemuExitCode::Success);
        }
    }

    // Tell the PIC we're done, or it will never send another timer interrupt.
    unsafe {
        PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
    }
}

/// IRQ 1. The keyboard controller has a scancode waiting at I/O port 0x60;
/// we must read it (or no more keyboard interrupts arrive), decode it with
/// `pc-keyboard`, and echo the character to the screen and serial port.
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
    use pc_keyboard::{DecodedKey, HandleControl, KeyCode, Keyboard, ScancodeSet1, layouts};
    use spin::Mutex;
    use x86_64::instructions::port::Port;

    // Decoder state (shift held? multi-byte scancode in progress?) persists
    // between interrupts, so it lives in a static.
    static KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> = Mutex::new(Keyboard::new(
        ScancodeSet1::new(),
        layouts::Us104Key,
        HandleControl::Ignore,
    ));

    let mut keyboard = KEYBOARD.lock();
    let mut port = Port::new(0x60);
    let scancode: u8 = unsafe { port.read() };

    // add_byte: scancode -> key event (press/release); process_keyevent:
    // key event -> character, applying shift/caps-lock etc.
    if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
        if let Some(key) = keyboard.process_keyevent(key_event) {
            match key {
                DecodedKey::Unicode(character) => {
                    print!("{}", character);
                    serial_print!("{}", character);
                }
                // Modifier keys only change how *other* keys decode; the
                // decoder already tracks them, so don't print anything.
                DecodedKey::RawKey(
                    KeyCode::LShift | KeyCode::RShift | KeyCode::LControl
                    | KeyCode::RControl | KeyCode::LAlt | KeyCode::RAltGr
                    | KeyCode::CapsLock,
                ) => {}
                DecodedKey::RawKey(key) => {
                    // Other keys without a character (arrows, F1, ...)
                    print!("<{:?}>", key);
                    serial_print!("<{:?}>", key);
                }
            }
        }
    }

    unsafe {
        PICS.lock().notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
    }
}