Skip to content
jwinarskePublic

About

Rust port of drm-cxx: a layered Linux DRM/KMS display framework. Behavioral-parity port; work in progress.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

drmkit

A Rust port of drm-cxx — a layered Linux DRM/KMS display framework covering modesetting, plane allocation, scene composition, and scanout.

ci vkms

Status: phases 0–4 met, phase 5 (the v0.1.0-minimal ship gate) under way. The 0.1 releases ship ahead of that gate's last item, the smoke run on RK3588 and VisionFive 2 boards: 0.1.1 is current, on crates.io and docs.rs, and CHANGELOG.md says what is in each. Twenty-one published crates and a test kit; 1,037 unit and integration tests plus 29 doctests on the host, 221 more that need a card, and the host suite again under qemu on the aarch64 and riscv64 cross lanes. All six INVARIANTS.md contracts are pinned against a real device. A scene can build a frame, allocate planes, commit it, composite what it cannot place, and hand buffers back on the right vblank; EDID, color management, the cursor and capture paths are in and tested.

The present spine, cross-output orchestration, GBM surface sources and client-side decorations have since landed. The port matches drm-cxx v5.0.0 (2b260d0): of the 108 test files upstream has there, 75 are ported, 14 partial and 3 not applicable. Each of the 16 pending waits on hardware or a library this tree does not have: a working GPU render node, the vgem, vivid or vicodec modules, NVIDIA silicon for EGL streams, or the GL, Vulkan, V4L2 and GStreamer producers that sit on top of them. TEST_PARITY.md records which, per file, and docs/parity-findings.md records the three findings still open: two wait on hardware (P-30, P-14), and P-42 is by design. See plan.md for the full porting plan.

It has run on more than vkms. amdgpu and vc4 (Raspberry Pi 5) by hand, a rockchip VOP2 for the case that needed it, the SA8155P's display controller for the scene and GBM suites, and — for the assumptions no lab can settle — the drmdb corpus of ~1,200 real devices across 50 drivers, replayed through this tree's own capability logic. Several findings here came from hardware nobody owns.

Phase gates

Phase 0 — met. Workspace, pinned toolchain, cross fragments, all CI lanes green, drift linters negative-tested, and the dependency gap list decided (docs/phase0-spikes.md). The one item left open then — a trivial modeset under vkms — is now met by Phase 1: drmkit-core and drmkit-dumb allocate, map, and register framebuffers against the vkms card in the lane on every push.

Phase 1 — met. Its one carry-over closed in phase 4.

Item State
drmkit-core, -modeset, -dumb, -sync, -fmt done
Ported unit tests for these subsystems pass done — 10 upstream files ported, 1 partial
Invariant 3 pinned (PageFlip::dispatch) done
Invariant 6 pinned (Buffer::map) done
Fuzz target seeded for IN_FORMATS done — ~1.5M execs per CI run
Fuzz target seeded for EDID-adjacent parsers done in phase 4 — edid_blob, once the parser it targets existed

The EDID parser landed with drmkit-display in phase 4 and the target was seeded then, closing phase 1's one carry-over.

Phase 2 — met.

Item State
PlaneRegistry::from_capabilities first done — every allocator test runs on synthetic fixtures, no hardware
Matching, allocator, warm-start + stability bonus done
Invariant 4 hash-classification table done — an exhaustive match, so an unclassified property does not compile
Externally-bound exclusion hooks done — is_externally_bound / is_pinned skip placement
test_matching, test_plane_allocator, test_layer, test_output ported done
Proptest: cardinality-first done — against a brute-force oracle
Proptest: stability bonus never reduces placements done

Every card-dependent test is #[ignore]d so a developer machine without vkms stays green, and the lane runs them with --include-ignored under DRMKIT_REQUIRE_MASTER=1 — which turns "another client holds DRM master" from a tolerated local condition into a lane failure, so a card test cannot pass in CI by quietly doing nothing.

Phase 3 — met.

Item State
LayerScene: build/finalize split, EAGAIN flow control, commit report done
Invariants 1, 2 and 5 pinned against a device done — release_invariants_vkms
Invariant 4 pinned end to end done — reachability and the steady-state write census
Real commit path: property map, modeset, emission done
Composition fallback: blend, canvas, plane arming done
drmkit-scene-sources tier 1: dumb, external, ring, pool, cache done — GbmBufferSource waits on drmkit-gbm
T7 differential harness runs its first scenario done — seven scenarios, six diff-clean; all four the plan requires are present

