diff --git a/Cargo.lock b/Cargo.lock index 49e4dfa..be4cab2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,6 +526,8 @@ version = "0.1.0" dependencies = [ "bootloader_api", "noto-sans-mono-bitmap", + "pc-keyboard", + "pic8259", "spin", "x86_64", ] @@ -607,6 +609,21 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +[[package]] +name = "pc-keyboard" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed089a1fbffe3337a1a345501c981f1eb1e47e69de5a40e852433e12953c3174" + +[[package]] +name = "pic8259" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb844b5b01db1e0b17938685738f113bfc903846f18932b378bc0eabfa40e194" +dependencies = [ + "x86_64", +] + [[package]] name = "pin-project" version = "1.1.3" diff --git a/README.md b/README.md index bac8429..aa39786 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,11 @@ apt install qemu-system-x86 ### Windows - Install QEMU from [here](https://qemu.weilnetz.de/w64/) -- Add toolchain +- Add qemu to PATH (example: `C:\Program Files\qemu`) +- Add nightly toolchain ``` rustup component add rust-src --toolchain nightly-x86_64-pc-windows-msvc ``` -- Install llvm - ``` - rustup component add llvm-tools-preview - ``` -- Add qemu to PATH (example: `C:\Program Files\qemu`) # Run This will build the kernel, create an image and launch it with qemu. diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 1649532..8003c17 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -12,4 +12,5 @@ bootloader_api = "0.11.4" noto-sans-mono-bitmap = "0.2.0" spin = "0.9.8" x86_64 = "0.14.10" - +pic8259 = "0.10.4" +pc-keyboard = "0.7.0" diff --git a/kernel/src/interrupt.rs b/kernel/src/interrupt.rs index e5217ae..d2e6b18 100644 --- a/kernel/src/interrupt.rs +++ b/kernel/src/interrupt.rs @@ -1,8 +1,14 @@ -use crate::println; +use crate::{print, println}; +use core::fmt::Debug; +use pc_keyboard::{HandleControl, Keyboard}; +use pic8259::ChainedPics; use spin::once::Once; use x86_64::registers::control::Cr2; +use spin::Mutex; use x86_64::structures::idt::PageFaultErrorCode; use x86_64::{ + instructions, + instructions::port::Port, instructions::tables, registers::segmentation::{Segment as _, CS, SS}, structures::{ @@ -13,13 +19,24 @@ use x86_64::{ VirtAddr, }; +type SupportedKeyboard = Keyboard; + static IDT: Once = Once::new(); static TSS: Once = Once::new(); static GDT: Once = Once::new(); static SEGMENT_SELECTORS: Once = Once::new(); +static PICS: Once> = Once::new(); +static KEYBOARD: Once> = Once::new(); const DOUBLE_FAULT_IST_INDEX: u16 = 0; +// hardware interrupts PICs (slots 32-47) +// Safety - ensure that the PICs does not overlap +const PIC_1_OFFSET: u8 = 32; +const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + +const PS2_CONTROLLER_PORT: u16 = 0x60; + /// Initialize interrupt handlers. /// /// # Panics @@ -27,6 +44,33 @@ const DOUBLE_FAULT_IST_INDEX: u16 = 0; pub fn init() { init_gdt(); init_idt(); + init_pic(); +} + +/// Enable interrupts if they are not enabled. +pub fn enable_interrupts() { + if !instructions::interrupts::are_enabled() { + instructions::interrupts::enable(); + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum InterruptIndex { + Timer = PIC_1_OFFSET, + Keyboard, +} + +impl From for u8 { + fn from(value: InterruptIndex) -> Self { + value as u8 + } +} + +impl From for usize { + fn from(value: InterruptIndex) -> Self { + value as usize + } } #[derive(Debug)] @@ -49,6 +93,8 @@ struct SegmentSelectors { fn init_idt() { let idt = IDT.call_once(|| { let mut idt = InterruptDescriptorTable::new(); + + // # exceptions idt.alignment_check.set_handler_fn(alignment_check_handler); idt.breakpoint.set_handler_fn(breakpoint_handler); idt.bound_range_exceeded @@ -71,6 +117,10 @@ fn init_idt() { .set_handler_fn(double_fault_handler) .set_stack_index(DOUBLE_FAULT_IST_INDEX); } + + idt[InterruptIndex::Timer.into()].set_handler_fn(timer_interrupt_handler); + idt[InterruptIndex::Keyboard.into()].set_handler_fn(keyboard_interrupt_handler); + idt }); idt.load(); @@ -129,6 +179,45 @@ fn init_gdt() { } } +/// Initialize Programmable Interrupt Controllers (PICs). +/// +/// This functions setups the standard PIC 1 and PIC 2 controllers to properly handle hardware +/// interrupts. +/// +/// # Panics +/// This function will panic if it is called more than once. +fn init_pic() { + let pics = PICS.call_once(|| { + // # Safety + // we ensure that the PICs does not overlap and the offsets are correct + let chained_pics = unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }; + + Mutex::new(chained_pics) + }); + + // initialize keyboard before enabling interrupts + init_keyboard(); + + // # Safety + // we ensure that the PICs are properly configured + unsafe { + pics.lock().initialize(); + } +} + +fn init_keyboard() { + KEYBOARD.call_once(|| { + let keyboard = Keyboard::new( + pc_keyboard::ScancodeSet1::new(), + pc_keyboard::layouts::Us104Key, + HandleControl::Ignore, + ); + + Mutex::new(keyboard) + }); +} + +// Exception handlers extern "x86-interrupt" fn breakpoint_handler(frame: InterruptStackFrame) { // FIXME handle breakpoint println!("Exception: breakpoint\n{:#?}", frame) @@ -196,3 +285,49 @@ extern "x86-interrupt" fn double_fault_handler(frame: InterruptStackFrame, _erro // FIXME handle double fault panic!("Exception: double fault\n{:#?}", frame); } + +extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { + // # Safety + // we use the same interrupt number as the handler is registered for + unsafe { + PICS.get() + .unwrap() + .lock() + .notify_end_of_interrupt(InterruptIndex::Timer.into()); + } +} + +extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { + // # Safety + // we read from the keyboard port only on keyboard interrupt + let scancode: u8 = unsafe { Port::new(PS2_CONTROLLER_PORT).read() }; + + let mut keyboard = KEYBOARD.get().unwrap().lock(); + + match keyboard.add_byte(scancode) { + Ok(Some(key_event)) => { + if let Some(key) = keyboard.process_keyevent(key_event) { + use pc_keyboard::DecodedKey::Unicode; + match key { + Unicode('\x1b') => print!("ESC"), + Unicode('\x08') => print!("BS"), + Unicode('\x7f') => print!("DEL"), + Unicode('\t') => print!("TAB"), // FIXME: tab not supported in logger? + Unicode(character) => print!("{}", character), + pc_keyboard::DecodedKey::RawKey(key) => print!("RAW[{:?}]", key), + } + } + } + Ok(None) => {} + Err(e) => println!("Keyboard error: {:?}", e), + } + + // # Safety + // we use the same interrupt number as the handler is registered for + unsafe { + PICS.get() + .unwrap() + .lock() + .notify_end_of_interrupt(InterruptIndex::Keyboard.into()); + } +} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 666879b..20f802f 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -36,4 +36,12 @@ pub fn init(boot_info: &'static mut BootInfo) { }; memory::init_global(physical_memory_offset, &boot_info.memory_regions); + interrupt::enable_interrupts(); +} + +pub fn halt_loop() -> ! { + loop { + // halts until next interrupt + x86_64::instructions::hlt(); + } } diff --git a/kernel/src/logger.rs b/kernel/src/logger.rs index d823bf4..5061101 100644 --- a/kernel/src/logger.rs +++ b/kernel/src/logger.rs @@ -21,7 +21,10 @@ macro_rules! println { #[doc(hidden)] pub fn _print(args: fmt::Arguments) { use core::fmt::Write; - LOGGER.get().unwrap().lock().write_fmt(args).unwrap(); + // FIXME (temporary solution): disable interrupts while printing + x86_64::instructions::interrupts::without_interrupts(|| { + LOGGER.get().unwrap().lock().write_fmt(args).unwrap(); + }) } static LOGGER: Once>> = Once::new(); diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 7b6d2b5..40f4a53 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -13,12 +13,10 @@ fn main(boot_info: &'static mut BootInfo) -> ! { println!("it works"); - #[allow(clippy::empty_loop)] - loop {} + kernel::halt_loop(); } - #[panic_handler] fn panic(info: &PanicInfo) -> ! { println!("{info}"); - loop {} + kernel::halt_loop(); } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index dcc7a1c..fbca2fe 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,4 @@ [toolchain] channel = "nightly" +components = ["llvm-tools-preview"] targets = ["x86_64-unknown-none"] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 315862b..b4b0b97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,9 @@ fn main() -> eyre::Result<()> { .arg(format!("format=raw,file={bios_path}")) .arg("-boot") .arg(format!("menu=on,splash={bootsplash_path},splash-time=1000")) + .arg("-no-reboot") + .arg("-d") + .arg("cpu_reset") .spawn()? .wait()?;