diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-31 02:58:31 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-31 02:58:31 +0200 |
| commit | 6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b (patch) | |
| tree | ed59a5bd59a94b3a731b28c68c507f18afc44b0b /rsnix/src/gop.rs | |
| parent | auto-version all strings from Cargo.toml via env!("CARGO_PKG_VERSION") (diff) | |
| download | rs-dos-6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b.tar.gz rs-dos-6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b.zip | |
restructure: rsnix kernel + shell crate + rsdos entry point
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").
Diffstat (limited to 'rsnix/src/gop.rs')
| -rw-r--r-- | rsnix/src/gop.rs | 232 |
1 files changed, 232 insertions, 0 deletions
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 @@ | |||
| 1 | /// GOP framebuffer driver — renders text via a bitmap font. | ||
| 2 | use crate::font; | ||
| 3 | use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; | ||
| 4 | use core::fmt; | ||
| 5 | use core::ptr; | ||
| 6 | |||
| 7 | /// Framebuffer state captured before ExitBootServices. | ||
| 8 | pub struct Framebuffer { | ||
| 9 | base: *mut u8, | ||
| 10 | #[allow(dead_code)] | ||
| 11 | width: usize, | ||
| 12 | height: usize, | ||
| 13 | pitch: usize, // bytes per scan line | ||
| 14 | bytes_per_pixel: usize, | ||
| 15 | bg: u32, // packed BGRA or RGBA background colour | ||
| 16 | fg: u32, // packed foreground colour | ||
| 17 | cursor_x: usize, | ||
| 18 | cursor_y: usize, | ||
| 19 | rows: usize, | ||
| 20 | cols: usize, | ||
| 21 | } | ||
| 22 | |||
| 23 | static mut FB: Option<Framebuffer> = None; | ||
| 24 | |||
| 25 | /// Initialise the framebuffer from GOP. Call *before* ExitBootServices. | ||
| 26 | pub fn init(gop: &mut GraphicsOutputProtocol) { | ||
| 27 | let mode = unsafe { &*gop.mode }; | ||
| 28 | let info = unsafe { &*mode.info }; | ||
| 29 | let base = mode.framebuffer_base as *mut u8; | ||
| 30 | let width = info.horizontal_resolution as usize; | ||
| 31 | let height = info.vertical_resolution as usize; | ||
| 32 | let pitch = info.pixels_per_scan_line as usize * 4; // 32-bit pixel | ||
| 33 | let bpp = 4; | ||
| 34 | |||
| 35 | // Pick colours based on pixel format | ||
| 36 | let (fg, bg) = match info.pixel_format { | ||
| 37 | PIXEL_BGR => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), // white fg, black bg | ||
| 38 | PIXEL_RGB => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), | ||
| 39 | _ => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), | ||
| 40 | }; | ||
| 41 | |||
| 42 | let rows = height / 16; | ||
| 43 | let cols = width / 8; | ||
| 44 | |||
| 45 | let fb = Framebuffer { | ||
| 46 | base, | ||
| 47 | width, | ||
| 48 | height, | ||
| 49 | pitch, | ||
| 50 | bytes_per_pixel: bpp, | ||
| 51 | bg, | ||
| 52 | fg, | ||
| 53 | cursor_x: 0, | ||
| 54 | cursor_y: 0, | ||
| 55 | rows, | ||
| 56 | cols, | ||
| 57 | }; | ||
| 58 | |||
| 59 | fb.clear(); | ||
| 60 | |||
| 61 | unsafe { | ||
| 62 | FB = Some(fb); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Draw a character at the current cursor position and advance. | ||
| 67 | pub fn put_char(c: char) { | ||
| 68 | let fb = unsafe { | ||
| 69 | match FB.as_mut() { | ||
| 70 | Some(f) => f, | ||
| 71 | None => return, | ||
| 72 | } | ||
| 73 | }; | ||
| 74 | |||
| 75 | if c == '\x08' { | ||
| 76 | // Backspace - move cursor back and erase | ||
| 77 | if fb.cursor_x > 0 { | ||
| 78 | fb.cursor_x -= 1; | ||
| 79 | } else if fb.cursor_y > 0 { | ||
| 80 | fb.cursor_y -= 1; | ||
| 81 | fb.cursor_x = fb.cols - 1; | ||
| 82 | } else { | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | // Draw glyph 32 (space) to erase — glyph 0 is NUL, not blank | ||
| 86 | fb.draw_glyph(32, fb.cursor_x, fb.cursor_y); | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | if c == '\n' { | ||
| 90 | fb.cursor_x = 0; | ||
| 91 | fb.cursor_y += 1; | ||
| 92 | if fb.cursor_y >= fb.rows { | ||
| 93 | fb.scroll(); | ||
| 94 | } | ||
| 95 | return; | ||
| 96 | } | ||
| 97 | if c == '\r' { | ||
| 98 | fb.cursor_x = 0; | ||
| 99 | return; | ||
| 100 | } | ||
| 101 | |||
| 102 | let idx = c as usize; | ||
| 103 | if idx >= 256 { | ||
| 104 | // Unmapped character: draw a space | ||
| 105 | fb.draw_glyph(0, fb.cursor_x, fb.cursor_y); | ||
| 106 | } else { | ||
| 107 | fb.draw_glyph(idx, fb.cursor_x, fb.cursor_y); | ||
| 108 | } | ||
| 109 | |||
| 110 | fb.cursor_x += 1; | ||
| 111 | if fb.cursor_x >= fb.cols { | ||
| 112 | fb.cursor_x = 0; | ||
| 113 | fb.cursor_y += 1; | ||
| 114 | if fb.cursor_y >= fb.rows { | ||
| 115 | fb.scroll(); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | /// Write a string to the framebuffer. | ||
| 121 | pub fn write_str(s: &str) { | ||
| 122 | for c in s.chars() { | ||
| 123 | put_char(c); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Clear the entire screen to background colour and reset cursor to (0, 0). | ||
| 128 | pub fn clear_screen() { | ||
| 129 | let fb = unsafe { | ||
| 130 | match FB.as_mut() { | ||
| 131 | Some(f) => f, | ||
| 132 | None => return, | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | fb.clear(); | ||
| 136 | // Reset cursor back to top-left so the next prompt appears at (0, 0) | ||
| 137 | fb.cursor_x = 0; | ||
| 138 | fb.cursor_y = 0; | ||
| 139 | } | ||
| 140 | |||
| 141 | // --------------------------------------------------------------------------- | ||
| 142 | // Framebuffer implementation | ||
| 143 | // --------------------------------------------------------------------------- | ||
| 144 | impl Framebuffer { | ||
| 145 | fn clear(&self) { | ||
| 146 | let size = self.pitch * self.height; | ||
| 147 | // Fill with background colour (32-bit writes) | ||
| 148 | let mut p = self.base; | ||
| 149 | let end = unsafe { p.add(size) }; | ||
| 150 | unsafe { | ||
| 151 | while p < end { | ||
| 152 | ptr::write_volatile(p as *mut u32, self.bg); | ||
| 153 | p = p.add(4); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | fn scroll(&mut self) { | ||
| 159 | // Move everything up one row (16 pixels) | ||
| 160 | let row_bytes = self.pitch * 16; | ||
| 161 | let total = self.pitch * self.height; | ||
| 162 | unsafe { | ||
| 163 | ptr::copy(self.base.add(row_bytes), self.base, total - row_bytes); | ||
| 164 | // Clear last row | ||
| 165 | let last_row = self.base.add(total - row_bytes); | ||
| 166 | for i in 0..(self.pitch * 16 / 4) { | ||
| 167 | ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | self.cursor_y -= 1; | ||
| 171 | } | ||
| 172 | |||
| 173 | fn draw_glyph(&self, glyph_idx: usize, col: usize, row: usize) { | ||
| 174 | let glyph = font::GLYPH_8X16; | ||
| 175 | let data = match glyph.get(glyph_idx) { | ||
| 176 | Some(d) => *d, | ||
| 177 | None => return, | ||
| 178 | }; | ||
| 179 | |||
| 180 | let x = col * 8; | ||
| 181 | let y = row * 16; | ||
| 182 | |||
| 183 | for cy in 0..16 { | ||
| 184 | let row_bits = data[cy]; | ||
| 185 | for cx in 0..8 { | ||
| 186 | if row_bits & (0x80 >> cx) != 0 { | ||
| 187 | // Set foreground pixel | ||
| 188 | let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel; | ||
| 189 | unsafe { | ||
| 190 | ptr::write_volatile(self.base.add(offset) as *mut u32, self.fg); | ||
| 191 | } | ||
| 192 | } else { | ||
| 193 | // Set background pixel | ||
| 194 | let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel; | ||
| 195 | unsafe { | ||
| 196 | ptr::write_volatile(self.base.add(offset) as *mut u32, self.bg); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | // --------------------------------------------------------------------------- | ||
| 205 | // fmt::Write wrapper | ||
| 206 | // --------------------------------------------------------------------------- | ||
| 207 | pub struct GopWriter; | ||
| 208 | |||
| 209 | impl fmt::Write for GopWriter { | ||
| 210 | fn write_str(&mut self, s: &str) -> fmt::Result { | ||
| 211 | write_str(s); | ||
| 212 | Ok(()) | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | /// Write a formatted string to the framebuffer. | ||
| 217 | #[macro_export] | ||
| 218 | macro_rules! gop_print { | ||
| 219 | ($($arg:tt)*) => {{ | ||
| 220 | use core::fmt::Write; | ||
| 221 | let _ = write!(&mut $crate::gop::GopWriter, $($arg)*); | ||
| 222 | }}; | ||
| 223 | } | ||
| 224 | |||
| 225 | #[macro_export] | ||
| 226 | macro_rules! gop_println { | ||
| 227 | () => { $crate::gop::write_str("\n"); }; | ||
| 228 | ($($arg:tt)*) => {{ | ||
| 229 | $crate::gop_print!($($arg)*); | ||
| 230 | $crate::gop::write_str("\n"); | ||
| 231 | }}; | ||
| 232 | } | ||
