R5a: misc/drm_core with a versioned C facade, cdrmnull (C) on it #9

Merged
toasterson merged 29 commits from r5a-facade into main 2026-10-05 16:46:48 +00:00
Owner

Anima work item: R5a 01a0f883-0bd5-7c83-a6b3-9f0848a39255

The DRM core as one kernel module, misc/drm_core, with the first versioned C facade (include/drm), and cdrmnull, a C port of rdrmnull that proves the facade. Stacked on #8 (g6-minors); needs illumos-ddi r5a-char-table (pinned at 10bdfba, illumos-ddi PR #8, stacked on its #7).

Rename: the work item asked for misc/drm; the module is misc/drm_core (crate drm-module, drm_core.mapfile, package driver/graphics/drm-core), because gfx-drm's legacy C core is /kernel/misc/amd64/drm in driver/graphics/drm, which libdrm requires. Both packages install together; the two stacks are never loaded at once: misc/drm_core's _init refuses with EBUSY and a warning while the legacy drm module is loaded (the symmetric guard in gfx-drm is a separate item).

What was built

  • modules/drm_core (misc/drm_core): drm-core (renamed from crates/drm, feature kernel for the stateful layer) monomorphised once over a facade driver (CDriver, FObj, CKms); the device table and character entry points (drm_open … drm_segmap, drm_getinfo, DRM_CB_OPS) over illumos-ddi's CharTable/table_glue; the device numbers (absorbs misc/drm_minor); a module fence domain (drm:0:sync) for dma_fence_init on drivers' structures; per-thread ioctl frames for drm_copy_* and fd creation; 98 facade functions plus drm_debug.
  • Symbol scope: ld -B reduce -M modules/drm_core/drm_core.mapfile leaves exactly _init, _fini, _info and the facade (102 globals); the test scripts and CI compare the module's globals with the mapfile. tools/facade/gen_facade.py generates drm_core.mapfile, the drivers' include/drm/drm_facade.mapfile, facade_layout.c and xtask's table from the bindings; tools/facade/check-generated.sh (CI) checks they are current (and regenerates the bindings with bindgen where a gate is available).
  • Version: drm_facade_version(struct drm_facade_version *) reports major/minor and the sizes of 28 structures (append-only list). Policy: major on any size/offset/meaning change or removal, minor on additions; a driver loads only on the same major, a core minor ≥ its own and identical sizes. C drivers call drm_facade_check() in _init; Rust drivers call drm_facade::check_facade() from ddi::Driver::load; drm_dev_alloc() checks again. Version 0.1, unstable.
  • One code path: crates/drm-facade-sys (bindgen of include/drm, 98 functions, 38 structs, 233 offset tests) and crates/drm-facade (safe layer: Driver consts → static struct drm_driver, one ioctl thunk, GemObject → drm_gem_object_funcs, KMS through drm_rkms_funcs, owning handles whose drops are the facade's puts). rdrmnull and virtio_gpu use only the facade, -N misc/drm_core. Generic code stays in the module: the facade is non-generic C over #[repr(C)]; drivers compile drm-core without kernel (value helpers only), and CI/tests check that no driver carries a stateful core symbol (pattern generated from module names, with a positive control on the core's archive).
  • CTF: ctfconvert does not read Rust DWARF, so modules/drm_core/ctf/drm_facade_ctf.c (names every facade struct) is compiled with DWARF, converted with the gate's ctfconvert -l drm, stripped of DWARF (57 KB) and linked in: a standalone container of 2664 types; mdb -k prints ::print -t struct drm_device and live devices; kernel-check verifies drm-facade-sys against it.
  • drivers/cdrmnull (C, build.sh with the gate's kernel flags, -N misc/drm_core -z defs -M ddi.mapfile -M include/drm/drm_facade.mapfile -M private.mapfile): device, dumb buffers, movable objects, PUT_LATER, HOLD, the R2 fence timeline (soft interrupt), BO_WAIT, PRIME_SELFTEST (dma_buf_get/import/export/fd) and FENCE_SELFTEST; its detach races drm_dev_unregister against facade calls. Deferred: rdrmnull's virtual display (R3) to R5i, and rdrmnull's DMA test properties.
  • Cross-device PRIME between drivers works (closes R2's ENOTSUP): import returns the exporter's object, one handle per object per file; an import's re-export exports the original buffer, as on Linux.
  • Packaging (drm-core.p5m, cdrmnull.p5m, virtio-gpu.p5m requiring and incorporating drm-core@0.0.1), CI (build/link all modules, symbol scope, core-symbol check, cdrmnull -z defs, facade-check committed and live, check-generated), README (module layout, ABI policy, how a C driver links, concurrency, deviations, results).

Facade structures (public fields, Linux v7.2 names and order, MIT reimplementation per D7, opaque __core tail)

drm_device 22 (296 B, no mode_config until R5i), drm_minor 4, drm_file 6, drm_driver 22 (Linux hooks + illumos_*), drm_gem_object 15 (256 B), drm_gem_object_funcs 8 (free/open/close + 5 illumos), dma_fence 8 (96 B, unions counted once), dma_fence_ops 7, dma_fence_cb 2, dma_resv 2, dma_buf 8, dma_buf_attachment 4; plus the drm_rkms_* KMS structures for Rust drivers. Each header records its origin.

Design decisions and alternatives

  • Exporting the core's Rust interface (mangled names of one compiler build; generics cannot cross modules), trait objects across modules (unstable vtable layout), or a static core per Rust driver beside a C facade (two code paths, no shared state) were rejected for one C facade used by both languages.
  • Concurrency: drm_dev_unregister runs every refusal check on the published device; only then table → facade (drm_core::published::Published, SeqCst, waits for every in-flight call) → teardown → free. getinfo(9E) never touches the device. Fence callbacks: intrusive list through dma_fence_cb under a spin lock held with interrupts blocked (ddi_enter_critical); a callback may drop its own structure's last reference; callbacks queued behind it never run; releases wait only for callbacks running on other threads.
  • No unproven sleeping: dma_fence_init, drm_illumos_fence_create, dma_fence_signal never block; drm_gem_object_put/dma_buf_put document the contexts the core cannot tell apart (use drm_illumos_gem_object_put_deferred); Rust GemRef/GemAny drops defer, put(&Sleepable) releases at once; Syncobj is !Send.
  • Legacy guard: ddi::private::module_loaded(c"drm", &Sleepable) walks the module list under mod_lock by module name (the framework's identity; the file name changes with the load path), no hold (no deadlock with a symmetric guard).

Deviations from Linux

No mode_config (R5i); struct lx_mutex (illumos has struct mutex); dma_fence_wait_timeout in ticks; fence ops other than release not called; fence lock unused, cb_list not a list; dma_resv NULL ctx only, lock owned by the locking thread (EDEADLK on relock, foreign unlock ignored), unknown usages refused; drm_dev_unregister returns -EBUSY when refused; illumos backings/devmap for GEM memory; dma_buf_fd takes over the reference (as Linux).

Evidence (hephaistos guest r5a-run2, golden-20260930, illumos 7e802e88a3, libdrm 2.4.134 overlay, 2 vCPU / 3 GiB)

  • run-r5a-test.sh at b040324: PASS, 167 checks, 0 failures — misc/drm_core alone (102 globals = mapfile; facade layouts 38 sizes / 233 offsets via gcc; drivers built for facade major 1 or a larger drm_device refuse with the check's warning; with gfx-drm's misc/drm loaded, misc/drm_core refuses with its warning and loads after it is unloaded); three drivers together: R0/R1 + 32-bit probes on both null drivers, R2 200 checks on each, R3 on rdrmnull, R4 63 checks on virtio_gpu, drm_r5a_test 81 checks ×4 (PRIME across all pairs, cross-driver fences, PRIME_SELFTEST, FENCE_SELFTEST 0x1f/0x1f) after three unload orders; core refuses to unload while any driver depends on it; cdrmnull's 4 detaches raced 330k–600k facade calls (5.9k–73k during drm_dev_unregister); mdb -k CTF prints; run-g6-test PASS and run-r1-test (cdrmnull, two devices) fail=0 inside the run; /etc and /devices unchanged. Log: logs/hephaistos/illumos-drm/20261003-r5a-run2-combined-b040324.log.
  • At 08ca41e (kernel code differs only in message prefixes and callback-node reinit): same suite PASS 167 (…-run2-combined-08ca41e.log); cargo xtask kernel-check with misc/drm_core loaded: ddi-sys sizes 53/60, offsets 185/185, symbols 984/984; drm-facade-sys sizes 38/41, offsets 233/240 (rest anonymous unions), symbols 99/99; private sizes 3/3, offsets 46/46, symbols 15/15 (incl. modctl, mod_lock, modules) (20261003-r5a-kernel-check.log); facade-check against guest gcc: 271 lines, 0 differences (20261003-r5a-facade-check.log); packages driver/graphics/drm-core + driver/graphics/cdrmnull published from the build and installed beside driver/graphics/drm and libdrm 2.4.134 into a new BE (20261003-r5a-pkg-test.log).
  • Host: cargo test -p drm-core -p drm-uapi -p drm-facade (124 + facade 2 + uapi), virtio_gpu --no-default-features 6; clippy/fmt/doc clean; check-generated clean incl. bindgen regeneration.
  • Earlier runs on 1 Oct (20261001-r5a-run1-*) cover the pre-review iterations.

Review

Two rounds against the anima-review bar. Round 1 rejected (B1 fence self-put spin, B2 unregister ordering, B3 ddi soundness, B4 packaging, B5 vacuous tests, B6 placeholder) — all fixed, plus decisions on dma_buf_fd ownership, per-type GEM tag, resv usage/owner, callback lock vs. same-CPU interrupts, fd-0 test misuse, stale docs, CI regeneration check. Round 2 approved; notes 3 (callback node left stale) and 4 (drm: message prefixes) fixed in b040324.

Open issues / deferred

  • The reverse guard (gfx-drm's misc/drm refusing while misc/drm_core is loaded) belongs to gfx-drm.
  • Round-2 note: an interrupt that drops the last reference of a structure while another thread runs one of its callbacks (signalled through another structure of the same core fence) spins on the same CPU as the runner it interrupted; rare (driver fences' signallers hold a reference); fix by deferring that release to a task queue.
  • FENCE_SELFTEST is single-threaded; a concurrent signal-vs-remove/put test is still to add.
  • drm_illumos_fence_create and dma_fence_init now never sleep for memory (ENOMEM under pressure even in thread context).
  • misc/drm_core is ~16 MB on disk (Rust debug info; only allocatable sections load); stripping for packages is open.
  • CI not run here (owner's CI): the illumos job needs ctfconvert for CTF (skipped otherwise) and /usr/include/drm/drm.h for cdrmnull.
  • Facade version 0 is unstable; expect a new major with R5b/R5c/R5i.
Anima work item: R5a `01a0f883-0bd5-7c83-a6b3-9f0848a39255` The DRM core as one kernel module, `misc/drm_core`, with the first versioned C facade (`include/drm`), and `cdrmnull`, a C port of `rdrmnull` that proves the facade. Stacked on #8 (`g6-minors`); needs illumos-ddi `r5a-char-table` (pinned at `10bdfba`, illumos-ddi PR #8, stacked on its #7). **Rename:** the work item asked for `misc/drm`; the module is `misc/drm_core` (crate `drm-module`, `drm_core.mapfile`, package `driver/graphics/drm-core`), because gfx-drm's legacy C core is `/kernel/misc/amd64/drm` in `driver/graphics/drm`, which libdrm requires. Both packages install together; the two stacks are never loaded at once: `misc/drm_core`'s `_init` refuses with `EBUSY` and a warning while the legacy `drm` module is loaded (the symmetric guard in gfx-drm is a separate item). ## What was built - **`modules/drm_core`** (`misc/drm_core`): `drm-core` (renamed from `crates/drm`, feature `kernel` for the stateful layer) monomorphised once over a facade driver (`CDriver`, `FObj`, `CKms`); the device table and character entry points (`drm_open` … `drm_segmap`, `drm_getinfo`, `DRM_CB_OPS`) over illumos-ddi's `CharTable`/`table_glue`; the device numbers (absorbs `misc/drm_minor`); a module fence domain (`drm:0:sync`) for `dma_fence_init` on drivers' structures; per-thread ioctl frames for `drm_copy_*` and fd creation; 98 facade functions plus `drm_debug`. - **Symbol scope:** `ld -B reduce -M modules/drm_core/drm_core.mapfile` leaves exactly `_init`, `_fini`, `_info` and the facade (102 globals); the test scripts and CI compare the module's globals with the mapfile. `tools/facade/gen_facade.py` generates `drm_core.mapfile`, the drivers' `include/drm/drm_facade.mapfile`, `facade_layout.c` and xtask's table from the bindings; `tools/facade/check-generated.sh` (CI) checks they are current (and regenerates the bindings with bindgen where a gate is available). - **Version:** `drm_facade_version(struct drm_facade_version *)` reports major/minor and the sizes of 28 structures (append-only list). Policy: major on any size/offset/meaning change or removal, minor on additions; a driver loads only on the same major, a core minor ≥ its own and identical sizes. C drivers call `drm_facade_check()` in `_init`; Rust drivers call `drm_facade::check_facade()` from `ddi::Driver::load`; `drm_dev_alloc()` checks again. Version 0.1, unstable. - **One code path:** `crates/drm-facade-sys` (bindgen of `include/drm`, 98 functions, 38 structs, 233 offset tests) and `crates/drm-facade` (safe layer: `Driver` consts → static `struct drm_driver`, one ioctl thunk, `GemObject` → `drm_gem_object_funcs`, KMS through `drm_rkms_funcs`, owning handles whose drops are the facade's puts). `rdrmnull` and `virtio_gpu` use only the facade, `-N misc/drm_core`. Generic code stays in the module: the facade is non-generic C over `#[repr(C)]`; drivers compile `drm-core` without `kernel` (value helpers only), and CI/tests check that no driver carries a stateful core symbol (pattern generated from module names, with a positive control on the core's archive). - **CTF:** `ctfconvert` does not read Rust DWARF, so `modules/drm_core/ctf/drm_facade_ctf.c` (names every facade struct) is compiled with DWARF, converted with the gate's `ctfconvert -l drm`, stripped of DWARF (57 KB) and linked in: a standalone container of 2664 types; `mdb -k` prints `::print -t struct drm_device` and live devices; `kernel-check` verifies `drm-facade-sys` against it. - **`drivers/cdrmnull`** (C, `build.sh` with the gate's kernel flags, `-N misc/drm_core -z defs -M ddi.mapfile -M include/drm/drm_facade.mapfile -M private.mapfile`): device, dumb buffers, movable objects, PUT_LATER, HOLD, the R2 fence timeline (soft interrupt), BO_WAIT, `PRIME_SELFTEST` (dma_buf_get/import/export/fd) and `FENCE_SELFTEST`; its detach races `drm_dev_unregister` against facade calls. **Deferred:** rdrmnull's virtual display (R3) to R5i, and rdrmnull's DMA test properties. - **Cross-device PRIME** between drivers works (closes R2's `ENOTSUP`): import returns the exporter's object, one handle per object per file; an import's re-export exports the original buffer, as on Linux. - Packaging (`drm-core.p5m`, `cdrmnull.p5m`, `virtio-gpu.p5m` requiring and incorporating `drm-core@0.0.1`), CI (build/link all modules, symbol scope, core-symbol check, cdrmnull `-z defs`, facade-check committed and live, check-generated), README (module layout, ABI policy, how a C driver links, concurrency, deviations, results). ## Facade structures (public fields, Linux v7.2 names and order, MIT reimplementation per D7, opaque `__core` tail) `drm_device` 22 (296 B, no `mode_config` until R5i), `drm_minor` 4, `drm_file` 6, `drm_driver` 22 (Linux hooks + `illumos_*`), `drm_gem_object` 15 (256 B), `drm_gem_object_funcs` 8 (free/open/close + 5 illumos), `dma_fence` 8 (96 B, unions counted once), `dma_fence_ops` 7, `dma_fence_cb` 2, `dma_resv` 2, `dma_buf` 8, `dma_buf_attachment` 4; plus the `drm_rkms_*` KMS structures for Rust drivers. Each header records its origin. ## Design decisions and alternatives - Exporting the core's Rust interface (mangled names of one compiler build; generics cannot cross modules), trait objects across modules (unstable vtable layout), or a static core per Rust driver beside a C facade (two code paths, no shared state) were rejected for one C facade used by both languages. - Concurrency: `drm_dev_unregister` runs every refusal check on the published device; only then table → facade (`drm_core::published::Published`, SeqCst, waits for every in-flight call) → teardown → free. `getinfo(9E)` never touches the device. Fence callbacks: intrusive list through `dma_fence_cb` under a spin lock held with interrupts blocked (`ddi_enter_critical`); a callback may drop its own structure's last reference; callbacks queued behind it never run; releases wait only for callbacks running on other threads. - No unproven sleeping: `dma_fence_init`, `drm_illumos_fence_create`, `dma_fence_signal` never block; `drm_gem_object_put`/`dma_buf_put` document the contexts the core cannot tell apart (use `drm_illumos_gem_object_put_deferred`); Rust `GemRef`/`GemAny` drops defer, `put(&Sleepable)` releases at once; `Syncobj` is `!Send`. - Legacy guard: `ddi::private::module_loaded(c"drm", &Sleepable)` walks the module list under `mod_lock` by module name (the framework's identity; the file name changes with the load path), no hold (no deadlock with a symmetric guard). ## Deviations from Linux No `mode_config` (R5i); `struct lx_mutex` (illumos has `struct mutex`); `dma_fence_wait_timeout` in ticks; fence ops other than `release` not called; fence lock unused, `cb_list` not a list; `dma_resv` NULL ctx only, lock owned by the locking thread (EDEADLK on relock, foreign unlock ignored), unknown usages refused; `drm_dev_unregister` returns `-EBUSY` when refused; illumos backings/devmap for GEM memory; `dma_buf_fd` takes over the reference (as Linux). ## Evidence (hephaistos guest `r5a-run2`, golden-20260930, illumos 7e802e88a3, libdrm 2.4.134 overlay, 2 vCPU / 3 GiB) - `run-r5a-test.sh` at **`b040324`**: **PASS, 167 checks, 0 failures** — misc/drm_core alone (102 globals = mapfile; facade layouts 38 sizes / 233 offsets via gcc; drivers built for facade major 1 or a larger `drm_device` refuse with the check's warning; **with gfx-drm's `misc/drm` loaded, `misc/drm_core` refuses** with its warning and loads after it is unloaded); three drivers together: R0/R1 + 32-bit probes on both null drivers, R2 200 checks on each, R3 on rdrmnull, R4 63 checks on virtio_gpu, `drm_r5a_test` 81 checks ×4 (PRIME across all pairs, cross-driver fences, PRIME_SELFTEST, FENCE_SELFTEST 0x1f/0x1f) after three unload orders; core refuses to unload while any driver depends on it; cdrmnull's 4 detaches raced 330k–600k facade calls (5.9k–73k during `drm_dev_unregister`); `mdb -k` CTF prints; run-g6-test PASS and run-r1-test (cdrmnull, two devices) fail=0 inside the run; /etc and /devices unchanged. Log: `logs/hephaistos/illumos-drm/20261003-r5a-run2-combined-b040324.log`. - At `08ca41e` (kernel code differs only in message prefixes and callback-node reinit): same suite PASS 167 (`…-run2-combined-08ca41e.log`); `cargo xtask kernel-check` with misc/drm_core loaded: ddi-sys sizes 53/60, offsets 185/185, symbols 984/984; **drm-facade-sys sizes 38/41, offsets 233/240 (rest anonymous unions), symbols 99/99**; private sizes 3/3, offsets 46/46, symbols 15/15 (incl. `modctl`, `mod_lock`, `modules`) (`20261003-r5a-kernel-check.log`); `facade-check` against guest gcc: 271 lines, 0 differences (`20261003-r5a-facade-check.log`); packages `driver/graphics/drm-core` + `driver/graphics/cdrmnull` published from the build and **installed beside `driver/graphics/drm` and libdrm 2.4.134** into a new BE (`20261003-r5a-pkg-test.log`). - Host: `cargo test -p drm-core -p drm-uapi -p drm-facade` (124 + facade 2 + uapi), `virtio_gpu --no-default-features` 6; clippy/fmt/doc clean; check-generated clean incl. bindgen regeneration. - Earlier runs on 1 Oct (`20261001-r5a-run1-*`) cover the pre-review iterations. ## Review Two rounds against the anima-review bar. Round 1 rejected (B1 fence self-put spin, B2 unregister ordering, B3 ddi soundness, B4 packaging, B5 vacuous tests, B6 placeholder) — all fixed, plus decisions on dma_buf_fd ownership, per-type GEM tag, resv usage/owner, callback lock vs. same-CPU interrupts, fd-0 test misuse, stale docs, CI regeneration check. Round 2 approved; notes 3 (callback node left stale) and 4 (`drm:` message prefixes) fixed in `b040324`. ## Open issues / deferred - The reverse guard (gfx-drm's `misc/drm` refusing while `misc/drm_core` is loaded) belongs to gfx-drm. - Round-2 note: an interrupt that drops the last reference of a structure while another thread runs one of its callbacks (signalled through another structure of the same core fence) spins on the same CPU as the runner it interrupted; rare (driver fences' signallers hold a reference); fix by deferring that release to a task queue. - `FENCE_SELFTEST` is single-threaded; a concurrent signal-vs-remove/put test is still to add. - `drm_illumos_fence_create` and `dma_fence_init` now never sleep for memory (ENOMEM under pressure even in thread context). - `misc/drm_core` is ~16 MB on disk (Rust debug info; only allocatable sections load); stripping for packages is open. - CI not run here (owner's CI): the illumos job needs `ctfconvert` for CTF (skipped otherwise) and `/usr/include/drm/drm.h` for cdrmnull. - Facade version 0 is unstable; expect a new major with R5b/R5c/R5i.
The C interface of the coming misc/drm module, MIT-licensed so that MIT
drivers and CDDL code can include it. include/drm is the single source of
truth: drm_device, drm_file, drm_gem_object, dma_fence, dma_resv and
dma_buf keep the names and relative order of Linux v7.2's fields that the
R5 analysis counted, with illumos types, and an opaque fixed-size core tail.
The GPL-only Linux headers (dma-fence.h, dma-resv.h, dma-buf.h, sync_file.h)
are reimplemented from their documented interfaces (decision D7); each
header records its origin. The functions follow Linux's names and error
convention where the semantics match; illumos additions (per-device DMA
attributes and fence priority, GEM backings and devmap mappings, device
fences) are drm_illumos_*, and the core's R3 mode setting for the Rust
drivers is drm_rkms.h. drm_facade_version() and the inline
drm_facade_check() compare the major/minor version and the size of every
facade structure.

tools/facade/generate.sh runs bindgen (crates/drm-facade-sys, with the
kernel's scalar types and opaque structures in kernel.rs) and
gen_facade.py, which writes misc/drm's symbol-scope mapfile, the drivers'
EXTERN mapfile for -z defs, and both halves of the layout check: an
asm-offsets C file for gcc -S on illumos and the Rust table behind
cargo xtask facade-check (38 structures, 232 offsets; clang's view of the
gate headers matches). xtask also learns local mapfiles, drops a module's
own -N, and checks a linked module's global symbols against its mapfile
(cargo xtask symbols).
The core crate becomes drm-core, the library of the coming kernel module
misc/drm, which monomorphises it once for its facade driver; no driver
module links it any more.

* Driver: the associated constants become a runtime DriverInfo and an
  ioctl table (the facade driver reads them from a C struct drm_driver);
  new hooks file_open, dumb_create (the old flow is
  Device::dumb_create_default), prime_import, and the matched entry for
  ioctl. Object no longer needs From<SysMem>.
* Driver ioctls match the whole command value first, then the first entry
  with the same number and direction (Linux's size rule, for the facade's
  drm_ioctl_desc handlers).
* misc/drm_minor is folded in: its slot table is crate::slots, the device
  numbers come from device::NUMBERS (numbers_taken for _fini); the
  drm-minor and drm-minor-sys crates go.
* PRIME: a dma-buf of another device is imported through
  Driver::prime_import, and a file importing the same buffer again gets its
  handle again (GemObject::imported_from, GemCore::handle_find). The
  anonymous-file lookup finds dma-bufs, sync objects and sync files of any
  registered device (needs illumos-ddi's FileRef::char_dev); fenced objects
  of another fence priority stay ENOTSUP.
* For the facade's C callers: WwMutex::lock_raw/unlock_raw,
  Resv::add_fence_raw and reserve, KVec::reserve, GemCore::charge_reserved.
* illumos-ddi is pinned to r5a-char-table (448e824).

The driver crates are out of the workspace until they are ported to the
facade.
modules/drm builds the kernel module misc/drm: the core monomorphised once
for the facade driver (CDriver), which forwards every Driver hook to a
driver's struct drm_driver, and every function of include/drm.

* Devices: __drm_dev_alloc/drm_dev_alloc allocate the driver's structure
  and the module's DevCore behind drm_device.__core; drm_dev_register
  attaches the core device on the driver's node and publishes it in the
  device table, drm_dev_unregister waits for facade calls in progress
  (DevGuard), detaches, and republishes a refused device.
* Entry points: drm_open ... drm_segmap and drm_getinfo run illumos-ddi's
  table_glue over the device table (CharTable), so every driver's cb_ops
  point at the same glue; drm_minor_instance maps minors to instances.
* GEM: FObj is the core object behind a driver's drm_gem_object; the C
  structure holds no reference, every C reference is a count of the core
  object, and funcs->free runs once the object is armed. Imports of
  another device's dma-buf get a core-made structure (ImportBox).
* Fences: a dma_fence is the handle of a core fence (FenceCore in its
  tail); driver fences live in the module's fence domain (drm:0:sync),
  proxies wrap fences the core hands out; callbacks are removed before a
  structure goes. Reservations, sync objects, sync files, dma-bufs, the
  illumos GEM and mapping functions, device fences, and the R3 mode
  setting (CKms, drm_rkms_*) follow.
* Ioctls: a facade handler gets a kernel copy with Linux's size rule;
  ctxt records the ioctl in progress per thread for drm_copy_*_user and
  the file-descriptor functions.

The core gains dumb_map_offset as a hook, check_plane_geometry (the plane
rule on plain values), export_sync_file's cloexec, export_dmabuf and
dmabuf_object, Device::file_info, and GemRef::as_raw/clone_from_raw.
illumos-ddi is pinned to 72219f6 (TaskQueue::without_node,
IntrPriority::new, private::current_context).
* drm_illumos_ioctl_cmd/_user_cmd: the matched entry's and the caller's
  command value, so that one handler can serve several entries.
* drm_rkms: the layout callback hands its layout to drm_rkms_layout_set,
  which copies it while the driver's arrays are alive.
* drm-core gains a kernel feature (on by default, which misc/drm uses):
  without it the crate is only its value helpers (display modes and
  formats, EDID, dumb-buffer layouts, KArc/KVec/ksync, the software
  vblank), which drivers may compile in; none of the core's state.
crates/drm-facade is the safe Rust interface of the facade: a Driver trait
whose constants and thunks become the driver's static struct drm_driver,
ioctl table and rkms callbacks at compile time (generic associated
constants), Device<D> (the ddi instance: __drm_dev_alloc with the driver
state after the C device, the settings, drm_dev_register; unregister hands
the device back while refused), File, GemRef<T> over Gem<T> (the C object
followed by the driver's data; callbacks per type; lookups checked by type),
GemAny, Fence, Syncobj, sync files, IoctlData with Linux's kernel-copy
semantics and user copies, the R3 KMS types (KmsDriver, Fb, states), and
CB_OPS, the core's entry points for ddi::module!'s cb_ops form. It calls
only facade functions and holds no core state.

rdrmnull is ported onto it: dumb buffers through GemRef::new and
handle_create, the movable objects as a second GEM type with a second
backing and the illumos mapping callbacks, the test timeline with device
fences signalled from the soft interrupt, sync objects, reservations and
sync files through the facade, the display through the rkms thunks. Its
behaviour and ioctl ABI are unchanged, except that dma-mode root-node (the
2026-09-28 panic drill) is gone: the facade binds GEM memory only against
the driver's own node.
drm_gem_object_put tells interrupt threads only; a timeout(9F) handler
runs on a callout thread and must not release an object itself, so the
facade gets a put that always parks a last reference for the core's task
queue (GemAny::put_deferred; rdrmnull's PUT_LATER timer uses it, as the R1
test's deferred-release checks require). The headers now say what facade
version 0 does: dma_fence ops->release runs in the context of the last
put (any context, as on Linux), only release of the fence ops is called,
the GEM open/close callbacks run once per handle, and DRIVER_MODESET
without illumos_rkms is a device without display objects.
* drm-test-lib.sh links misc/drm (as misc-drm, since drm/ holds the uAPI
  headers) with -B reduce -M drm.mapfile, checks that its global symbols
  are exactly the mapfile's, and moves gfx-drm's own /kernel/misc/amd64/drm
  aside while a test runs; the run scripts link drivers with -N misc/drm.
* The R0 to R2 tests take the driver under test from DRM_DRIVER (rdrmnull
  by default, or cdrmnull): its name, date, bus ID and kstat module. The R2
  test adds the fences of misc/drm's own domain (drm:0:sync, a C driver's
  dma_fence_init fences) to the driver's sync kstat.
* R2's two-device check: card1 now imports card0's dma-buf (ENOTSUP while
  each driver had its own core) and gets the same handle again.
* The /devices state ignores the clone@0 nodes of idle drivers, which devfs
  drops a while after boot (the runs of G6 met that too).
Facade calls in thread context publish the counters of the domain of the
drivers' own fences; the domain's drain task publishes what interrupt
context changed.
drivers/cdrmnull is a DRM driver in C that uses nothing but the facade
(include/drm) and the DDI: linked with -N misc/drm -z defs against
ddi.mapfile, include/drm/drm_facade.mapfile and private.mapfile (only
devmap_umem_remap, which rdrmnull uses too, and the soft-interrupt
functions of the DDI that the gate's ddi.mapfile lacks). It checks the
facade version and structure sizes in _init (drm_facade_check), embeds its
struct drm_device (drm_dev_alloc_container), and implements rdrmnull's
R0 to R2 behaviour with the same private ioctl ABI: dumb buffers
(drm_mode_size_dumb, drm_gem_object_init, drm_gem_handle_create), movable
objects with a second backing and the illumos mapping callbacks
(devmap_umem_remap, refault), PUT_LATER from a timeout(9F) handler
(drm_illumos_gem_object_put_deferred), HOLD, the test timeline with its own
struct dma_fence objects (dma_fence_init, signalled from a soft interrupt)
put into sync objects, reservations and sync files, BO_WAIT, and per-file
state through open/postclose. A new test-only ioctl, PRIME_SELFTEST
(0x47), drives dma_buf_get, drm_gem_prime_import, drm_gem_prime_export and
dma_buf_fd. The display of rdrmnull (R3) is not ported: mode setting for C
drivers is Linux's KMS core (R5i). build.sh compiles it with the gate's
kernel flags for gcc and links it on illumos.

run-r1-test.sh takes DRM_DRIVER=cdrmnull (the module built by build.sh and
cdrmnull.conf, or cdrmnull-two.conf for the cross-device checks); the
modetest check after a refused detach is skipped for it.
* drm_gem_object_funcs.illumos_release runs before free once the core has
  released the object; returning false says the device may still use the
  memory, and the core then leaks the backings instead of freeing memory a
  device could write into (virtio-gpu without an answer to RESOURCE_UNREF,
  as it did before R5a). drm-facade's GemObject::release returns that.
* drm_illumos_sync_file_get_fence reports EBADF, EINVAL or ENOTSUP where
  sync_file_get_fence can only return NULL.
* Sync files are named after the driver of the ioctl's device, as the Rust
  drivers named theirs.
virtio_gpu now uses misc/drm only through drm-facade: its objects are
Gem<VgObject> with the core's memory (DMA-bound with the device's
attributes), release sends RESOURCE_UNREF and leaks the memory without an
answer (illumos_release), every file gets a small host context id of its
own (the clone ids are the core's now), fences come from the device's
domain, sync objects and sync files from the facade, the ioctls follow the
kernel-copy model with one table entry per command layout and dispatch by
the matched entry, and the display uses the rkms interface. Completed
requests drop their buffer references with put_deferred, so that an
object is never destroyed on the completion thread, which may hold the
submit lock. Host tests: 6 passed.

packaging/drm.p5m replaces drm-minor.p5m: driver/graphics/illumos-drm
delivers kernel/misc/amd64/drm and excludes OpenIndiana's
driver/graphics/drm, whose gfx-drm module of the same name has another
interface. virtio-gpu.p5m and cdrmnull.p5m depend on it.
The layout table missed drm_gem_object_funcs.illumos_release.
tools/facade/facade_layout.illumos.out is gcc's view of the facade on
illumos (golden-20260930), which facade-check compares with Rust: 38
sizes and 233 offsets, no difference. kernel-check now reads bindgen's
layout tests, so drm-facade-sys is checked against misc/drm's CTF, and
no longer takes 'mut' of 'pub static mut' for a symbol.
modules/drm/ctf/drm_facade_ctf.c names every facade structure;
build-ctf.sh compiles it with DWARF, converts it with the gate's
ctfconvert and drops the DWARF, and the object is linked into misc/drm
next to the Rust archive. Its standalone CTF container (2664 types) lets
mdb -k print struct drm_device, struct dma_fence and the rest of the
facade, and lets kernel-check compare drm-facade-sys with the module.
The test library links it when it is present.
run-r5a-test.sh loads misc/drm alone, compiles the facade layouts with
gcc, links rdrmnull and virtio_gpu against misc/drm (every undefined
symbol resolves, no stateful core symbol in a driver), attaches cdrmnull,
rdrmnull and virtio_gpu together, runs R0 to R2 on both null drivers, R3
on rdrmnull, R4 on virtio_gpu and drm_r5a_test (PRIME across every pair
of drivers, cross-driver fences, PRIME_SELFTEST), prints facade
structures with mdb -k, unloads in three orders and runs the G6 and R1
tests for cdrmnull. run-g6-test.sh marks misc/drm as its own when it
installed it.
deps took the crate graph of cargo metadata, which unifies the features
of the whole workspace, so misc/drm inherited virtio_gpu's -N
misc/virtio; it now asks cargo tree -p for the crates the package links.
A module no longer gets the mapfile of its own exports as undefined
symbols (misc/drm and include/drm/drm_facade.mapfile).
The hosted job tests drm-core instead of drm and drm_minor, drops the
removed DMA drill and compares the facade's layouts with gcc's
(facade-check). The illumos job links misc/drm with -B reduce and
drm.mapfile (with the facade's CTF where ctfconvert is installed) and
compares its global symbols with the mapfile, links rdrmnull and
virtio_gpu with -N misc/drm and checks that neither carries a symbol of
the stateful core, and builds cdrmnull with -z defs against the gate's
ddi.mapfile. cargo doc no longer links private items.
The counterpart of drm_facade_check(): the same major version, a core
minor version at least the driver's and the same size of all 28 facade
structures, indexed by the DRM_FACADE_SZ_* constants. rdrmnull and
virtio_gpu call it from ddi::Driver::load (illumos-ddi 7fbd602), so a
mismatched driver refuses to load before anything attaches, as cdrmnull
does from _init. Host tests of the comparison; CI runs them.
Module layout, one code path, how generic code stays inside misc/drm,
the facade version policy, the facade structures and their origin, CTF,
deviations from Linux, how a C driver is built and linked, and the
renamed crates and modules.
facade_mismatch, a misc module whose _init runs drm_facade_check(), is
built against copies of the headers with another major version and with
a larger struct drm_device; run-r5a-test.sh expects modload to fail with
the check's warning.
The core module is misc/drm_core (crate drm-module, drm_core.mapfile):
OpenIndiana's gfx-drm keeps /kernel/misc/amd64/drm in driver/graphics/drm,
which libdrm requires, and both cores may be loaded at once.

- drm_dev_unregister runs the refusal checks on the published device
  (Device::detach_check) and only then takes it out of the table and the
  facade and frees it; a refused device does not move. The publication
  is drm_core::published::Published (SeqCst, host-tested from eight
  threads). getinfo(9E) reads the node from the table, not the device.
- Fence callbacks: an intrusive list through dma_fence_cb under a spin
  lock with interrupts blocked (ddi_enter_critical); a callback may drop
  the last reference to its own structure (no self-wait), callbacks
  queued behind it never run, a release waits only for callbacks running
  on other threads. Fence::remove_callback_nowait in the core.
- dma_resv: the lock belongs to the thread that took it (unlock and
  add_fence only by it, EDEADLK on relock); unknown usages are refused.
- dma_buf_fd takes over the caller's reference, as on Linux.
- No unproven sleeping: dma_fence_init, drm_illumos_fence_create and
  dma_fence_signal never block; with_ctx is unsafe with the contract of
  ddi::private::current_context; kstat publication asks
  servicing_interrupt only.
- drm-facade: GemRef/GemAny drops defer (put(&Sleepable) releases at
  once), Syncobj is !Send, object types are told by a TypeId behind one
  non-generic free callback, Gem and Dev wrap the C structures in
  UnsafeCell.
- _init/_fini clean-up paths; header documentation; illumos-ddi pin
  d630acd (unsafe cb_ops form, unsafe current_context, bounded
  IntrPriority::new, servicing_interrupt).
cdrmnull's FENCE_SELFTEST drives the facade's fence callbacks (a
callback dropping its own structure's last reference, removal, a
signalled fence, a released structure, a queued callback). Its detach
races drm_dev_unregister against facade calls from a thread-level and a
soft-interrupt periodic handler and logs their count. dma_buf_fd's new
ownership; header notes on the DMA test properties it does not port.
rdrmnull and virtio_gpu use the unsafe cb_ops form of module!.
- Packaging: driver/graphics/drm-core delivers kernel/misc/amd64/drm_core
  beside gfx-drm (no exclusion); the drivers pin it (incorporate).
- Tests: the test library installs misc/drm_core without touching gfx-drm
  and removes only what it installed; the core-symbol pattern is built
  from the module names (five length prefixes were wrong) with a
  positive control; the facade-mismatch test requires the check's own
  warning; the reattach loop requires all six nodes; FENCE_SELFTEST and
  the unregistration race are checked; drm_r5a_test no longer uses fd 0
  after a failed export and requires the same handle on re-import.
- CI: check-generated.sh (generated files current), the live gcc layout
  check on illumos, the generated core-symbol check.
- README: the module's name, concurrency, Drop rules, deviations; stale
  paths in docs/anon-fd.md and drm_g6_test.c.
drm_gem_prime_export of an object that drm_gem_prime_import made from
another device's dma-buf exports that buffer again, as Linux re-exports
an import's dma-buf, so importing it anywhere finds the original object
and its handle. cdrmnull's unregistration race keeps a soft-interrupt
handler calling the facade until the detach is through and logs the
calls made while drm_dev_unregister ran. The test library's
core_symbols no longer clobbers its caller's loop variable (which made
the per-driver check vacuous).
A soft interrupt that loops keeps its processor's threads off it, the
detaching thread included, so the calls never overlapped the
unregistration. The thread-level caller is now a task queue thread that
calls until the detach is through; the soft interrupt handler calls a
few batches per millisecond. README: an import's re-export.
The two stacks install together but must never be loaded at once.
_init asks ddi::private::module_loaded(c"drm") (illumos-ddi 10bdfba: the
module list walked under mod_lock by module name, no hold taken) and
refuses with EBUSY and a warning while the legacy core is loaded; the
DDI has no way to ask without loading the module. run-r5a-test.sh loads
the legacy misc/drm from driver/graphics/drm, expects the refusal and
its warning, unloads it and loads misc/drm_core. README and the
package manifest state the rule.
Messages of misc/drm_core say drm_core: now that gfx-drm's misc/drm may
be installed beside it. A dma_fence_cb's node is left empty once the
callback is off the core's list (run, removed, refused, or released
with its structure), so that list_empty() works as on Linux (round-2
review notes 3 and 4).
README: R5a results at b040324
Some checks failed
ci / check-hosted (pull_request) Failing after 5m14s
ci / kmod-illumos (pull_request) Failing after 27m8s
2306e85929
toasterson changed target branch from g6-minors to main 2026-10-05 16:46:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
illumos/illumos-drm!9
No description provided.