/// GOP framebuffer driver — renders text via a bitmap font. /// /// Text surface (cursor, scrolling, colours) lives in `crate::vt`; this /// module is the low-level rasteriser: glyph blits and pixel shifts on the /// linear framebuffer. use crate::font; use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; 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 bgr: bool, } static mut FB: Option = None; /// Software backbuffer: a RAM copy of the whole framebuffer (same pitch /// layout, u32 pixels). /// /// Real-hardware GOP framebuffers are typically mapped uncacheable or /// write-combining (firmware MTRR/PAT), so *reading* them costs a bus /// round-trip per cache line — a full-screen scroll shift reads ~4-8 MB /// and crawls. Rasterisation therefore writes every pixel to the shadow /// too, and scrolling is a fast cached memcpy inside the shadow followed /// by a write-only push to the display: the display is never read. static mut SHADOW: *mut u32 = core::ptr::null_mut(); static mut SHADOW_WORDS: usize = 0; /// 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 bgr = info.pixel_format == PIXEL_BGR; let _ = fg; let fb = Framebuffer { base, width, height, pitch, bytes_per_pixel: bpp, bg, bgr, }; // Allocate the RAM backbuffer before first paint so every write goes // to both the display and the shadow. let words = (pitch * height) / 4; unsafe { if words > 0 { let v: Vec = vec![0; words]; SHADOW = Box::into_raw(v.into_boxed_slice()) as *mut u32; SHADOW_WORDS = words; } } fb.clear(); unsafe { FB = Some(fb); } } // --------------------------------------------------------------------------- // Public text API — the text surface now lives in `crate::vt`; these wrappers // keep callers (shell, macros) unchanged. // --------------------------------------------------------------------------- /// Draw a character at the current terminal cursor position and advance. pub fn put_char(c: char) { let b = if (c as u32) < 0x100 { c as u8 } else { b'?' }; crate::vt::put_char(b); } /// Write a string to the terminal. pub fn write_str(s: &str) { crate::vt::write_str(s); } /// Clear the terminal and home the cursor. pub fn clear_screen() { crate::vt::clear_all(); } /// (cols, rows) of the text surface, from the current framebuffer mode. pub fn dims() -> (usize, usize) { unsafe { match FB.as_ref() { Some(f) => (f.width / 8, f.height / 16), None => (0, 0), } } } /// Pack an (r, g, b) colour for the framebuffer's pixel format. pub fn rgb(r: u8, g: u8, b: u8) -> u32 { unsafe { match FB.as_ref() { Some(f) if f.bgr => ((r as u32) << 16) | ((g as u32) << 8) | b as u32, _ => ((b as u32) << 16) | ((g as u32) << 8) | r as u32, } } } // --------------------------------------------------------------------------- // Framebuffer implementation // --------------------------------------------------------------------------- impl Framebuffer { fn clear(&self) { let size = self.pitch * self.height; // Fill with background colour (32-bit writes), display + shadow. 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); } } if !unsafe { SHADOW.is_null() } { for i in 0..unsafe { SHADOW_WORDS } { unsafe { *SHADOW.add(i) = self.bg; } } } } /// Shift the framebuffer content up by `n` text rows (16 pixels each) /// and clear the exposed bottom rows. Called by `vt` when deferred /// scrolls are pushed to the display; `n` can batch many scrolls into /// one write-only push. /// /// With the RAM backbuffer, this is a cached shadow memcpy + a /// write-only push to the display — the display itself is never read /// (reads are uncached on real hardware and dominate the old path). pub(crate) fn shift_up_n(&self, n: usize) { let n = n.max(1).min(self.height / 16); // sanity let row_bytes = self.pitch * 16; let total = self.pitch * self.height; let row_words = row_bytes / 4; let words = total / 4; unsafe { if SHADOW.is_null() { // No shadow (allocation failed): fall back to shifting the // display directly. ptr::copy( self.base.add(n * row_bytes), self.base, total - n * row_bytes, ); let last_row = self.base.add(total - n * row_bytes); for i in 0..(n * row_words) { ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg); } return; } // Shift the shadow up n rows (cached RAM — fast). ptr::copy(SHADOW.add(n * row_words), SHADOW, words - n * row_words); // Clear the exposed bottom rows in the shadow. for i in (words - n * row_words)..words { *SHADOW.add(i) = self.bg; } // Push the shadow to the display: write-only, no reads. // A bulk copy (rep movsb) writes in 64-byte bursts that // coalesce well on WC framebuffers — far fewer bus // transactions than per-pixel stores. ptr::copy_nonoverlapping(SHADOW, self.base as *mut u32, words); } } } /// Blit one glyph cell at (col, row) with explicit fg/bg colours. /// Procedurally-rendered bitmaps for Unicode glyphs missing from the /// 256-glyph Latin-1 font: box-drawing (U+2500–U+257F), arrows /// (U+2190–U+2193) and the em dash (U+2014). Box drawing is pure /// geometry, so these are computed, not stored. Same format as the font: /// one byte per row, bit 7 = leftmost column. fn unicode_glyph_bits(ch: u16) -> Option<[u8; 16]> { let mut g = [0u8; 16]; match ch { // ─ light horizontal, ━ heavy horizontal 0x2500 => { g[7] = 0xFF; g[8] = 0xFF; } 0x2501 => { g[7] = 0xFF; g[8] = 0xFF; g[9] = 0xFF; } // │ light vertical, ┃ heavy vertical 0x2502 => { for y in 0..16 { g[y] = 0x18; } } 0x2503 => { for y in 0..16 { g[y] = 0x3C; } } // corners ┌ ┐ └ ┘ 0x250C => { g[7] = 0xF0; g[8] = 0xF8; for y in 9..16 { g[y] = 0x18; } } 0x2510 => { g[7] = 0x0F; g[8] = 0x1F; for y in 9..16 { g[y] = 0x18; } } 0x2514 => { for y in 0..7 { g[y] = 0x18; } g[7] = 0xF8; g[8] = 0xF0; } 0x2518 => { for y in 0..7 { g[y] = 0x18; } g[7] = 0x1F; g[8] = 0x0F; } // T junctions ├ ┤ ┬ ┴ and cross ┼ 0x251C => { for y in 0..16 { g[y] = 0x18; } g[7] = 0xF8; g[8] = 0xF8; } 0x2524 => { for y in 0..16 { g[y] = 0x18; } g[7] = 0x1F; g[8] = 0x1F; } 0x252C => { g[7] = 0xFF; g[8] = 0xFF; for y in 9..16 { g[y] = 0x18; } } 0x2534 => { for y in 0..7 { g[y] = 0x18; } g[7] = 0xFF; g[8] = 0xFF; } 0x253C => { for y in 0..16 { g[y] = 0x18; } g[7] = 0xFF; g[8] = 0xFF; } // ═ double horizontal, ║ double vertical 0x2550 => { g[6] = 0xFF; g[9] = 0xFF; } 0x2551 => { for y in 0..16 { g[y] = 0x24; } } // — em dash 0x2014 => { g[7] = 0xFF; g[8] = 0xFF; } // ← → ↑ ↓ 0x2190 => { g[8] = 0xFF; g[6] = 0xC0; g[7] = 0xC0; g[9] = 0xC0; g[10] = 0xC0; } 0x2192 => { g[8] = 0xFF; g[6] = 0x03; g[7] = 0x03; g[9] = 0x03; g[10] = 0x03; } 0x2191 => { g[0] = 0x18; g[1] = 0x3C; g[2] = 0x7E; for y in 3..16 { g[y] = 0x18; } } 0x2193 => { for y in 0..13 { g[y] = 0x18; } g[13] = 0x7E; g[14] = 0x3C; g[15] = 0x18; } _ => return None, } Some(g) } pub(crate) fn blit_glyph(col: usize, row: usize, ch: u16, fg: (u8, u8, u8), bg: (u8, u8, u8)) { if let Some(fb) = unsafe { FB.as_ref() } { let glyph = font::GLYPH_8X16; let data = if ch < 256 { match glyph.get(ch as usize) { Some(d) => *d, None => return, } } else if let Some(bits) = unicode_glyph_bits(ch) { bits } else { match glyph.get(32) { Some(d) => *d, None => return, } }; let fg = rgb(fg.0, fg.1, fg.2); let bg = rgb(bg.0, bg.1, bg.2); let x = col * 8; let y = row * 16; for cy in 0..16 { let row_bits = data[cy]; for cx in 0..8 { let offset = (y + cy) * fb.pitch + (x + cx) * fb.bytes_per_pixel; unsafe { let px = if row_bits & (0x80 >> cx) != 0 { fg } else { bg }; // Display (volatile; the fb is MMIO-ish) + RAM shadow. ptr::write_volatile(fb.base.add(offset) as *mut u32, px); if !SHADOW.is_null() { *SHADOW.add(offset / 4) = px; } } } } } } /// Shift the framebuffer content up `n` text rows and clear the exposed /// bottom rows. `n` batches deferred scrolls into one push. pub(crate) fn shift_up_n(n: usize) { if let Some(fb) = unsafe { FB.as_ref() } { fb.shift_up_n(n); } } // --------------------------------------------------------------------------- // 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"); }}; }