/// 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"); }}; }