7.1 KiB
The display service v2: a pluggable scanout backend
v1 (display.md) is a compositor that owns the GOP framebuffer — it composites a layer stack into a cacheable back buffer and streams damage to the linear framebuffer the firmware handed over. That path is portable and good: it drives any GPU, including a real NVIDIA card at an ultrawide's native resolution, with zero GPU-specific code. v2 keeps it as the floor and makes scanout — how a finished frame reaches the panel — a pluggable backend, so the compositor can upgrade to a real GPU driver when one is present and fall back to the framebuffer when it isn't.
The compositor itself (layers, back buffer, damage) does not change. Only the last step — "put this frame on screen" — becomes swappable.
The shape
compositor (display service) ── layer stack + back buffer + damage (unchanged)
│ composites a frame, then: backend.present(damage)
▼
scanout backend (selected at runtime — GOP by default, native when it appears)
│
├─ GopBackend the v1 path: WC copy back→front to the firmware LFB.
│ Always available. No mode-set, no vsync. THE FLOOR.
│
└─ VirtioGpuBackend talks to a virtio-gpu driver process over a `scanout`
service: present via a shared resource + flush (real vsync),
EDID mode list, runtime mode-set.
A backend is a small interface the compositor calls:
surface()→ the pixels to compose into and their geometry{ptr, pitch, format, w, h}(the LFB for GOP; a shared scanout resource for virtio-gpu),present(damage: Rect)→ make the damaged region visible (a no-op-ish WC copy for GOP; a virtio flush, optionally vsync-fenced, for the native path),- capability queries —
canModeSet,hasVsync— and, when supported,modes()/setMode(m).
The compositor composes into surface() and calls present(damage) exactly as it does
today; everything device-specific lives behind the interface.
Selection and hot-attach
The choice is dynamic, because a GPU driver is spawned asynchronously (the device manager brings it up after boot), and because danos is meant to be resilient:
- Boot on GOP. The compositor starts on
GopBackendimmediately, so there is never a blank screen while drivers load — the exact v1 behaviour. - Upgrade on announce. When the virtio-gpu driver has claimed its device and set up a
scanout, it announces itself to the display service (a
push: the driver looks up.displayand sends an attach-scanout message carrying itsscanoutendpoint as a capability). The compositor switches toVirtioGpuBackendand re-presents the current frame full-screen. Push beats polling — the compositor doesn't know a priori which driver, if any, exists, and danos has no service-registration pub/sub. - Native is restartable, not fallback-on-crash. Once a native driver has reprogrammed the device, the firmware's GOP framebuffer is stale — "native → GOP" is not a clean fall-back. So a native driver that crashes is restarted by its supervisor (the resilience work already merged), re-announces, and the compositor re-attaches (native → native). The screen freezes on the last frame during the gap — acceptable.
- GOP is the floor for "no driver was ever there." On a real GPU (NVIDIA/AMD/Intel)
the class-0x03 device matches nothing in the driver table, no
scanoutis ever announced, and the compositor stays on GOP forever — no special-casing. Only if a native driver permanently gives up (crash-loop cap) does the compositor attempt GOP again, and even then only if the LFB is still mappable.
The shared-memory primitive this needs
virtio-gpu's scanout resource is guest RAM — the driver allocates it and attaches it to a virtio resource, and the compositor composes into it. That means the compositor writing into the driver's buffer is cross-process memory sharing, the primitive v1 deferred (docs/display.md, "What v1 does not do"). v2 builds it: the natural generalization of M13 capability-passing from endpoints to memory objects —
shm_create(len) -> {handle, vaddr} // a shareable, page-aligned RAM region
… pass `handle` as the send_cap on an ipc_call …
shm_map(cap) -> vaddr // the receiver maps the same physical pages
The payoff is leverage: the same primitive unlocks both native GPU drivers and client-rendered surfaces (an app composing its own bitmap and handing the compositor a reference instead of drawing by command). One piece of kernel work, two features.
The virtio-gpu driver
A new ring-3 driver process (the topology v1 anticipated — "split the driver from the compositor when a second backend arrives"). It claims the virtio-gpu PCI function, and:
- sets up the virtqueues (control + cursor) and the device's config space,
- creates a 2D scanout resource backed by an
shmregion,attach_backings it,set_scanouts it to a CRTC, andresource_flushes damaged rectangles, - reads EDID (the
GET_EDIDcontrol command) for the mode list, andset_scanoutat a chosen mode for runtime mode-setting, - registers a
scanoutservice and announces to the display service.
Its resource_flush is the real present — and gives a genuine vsync/tear-free
path a dumb GOP framebuffer can't.
What v2 unlocks — and its honest scope
Behind the abstraction, a native backend gives runtime mode-setting (resolution / refresh / bpp), EDID enumeration, and vsync. But only on devices we have a driver for — realistically VMs (virtio-gpu, and later maybe Bochs DISPI). Real discrete GPUs need per-vendor KMS-class drivers that aren't getting written, so they stay on GOP — which is genuinely fine (v1 on the NVIDIA box is smooth). So v2's real value is twofold: the pluggable architecture (a driver slots in when one exists) and a rich, vsync'd path in VMs, where danos development happens. The framebuffer floor never goes away.
Locked decisions
- First native backend: virtio-gpu — the VM standard; gives mode-set + a real
present/flush (and vsync), and exercises the whole pluggable design. Tested with QEMU
-device virtio-gpu. - Dynamic hot-attach — boot on GOP, upgrade to native on the driver's announce, re-attach across driver restarts; GOP is the floor for "no driver ever," not a live fall-back after a reprogram.
- Detection = push (the driver announces to
.display), not compositor polling. - v2 builds the
shmcapability (endpoints → memory objects), shared with the future client-surface path.
See also
- display.md — v1: the compositor, the GOP-vs-device split, the WC discipline.
- display-v2-plan.md — the ordered build-out.
- driver-model.md — claim /
mmio_map/ MSI / capability passing (M13). - resilience.md — the restart machinery the hot-attach leans on.