RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / main.rs
3741 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
//! 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);
    }
}