From 6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 31 Jul 2026 02:58:31 +0200 Subject: restructure: rsnix kernel + shell crate + rsdos entry point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture: rs-dos/ (workspace root, was raisernics/) ├── rsnix/ — kernel lib crate (uefi, serial, gop, ps2, interrupts, memory) ├── shell/ — shell lib crate (prompt, commands, line input) ├── rsdos/ — binary crate (efi_main, init, panic handler) └── rsboot/ — future git submodule No functional changes — boots to shell prompt identically. Version auto-pulled from Cargo.toml via env!("CARGO_PKG_VERSION"). --- src/font.rs | 5 - src/gop.rs | 232 ------------------------------- src/interrupts.rs | 401 ------------------------------------------------------ src/main.rs | 186 ------------------------- src/memory.rs | 196 -------------------------- src/ps2.rs | 364 ------------------------------------------------- src/serial.rs | 118 ---------------- src/shell.rs | 275 ------------------------------------- src/uefi.rs | 289 --------------------------------------- 9 files changed, 2066 deletions(-) delete mode 100644 src/font.rs delete mode 100644 src/gop.rs delete mode 100644 src/interrupts.rs delete mode 100644 src/main.rs delete mode 100644 src/memory.rs delete mode 100644 src/ps2.rs delete mode 100644 src/serial.rs delete mode 100644 src/shell.rs delete mode 100644 src/uefi.rs (limited to 'src') diff --git a/src/font.rs b/src/font.rs deleted file mode 100644 index e188513..0000000 --- a/src/font.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// 8×16 bitmap font. -/// -/// Glyphs are generated at build time by `build.rs` from the system PSF2 font -/// (`/usr/share/kbd/consolefonts/lat1-16.psfu.gz`). -include!(concat!(env!("OUT_DIR"), "/font_data.rs")); diff --git a/src/gop.rs b/src/gop.rs deleted file mode 100644 index 9645bb0..0000000 --- a/src/gop.rs +++ /dev/null @@ -1,232 +0,0 @@ -/// GOP framebuffer driver — renders text via a bitmap font. -use crate::font; -use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; -use core::fmt; -use core::ptr; - -/// Framebuffer state captured before ExitBootServices. -pub struct Framebuffer { - base: *mut u8, - #[allow(dead_code)] - width: usize, - height: usize, - pitch: usize, // bytes per scan line - bytes_per_pixel: usize, - bg: u32, // packed BGRA or RGBA background colour - fg: u32, // packed foreground colour - cursor_x: usize, - cursor_y: usize, - rows: usize, - cols: usize, -} - -static mut FB: Option = None; - -/// Initialise the framebuffer from GOP. Call *before* ExitBootServices. -pub fn init(gop: &mut GraphicsOutputProtocol) { - let mode = unsafe { &*gop.mode }; - let info = unsafe { &*mode.info }; - let base = mode.framebuffer_base as *mut u8; - let width = info.horizontal_resolution as usize; - let height = info.vertical_resolution as usize; - let pitch = info.pixels_per_scan_line as usize * 4; // 32-bit pixel - let bpp = 4; - - // Pick colours based on pixel format - let (fg, bg) = match info.pixel_format { - PIXEL_BGR => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), // white fg, black bg - PIXEL_RGB => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), - _ => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), - }; - - let rows = height / 16; - let cols = width / 8; - - let fb = Framebuffer { - base, - width, - height, - pitch, - bytes_per_pixel: bpp, - bg, - fg, - cursor_x: 0, - cursor_y: 0, - rows, - cols, - }; - - fb.clear(); - - unsafe { - FB = Some(fb); - } -} - -/// Draw a character at the current cursor position and advance. -pub fn put_char(c: char) { - let fb = unsafe { - match FB.as_mut() { - Some(f) => f, - None => return, - } - }; - - if c == '\x08' { - // Backspace - move cursor back and erase - if fb.cursor_x > 0 { - fb.cursor_x -= 1; - } else if fb.cursor_y > 0 { - fb.cursor_y -= 1; - fb.cursor_x = fb.cols - 1; - } else { - return; - } - // Draw glyph 32 (space) to erase — glyph 0 is NUL, not blank - fb.draw_glyph(32, fb.cursor_x, fb.cursor_y); - return; - } - if c == '\n' { - fb.cursor_x = 0; - fb.cursor_y += 1; - if fb.cursor_y >= fb.rows { - fb.scroll(); - } - return; - } - if c == '\r' { - fb.cursor_x = 0; - return; - } - - let idx = c as usize; - if idx >= 256 { - // Unmapped character: draw a space - fb.draw_glyph(0, fb.cursor_x, fb.cursor_y); - } else { - fb.draw_glyph(idx, fb.cursor_x, fb.cursor_y); - } - - fb.cursor_x += 1; - if fb.cursor_x >= fb.cols { - fb.cursor_x = 0; - fb.cursor_y += 1; - if fb.cursor_y >= fb.rows { - fb.scroll(); - } - } -} - -/// Write a string to the framebuffer. -pub fn write_str(s: &str) { - for c in s.chars() { - put_char(c); - } -} - -/// Clear the entire screen to background colour and reset cursor to (0, 0). -pub fn clear_screen() { - let fb = unsafe { - match FB.as_mut() { - Some(f) => f, - None => return, - } - }; - fb.clear(); - // Reset cursor back to top-left so the next prompt appears at (0, 0) - fb.cursor_x = 0; - fb.cursor_y = 0; -} - -// --------------------------------------------------------------------------- -// Framebuffer implementation -// --------------------------------------------------------------------------- -impl Framebuffer { - fn clear(&self) { - let size = self.pitch * self.height; - // Fill with background colour (32-bit writes) - let mut p = self.base; - let end = unsafe { p.add(size) }; - unsafe { - while p < end { - ptr::write_volatile(p as *mut u32, self.bg); - p = p.add(4); - } - } - } - - fn scroll(&mut self) { - // Move everything up one row (16 pixels) - let row_bytes = self.pitch * 16; - let total = self.pitch * self.height; - unsafe { - ptr::copy(self.base.add(row_bytes), self.base, total - row_bytes); - // Clear last row - let last_row = self.base.add(total - row_bytes); - for i in 0..(self.pitch * 16 / 4) { - ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg); - } - } - self.cursor_y -= 1; - } - - fn draw_glyph(&self, glyph_idx: usize, col: usize, row: usize) { - let glyph = font::GLYPH_8X16; - let data = match glyph.get(glyph_idx) { - Some(d) => *d, - None => return, - }; - - let x = col * 8; - let y = row * 16; - - for cy in 0..16 { - let row_bits = data[cy]; - for cx in 0..8 { - if row_bits & (0x80 >> cx) != 0 { - // Set foreground pixel - let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel; - unsafe { - ptr::write_volatile(self.base.add(offset) as *mut u32, self.fg); - } - } else { - // Set background pixel - let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel; - unsafe { - ptr::write_volatile(self.base.add(offset) as *mut u32, self.bg); - } - } - } - } - } -} - -// --------------------------------------------------------------------------- -// fmt::Write wrapper -// --------------------------------------------------------------------------- -pub struct GopWriter; - -impl fmt::Write for GopWriter { - fn write_str(&mut self, s: &str) -> fmt::Result { - write_str(s); - Ok(()) - } -} - -/// Write a formatted string to the framebuffer. -#[macro_export] -macro_rules! gop_print { - ($($arg:tt)*) => {{ - use core::fmt::Write; - let _ = write!(&mut $crate::gop::GopWriter, $($arg)*); - }}; -} - -#[macro_export] -macro_rules! gop_println { - () => { $crate::gop::write_str("\n"); }; - ($($arg:tt)*) => {{ - $crate::gop_print!($($arg)*); - $crate::gop::write_str("\n"); - }}; -} diff --git a/src/interrupts.rs b/src/interrupts.rs deleted file mode 100644 index 90d5bbb..0000000 --- a/src/interrupts.rs +++ /dev/null @@ -1,401 +0,0 @@ -// --------------------------------------------------------------------------- -// Interrupt handling: IDT, PIC remap, ISR dispatch. -// --------------------------------------------------------------------------- -use core::arch::asm; -use core::sync::atomic::{AtomicU16, Ordering}; - -// --------------------------------------------------------------------------- -// IDT entry and table -// --------------------------------------------------------------------------- - -/// A single 64-bit IDT entry (16 bytes). -#[derive(Copy, Clone)] -#[repr(C, packed)] -struct IdtEntry { - offset_lo: u16, - selector: u16, - ist: u8, - flags: u8, - offset_mid: u16, - offset_hi: u32, - _reserved: u32, -} - -/// Fill one IDT entry at the given offset (in bytes). -unsafe fn idt_set_entry(idt: *mut u8, vec: u8, handler: u64, selector: u16, flags: u8) { - unsafe { - let off = (vec as usize) * 16; - let p = idt.add(off) as *mut IdtEntry; - p.write(IdtEntry { - offset_lo: handler as u16, - selector, - ist: 0, - flags: flags | 0x80, // bit 7 = present - offset_mid: (handler >> 16) as u16, - offset_hi: (handler >> 32) as u32, - _reserved: 0, - }); - } -} - -/// The one true IDT (live in .bss, zero-initialised). -static mut IDT: [u8; 256 * 16] = [0u8; 256 * 16]; -static mut IDTR: [u16; 5] = [0; 5]; // 2-byte limit + 4-byte base (48-bit pseudo-desc) - -// --------------------------------------------------------------------------- -// PIC (8259A) -// --------------------------------------------------------------------------- - -const PIC1_CMD: u16 = 0x20; -const PIC1_DATA: u16 = 0x21; -const PIC2_CMD: u16 = 0xA0; -const PIC2_DATA: u16 = 0xA1; - -const PIC_EOI: u8 = 0x20; - -const ICW1: u8 = 0x11; // edge, cascade, icw4 -const ICW4: u8 = 0x01; // x86 mode - -/// Remap PIC IRQs 0-15 to IDT vectors 32-47, then mask all. -unsafe fn remap_pic() { - unsafe { - asm!("out dx, al", in("dx") PIC1_CMD, in("al") ICW1, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC2_CMD, in("al") ICW1, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x20u8, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x28u8, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x04u8, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x02u8, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC1_DATA, in("al") ICW4, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC2_DATA, in("al") ICW4, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0xFFu8, options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0xFFu8, options(nostack, preserves_flags)); - } -} - -/// Send End-Of-Interrupt to the master PIC (and slave if needed). -fn eoi(irq: u8) { - unsafe { - // Slave IRQs are 8-15, mapped to vectors 40-47. - if irq >= 8 { - asm!("out dx, al", in("dx") PIC2_CMD, in("al") PIC_EOI, - options(nostack, preserves_flags)); - } - asm!("out dx, al", in("dx") PIC1_CMD, in("al") PIC_EOI, - options(nostack, preserves_flags)); - } -} - -// --------------------------------------------------------------------------- -// PIT timer (channel 0, ~100 Hz) -// --------------------------------------------------------------------------- - -const PIT_CMD: u16 = 0x43; -const PIT_CH0: u16 = 0x40; -const PIT_FREQ: u64 = 1_193_182; - -/// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish). -fn pit_set_rate(hz: u64) { - let divisor = (PIT_FREQ / hz) as u16; - unsafe { - // Mode 2, channel 0, lobyte/hibyte, binary - asm!("out dx, al", in("dx") PIT_CMD, in("al") 0b00_11_010_0u8, - options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor & 0xFF) as u8, - options(nostack, preserves_flags)); - asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor >> 8) as u8, - options(nostack, preserves_flags)); - } -} - -// --------------------------------------------------------------------------- -// Lock-free SPSC ring buffer for keyboard scancodes -// --------------------------------------------------------------------------- - -/// Single-producer (ISR) / single-consumer (main) lock-free ring buffer. -struct KeyBuf { - buf: [core::cell::UnsafeCell; 256], - head: AtomicU16, // next slot to read (written by consumer / main) - tail: AtomicU16, // next slot to write (written by producer / ISR) -} - -unsafe impl Sync for KeyBuf {} - -impl KeyBuf { - const fn new() -> Self { - // UnsafeCell requires const initialisation in a const ctx. - // Build an array the long way — one cell at a time. - const NEW_CELL: core::cell::UnsafeCell = core::cell::UnsafeCell::new(0); - // `[NEW_CELL; 256]` needs Clone, which UnsafeCell doesn't have. - // Use a union trick or just mark the whole buffer init with zeroes. - // SAFETY: we initialise via the const-fn path in init(). - Self { - buf: [ - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, - ], - head: AtomicU16::new(0), - tail: AtomicU16::new(0), - } - } - - /// Push one byte — called from ISR (interrupts disabled, single-producer). - fn push(&self, byte: u8) { - let t = self.tail.load(Ordering::Relaxed); - let h = self.head.load(Ordering::Acquire); - let next = t.wrapping_add(1); - // Full if the distance between head and tail == capacity (256). - if next.wrapping_sub(h) as u16 > 255 { - return; // drop — ring is full - } - unsafe { - *self.buf[t as usize & 255].get() = byte; - } - // Ensure byte is globally visible before incrementing tail. - self.tail.store(next, Ordering::Release); - } - - /// Pop one byte — called from main context. Returns None when empty. - fn pop(&self) -> Option { - let h = self.head.load(Ordering::Relaxed); - let t = self.tail.load(Ordering::Acquire); - if h == t { - return None; - } - let byte = unsafe { *self.buf[h as usize & 255].get() }; - self.head.store(h.wrapping_add(1), Ordering::Release); - Some(byte) - } -} - -static KEYBUF: KeyBuf = KeyBuf::new(); - -// --------------------------------------------------------------------------- -// Interrupt stubs (assembly) and dispatch -// --------------------------------------------------------------------------- - -core::arch::global_asm!( - // ISR stubs — push vector number + error code, jump to common. - ".globl isr_stub_32", - ".p2align 4", - "isr_stub_32:", - " push 0", // error code (non-exception) - " push 32", // vector - " jmp isr_common", - "", - ".globl isr_stub_33", - ".p2align 4", - "isr_stub_33:", - " push 0", - " push 33", - " jmp isr_common", - "", - // Catch-all for unexpected interrupts. - ".globl isr_stub_spurious", - ".p2align 4", - "isr_stub_spurious:", - " push 0", - " push 0xFF", - " jmp isr_common", - "", - // Common handler — save all registers, call into Rust, restore, iretq. - "isr_common:", - " push rax", - " push rcx", - " push rdx", - " push rbx", - " push rbp", - " push rsi", - " push rdi", - " push r8", - " push r9", - " push r10", - " push r11", - " push r12", - " push r13", - " push r14", - " push r15", - "", - " mov rcx, rsp", // first arg (rcx = MS x64 ABI) - " call isr_dispatch", - "", - " pop r15", - " pop r14", - " pop r13", - " pop r12", - " pop r11", - " pop r10", - " pop r9", - " pop r8", - " pop rdi", - " pop rsi", - " pop rbp", - " pop rbx", - " pop rdx", - " pop rcx", - " pop rax", - " add rsp, 16", // pop vector + error code - " iretq", -); - -/// Saved register frame (as pushed by `isr_common`). -#[repr(C)] -struct IntFrame { - r15: u64, - r14: u64, - r13: u64, - r12: u64, - r11: u64, - r10: u64, - r9: u64, - r8: u64, - rdi: u64, - rsi: u64, - rbp: u64, - rbx: u64, - rdx: u64, - rcx: u64, - rax: u64, - vector: u64, - error: u64, - rip: u64, - cs: u64, - rflags: u64, -} - -/// Called from the asm common handler — dispatches to the right Rust handler. -#[no_mangle] -unsafe extern "C" fn isr_dispatch(frame: &IntFrame) { - unsafe { - match frame.vector { - 32 => eoi(0), - 33 => { - let byte: u8; - core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16, - options(nostack, preserves_flags)); - // Push scancode to ring buffer (called with interrupts disabled). - KEYBUF.push(byte); - eoi(1); - } - _ => {} - } - } -} - -/// Timer IRQ (PIT channel 0 ~ 100 Hz). -fn timer_irq() { - eoi(0); -} - -/// Keyboard IRQ — read scancode from port 0x60 and push to ring buffer. -fn keyboard_irq() { - let byte: u8; - unsafe { - asm!("in al, dx", out("al") byte, in("dx") 0x60, - options(nostack, preserves_flags)); - } - KEYBUF.push(byte); - eoi(1); -} - -// --------------------------------------------------------------------------- -// Public API -// --------------------------------------------------------------------------- - -/// Read one scancode from the interrupt-driven ring buffer (non-blocking). -pub fn read_scancode() -> Option { - KEYBUF.pop() -} - -/// Global tick counter (incremented by timer IRQ, ~100 Hz). -pub static TICKS: AtomicU16 = AtomicU16::new(0); - -/// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts. -pub fn init() { - unsafe { - // Read the current code segment selector from CS. - let cs: u64; - asm!("mov {0}, cs", out(reg) cs, options(nostack, preserves_flags)); - let cs = cs as u16; - - // Build the IDT. - let idt_ptr = IDT.as_mut_ptr(); - let spurious = isr_stub_spurious as *const () as u64; - - // Fill all 256 entries with the spurious handler. - for vec in 0..=255u8 { - idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8); - } - - // Override vectors for interrupt we actually handle. - idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8); - idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8); - - // Build the IDTR pseudo-descriptor. - IDTR = [ - (256u16 * 16 - 1) as u16, // limit = 256*16 - 1 - idt_ptr as u16, // base lo - (idt_ptr as u64 >> 16) as u16, // base mid - (idt_ptr as u64 >> 32) as u16, // base hi - (idt_ptr as u64 >> 48) as u16, // base top - ]; - - // Load IDT. - asm!("lidt [{}]", in(reg) &IDTR, options(nostack, preserves_flags)); - - // Remap PIC and mask everything. - remap_pic(); - - // Set up PIT timer at ~100 Hz. - pit_set_rate(100); - - // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master. - let mut mask: u8; - asm!("in al, dx", out("al") mask, in("dx") PIC1_DATA, - options(nostack, preserves_flags)); - mask &= !0x03u8; - asm!("out dx, al", in("dx") PIC1_DATA, in("al") mask, - options(nostack, preserves_flags)); - - // Enable interrupts. - asm!("sti", options(nostack)); - } -} - -// --------------------------------------------------------------------------- -// Extern symbols from global_asm! -// --------------------------------------------------------------------------- -extern "C" { - fn isr_stub_32(); - fn isr_stub_33(); - fn isr_stub_spurious(); -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9a901f5..0000000 --- a/src/main.rs +++ /dev/null @@ -1,186 +0,0 @@ -#![no_std] -#![no_main] -#![allow(static_mut_refs, unsafe_code, dead_code, unused_doc_comments)] - -// SAFETY: single-core kernel, no preemption — static mut access is sound -// Toy kernel: dead code and unused items during active development - -extern crate alloc; - -mod font; -mod gop; -mod interrupts; -mod memory; -mod ps2; -mod serial; -mod shell; -mod uefi; - -use core::panic::PanicInfo; -use uefi::*; - -// --------------------------------------------------------------------------- -// Panic handler -// --------------------------------------------------------------------------- -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - use core::fmt::Write; - serial::write_str("\r\n*** PANIC ***\r\n"); - let _ = write!(&mut serial::SerialWriter, "{}\r\n", info.message()); - if let Some(loc) = info.location() { - let _ = write!( - &mut serial::SerialWriter, - " at {}:{}\r\n", - loc.file(), - loc.line() - ); - } - loop { - unsafe { - core::arch::asm!("cli; hlt"); - } - } -} - -// --------------------------------------------------------------------------- -// EFI entry point -// --------------------------------------------------------------------------- -#[no_mangle] -pub extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemTable) -> ! { - let st = unsafe { &mut *system_table }; - let bs = unsafe { &mut *st.boot_services }; - let con_out = unsafe { &mut *st.con_out }; - - // ---- Phase 1: UEFI boot services alive ---- - - // Print banner via UEFI console - unsafe { - let banner: [u16; 28] = [ - 'r' as u16, 'a' as u16, 'i' as u16, 's' as u16, 'e' as u16, 'r' as u16, 'n' as u16, - 'i' as u16, 'c' as u16, 's' as u16, ' ' as u16, 'v' as u16, '0' as u16, '.' as u16, - '1' as u16, ' ' as u16, '-' as u16, ' ' as u16, 'b' as u16, 'o' as u16, 'o' as u16, - 't' as u16, 'i' as u16, 'n' as u16, 'g' as u16, '.' as u16, '.' as u16, 0x0A, - ]; - (con_out.output_string)(con_out, banner.as_ptr()); - } - - // Init serial early so we can debug-print before EBS - serial::init(); - - serial_println!("=== raisernics boot ==="); - - // Stash the memory map - let map_key = memory::capture_memory_map(bs); - let map_key = match map_key { - Ok(k) => k, - Err(e) => { - serial_println!("mem: capture_memory_map failed 0x{:x}", e); - 0 - } - }; - - // Locate GOP (Graphics Output Protocol) - let mut gop_interface: *mut core::ffi::c_void = core::ptr::null_mut(); - let gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; - let gop_result = unsafe { - (bs.locate_protocol)( - &gop_guid as *const Guid, - core::ptr::null_mut(), - &mut gop_interface, - ) - }; - if gop_result == EFI_SUCCESS { - let gop = unsafe { &mut *(gop_interface as *mut GraphicsOutputProtocol) }; - serial_println!("gop: framebuffer found"); - gop::init(gop); - } else { - serial_println!("gop: not found (0x{:x})", gop_result); - } - - // ExitBootServices — we now own the machine - let ebs_result = unsafe { (bs.exit_boot_services)(image_handle, map_key) }; - if ebs_result != EFI_SUCCESS { - // Memory map may have changed; try again with fresh map - // (This is the standard UEFI EBS retry dance.) - serial_println!( - "WARN: ExitBootServices failed (0x{:x}), retrying...", - ebs_result - ); - let _map_key = match memory::refresh_and_exit(bs, image_handle) { - Ok(k) => k, - Err(e) => { - serial_println!("FATAL: cannot ExitBootServices (0x{:x})", e); - loop { - unsafe { - core::arch::asm!("cli; hlt"); - } - } - } - }; - } - - // ---- Phase 2: Native mode ---- - - serial::init(); - serial_println!("serial: COM1 115200 8N1"); - - // PS/2 keyboard - ps2::init(); - serial_println!("ps2: keyboard initialized"); - - // Interrupts — IDT, PIC, PIT timer, keyboard IRQ handler - interrupts::init(); - serial_println!("intr: IDT loaded, PIC remapped, timer ~100 Hz"); - - // Memory: the bump allocator was set up during capture_memory_map. - // `alloc::Vec`, `alloc::String`, `format!` etc. are now usable. - - // Print memory summary - dump_memory_summary(); - - serial_println!("boot: entering shell"); - shell::run(); -} - -// --------------------------------------------------------------------------- -// Print a memory summary from the stashed memory map -// --------------------------------------------------------------------------- -fn dump_memory_summary() { - let (map, key) = memory::get_map(); - let mut conventional_4k = 0u64; - let mut addr_space_4k = 0u64; - for desc in map.iter() { - let pages = desc.number_of_pages; - addr_space_4k += pages; - if desc.kind == 7 { - conventional_4k += pages; - } - } - serial_println!( - "mem: {} entries, key=0x{:x}, usable: {} KB ({} MB)", - map.len(), - key, - conventional_4k * 4, - (conventional_4k * 4) / 1024, - ); - serial_println!( - "mem: address space: {} MB (incl. PCIe MMIO)", - (addr_space_4k * 4) / 1024, - ); -} - -// --------------------------------------------------------------------------- -// Helper: write a panic-proof string via UEFI console (used before serial) -// --------------------------------------------------------------------------- -#[allow(dead_code)] -unsafe fn uefi_puts(con_out: *mut SimpleTextOutput, s: &str) { - // Simple ASCII → UEFI-CHAR16 conversion (stack buffer, panic-safe) - let mut buf: [u16; 128] = [0; 128]; - let len = s.len().min(127); - for (i, &b) in s.as_bytes()[..len].iter().enumerate() { - buf[i] = b as u16; - } - buf[len] = 0x0A; // LF - let out = unsafe { &mut *con_out }; - (out.output_string)(out, buf.as_ptr()); -} diff --git a/src/memory.rs b/src/memory.rs deleted file mode 100644 index d223f69..0000000 --- a/src/memory.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::uefi::*; -use core::alloc::{GlobalAlloc, Layout}; -use core::mem; -use core::ptr; - -const MAX_MAP_ENTRIES: usize = 512; - -static mut MEMORY_MAP: [MemoryDescriptor; MAX_MAP_ENTRIES] = unsafe { mem::zeroed() }; -static mut MAP_ENTRIES: usize = 0; - -/// The map key returned by GetMemoryMap, needed for ExitBootServices. -static mut MAP_KEY: usize = 0; - -/// Static working buffer for GetMemoryMap (64 KB, way more than enough). -/// Lives in .bss; no pool allocation, so no map-key staleness. -static mut MAP_WORK_BUF: [u8; 65536] = [0u8; 65536]; - -// --------------------------------------------------------------------------- -// Bump allocator — no deallocation, just pointer advancement. -// Used as Rust's #[global_allocator] once the heap region is set up. -// --------------------------------------------------------------------------- -struct BumpAllocator; - -unsafe impl Sync for BumpAllocator {} - -unsafe impl GlobalAlloc for BumpAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let align = layout.align().max(16); - let size = layout.size(); - unsafe { - let a = (HEAP_CURSOR + align - 1) & !(align - 1); - let e = a + size; - if e > HEAP_END || e < a { - return ptr::null_mut(); - } - HEAP_CURSOR = e; - a as *mut u8 - } - } - - unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { - // Bump allocator — cannot free individual allocations. - // Memory is reclaimed on reboot. Good enough for now. - unsafe { - core::ptr::read_volatile(&_ptr); - } - let _ = _layout; - } -} - -#[global_allocator] -static ALLOCATOR: BumpAllocator = BumpAllocator; - -/// Heap bump allocator bounds (`static mut` accessed only inside -/// `GlobalAlloc` impl, which is already `unsafe`). -static mut HEAP_START: usize = 0; -static mut HEAP_END: usize = 0; -static mut HEAP_CURSOR: usize = 0; - -/// Capture the UEFI memory map into the static buffer. Uses a pre-allocated -/// .bss working buffer so no pool allocation (and thus no memory-map change) -/// occurs between GetMemoryMap and ExitBootServices. -pub fn capture_memory_map(bs: &BootServices) -> Result { - let mut map_key: usize = 0; - let mut desc_size: usize = 0; - let mut desc_ver: u32 = 0; - - // Probe with null to get required size + descriptor size - let mut buf_size: usize = 0; - let status = unsafe { - (bs.get_memory_map)( - &mut buf_size, - ptr::null_mut(), - &mut map_key, - &mut desc_size, - &mut desc_ver, - ) - }; - if status != EFI_BUFFER_TOO_SMALL { - // Fallback: allocate a sensible size - buf_size = 64 * 1024; - } - if desc_size == 0 { - desc_size = mem::size_of::(); - } - - // Round up to desc_size alignment - let buf_size = (buf_size + desc_size - 1) / desc_size * desc_size; - - // If our static buffer is too small, fall back to pool (rare) - if buf_size > unsafe { MAP_WORK_BUF.len() } { - return Err(EFI_BUFFER_TOO_SMALL); - } - - let mut actual_size = buf_size; - let status = unsafe { - (bs.get_memory_map)( - &mut actual_size, - &raw mut MAP_WORK_BUF[0] as *mut MemoryDescriptor, - &mut map_key, - &mut desc_size, - &mut desc_ver, - ) - }; - - if status == EFI_BUFFER_TOO_SMALL { - // The map grew between the probe and the real call. Try a larger - // static buffer (can't grow here without pool, so return error to - // let the caller retry if they can allocate pool). - // For our 64 KB buffer this should never happen on OVMF. - return Err(EFI_BUFFER_TOO_SMALL); - } - if status != EFI_SUCCESS { - return Err(status); - } - - // Copy descriptors into our indexed array - let count = actual_size / desc_size; - let count = count.min(MAX_MAP_ENTRIES); - unsafe { - for i in 0..count { - let p = (&raw const MAP_WORK_BUF[..]) as *const u8; - let off = i * desc_size; - let src = p.add(off) as *const MemoryDescriptor; - MEMORY_MAP[i] = ptr::read(src); - } - MAP_ENTRIES = count; - MAP_KEY = map_key; - } - - // Do NOT free anything — the static buffer stays. Map key is still - // valid because no pool allocation/free happened after GetMemoryMap. - - init_bump_allocator(); - Ok(map_key) -} - -/// Retry ExitBootServices with a fresh memory map. -pub fn refresh_and_exit(bs: &BootServices, image_handle: Handle) -> Result { - let key = capture_memory_map(bs)?; - let status = unsafe { (bs.exit_boot_services)(image_handle, key) }; - if status == EFI_SUCCESS { - Ok(key) - } else { - Err(status) - } -} - -fn init_bump_allocator() { - unsafe { - let mut best_start = 0usize; - let mut best_size = 0usize; - for i in 0..MAP_ENTRIES { - let d = &MEMORY_MAP[i]; - if d.kind == MEMORY_TYPE_CONVENTIONAL { - let sz = d.number_of_pages as usize * 4096; - if sz > best_size { - best_start = d.physical_start as usize; - best_size = sz; - } - } - } - if best_size > 2 * 1024 * 1024 { - let s = best_start + 2 * 1024 * 1024; - let e = best_start + best_size; - if s < e { - HEAP_START = s; - HEAP_END = e; - HEAP_CURSOR = s; - } - } - } -} - -#[allow(dead_code)] -pub fn allocate(size: usize) -> *mut u8 { - unsafe { - let a = (HEAP_CURSOR + 15) & !15; - let e = a + size; - if e > HEAP_END || e < a { - return ptr::null_mut(); - } - HEAP_CURSOR = e; - a as *mut u8 - } -} - -#[allow(dead_code)] -pub fn get_map() -> (&'static [MemoryDescriptor], usize) { - unsafe { - ( - core::slice::from_raw_parts(&MEMORY_MAP[0], MAP_ENTRIES), - MAP_KEY, - ) - } -} diff --git a/src/ps2.rs b/src/ps2.rs deleted file mode 100644 index 4ea8b7a..0000000 --- a/src/ps2.rs +++ /dev/null @@ -1,364 +0,0 @@ -/// PS/2 keyboard driver — polling, scancode set 1, US QWERTY layout. -// Port addresses -const DATA_PORT: u16 = 0x60; -const STATUS_PORT: u16 = 0x64; - -// Status register bits -const STAT_OUTPUT_FULL: u8 = 0x01; - -// Modifier bit constants -const MOD_LSHIFT: u8 = 0x01; -const MOD_RSHIFT: u8 = 0x02; -const MOD_LCTRL: u8 = 0x04; -const MOD_RCTRL: u8 = 0x08; -const MOD_LALT: u8 = 0x10; -const MOD_RALT: u8 = 0x20; -const MOD_CAPS: u8 = 0x40; - -/// A decoded key event. -pub enum KeyEvent { - Char(char), - Enter, - Backspace, - Tab, - Escape, - ArrowUp, - ArrowDown, - ArrowLeft, - ArrowRight, - Home, - End, - PageUp, - PageDown, - Delete, - F(u8), - Unknown(u8), -} - -// ----- scancode-set-1 translation tables (US QWERTY) ----- - -/// Normal (unshifted) scancode → ASCII mapping. 0 = unmapped. -static SCAN_NORMAL: [u8; 128] = { - let mut t = [0u8; 128]; - t[0x02] = b'1'; - t[0x03] = b'2'; - t[0x04] = b'3'; - t[0x05] = b'4'; - t[0x06] = b'5'; - t[0x07] = b'6'; - t[0x08] = b'7'; - t[0x09] = b'8'; - t[0x0A] = b'9'; - t[0x0B] = b'0'; - t[0x0C] = b'-'; - t[0x0D] = b'='; - t[0x10] = b'q'; - t[0x11] = b'w'; - t[0x12] = b'e'; - t[0x13] = b'r'; - t[0x14] = b't'; - t[0x15] = b'y'; - t[0x16] = b'u'; - t[0x17] = b'i'; - t[0x18] = b'o'; - t[0x19] = b'p'; - t[0x1A] = b'['; - t[0x1B] = b']'; - t[0x1E] = b'a'; - t[0x1F] = b's'; - t[0x20] = b'd'; - t[0x21] = b'f'; - t[0x22] = b'g'; - t[0x23] = b'h'; - t[0x24] = b'j'; - t[0x25] = b'k'; - t[0x26] = b'l'; - t[0x27] = b';'; - t[0x28] = b'\''; - t[0x29] = b'`'; - t[0x2B] = b'\\'; - t[0x2C] = b'z'; - t[0x2D] = b'x'; - t[0x2E] = b'c'; - t[0x2F] = b'v'; - t[0x30] = b'b'; - t[0x31] = b'n'; - t[0x32] = b'm'; - t[0x33] = b','; - t[0x34] = b'.'; - t[0x35] = b'/'; - t[0x37] = b'*'; - t[0x39] = b' '; - t -}; - -/// Shifted scancode → ASCII. -static SCAN_SHIFT: [u8; 128] = { - let mut t = [0u8; 128]; - t[0x02] = b'!'; - t[0x03] = b'@'; - t[0x04] = b'#'; - t[0x05] = b'$'; - t[0x06] = b'%'; - t[0x07] = b'^'; - t[0x08] = b'&'; - t[0x09] = b'*'; - t[0x0A] = b'('; - t[0x0B] = b')'; - t[0x0C] = b'_'; - t[0x0D] = b'+'; - t[0x10] = b'Q'; - t[0x11] = b'W'; - t[0x12] = b'E'; - t[0x13] = b'R'; - t[0x14] = b'T'; - t[0x15] = b'Y'; - t[0x16] = b'U'; - t[0x17] = b'I'; - t[0x18] = b'O'; - t[0x19] = b'P'; - t[0x1A] = b'{'; - t[0x1B] = b'}'; - t[0x1E] = b'A'; - t[0x1F] = b'S'; - t[0x20] = b'D'; - t[0x21] = b'F'; - t[0x22] = b'G'; - t[0x23] = b'H'; - t[0x24] = b'J'; - t[0x25] = b'K'; - t[0x26] = b'L'; - t[0x27] = b':'; - t[0x28] = b'"'; - t[0x29] = b'~'; - t[0x2B] = b'|'; - t[0x2C] = b'Z'; - t[0x2D] = b'X'; - t[0x2E] = b'C'; - t[0x2F] = b'V'; - t[0x30] = b'B'; - t[0x31] = b'N'; - t[0x32] = b'M'; - t[0x33] = b'<'; - t[0x34] = b'>'; - t[0x35] = b'?'; - t[0x37] = b'*'; - t[0x39] = b' '; - t -}; - -/// Internal keyboard state -struct KbdState { - modifiers: u8, - got_e0: bool, -} - -static mut KBD: KbdState = KbdState { - modifiers: 0, - got_e0: false, -}; - -/// Initialise the PS/2 port: drain stale bytes + enable keyboard interrupt. -pub fn init() { - drain_input(); - - // Enable the keyboard interrupt on the PS/2 controller. - // Read Configuration Byte (command 0x20), set bit 0 (IRQ1 enable), - // write back (command 0x60). - unsafe { - // Tell the keyboard controller we want to read the config byte. - // Need to wait for the controller to be ready first. - wait_write(); - core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x20u8, - options(nostack, preserves_flags)); - wait_read(); - let mut cfg: u8; - core::arch::asm!("in al, dx", out("al") cfg, in("dx") 0x60u16, - options(nostack, preserves_flags)); - - cfg |= 0x01; // enable keyboard interrupt - - // Write config byte back - wait_write(); - core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x60u8, - options(nostack, preserves_flags)); - wait_write(); - core::arch::asm!("out dx, al", in("dx") 0x60u16, in("al") cfg, - options(nostack, preserves_flags)); - } -} - -/// Wait until the PS/2 controller is ready to receive a command. -fn wait_write() { - unsafe { - loop { - let status: u8; - core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16, - options(nostack, preserves_flags)); - if status & 0x02 == 0 { - // Input buffer empty — controller ready to receive - break; - } - } - } -} - -/// Wait until the PS/2 controller has data available to read. -fn wait_read() { - unsafe { - loop { - let status: u8; - core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16, - options(nostack, preserves_flags)); - if status & 0x01 != 0 { - // Output buffer full — data ready to read - break; - } - } - } -} - -fn drain_input() { - unsafe { - loop { - let status: u8; - core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, - options(nostack, preserves_flags)); - if status & STAT_OUTPUT_FULL == 0 { - break; - } - let _data: u8; - core::arch::asm!("in al, dx", out("al") _data, in("dx") DATA_PORT, - options(nostack, preserves_flags)); - } - } -} - -/// Poll for a key event. Blocks via `hlt` until a key is pressed. -/// The keyboard interrupt handler pushes raw scancodes to a ring buffer; -/// this function pops and decodes them. -pub fn poll_key() -> KeyEvent { - loop { - if let Some(byte) = crate::interrupts::read_scancode() { - if let Some(ev) = process_byte(byte) { - return ev; - } - } else { - unsafe { - core::arch::asm!("sti; hlt", options(nostack, preserves_flags)); - } - } - } -} - -/// Non-blocking poll — read from the interrupt ring buffer if available. -pub fn try_poll_key() -> Option { - let byte = crate::interrupts::read_scancode()?; - process_byte(byte) -} - -/// Process a single scancode byte. Returns Some(KeyEvent) when a visible -/// event happens; returns None for modifier changes, releases, and prefixes. -fn process_byte(byte: u8) -> Option { - let state = unsafe { &mut KBD }; - - if byte == 0xE0 { - state.got_e0 = true; - return None; - } - - if state.got_e0 { - state.got_e0 = false; - return process_e0(byte); - } - - // Break code (key release) — bit 7 set - if byte & 0x80 != 0 { - update_modifiers(byte & 0x7F, false, false); - return None; - } - - // Make code (key press) - update_modifiers(byte, true, false); - - match byte { - 0x01 => Some(KeyEvent::Escape), - 0x0E => Some(KeyEvent::Backspace), - 0x0F => Some(KeyEvent::Tab), - 0x1C => Some(KeyEvent::Enter), - 0x37 => Some(KeyEvent::Char('*')), - 0x3B..=0x44 => Some(KeyEvent::F(byte - 0x3B + 1)), - _ => { - let shifted = (state.modifiers & (MOD_LSHIFT | MOD_RSHIFT)) != 0; - // CapsLock inverts case for letters - let caps = state.modifiers & MOD_CAPS != 0; - let table = if shifted { SCAN_SHIFT } else { SCAN_NORMAL }; - let mut ch = table[byte as usize]; - // Handle CapsLock for letters - if caps && ch.is_ascii_alphabetic() { - // Swap case: if table gives lowercase, make uppercase; etc. - // Since normal table has lowercase, shift table has uppercase: - ch = if shifted { - ch.to_ascii_lowercase() - } else { - ch.to_ascii_uppercase() - }; - } - if ch != 0 { - Some(KeyEvent::Char(ch as char)) - } else { - Some(KeyEvent::Unknown(byte)) - } - } - } -} - -/// Process an E0-prefixed scancode. -fn process_e0(byte: u8) -> Option { - if byte & 0x80 != 0 { - update_modifiers(byte & 0x7F, false, true); - return None; - } - - update_modifiers(byte, true, true); - - match byte { - 0x48 => Some(KeyEvent::ArrowUp), - 0x50 => Some(KeyEvent::ArrowDown), - 0x4B => Some(KeyEvent::ArrowLeft), - 0x4D => Some(KeyEvent::ArrowRight), - 0x47 => Some(KeyEvent::Home), - 0x4F => Some(KeyEvent::End), - 0x49 => Some(KeyEvent::PageUp), - 0x51 => Some(KeyEvent::PageDown), - 0x53 => Some(KeyEvent::Delete), - 0x35 => Some(KeyEvent::Char('/')), - _ => Some(KeyEvent::Unknown(byte)), - } -} - -fn update_modifiers(scancode: u8, pressed: bool, e0: bool) { - let state = unsafe { &mut KBD }; - match (scancode, e0) { - (0x2A, false) => set_mod(state, MOD_LSHIFT, pressed), - (0x36, false) => set_mod(state, MOD_RSHIFT, pressed), - (0x1D, false) => set_mod(state, MOD_LCTRL, pressed), - (0x1D, true) => set_mod(state, MOD_RCTRL, pressed), - (0x38, false) => set_mod(state, MOD_LALT, pressed), - (0x38, true) => set_mod(state, MOD_RALT, pressed), - (0x3A, false) => { - if pressed { - state.modifiers ^= MOD_CAPS; - } - } - _ => {} - } -} - -fn set_mod(state: &mut KbdState, bit: u8, pressed: bool) { - if pressed { - state.modifiers |= bit; - } else { - state.modifiers &= !bit; - } -} diff --git a/src/serial.rs b/src/serial.rs deleted file mode 100644 index 03fa570..0000000 --- a/src/serial.rs +++ /dev/null @@ -1,118 +0,0 @@ -/// UART 16550 serial driver — port-mapped I/O on COM1 (0x3F8) -use core::fmt; - -const COM1: u16 = 0x3F8; - -const DATA_REG: u16 = COM1; -const INTR_ENABLE: u16 = COM1 + 1; -const FIFO_CONTROL: u16 = COM1 + 2; -const LINE_CONTROL: u16 = COM1 + 3; -const MODEM_CONTROL: u16 = COM1 + 4; -const LINE_STATUS: u16 = COM1 + 5; - -const LSR_TX_EMPTY: u8 = 0x20; -const LSR_DATA_READY: u8 = 0x01; - -/// Initialise the UART with 115200 baud, 8N1. -pub fn init() { - unsafe { - // Disable interrupts - port_out(INTR_ENABLE, 0x00); - // Set DLAB=1 (baud rate divisor) - port_out(LINE_CONTROL, 0x80); - // Divisor = 1 → 115200 baud (with 1.8432 MHz clock) - port_out(DATA_REG, 0x01); - port_out(INTR_ENABLE, 0x00); - // 8-bit, 1 stop, no parity, DLAB=0 - port_out(LINE_CONTROL, 0x03); - // Enable FIFO, clear, 14-byte threshold - port_out(FIFO_CONTROL, 0xC7); - // RTS/DSR set - port_out(MODEM_CONTROL, 0x0B); - } -} - -/// Write a single byte to the serial port (blocking poll). -pub fn put_byte(b: u8) { - unsafe { - // Wait for transmitter holding register empty - while port_in(LINE_STATUS) & LSR_TX_EMPTY == 0 {} - port_out(DATA_REG, b); - } -} - -/// Read a single byte from the serial port (blocking poll). -pub fn get_byte() -> u8 { - unsafe { - while port_in(LINE_STATUS) & LSR_DATA_READY == 0 {} - port_in(DATA_REG) - } -} - -/// Non-blocking read: returns Some(byte) if data ready, None otherwise. -pub fn try_get_byte() -> Option { - unsafe { - if port_in(LINE_STATUS) & LSR_DATA_READY != 0 { - Some(port_in(DATA_REG)) - } else { - None - } - } -} - -/// Write a string to serial. -pub fn write_str(s: &str) { - for &b in s.as_bytes() { - if b == b'\n' { - put_byte(b'\r'); - } - put_byte(b); - } -} - -// --------------------------------------------------------------------------- -// fmt::Write implementation -// --------------------------------------------------------------------------- -pub struct SerialWriter; - -impl fmt::Write for SerialWriter { - fn write_str(&mut self, s: &str) -> fmt::Result { - write_str(s); - Ok(()) - } -} - -/// Helper macro — prints to serial via format_args!. -#[macro_export] -macro_rules! serial_print { - ($($arg:tt)*) => {{ - use core::fmt::Write; - let _ = write!(&mut $crate::serial::SerialWriter, $($arg)*); - }}; -} - -#[macro_export] -macro_rules! serial_println { - () => { $crate::serial::write_str("\r\n"); }; - ($($arg:tt)*) => {{ - $crate::serial_print!($($arg)*); - $crate::serial::write_str("\r\n"); - }}; -} - -// --------------------------------------------------------------------------- -// Port I/O helpers -// --------------------------------------------------------------------------- -unsafe fn port_in(port: u16) -> u8 { - let val: u8; - unsafe { - core::arch::asm!("in al, dx", out("al") val, in("dx") port, options(nostack, preserves_flags)); - } - val -} - -unsafe fn port_out(port: u16, val: u8) { - unsafe { - core::arch::asm!("out dx, al", in("al") val, in("dx") port, options(nostack, preserves_flags)); - } -} diff --git a/src/shell.rs b/src/shell.rs deleted file mode 100644 index 8e4cb62..0000000 --- a/src/shell.rs +++ /dev/null @@ -1,275 +0,0 @@ -/// Minimal command shell — reads from PS/2 keyboard, writes to GOP + serial. -use crate::ps2::{self, KeyEvent}; -use crate::{gop_print, gop_println, serial_print, serial_println}; - -/// Maximum input line length. -const LINE_BUF_SIZE: usize = 256; - -/// Run the shell forever. -pub fn run() -> ! { - gop_println!("raisernics v{} — type 'help' for commands", env!("CARGO_PKG_VERSION")); - serial_println!("========== raisernics v{} ==========", env!("CARGO_PKG_VERSION")); - - loop { - // Print prompt - gop_print!("> "); - serial_print!("> "); - - // Read one line from keyboard - let line = read_line(); - - // Parse and execute - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - - let (cmd, args) = split_first_word(trimmed); - - match cmd { - "help" => cmd_help(), - "echo" => cmd_echo(args), - "clear" => cmd_clear(), - "halt" | "poweroff" | "exit" => cmd_halt(), - "hello" => { - gop_println!("World!"); - serial_println!("World!"); - } - "info" => cmd_info(), - "mem" => cmd_mem(), - _ => { - gop_println!("unknown command: {}", cmd); - serial_println!("unknown command: {}", cmd); - } - } - } -} - -// --------------------------------------------------------------------------- -// Line input (PS/2 keyboard, no heap) -// --------------------------------------------------------------------------- -fn read_line() -> heapless_str::String { - let mut buf = heapless_str::String::::new(); - loop { - match ps2::poll_key() { - KeyEvent::Enter => { - crate::gop::put_char('\n'); - crate::serial::write_str("\r\n"); - return buf; - } - KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => { - if buf.push(c).is_ok() { - crate::gop::put_char(c); - // Echo to serial too - let mut s = [0u8; 4]; - let s = c.encode_utf8(&mut s); - crate::serial::write_str(s); - } - } - KeyEvent::Backspace => { - if buf.pop().is_some() { - // GOP backspace: move cursor back and erase - crate::gop::put_char('\x08'); - // Serial backspace: send BS SP BS (standard terminal protocol) - crate::serial::write_str("\x08 \x08"); - } - } - KeyEvent::Tab => { - for _ in 0..4 { - if buf.push(' ').is_ok() { - crate::gop::put_char(' '); - crate::serial::write_str(" "); - } - } - } - _ => {} - } - } -} - -/// Split a command line into the first word and the rest. -fn split_first_word(s: &str) -> (&str, &str) { - let s = s.trim_start(); - if let Some(i) = s.find(char::is_whitespace) { - (&s[..i], s[i..].trim_start()) - } else { - (s, "") - } -} - -// --------------------------------------------------------------------------- -// Built-in commands -// --------------------------------------------------------------------------- - -fn cmd_help() { - gop_println!("available commands:"); - gop_println!(" help — show this message"); - gop_println!(" echo — echo arguments"); - gop_println!(" clear — clear screen"); - gop_println!(" hello — print 'World!'"); - gop_println!(" info — show system info"); - gop_println!(" mem — show memory info"); - gop_println!(" halt — halt the system"); - gop_println!(" exit — alias for halt"); - serial_println!("available commands:"); - serial_println!(" help, echo, clear, hello, info, mem, halt, exit"); -} - -fn cmd_echo(args: &str) { - gop_println!("{}", args); -} - -fn cmd_clear() { - crate::gop::clear_screen(); -} - -fn cmd_info() { - // Test that the global allocator works: build a String on the heap - let mut s = alloc::string::String::from("raisernics v"); - s.push_str(env!("CARGO_PKG_VERSION")); - s.push_str(" — RNG custom kernel"); - gop_println!("{}", s); - gop_println!("arch: x86_64, freestanding, no userspace"); - serial_println!("{}", s); - serial_println!("arch: x86_64, freestanding, no userspace"); - // String is freed (dropped) when this scope ends — tests dealloc too -} - -fn cmd_mem() { - let (map, key) = crate::memory::get_map(); - - let mut total_kb = 0u64; - let mut conventional_kb = 0u64; - let mut loader_kb = 0u64; - let mut boot_code_kb = 0u64; - let mut boot_data_kb = 0u64; - let mut reserved_kb = 0u64; - let mut acpi_kb = 0u64; - let mut mmio_kb = 0u64; - - for desc in map { - let pages = desc.number_of_pages; - let bytes_4k = pages * 4; - total_kb += bytes_4k; - match desc.kind { - 1 => reserved_kb += bytes_4k, - 3 => boot_code_kb += bytes_4k, - 4 => boot_data_kb += bytes_4k, - 5 | 10 => acpi_kb += bytes_4k, - 6 => loader_kb += bytes_4k, - 7 => conventional_kb += bytes_4k, - 11 | 12 => mmio_kb += bytes_4k, - _ => reserved_kb += bytes_4k, - } - } - - // Build a big heap string with format! to test the allocator - let mut out = alloc::string::String::new(); - use alloc::fmt::Write; - let _ = writeln!(out, "memory map (key=0x{:x}, {} entries)", key, map.len()); - let _ = writeln!( - out, - " usable {:4} MB Conventional", - conventional_kb / 1024 - ); - let _ = writeln!( - out, - " boot code {:4} MB BootServicesCode", - boot_code_kb / 1024 - ); - let _ = writeln!( - out, - " boot data {:4} MB BootServicesData", - boot_data_kb / 1024 - ); - let _ = writeln!(out, " loader {:4} MB LoaderData", loader_kb / 1024); - let _ = writeln!(out, " ACPI {:4} MB", acpi_kb / 1024); - let _ = writeln!(out, " MMIO {:4} MB", mmio_kb / 1024); - let _ = writeln!(out, " reserved {:4} MB", reserved_kb / 1024); - let _ = writeln!( - out, - " raw total {:4} MB (incl. PCIe MMIO)", - total_kb / 1024 - ); - - gop_print!("{}", out); - serial_print!("{}", out); -} - -fn cmd_halt() -> ! { - gop_println!("halting..."); - serial_println!("halting..."); - loop { - unsafe { - core::arch::asm!("cli; hlt"); - } - } -} - -// --------------------------------------------------------------------------- -// Minimal fixed-capacity string for no-stack use (avoids alloc dependency) -// --------------------------------------------------------------------------- -mod heapless_str { - use core::fmt; - - /// A fixed-capacity string allocated entirely on the stack. - pub struct String { - buf: [u8; N], - len: usize, - } - - impl String { - pub const fn new() -> Self { - String { - buf: [0u8; N], - len: 0, - } - } - - pub fn as_str(&self) -> &str { - // SAFETY: we only write valid ASCII bytes. - unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) } - } - - pub fn push(&mut self, c: char) -> Result<(), ()> { - if self.len >= N { - return Err(()); - } - self.buf[self.len] = c as u8; - self.len += 1; - Ok(()) - } - - pub fn pop(&mut self) -> Option { - if self.len == 0 { - return None; - } - self.len -= 1; - Some(self.buf[self.len] as char) - } - - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - pub fn clear(&mut self) { - self.len = 0; - } - - pub fn trim(&self) -> &str { - self.as_str().trim() - } - } - - impl fmt::Display for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } - } - - impl fmt::Debug for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.as_str()) - } - } -} diff --git a/src/uefi.rs b/src/uefi.rs deleted file mode 100644 index 2e3ed6b..0000000 --- a/src/uefi.rs +++ /dev/null @@ -1,289 +0,0 @@ -use core::ffi::c_void; - -/// Opaque EFI handle -pub type Handle = *mut c_void; - -/// EFI status (0 = success, bit 63 = error code) -pub type Status = u64; - -/// Helper: UEFI error codes always have bit 63 set -const fn efi_err(n: u64) -> u64 { - n | (1u64 << 63) -} - -pub const EFI_SUCCESS: Status = 0; -pub const EFI_LOAD_ERROR: Status = efi_err(1); -pub const EFI_INVALID_PARAMETER: Status = efi_err(2); -#[allow(dead_code)] -pub const EFI_UNSUPPORTED: Status = efi_err(3); -pub const EFI_BUFFER_TOO_SMALL: Status = efi_err(5); -#[allow(dead_code)] -pub const EFI_NOT_FOUND: Status = efi_err(14); -pub const EFI_OUT_OF_RESOURCES: Status = efi_err(9); - -// --------------------------------------------------------------------------- -// GUID -// --------------------------------------------------------------------------- -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct Guid { - pub a: u32, - pub b: u16, - pub c: u16, - pub d: [u8; 8], -} - -pub const EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID: Guid = Guid { - a: 0x9042a9de, - b: 0x23dc, - c: 0x4a38, - d: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a], -}; - -// --------------------------------------------------------------------------- -// Table header -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct TableHeader { - pub signature: u64, - pub revision: u32, - pub header_size: u32, - pub crc32: u32, - pub reserved: u32, -} - -// --------------------------------------------------------------------------- -// Simple text output protocol (console output) -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct SimpleTextOutput { - _reset: *mut c_void, - pub output_string: unsafe extern "efiapi" fn(*mut SimpleTextOutput, *const u16) -> Status, - _test_string: *mut c_void, - _query_mode: *mut c_void, - _set_mode: *mut c_void, - _set_attribute: *mut c_void, - _clear_screen: *mut c_void, - _set_cursor_position: *mut c_void, - _enable_cursor: *mut c_void, - _mode: *mut c_void, -} - -// --------------------------------------------------------------------------- -// Simple text input protocol (keyboard input) -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct InputKey { - pub scan_code: u16, - pub unicode_char: u16, -} - -#[repr(C)] -pub struct SimpleTextInput { - _reset: *mut c_void, - pub read_key_stroke: unsafe extern "efiapi" fn(*mut SimpleTextInput, *mut InputKey) -> Status, - _wait_for_key: Handle, -} - -// --------------------------------------------------------------------------- -// Runtime services (stub — we don't use them) -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct RuntimeServices { - _hdr: TableHeader, - _reserved: [*mut c_void; 30], -} - -// --------------------------------------------------------------------------- -// Configuration table entry -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct ConfigurationTable { - pub vendor_guid: Guid, - pub vendor_table: *mut c_void, -} - -// --------------------------------------------------------------------------- -/// UEFI memory descriptor (from GetMemoryMap) -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct MemoryDescriptor { - pub kind: u32, - pub pad: u32, - pub physical_start: u64, - pub virtual_start: u64, - pub number_of_pages: u64, - pub attribute: u64, -} - -pub const MEMORY_TYPE_CONVENTIONAL: u32 = 7; -#[allow(dead_code)] -pub const MEMORY_TYPE_LOADER_DATA: u32 = 6; -#[allow(dead_code)] -pub const MEMORY_TYPE_BOOT_SERVICES_CODE: u32 = 3; -#[allow(dead_code)] -pub const MEMORY_TYPE_BOOT_SERVICES_DATA: u32 = 4; - -// --------------------------------------------------------------------------- -// Boot services table -// --------------------------------------------------------------------------- -pub type EfiGetMemoryMap = unsafe extern "efiapi" fn( - *mut usize, - *mut MemoryDescriptor, - *mut usize, - *mut usize, - *mut u32, -) -> Status; - -pub type EfiExitBootServices = unsafe extern "efiapi" fn(Handle, usize) -> Status; - -pub type EfiHandleProtocol = - unsafe extern "efiapi" fn(Handle, *const Guid, *mut *mut c_void) -> Status; - -pub type EfiLocateProtocol = - unsafe extern "efiapi" fn(*const Guid, *mut c_void, *mut *mut c_void) -> Status; - -pub type EfiAllocatePool = unsafe extern "efiapi" fn(u8, usize, *mut *mut c_void) -> Status; - -pub type EfiFreePool = unsafe extern "efiapi" fn(*mut c_void) -> Status; - -pub type EfiStall = unsafe extern "efiapi" fn(usize) -> Status; - -#[repr(C)] -pub struct BootServices { - pub hdr: TableHeader, - - // 0-1: task priority - pub raise_tpl: *mut c_void, - pub restore_tpl: *mut c_void, - - // 2-3: memory allocation - pub allocate_pages: *mut c_void, - pub free_pages: *mut c_void, - - // 4: GetMemoryMap - pub get_memory_map: EfiGetMemoryMap, - - // 5-6: pool - pub allocate_pool: EfiAllocatePool, - pub free_pool: EfiFreePool, - - // 7-12: events - pub create_event: *mut c_void, - pub set_timer: *mut c_void, - pub wait_for_event: *mut c_void, - pub signal_event: *mut c_void, - pub close_event: *mut c_void, - pub check_event: *mut c_void, - - // 13-14: protocol install - pub install_protocol_interface: *mut c_void, - pub reinstall_protocol_interface: *mut c_void, - pub uninstall_protocol_interface: *mut c_void, - - // 16: HandleProtocol - pub handle_protocol: EfiHandleProtocol, - - pub _reserved: *mut c_void, - - // 18: register protocol notify - pub register_protocol_notify: *mut c_void, - - // 19-21: locate - pub locate_handle: *mut c_void, - pub locate_device_path: *mut c_void, - pub install_configuration_table: *mut c_void, - - // 22-23: image loading - pub load_image: *mut c_void, - pub start_image: *mut c_void, - - // 24-25 - pub exit: *mut c_void, - pub unload_image: *mut c_void, - - // 26: ExitBootServices - pub exit_boot_services: EfiExitBootServices, - - // 27-29 - pub get_next_monotonic_count: *mut c_void, - pub stall: EfiStall, - pub set_watchdog_timer: *mut c_void, - - // 30-36 - pub connect_controller: *mut c_void, - pub disconnect_controller: *mut c_void, - pub open_protocol: *mut c_void, - pub close_protocol: *mut c_void, - pub open_protocol_information: *mut c_void, - pub protocols_per_handle: *mut c_void, - pub locate_handle_buffer: *mut c_void, - - // 37: LocateProtocol - pub locate_protocol: EfiLocateProtocol, - - // 38-43 - pub install_multiple_protocol_interfaces: *mut c_void, - pub uninstall_multiple_protocol_interfaces: *mut c_void, - pub calculate_crc32: *mut c_void, - pub copy_mem: *mut c_void, - pub set_mem: *mut c_void, - pub create_event_ex: *mut c_void, -} - -// --------------------------------------------------------------------------- -// Graphics Output Protocol (GOP) -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct GopModeInfo { - pub version: u32, - pub horizontal_resolution: u32, - pub vertical_resolution: u32, - pub pixel_format: u32, // PixelFormat enum - pub red_mask: u32, - pub green_mask: u32, - pub blue_mask: u32, - pub reserved_mask: u32, - pub pixels_per_scan_line: u32, -} - -pub const PIXEL_RGB: u32 = 0; -pub const PIXEL_BGR: u32 = 1; - -#[repr(C)] -pub struct GopMode { - pub max_mode: u32, - pub mode: u32, - pub info: *mut GopModeInfo, - pub info_size: usize, - pub framebuffer_base: *mut c_void, - pub framebuffer_size: usize, -} - -#[repr(C)] -pub struct GraphicsOutputProtocol { - pub query_mode: *mut c_void, - pub set_mode: *mut c_void, - pub get_current_mode: *mut c_void, - pub mode: *mut GopMode, -} - -// --------------------------------------------------------------------------- -// System table -// --------------------------------------------------------------------------- -#[repr(C)] -pub struct SystemTable { - pub hdr: TableHeader, - pub firmware_vendor: *const u16, - pub firmware_revision: u32, - pub console_in_handle: Handle, - pub con_in: *mut SimpleTextInput, - pub console_out_handle: Handle, - pub con_out: *mut SimpleTextOutput, - pub stderr_handle: Handle, - pub std_err: *mut SimpleTextOutput, - pub runtime_services: *mut RuntimeServices, - pub boot_services: *mut BootServices, - pub number_of_table_entries: usize, - pub configuration_table: *const ConfigurationTable, -} -- cgit v1.2.3