diff options
Diffstat (limited to 'rsnix/src/gop.rs')
| -rw-r--r-- | rsnix/src/gop.rs | 114 |
1 files changed, 110 insertions, 4 deletions
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs index dad4505..47a2208 100644 --- a/rsnix/src/gop.rs +++ b/rsnix/src/gop.rs | |||
| @@ -19,12 +19,49 @@ pub struct Framebuffer { | |||
| 19 | height: usize, | 19 | height: usize, |
| 20 | pitch: usize, // bytes per scan line | 20 | pitch: usize, // bytes per scan line |
| 21 | bytes_per_pixel: usize, | 21 | bytes_per_pixel: usize, |
| 22 | /// Size of the firmware's linear framebuffer mapping, in bytes. | ||
| 23 | /// Everything written to the display is clamped to this, so a mode | ||
| 24 | /// whose `pitch * height` exceeds the actual mapping (some real | ||
| 25 | /// firmware pads the scan-line pitch but sizes the buffer for the | ||
| 26 | /// resolution) degrades to a truncated view instead of faulting. | ||
| 27 | framebuffer_size: usize, | ||
| 22 | bg: u32, // packed BGRA or RGBA background colour | 28 | bg: u32, // packed BGRA or RGBA background colour |
| 23 | bgr: bool, | 29 | bgr: bool, |
| 24 | } | 30 | } |
| 25 | 31 | ||
| 26 | static mut FB: Option<Framebuffer> = None; | 32 | static mut FB: Option<Framebuffer> = None; |
| 27 | 33 | ||
| 34 | /// The GOP framebuffer region (base + mapping size), stashed before the | ||
| 35 | /// memory map is captured so the heap and page allocator can exclude it. | ||
| 36 | /// Some real-world firmware lists the framebuffer's memory as | ||
| 37 | /// `EfiConventionalMemory`; handing those pages out would let kernel | ||
| 38 | /// allocations (stacks, DMA buffers) overwrite the display. | ||
| 39 | static mut FB_REGION_BASE: usize = 0; | ||
| 40 | static mut FB_REGION_SIZE: usize = 0; | ||
| 41 | |||
| 42 | /// Stash the current GOP framebuffer region *before* the memory map is | ||
| 43 | /// captured (must run before `memory::capture_memory_map`). Updated | ||
| 44 | /// again after `SetMode` in `init` — the base can move. | ||
| 45 | pub fn peek_fb_region(gop: &mut GraphicsOutputProtocol) { | ||
| 46 | unsafe { | ||
| 47 | let mode = &*gop.mode; | ||
| 48 | FB_REGION_BASE = mode.framebuffer_base as usize; | ||
| 49 | FB_REGION_SIZE = mode.framebuffer_size; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | /// The framebuffer region currently excluded from the allocators | ||
| 54 | /// (base, size in bytes), if any. | ||
| 55 | pub fn framebuffer_region() -> Option<(usize, usize)> { | ||
| 56 | unsafe { | ||
| 57 | if FB_REGION_SIZE == 0 { | ||
| 58 | None | ||
| 59 | } else { | ||
| 60 | Some((FB_REGION_BASE, FB_REGION_SIZE)) | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 28 | /// Software backbuffer: a RAM copy of the whole framebuffer (same pitch | 65 | /// Software backbuffer: a RAM copy of the whole framebuffer (same pitch |
| 29 | /// layout, u32 pixels). | 66 | /// layout, u32 pixels). |
| 30 | /// | 67 | /// |
| @@ -147,6 +184,7 @@ pub fn init(gop: &mut GraphicsOutputProtocol) { | |||
| 147 | height, | 184 | height, |
| 148 | pitch, | 185 | pitch, |
| 149 | bytes_per_pixel: bpp, | 186 | bytes_per_pixel: bpp, |
| 187 | framebuffer_size: mode.framebuffer_size, | ||
| 150 | bg, | 188 | bg, |
| 151 | bgr, | 189 | bgr, |
| 152 | }; | 190 | }; |
| @@ -162,6 +200,23 @@ pub fn init(gop: &mut GraphicsOutputProtocol) { | |||
| 162 | } | 200 | } |
| 163 | } | 201 | } |
| 164 | 202 | ||
| 203 | // Re-stash the post-SetMode region (base can move) for the page | ||
| 204 | // allocator, and log the authoritative geometry for diagnostics. | ||
| 205 | unsafe { | ||
| 206 | FB_REGION_BASE = mode.framebuffer_base as usize; | ||
| 207 | FB_REGION_SIZE = mode.framebuffer_size; | ||
| 208 | } | ||
| 209 | crate::serial_println!( | ||
| 210 | "gop: fb base={:#x} size={:#x} pitch={:#x} {}x{} bpp={} bgr={}", | ||
| 211 | base as usize, | ||
| 212 | { mode.framebuffer_size }, | ||
| 213 | pitch, | ||
| 214 | width, | ||
| 215 | height, | ||
| 216 | bpp, | ||
| 217 | bgr | ||
| 218 | ); | ||
| 219 | |||
| 165 | fb.clear(); | 220 | fb.clear(); |
| 166 | 221 | ||
| 167 | unsafe { | 222 | unsafe { |
| @@ -231,10 +286,17 @@ pub(crate) fn push_rect(x: usize, y: usize, w: usize, h: usize) { | |||
| 231 | let w = w.min(fb.width - x); | 286 | let w = w.min(fb.width - x); |
| 232 | let h = h.min(fb.height - y); | 287 | let h = h.min(fb.height - y); |
| 233 | let row_words = fb.pitch / 4; | 288 | let row_words = fb.pitch / 4; |
| 289 | // Words of the firmware's actual display mapping — per-row clamp so a | ||
| 290 | // pitch-padded mode never writes past the buffer. | ||
| 291 | let fb_words = fb.framebuffer_size / 4; | ||
| 234 | unsafe { | 292 | unsafe { |
| 235 | for dy in 0..h { | 293 | for dy in 0..h { |
| 236 | let off = (y + dy) * row_words + x; | 294 | let off = (y + dy) * row_words + x; |
| 237 | ptr::copy_nonoverlapping(SHADOW.add(off), fb.base.add(off * 4) as *mut u32, w); | 295 | let row_w = w.min(fb_words.saturating_sub(off)); |
| 296 | if row_w == 0 { | ||
| 297 | continue; | ||
| 298 | } | ||
| 299 | ptr::copy_nonoverlapping(SHADOW.add(off), fb.base.add(off * 4) as *mut u32, row_w); | ||
| 238 | } | 300 | } |
| 239 | } | 301 | } |
| 240 | overlay_cursor(x, y, w, h); | 302 | overlay_cursor(x, y, w, h); |
| @@ -275,6 +337,39 @@ pub fn fill_rect(x: usize, y: usize, w: usize, h: usize, color: u32) { | |||
| 275 | push_rect(x, y, w, h); | 337 | push_rect(x, y, w, h); |
| 276 | } | 338 | } |
| 277 | 339 | ||
| 340 | /// Print crash/fault lines directly to the framebuffer, bypassing the vt | ||
| 341 | /// entirely (the vt state may be corrupt when an exception fires). | ||
| 342 | /// | ||
| 343 | /// White-on-red band at the top of the screen, written through the same | ||
| 344 | /// clamped glyph path — safe even if the fault was inside the vt/gop | ||
| 345 | /// machinery, and visible on real hardware with no serial console. | ||
| 346 | pub fn dump_lines(lines: &[&str]) { | ||
| 347 | let Some(fb) = fb().as_ref() else { | ||
| 348 | return; | ||
| 349 | }; | ||
| 350 | let cols = fb.width / 8; | ||
| 351 | let rows = fb.height / 16; | ||
| 352 | let red = (0xC0u8, 0x00u8, 0x00u8); | ||
| 353 | let white = (0xFFu8, 0xFFu8, 0xFFu8); | ||
| 354 | let band = lines.len().min(rows); | ||
| 355 | // Red band behind the text. | ||
| 356 | if cols > 0 && band > 0 { | ||
| 357 | fill_rect(0, 0, cols * 8, band * 16, rgb(red.0, red.1, red.2)); | ||
| 358 | } | ||
| 359 | for (i, line) in lines.iter().enumerate().take(band) { | ||
| 360 | let mut c = 0; | ||
| 361 | for &b in line.as_bytes() { | ||
| 362 | if c >= cols { | ||
| 363 | break; | ||
| 364 | } | ||
| 365 | if b < 0x80 { | ||
| 366 | blit_glyph(c, i, b as u16, white, red); | ||
| 367 | c += 1; | ||
| 368 | } | ||
| 369 | } | ||
| 370 | } | ||
| 371 | } | ||
| 372 | |||
| 278 | /// Read one pixel back from the shadow (RAM; cheap). Test/demo use only. | 373 | /// Read one pixel back from the shadow (RAM; cheap). Test/demo use only. |
| 279 | pub fn shadow_pixel(x: usize, y: usize) -> Option<u32> { | 374 | pub fn shadow_pixel(x: usize, y: usize) -> Option<u32> { |
| 280 | let fb = fb().as_ref()?; | 375 | let fb = fb().as_ref()?; |
| @@ -433,7 +528,10 @@ fn draw_bmp(x: usize, y: usize, color: u32) { | |||
| 433 | } | 528 | } |
| 434 | if row & (1u16 << (11 - dx)) != 0 { | 529 | if row & (1u16 << (11 - dx)) != 0 { |
| 435 | let off = yy * row_words + xx; | 530 | let off = yy * row_words + xx; |
| 436 | ptr::write_volatile(fb.base.add(off * 4) as *mut u32, color); | 531 | // Clamp to the display mapping (defensive). |
| 532 | if off < fb.framebuffer_size / 4 { | ||
| 533 | ptr::write_volatile(fb.base.add(off * 4) as *mut u32, color); | ||
| 534 | } | ||
| 437 | } | 535 | } |
| 438 | } | 536 | } |
| 439 | } | 537 | } |
| @@ -453,7 +551,10 @@ pub fn rgb(r: u8, g: u8, b: u8) -> u32 { | |||
| 453 | // --------------------------------------------------------------------------- | 551 | // --------------------------------------------------------------------------- |
| 454 | impl Framebuffer { | 552 | impl Framebuffer { |
| 455 | fn clear(&self) { | 553 | fn clear(&self) { |
| 456 | let size = self.pitch * self.height; | 554 | // Clamp the display write to the firmware's actual mapping — some |
| 555 | // real GPUs size the buffer for the resolution while the pitch | ||
| 556 | // (with scan-line padding) is larger. | ||
| 557 | let size = (self.pitch * self.height).min(self.framebuffer_size); | ||
| 457 | // Fill with background colour (32-bit writes), display + shadow. | 558 | // Fill with background colour (32-bit writes), display + shadow. |
| 458 | let mut p = self.base; | 559 | let mut p = self.base; |
| 459 | let end = unsafe { p.add(size) }; | 560 | let end = unsafe { p.add(size) }; |
| @@ -511,7 +612,12 @@ impl Framebuffer { | |||
| 511 | // A bulk copy (rep movsb) writes in 64-byte bursts that | 612 | // A bulk copy (rep movsb) writes in 64-byte bursts that |
| 512 | // coalesce well on WC framebuffers — far fewer bus | 613 | // coalesce well on WC framebuffers — far fewer bus |
| 513 | // transactions than per-pixel stores. | 614 | // transactions than per-pixel stores. |
| 514 | ptr::copy_nonoverlapping(SHADOW, self.base as *mut u32, words); | 615 | // |
| 616 | // Clamp to the firmware's mapping size; a pitch-padded mode | ||
| 617 | // whose `pitch * height` exceeds the buffer degrades to a | ||
| 618 | // truncated view instead of faulting past the mapping. | ||
| 619 | let push_words = words.min(self.framebuffer_size / 4); | ||
| 620 | ptr::copy_nonoverlapping(SHADOW, self.base as *mut u32, push_words); | ||
| 515 | // The scroll copied the shadow (which never contains the | 621 | // The scroll copied the shadow (which never contains the |
| 516 | // cursor); re-composite it on top of the pushed frame. | 622 | // cursor); re-composite it on top of the pushed frame. |
| 517 | crate::gop::cursor_redraw(); | 623 | crate::gop::cursor_redraw(); |
