Skip to content
Snippets Groups Projects
handler.rs 5.78 KiB
Newer Older
//! Implementation of CPU interruptions

use core::ops::IndexMut;
use core::sync::atomic::Ordering;

use pc_keyboard::{DecodedKey, KeyCode, KeyState};
use spin::Lazy;
use x86_64::instructions::port::Port;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};

use super::cmos::CMOS;
use super::keyboard::{get_scancode, send_csi, ALT_PRESSED, CTRL_PRESSED, KEYBOARD, SHIFT_PRESSED};
use super::pics::{PIC1_OFFSET, PIC2_OFFSET, PICS};
use super::pit::{self, LAST_RTC_UPDATE, TICKS};
use crate::encoding::ascii::Char;
use crate::kernel::interrupt::keyboard::send_key;
use crate::println;

/// Index of the interrupt numbers (IRQ)
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub(super) enum InterruptIndex {
    /// Timer interrupt
    Timer = PIC1_OFFSET,

    /// Keyboards interrupt
    Keyboard,

    /// Real-time controller interrupt
    Rtc = PIC2_OFFSET,
}

impl From<InterruptIndex> for u8 {
    fn from(value: InterruptIndex) -> Self {
        value as Self
    }
}

impl From<InterruptIndex> for usize {
    fn from(value: InterruptIndex) -> Self {
        u8::from(value).into()
    }
}

/// Global Interruption Descriptor Table (IDT)
pub(super) static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
    let mut idt = InterruptDescriptorTable::new();
    idt.breakpoint.set_handler_fn(breakpoint_handler);
    idt.double_fault.set_handler_fn(double_fault_handler);
    set_handler_fn(&mut idt, InterruptIndex::Timer, timer_interrupt_handler);
    set_handler_fn(&mut idt, InterruptIndex::Keyboard, keyboard_interrupt_handler);
    set_handler_fn(&mut idt, InterruptIndex::Rtc, rtc_interrupt_handler);

    idt
});

/// Set a given `handler` for the given `idt` at a given `pin`
fn set_handler_fn(
    idt: &mut InterruptDescriptorTable,
    pin: InterruptIndex,
    handler: extern "x86-interrupt" fn(InterruptStackFrame),
) {
    idt.index_mut(pin.into()).set_handler_fn(handler);

    let index: u8 = Into::<u8>::into(pin) - PIC1_OFFSET;
    let (interrupt_line, port_number) = if index < 8 { (index, 0x21) } else { (index - 8, 0xA1) };
    let mut port = Port::<u8>::new(port_number);

    // SAFETY: no memory violation is made here
    let byte = unsafe { port.read() };

    // SAFETY: and neither here
    unsafe {
        port.write(byte & !(1 << interrupt_line));
    };
}

/// Custom interruption handler to print useful informations on the error
#[cfg(not(test))]
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
    println!("Exception: breakpoint\n{:#?}", stack_frame);
}

#[cfg(test)]
const extern "x86-interrupt" fn breakpoint_handler(_stack_frame: InterruptStackFrame) {}

/// Same purpose as [`breakpoint_handler`] but for double faults
extern "x86-interrupt" fn double_fault_handler(stack_frame: InterruptStackFrame, error_code: u64) -> ! {
    panic!("Double fault code {}\n{:#?}", error_code, stack_frame);
}

/// Same purpose as [`breakpoint_handler`] but for time interruptions
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
    TICKS.fetch_add(1, Ordering::Relaxed);

    // SAFETY: Timer index is the same everywhere it should
    unsafe {
        PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.into());
    };
}

/// Same purpose as [`breakpoint_handler`] but for real-time controller interruptions
extern "x86-interrupt" fn rtc_interrupt_handler(_stack_frame: InterruptStackFrame) {
    LAST_RTC_UPDATE.store(pit::ticks(), Ordering::Relaxed);
    CMOS.notify_end_of_interrupt();

    // SAFETY: Timer index is the same everywhere it should
    unsafe {
        PICS.lock().notify_end_of_interrupt(InterruptIndex::Rtc.into());
    };
}

/// Same purpose as [`breakpoint_handler`] but for real-time controller interruptions
#[allow(clippy::wildcard_enum_match_arm)] // To let the code be readable
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
    /// Tabulation char
    const TAB: char = Char::Tab.into();

    /// Delete char
    const DEL: char = Char::DEL.into();

    let mut binding = KEYBOARD.lock();
    let keyboard = binding.get_mut().expect("Tried to get the keyboard before its initialization");

    let scancode = get_scancode();
    if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
        match key_event.code {
            KeyCode::LAlt | KeyCode::RAltGr => ALT_PRESSED.store(key_event.state == KeyState::Down, Ordering::Relaxed),
            KeyCode::LShift | KeyCode::RShift => SHIFT_PRESSED.store(key_event.state == KeyState::Down, Ordering::Relaxed),
            KeyCode::LControl | KeyCode::RControl => SHIFT_PRESSED.store(key_event.state == KeyState::Down, Ordering::Relaxed),
            _ => {},
        };

        let alt_pressed = ALT_PRESSED.load(Ordering::Relaxed);
        let ctrl_pressed = CTRL_PRESSED.load(Ordering::Relaxed);
        let shift_pressed = SHIFT_PRESSED.load(Ordering::Relaxed);

        if let Some(key) = keyboard.process_keyevent(key_event) {
            match key {
                DecodedKey::RawKey(KeyCode::ArrowUp) => send_csi("1A"),
                DecodedKey::RawKey(KeyCode::ArrowDown) => send_csi("1B"),
                DecodedKey::RawKey(KeyCode::ArrowRight) => send_csi("1C"),
                DecodedKey::RawKey(KeyCode::ArrowLeft) => send_csi("1D"),
                DecodedKey::Unicode(TAB) if shift_pressed => send_csi("2"),
                DecodedKey::Unicode(DEL) if alt_pressed && ctrl_pressed => println!("TODO: implement reboot"),
                DecodedKey::Unicode(key) => send_key(key),
                _ => {},
            }
        };
    };

    // SAFETY: Timer index is the same everywhere it should
    unsafe {
        PICS.lock().notify_end_of_interrupt(InterruptIndex::Rtc.into());
    };
}

#[cfg(test)]
mod test {
    use x86_64::instructions::interrupts;

    #[test_case]
    fn interruption() {
        interrupts::int3();
    }
}