summaryrefslogtreecommitdiffstats
path: root/src/gop.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/gop.rs')
-rw-r--r--src/gop.rs232
1 files changed, 232 insertions, 0 deletions
diff --git a/src/gop.rs b/src/gop.rs
new file mode 100644
index 0000000..9645bb0
--- /dev/null
+++ b/src/gop.rs
@@ -0,0 +1,232 @@
1/// GOP framebuffer driver — renders text via a bitmap font.
2use crate::font;
3use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB};
4use core::fmt;
5use core::ptr;
6
7/// Framebuffer state captured before ExitBootServices.
8pub 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
23static mut FB: Option<Framebuffer> = None;
24
25/// Initialise the framebuffer from GOP. Call *before* ExitBootServices.
26pub 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.
67pub 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.
121pub 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).
128pub 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// ---------------------------------------------------------------------------
144impl 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// ---------------------------------------------------------------------------
207pub struct GopWriter;
208
209impl 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]
218macro_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]
226macro_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}