//! A small freestanding x86_64 kernel. //! //! Boot flow: BIOS -> `bootloader` crate (switches to 64-bit long mode, sets up //! paging) -> `_start` below. There is no OS underneath us, so no `std`, no //! `main`, and no heap. Everything we print goes straight to hardware. #![no_std] // don't link the Rust standard library (it needs an OS) #![no_main] // no normal `main`: the bootloader jumps to `_start` #![feature(abi_x86_interrupt)] // nightly: lets us write `extern "x86-interrupt" fn` // The bootloader is linked in by `cargo bootimage`, not called from our code; // this line just tells Rust/cargo the dependency is intentional. extern crate bootloader; mod gdt; // GDT + TSS: gives the double-fault handler its own stack mod interrupts; // IDT, exception handlers, PIC, timer + keyboard mod serial; // COM1 serial port -> shows up in your terminal via `-serial stdio` mod vga_buffer; // 80x25 VGA text mode screen at physical address 0xb8000 use core::panic::PanicInfo; /// Kernel entry point. The bootloader calls this after setting up the CPU. /// `no_mangle` keeps the symbol name `_start` so the linker can find it. #[unsafe(no_mangle)] pub extern "C" fn _start() -> ! { println!("Hello from Tyler's Rust kernel!"); serial_println!("Hello from Tyler's Rust kernel! (via serial COM1)"); init(); // Fire a breakpoint exception on purpose: the handler prints a message, // returns, and we carry on as if nothing happened. x86_64::instructions::interrupts::int3(); println!("Survived the breakpoint, still running."); serial_println!("Survived the breakpoint, still running."); #[cfg(feature = "stack-overflow")] { serial_println!("Overflowing the stack on purpose..."); stack_overflow(); } println!("Type something! (keyboard echo)"); serial_println!("Timer + keyboard interrupts enabled."); // Nothing else to do: sleep until the next interrupt, forever. The timer // and keyboard handlers do their work in between. hlt_loop(); } /// Set up everything interrupt-related. Order matters: /// GDT/TSS first (the IDT refers to the IST stack), then the IDT, then the /// PICs, and only then enable interrupts (`sti`). fn init() { gdt::init(); interrupts::init_idt(); unsafe { interrupts::PICS.lock().initialize() }; x86_64::instructions::interrupts::enable(); } /// Recurse forever until we run off the end of the stack into the guard page. #[cfg(feature = "stack-overflow")] #[allow(unconditional_recursion)] fn stack_overflow() { stack_overflow(); // Stop the compiler turning the recursion into a loop (tail call). unsafe { core::ptr::read_volatile(&0u8) }; } /// Called on `panic!`. Print the message to both outputs, then stop. #[panic_handler] fn panic(info: &PanicInfo) -> ! { println!("{}", info); serial_println!("KERNEL PANIC: {}", info); #[cfg(feature = "headless")] exit_qemu(QemuExitCode::Failed); hlt_loop(); } /// Sleep the CPU until the next interrupt, forever (cheaper than `loop {}`, /// which would keep a host CPU core at 100%). pub fn hlt_loop() -> ! { loop { x86_64::instructions::hlt(); } } /// Exit codes for QEMU's `isa-debug-exit` device. QEMU exits with /// status `(value << 1) | 1`, so Success = 0x10 -> shell exit code 33. #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum QemuExitCode { Success = 0x10, Failed = 0x11, } /// Write to I/O port 0xf4 (where we put `isa-debug-exit`) to quit QEMU. #[allow(dead_code)] pub fn exit_qemu(exit_code: QemuExitCode) { use x86_64::instructions::port::Port; unsafe { let mut port = Port::new(0xf4); port.write(exit_code as u32); } }