diff options
Diffstat (limited to 'rsnix')
| -rw-r--r-- | rsnix/src/gop.rs | 158 | ||||
| -rw-r--r-- | rsnix/src/memory.rs | 37 |
2 files changed, 82 insertions, 113 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). |
| 81 | pub fn last_boot_stage() -> alloc::string::String { | 81 | pub 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. | ||
| 268 | pub 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 | ||
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs index a947c53..605125a 100644 --- a/rsnix/src/memory.rs +++ b/rsnix/src/memory.rs | |||
| @@ -366,18 +366,33 @@ fn init_heap() { | |||
| 366 | if d.kind == MEMORY_TYPE_CONVENTIONAL { | 366 | if d.kind == MEMORY_TYPE_CONVENTIONAL { |
| 367 | let s = d.physical_start as usize; | 367 | let s = d.physical_start as usize; |
| 368 | let e = s + d.number_of_pages as usize * 4096; | 368 | let e = s + d.number_of_pages as usize * 4096; |
| 369 | // Subtract any overlap with the framebuffer region. | 369 | // Split at the framebuffer hole so the free list NEVER |
| 370 | let (cs, ce) = match fb { | 370 | // contains fb pages: a coarse size subtraction leaves one |
| 371 | Some((b, sz)) if e > b && s < b + sz => (s.max(b), e.min(b + sz)), | 371 | // block *spanning* the hole, letting a large allocation |
| 372 | _ => (e, s), // no overlap → empty range | 372 | // (e.g. the 1.9 MB display shadow) straddle the display. |
| 373 | }; | 373 | let mut lo = s; |
| 374 | let mut sz = e - s; | 374 | if let Some((b, sz)) = fb |
| 375 | if ce > cs { | 375 | && e > b |
| 376 | sz -= ce - cs; | 376 | && s < b + sz |
| 377 | { | ||
| 378 | let h0 = b.max(s); | ||
| 379 | let h1 = (b + sz).min(e); | ||
| 380 | if h0 > lo { | ||
| 381 | // Segment before the hole. | ||
| 382 | let seg = h0 - lo; | ||
| 383 | if seg > best_size { | ||
| 384 | best_start = lo; | ||
| 385 | best_size = seg; | ||
| 386 | } | ||
| 387 | } | ||
| 388 | lo = h1; | ||
| 377 | } | 389 | } |
| 378 | if sz > best_size { | 390 | if e > lo { |
| 379 | best_start = s; | 391 | let seg = e - lo; |
| 380 | best_size = sz; | 392 | if seg > best_size { |
| 393 | best_start = lo; | ||
| 394 | best_size = seg; | ||
| 395 | } | ||
| 381 | } | 396 | } |
| 382 | } | 397 | } |
| 383 | } | 398 | } |
