07 · Graphics — DRM/KMS & Wayland¶
Fbdev is gone from any modern embedded product with a display. The kernel side is DRM/KMS (Direct Rendering Manager / Kernel Mode Setting); the userspace side that composites and hands buffers to it is almost always a Wayland compositor now, not X11. This module is about the plumbing between a GPU driver and a pixel actually appearing on a panel — where things go wrong, they go wrong at that boundary.
The DRM/KMS object model¶
Four objects, always in this relationship:
CRTC ──drives──▶ Encoder ──drives──▶ Connector ──▶ physical output
│
uses a
│
Plane (framebuffer source: primary / overlay / cursor)
- Connector: the physical thing (HDMI port, MIPI-DSI panel, eDP). Carries EDID and hotplug status.
- Encoder: converts a CRTC's pixel stream to the connector's signal format (TMDS for HDMI, DSI lanes for a panel).
- CRTC: scans out a framebuffer at a mode (resolution/refresh), reading from one or more planes.
- Plane: an actual buffer source — primary (fullscreen), overlay (e.g. video), cursor.
Inspect the live topology with modetest (from libdrm-tests) before
touching any code:
$ modetest -M imx-drm -c
Connectors:
id encoder status name size (mm) modes encoders
34 33 connected DSI-1 154x86 1 33
modes:
name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot)
1920x1080 60.00 1920 1968 2000 2200 1080 1083 1088 1125 flags: nhsync, nvsync; type: preferred, driver
status disconnected on a panel physically attached almost always means
either the DSI lane count/timing in the device tree doesn't match the
panel's actual EDID/datasheet timings, or the panel driver never bound —
check dmesg for the panel's compatible string before assuming a wiring
fault.
The panel device tree node¶
&mipi_dsi {
status = "okay";
panel@0 {
compatible = "acme,dsi-panel-1080p";
reg = <0>;
reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
port {
panel_in: endpoint {
remote-endpoint = <&dsi_out>;
};
};
};
};
The port/endpoint/remote-endpoint pattern — the DT graph binding
— is how DRM components describe their pixel-data connections separately
from the bus they're probed on; a mismatched or missing
remote-endpoint phandle is a very common reason a panel driver probes
successfully (its compatible matched) but never gets linked into the
DRM pipeline, so nothing ever lights up despite no error anywhere in
dmesg.
KMS atomic modesetting¶
Legacy KMS set one property at a time, with intermediate invalid states possible. Atomic KMS validates an entire desired state — CRTC mode, plane buffer, connector — as one transaction, either fully applied or rejected:
drmModeAtomicReq *req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, plane_id, prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_id, crtc_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_x, 0);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_y, 0);
int ret = drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
if (ret)
fprintf(stderr, "atomic commit failed: %s\n", strerror(-ret));
drmModeAtomicFree(req);
A rejected atomic commit returns a clean errno rather than leaving the
display in a half-configured state — this is the entire point over legacy
KMS, where a failed intermediate drmModeSetCrtc call could leave a mode
set but no framebuffer bound, producing a black or garbage screen with no
single failing call to point at.
Wayland: who owns the screen now¶
X11 let any client draw anywhere and read any other client's pixels — a security and architecture mismatch for embedded kiosk/HMI products. Wayland's model: the compositor is the only DRM/KMS client; every application is a Wayland client that hands the compositor buffers to composite, never touching KMS directly.
┌────────────┐ wl_surface + buffer ┌───────────────┐ DRM/KMS
│ App (client)│ ───────────────────────▶│ Compositor │──atomic──▶ display
│ (e.g. Qt, │◀── input events ─────────│ (weston, etc.) │ commit
│ GTK app) │ └───────────────┘
└────────────┘
For embedded single-app kiosk products, a minimal compositor (weston
with a single fullscreen client, or a custom libweston-based shell) is
common — running a full desktop compositor stack on a resource-constrained
SoC is usually unnecessary weight.
Trap: two processes racing to become the DRM master (drmSetMaster)
— usually a stray X server or a debug tool like modetest left running
— causes the compositor's own drmModeSetCrtc/atomic calls to fail with
EACCES or EBUSY, and the resulting symptom ("compositor won't start,
no clear error") gets misdiagnosed as a driver bug far more often than
the actual cause (something else holding DRM master).
Tearing, vsync, and the buffer handoff¶
A client that renders and swaps buffers faster than the display's refresh
without waiting for the compositor's frame callback either tears (if
directly scanning out) or simply wastes power rendering frames that never
display (composited case). The correct client-side pattern under Wayland
is to render only after the compositor's frame callback fires:
static void frame_done(void *data, struct wl_callback *cb, uint32_t time)
{
wl_callback_destroy(cb);
render_next_frame();
struct wl_callback *next = wl_surface_frame(surface);
wl_callback_add_listener(next, &frame_listener, NULL);
wl_surface_commit(surface);
}
Rendering on a free-running timer instead of this callback is a common cause of "smooth in isolation, stutters once a second client is running" — you're now competing for compositor time with no back-pressure signal.
Traps¶
- DSI lane/timing mismatch between the device tree and the panel datasheet — the panel may appear to init (backlight comes on) while showing garbage or a shifted image, because timing is close enough to clock but not to align correctly.
- Overlay planes with format/scaling limits the SoC's blitter can't meet — requesting a plane format or scale factor the hardware doesn't support silently falls back to a slow software composite path on some drivers, showing up only as "why is the CPU pegged during video playback," never as an explicit error.
- Holding DRM master in a debug tool left running after use —
modetest,kmscube, or a stray X server holds it until killed, and the compositor's failure to acquire master looks exactly like a broken driver.
Cheat sheet¶
| Concept | Purpose |
|---|---|
| Connector / Encoder / CRTC / Plane | DRM/KMS object hierarchy |
modetest -M <driver> -c |
Inspect live connector/mode topology |
DT port/endpoint/remote-endpoint |
Graph binding linking DRM components |
drmModeAtomicCommit |
Validate-then-apply an entire display state at once |
wl_surface_frame callback |
Correct pacing signal for a Wayland client |
drmSetMaster/EBUSY |
Symptom of two processes fighting over DRM master |
On verification
DRM/KMS object model, atomic modesetting semantics, and the Wayland
frame-callback pattern were checked against documented DRM/libdrm and
Wayland protocol behavior; no GPU driver or display pipeline was
exercised on this machine — modetest/drmModeAtomicCommit output
shown is representative, not captured from real hardware.
How It Actually Works¶
DRM/KMS models a display pipeline as a graph of kernel objects a
compositor wires together, not a framebuffer it just writes pixels
into. CRTC, encoder, connector, and plane objects each
correspond to real hardware blocks (a scanout engine, a display-
interface serializer, a physical port, a compositing layer) exposed
through the DRM ioctl API and /sys/class/drm. An atomic modeset
(DRM_IOCTL_MODE_ATOMIC) submits a full proposed graph state — which
plane feeds which CRTC, at what mode — and the kernel's atomic-check
path validates the entire configuration against hardware constraints
before committing any of it, then commits every object's state together,
typically synced to vblank. This all-or-nothing validate-then-commit
design is precisely what eliminates the tearing/half-applied-mode glitches
that plagued the older non-atomic KMS API, which let you set properties
one ioctl at a time with no consistency check across them.
Wayland's compositor owns the whole scanout path, which is the
actual difference from X11. In X11, the X server itself was
effectively the DRM client, and applications drew into shared memory the
server later composited from a separate process. A Wayland compositor
is the sole DRM client — it does the atomic modeset directly, and
each client renders into its own buffer (via EGL/GBM, backed by DMA-BUF
handles) that it hands to the compositor over the Wayland protocol's
wl_surface/wl_buffer messages; the compositor then either GPU-
composites those buffers together or, in the direct-scanout case, hands
a client's buffer straight to a hardware plane with zero copies. That's
why a fullscreen Wayland client can bypass compositing entirely (a real
hardware plane swap) in a way an X11 app fundamentally couldn't — there's
no intermediary process's compositing step required by the protocol.
Exercise¶
(1) Given a panel that shows a correctly-timed but half-width, doubled
image, diagnose which DT property (lane count vs pixel clock vs timing
values) is the likely cause and explain your reasoning. (2) Write the
atomic-commit property list needed to move a fullscreen primary plane's
framebuffer without touching the CRTC mode, and explain why atomic commit
makes this safer than the legacy drmModeSetCrtc equivalent. (3) One
paragraph: your kiosk app renders on a free-running 60 Hz timer instead of
the compositor's frame callback, and testers report occasional stutter
only when a second background client starts. Explain the mechanism and
the fix.