display: framebuffer handoff primitive + service design (D1)

Kick off the display service track (docs/display.md, docs/display-plan.md): a
user-space compositor that owns the framebuffer. GOP and the PCI display device
are two views of one controller; GOP dies at ExitBootServices, so the portable
base is the boot-handoff linear framebuffer.

D1 makes that framebuffer reachable from user space over the existing device
claim/mmio_map path rather than a bespoke syscall:

- device-abi: a `display` DeviceClass, a DisplayInfo{w,h,pitch,format} on the
  descriptor, and a flags field on resources with a write-combining bit.
- devices-broker: seedDisplay() publishes the loader's framebuffer as a
  root-level `display` node (one WC-flagged memory resource); kmain seeds it
  after discovery. displayDevice()/displayClaimed() track the claim.
- paging/mmio_map: mapUserDeviceInto gains a write_combining bool — a WC-flagged
  resource maps through PAT entry 4 instead of strong-uncacheable (an
  uncacheable framebuffer blit is glacial).
- console: falls silent while a display service holds the framebuffer, and is
  forced back on by the panic/exception paths.

Gate: the `display` kernel test asserts the seeded node's shape and that the
claim + mmio_map leaf is genuinely write-combining (PAT bit set, PCD/PWT clear).
Regression-checked discovery/ioport/claim-release/supervision/device-list/
device-manager with the +1 device in the table.
This commit is contained in:
Daniel Samson
2026-07-14 00:54:56 +01:00
parent f157a93c9c
commit cd812cc00e
12 changed files with 663 additions and 17 deletions
+11
View File
@@ -195,6 +195,13 @@ fn kmain(boot_information: *const BootInformation) noreturn {
log.print("/system/kernel: WARNING {d} device(s) dropped — table full\n", .{devices_broker.dropped});
}
// Publish the loader's framebuffer as a claimable `display` device, so a
// user-space display service can take it over the same claim + mmio_map path as
// any other hardware (it is not firmware-discovered; it rides the boot handoff).
if (devices_broker.seedDisplay(fb.base, fb.width, fb.height, fb.pitch, @intFromEnum(fb.format))) |display_id| {
log.print("/system/kernel: framebuffer device {d} seeded ({d}x{d}, pitch {d}, write-combining)\n", .{ display_id, fb.width, fb.height, fb.pitch });
}
// Install the device-IRQ trampolines, so a driver's irq_bind has vectors to
// land on. Every line stays masked until something binds it (ioapic.init).
irq.init();
@@ -479,6 +486,9 @@ fn onException(state: *const architecture.CpuState) noreturn {
}
log.checkpoint(cp_exception);
// The machine is going down: force the console back on even if a display service
// was holding the framebuffer, so the exception actually reaches the screen.
console.setSuppressed(false);
const core = scheduler.currentCpuIndex();
// A fault is user-facing enough to paint on screen too (via statusPrint), on
// top of the diagnostic log.
@@ -501,6 +511,7 @@ pub const panic = std.debug.FullPanic(struct {
_ = first_trace_address;
log.checkpoint(cp_panic);
log.recordPanic(message);
console.setSuppressed(false); // a panic outranks any display service holding the screen
status("\nKERNEL PANIC: ");
status(message);
status("\n");