Phase 4 — met.

Item State
drmkit-gbm done
drmkit-display: tone mapper and color curves done — diffed pixel-for-pixel against the reference
drmkit-display: EDID via libdisplay-info done — behind the edid feature
Fuzz target for EDID done — seeded, closing phase 1's carry-over
allocator_torture thresholds asserted on vkms done, and measured: three of the six pin a caching mechanism and between them cover both; one is about placement rather than caching; one skips here; rapid_churn pins nothing at all (P-14)
plane_stress measurement tool done — benches/plane-stress, CSV per frame
drmkit-display: DriverProfile done — capability probe, never the driver name
drmkit-display: HDR metadata and cache done — byte-identical to the reference's serializer over twelve vectors
drmkit-capture: plane readback and PNG done — including RGB565, which the reference cannot read (#243)
drmkit-cursor done — own .xcursor parser, hardware probe, ping-pong buffers
drmkit-session done — sequencing over a Backend trait, libseat behind a feature
drmkit-input done — xkb keyboard, auto-repeat, libinput seat with its log routed into drmkit's
drmkit-display: connector and CRTC capabilities done — measured on vkms and amdgpu
drmkit-display: mode lists, hotplug watch, color pipeline, scanout discovery done
drmkit-planes: reading planes off a device done — behind a feature, so the crate stays device-free without it
drmkit-scene-sources: cursor as an ordinary layer done — for hardware with no cursor plane

Phase 5 — under way. The v0.1.0-minimal ship gate.

Item State
T7 scenario corpus, including the §4.6 stability scenario done — all four the plan requires; §4.6 only began discriminating its own feature once insertion was added (P-27)
INVARIANTS.md (Rust) done — six contracts, numbering and titles linted against upstream
T3: Miri on the pure crates done — and drmkit-planes added, which the unsafe policy named and the lane had missed
T3: ASan on the fd-bound crates done — 389 cases, including the va_list trampoline docs/phase0-spikes.md promised a run
T3: TSan done, and the plan was right about why it is small. The three modules the C++ used atomics for are ring (no shared-state primitive at all), pool (two Mutexes) and seat (Rc/RefCell, which is not Send); no library code here spawns a thread. What remains is drmkit-modeset, whose tests spawn waker threads for the EINTR case invariant 3 covers — the one place a race could hide, and now checked
A validation process for the device pool done — cargo xtask validate, capability baselines, and coverage attribution per device (docs/validation.md)
The drmdb corpus as a standing check done — five gating rules and ten surveys over ~1,200 devices, run weekly and on the pull requests that touch what it reads
HIL smoke on RK3588 and VisionFive 2 not started. Needs the boards
Docs mostly. Rustdoc is clean under -Dwarnings and stays that way in CI; all 940 public items are documented, missing_docs having been enforced throughout; and each of the six invariants is cross-referenced from the crates that carry it. The crate front pages a consumer lands on have been read and rewritten where they described an earlier version of the library or omitted how to use it. What is left is the long tail — the individual item prose, which no one has read end to end

The differential harness

The T7 harness runs identical scenario scripts through both implementations against one vkms instance and diffs the property writes that reach the kernel, captured at the ioctl boundary so neither side is instrumented.

Building it found eleven defects in this port — a missing modeset path, a composition fallback that blinked layers off screen, inverted z-order on hardware that cannot reorder planes — most of them invisible to every counter and test in the tree. Six of the seven scenarios produce byte-identical traces; the remaining one is a reproduction for an open upstream question and is expected to diverge. The corpus now carries all four the plan requires — the fence-dup scenario was the missing one, and writing it corrected a documented rationale that measurement did not support. See docs/parity-findings.md.

Upstream first. drm-cxx is the origin of authority: anything this port surfaces is filed there rather than fixed only here, and the default for a bug is to reproduce the C++ behavior so the two agree byte for byte. Forty issues filed so far, plus three against third-party libraries.

Three drivers, then fifty. Every device test runs on vkms in CI and is also run against amdgpu and vc4 (Raspberry Pi 5) by hand. They disagree in the places that matter: rotation masks of 0x3f, 0xf and 0x35 — vc4's planes do 0/180 and both reflects but not 90 or 270 — and color pipelines of gamma-only, all three stages, and none at all. Several branches are reachable on exactly one of them; see docs/parity-findings.md.

Three boards still only answer for three boards. validation/drmdb replays the drmdb corpus — roughly 1,200 drm_info dumps across 50 drivers — through drmkit's own capability types and decisions, and gates on the assumptions this tree acts on. It has already closed a finding that sat open for want of hardware (P-6, on two Allwinner boards) and found a live defect on hardware nobody here owns (P-23, Tegra's cursor plane). See docs/validation.md.

