summaryrefslogtreecommitdiffstats
path: root/rsnix/src/gop.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix/src/gop.rs')
-rw-r--r--rsnix/src/gop.rs158
1 files changed, 56 insertions, 102 deletions
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index 90c24bf..d37468f 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -80,8 +80,7 @@ const STAGE_MAX: usize = 96;
80/// The last boot stage string (for the panic band). 80/// The last boot stage string (for the panic band).
81pub fn last_boot_stage() -> alloc::string::String { 81pub fn last_boot_stage() -> alloc::string::String {
82 unsafe { 82 unsafe {
83 alloc::string::String::from_utf8_lossy(&LAST_BOOT_STAGE[..LAST_BOOT_STAGE_LEN]) 83 alloc::string::String::from_utf8_lossy(&LAST_BOOT_STAGE[..LAST_BOOT_STAGE_LEN]).into_owned()
84 .into_owned()
85 } 84 }
86} 85}
87 86
@@ -210,7 +209,7 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
210 let bgr = info.pixel_format == PIXEL_BGR; 209 let bgr = info.pixel_format == PIXEL_BGR;
211 let _ = fg; 210 let _ = fg;
212 211
213 let fb = Framebuffer { 212 let fbf = Framebuffer {
214 base, 213 base,
215 width, 214 width,
216 height, 215 height,
@@ -221,14 +220,8 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
221 bgr, 220 bgr,
222 }; 221 };
223 222
224 // The RAM backbuffer is allocated *after* ExitBootServices by 223 // Re-stash the post-SetMode region (base can move) for the
225 // `init_shadow()` (main.rs) — via the fb-aware page allocator so it 224 // allocators, and log the authoritative geometry for diagnostics.
226 // can never land on the framebuffer (see init_shadow). Nothing is
227 // drawn between fb.clear() here and init_shadow(), so the blank
228 // shadow is in sync with the cleared display.
229
230 // Re-stash the post-SetMode region (base can move) for the page
231 // allocator, and log the authoritative geometry for diagnostics.
232 unsafe { 225 unsafe {
233 FB_REGION_BASE = mode.framebuffer_base as usize; 226 FB_REGION_BASE = mode.framebuffer_base as usize;
234 FB_REGION_SIZE = mode.framebuffer_size; 227 FB_REGION_SIZE = mode.framebuffer_size;
@@ -244,104 +237,65 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
244 bgr 237 bgr
245 ); 238 );
246 239
247 fb.clear(); 240 fbf.clear();
248 241
249 unsafe { 242 unsafe {
250 FB = Some(fb); 243 FB = Some(fbf);
251 } 244 }
252} 245
253 246 // The RAM backbuffer is the *first* heap allocation: allocated here
254/// Allocate the RAM backbuffer. Must run *after* 247 // (pre-ExitBootServices, before vt::init builds its grids) so no
255/// `memory::init_page_allocator` (which excludes the GOP framebuffer 248 // other heap object can collide with it — the v0.5.3 layout that
256/// pages per-page), and before any text is painted. 249 // provably boots on real hardware. The heap free list excludes the
257/// 250 // fb region (init_heap splits at the fb hole) and we additionally
258/// The heap (pre-ExitBootServices) is excluded from the fb region only 251 // reject any range overlapping the fb, so the shadow can never
259/// by a coarse overlap subtraction that still leaves one contiguous 252 // alias the display (an aliased shadow turns every scroll into a
260/// free block *spanning* the fb hole — a 1.9 MB shadow allocation could 253 // 1.8 MB MMIO→MMIO memcpy on an uncached real fb — a wedge).
261/// therefore land *on* the framebuffer on machines where firmware 254 // Allocation failure degrades to no-shadow mode, never panics.
262/// reports it as ConventionalMemory. A shadow aliased over the display
263/// makes every scroll a 1.8 MB MMIO→MMIO memcpy, which can wedge a
264/// real uncached framebuffer (a stall, not a fault — hence no dump).
265/// The page allocator marks those pages used, so the shadow can never
266/// land there. Heap allocation is kept only as a fallback, with an
267/// explicit overlap rejection.
268pub fn init_shadow() {
269 unsafe { 255 unsafe {
270 let Some(fb) = fb().as_ref() else { 256 if let Some(fbinfo) = fb().as_ref() {
271 return; 257 let words = (fbinfo.pitch * fbinfo.height) / 4;
272 }; 258 if words > 0 {
273 let words = (fb.pitch * fb.height) / 4; 259 let bytes = words * 4;
274 if words == 0 { 260 #[allow(clippy::slow_vector_initialization)] // deliberate: try_reserve must not panic
275 return; 261 let mut v: alloc::vec::Vec<u32> = alloc::vec::Vec::new();
276 } 262 if v.try_reserve_exact(words).is_ok() {
277 let bytes = words * 4; 263 v.resize(words, 0); // can't fail after the reserve
278 let pages = bytes.div_ceil(crate::memory::PAGE_SIZE); 264 let p = v.as_ptr() as usize;
279 let mut origin = "none"; 265 let overlaps = FB_REGION_SIZE > 0
280 266 && p < FB_REGION_BASE + FB_REGION_SIZE
281 // Primary: fb-aware page allocator (fb pages already marked 267 && p + bytes > FB_REGION_BASE;
282 // used by init_page_allocator). Defensively refuse the range if 268 if !overlaps {
283 // it ever overlaps the fb region anyway. 269 SHADOW = v.as_ptr() as *mut u32;
284 if let Some(p) = crate::memory::alloc_pages(pages) { 270 SHADOW_WORDS = words;
285 let overlaps = FB_REGION_SIZE > 0 271 for i in 0..words {
286 && p < FB_REGION_BASE + FB_REGION_SIZE 272 *SHADOW.add(i) = fbinfo.bg;
287 && p + bytes > FB_REGION_BASE; 273 }
288 if !overlaps { 274 core::mem::forget(v); // shadow owns the memory now
289 SHADOW = p as *mut u32; 275 }
290 SHADOW_WORDS = words;
291 origin = "page-alloc";
292 for i in 0..words {
293 *SHADOW.add(i) = fb.bg;
294 } 276 }
295 } 277 let (rbase, rsize) = (FB_REGION_BASE, FB_REGION_SIZE);
296 } 278 let (shadow, swords) = (SHADOW as usize, SHADOW_WORDS);
297 if origin == "none" { 279 crate::serial_println!(
298 // Fallback: heap — must never panic here: an OOM would halt 280 "gop: shadow @ {:#x} words={} fb={:#x}..{:#x} origin=heap",
299 // the kernel before the shell banner, a blank screen on real 281 shadow,
300 // hardware (the old `vec![0; words]` could abort). On any 282 swords,
301 // failure we degrade to no-shadow mode instead. 283 rbase,
302 #[allow(clippy::slow_vector_initialization)] // deliberate: try_reserve must not panic 284 rbase + rsize,
303 let mut v: alloc::vec::Vec<u32> = alloc::vec::Vec::new(); 285 );
304 if v.try_reserve_exact(words).is_ok() { 286 if SHADOW.is_null() {
305 v.resize(words, 0); // can't fail after the reserve 287 let msg =
306 let p = v.as_ptr() as usize; 288 "gop: WARNING — shadow rejected (fb overlap); no-shadow fallback";
307 let overlaps = FB_REGION_SIZE > 0 289 crate::serial_println!("{}", msg);
308 && p < FB_REGION_BASE + FB_REGION_SIZE 290 let lines: alloc::vec::Vec<&str> = msg
309 && p + bytes > FB_REGION_BASE; 291 .as_bytes()
310 if !overlaps { 292 .chunks(100)
311 SHADOW = v.as_ptr() as *mut u32; 293 .map(|c| core::str::from_utf8(c).unwrap_or("?"))
312 SHADOW_WORDS = words; 294 .collect();
313 origin = "heap"; 295 dump_lines(&lines);
314 core::mem::forget(v); // shadow owns the memory now
315 } 296 }
316 } 297 }
317 } 298 }
318 let (rbase, rsize) = (FB_REGION_BASE, FB_REGION_SIZE);
319 let (shadow, swords) = (SHADOW as usize, SHADOW_WORDS);
320 crate::serial_println!(
321 "gop: shadow @ {:#x} words={} fb={:#x}..{:#x} origin={}",
322 shadow,
323 swords,
324 rbase,
325 rbase + rsize,
326 origin
327 );
328 // Real hardware has no serial console: surface a degraded shadow
329 // (heap fallback or rejected allocation) as a visible warning
330 // band so a boot on hardware shows it on the screen.
331 if origin != "page-alloc" {
332 let msg = if SHADOW.is_null() {
333 "gop: WARNING — shadow allocation rejected (fb overlap); no-shadow fallback"
334 } else {
335 "gop: WARNING — shadow from heap (page allocator unavailable)"
336 };
337 crate::serial_println!("{}", msg);
338 let lines: alloc::vec::Vec<&str> = msg
339 .as_bytes()
340 .chunks(100)
341 .map(|c| core::str::from_utf8(c).unwrap_or("?"))
342 .collect();
343 dump_lines(&lines);
344 }
345 } 299 }
346} 300}
347 301