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"). --- rsnix/src/gop.rs | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 rsnix/src/gop.rs (limited to 'rsnix/src/gop.rs') diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs new file mode 100644 index 0000000..9645bb0 --- /dev/null +++ b/rsnix/src/gop.rs @@ -0,0 +1,232 @@ +/// 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"); + }}; +} -- cgit v1.2.3