Upstream baseline: jwinarske/drm-cxx @ 2b260d0 (v5.0.0, PR #362).

What this is

A behavioral-parity port, not a redesign. The C++ tree is the reference implementation; its test suite, benchmark thresholds, and INVARIANTS.md are the contract. Where Rust's type system can turn a documented hazard into a compile error it does — but the timing contracts still get their vkms pins, because ownership cannot express "two commits deep".

Two files keep the port honest as upstream moves:

  • TEST_PARITY.md — every upstream test file mapped to its Rust counterpart and status. A test appearing upstream without a row fails CI.
  • INVARIANTS.md — the six externally-relied-upon contracts, numbered and titled identically to upstream. Divergence fails CI.

Both are enforced by cargo xtask check.

Getting a build onto a board is written down too: docs/cross-to-a-board.md. The committed cross fragment assumes a target sysroot the host distribution may not provide, and the way round it — borrowing one from the Pi itself — installs nothing on either machine.

A third file records the flow in the other direction: docs/upstream-findings.md tracks every defect and improvement the port surfaces, the drm-cxx issue it was filed as, and what the Rust side does in the meantime. drm-cxx is the origin of authority — a fix that lived only here would make the two implementations diverge.

Crates

Crate Purpose Phase
drmkit-log Diagnostic surface: level, installable sink, per-category channels 0 ✅
drmkit-time Monotonic clock abstraction over CLOCK_MONOTONIC 0 ✅
xtask TEST_PARITY / INVARIANTS drift linters 0 ✅
drmkit-fmt Format modifiers, IN_FORMATS tables, bandwidth cost (no_std) 1 ✅
drmkit-sync sync_file fences for explicit synchronization 1 ✅
drmkit-core Device handles, property cache, atomic requests, property blobs 1 ✅
drmkit-modeset Mode selection, page-flip dispatch — invariant 3 1 ✅
drmkit-dumb Dumb buffers, lifetime mmaps — invariant 6 1 ✅
drmkit-planes Registry, layer model, matcher, allocator — invariant 4 2 ✅
drmkit-scene Scene, commit path, release ring, software composition — invariants 1, 2, 5 3 ✅
drmkit-scene-sources Tier-1 sources: dumb, external dma-buf, ring, pool, cache 3 ✅
drmkit-gbm GBM allocation and dma-buf export 4 ✅
drmkit Umbrella re-export, feature-gated —

Plus parity/: the T7 differential harness — an LD_PRELOAD trace shim, a scenario runner per implementation, and the corpus.

The remaining ~25 crates and the phase that lands each are listed in plan.md §5 and §8.

Building

Requires Rust 1.94.1 exactly, pinned by rust-toolchain.toml; rustup installs it automatically.

cargo build --workspace
cargo test  --workspace

Drift linters

Both need a drm-cxx checkout, from --ref or $DRMKIT_DRM_CXX:

export DRMKIT_DRM_CXX=/path/to/drm-cxx
cargo xtask check        # parity + invariants
cargo xtask parity
cargo xtask invariants

They fail on a missing reference rather than passing vacuously — a linter that silently skips is worse than no linter.

Cross compilation

Per-target fragments are pulled in through Cargo's include config key:

cargo build --target aarch64-unknown-linux-gnu      # RK3588, RK3566, SA8155P, Orin
cargo build --target riscv64gc-unknown-linux-gnu    # StarFive VisionFive 2

License

MIT, matching drm-cxx. See LICENSE.

About

Rust port of drm-cxx: a layered Linux DRM/KMS display framework. Behavioral-parity port; work in progress.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages