display: pace the frame clock by the panel's EDID refresh rate

Both EDID moments the system has are now captured and carried to the
compositor's frame clock:

- EFI: the loader derives refresh from the preferred detailed timing
  (pixel clock / total pixels) while GOP is still alive — the only moment
  it is readable — and hands it through the boot handoff into the
  display0 node's DisplayInfo (new refresh_hz field, 0 = unknown).
- GPU: the virtio-gpu driver derives the same figure from its own EDID
  read and carries it in the attach_scanout announce (request.y).

updateFrameClock() re-derives the interval from the active backend's
info at bring-up and again on every backend change — the boot
framebuffer's clock dies with the GOP floor at upgrade, replaced by the
GPU's rate. Unknown rate defaults to 60 Hz; the result is clamped to
[30, 120] Hz so a mis-parsed EDID can neither starve nor flood the
compositor. Rate only, never phase: without vblank, presents still
free-run (docs/display-v2.md, 'Fenced is not vsync').

Observed in QEMU: OVMF exposes no EDID for the VGA adapter, so the GOP
floor logs 'frame clock 62 Hz (default)' (real firmware does expose it);
the virtio-gpu EDID advertises 75 Hz and the upgrade logs 'frame clock
76 Hz (panel EDID)'. The display kernel test asserts refresh_hz rides
the seeded node; all 7 display QEMU cases pass.
This commit is contained in:
Daniel Samson
2026-07-21 11:37:29 +01:00
parent 4f02f75602
commit ec6e888076
10 changed files with 87 additions and 25 deletions
+7 -5
View File
@@ -24,11 +24,13 @@ pub const Operation = enum(u32) {
damage = 6,
/// present(): composite the dirty layers and flush to the screen.
present = 7,
/// attach_scanout(x=stride, width, height, colour=format) + <surface capability>: a native
/// scanout driver announces itself, handing over the shared scanout surface as an `ipc_call`
/// send_cap. The compositor maps it, looks up the driver's `.scanout` present channel, and
/// upgrades off the GOP floor (docs/display-v2.md V4). `x` is the surface's row stride in
/// pixels, `colour` the DisplayFormat.
/// attach_scanout(x=stride, y=refresh_hz, width, height, colour=format) + <surface
/// capability>: a native scanout driver announces itself, handing over the shared scanout
/// surface as an `ipc_call` send_cap. The compositor maps it, looks up the driver's
/// `.scanout` present channel, and upgrades off the GOP floor (docs/display-v2.md V4).
/// `x` is the surface's row stride in pixels, `y` the panel refresh rate from the
/// driver's EDID read (0 = unknown; paces the compositor's frame clock), `colour` the
/// DisplayFormat.
attach_scanout = 8,
/// set_mode(width, height): change the display resolution — only a native backend that
/// reports `canModeSet` honours it; on the GOP floor it fails (docs/display-v2.md V5).