R0: drm-uapi crate and DRM core with card/render minors (WI-452) #1

Merged
forge merged 7 commits from r0-uapi-core into main 2026-09-28 18:11:01 +00:00
Contributor

Milestone R0 of the Rust DRM core (Anima WI-452): the uAPI crate, the DRM core library with card and render minors, per-file state and the core ioctls, a Driver trait and a null driver. It was built, loaded and tested with libdrm 2.4.109 on oibldofgate.

What was built

  • crates/drm-uapi: the #[repr(C)] structures and constants of drm.h, drm_mode.h and virtgpu_drm.h from libdrm 2.4.109.
    • Generated with bindgen (tools/uapi/generate.sh, target x86_64-pc-solaris2.11, illumos-gate system headers) from the release gfx-drm builds (same SHA-256), with gfx-drm's illumos drm.h patch vendored in tools/uapi/patches.
    • The 115 DRM_IOCTL_* numbers are generated from the same headers in the illumos _IOWR encoding (tools/uapi/gen_uapi.py), because bindgen cannot expand function-like macros.
    • Static checks: bindgen's layout assertions for every structure, plus abi.rs, which pins 50 structures (drm_version, drm_get_cap, drm_set_client_cap, drm_gem_close, drm_prime_handle, all drm_syncobj_*, the atomic and KMS drm_mode_*, all drm_virtgpu_*) and the 11 R0 command numbers to the gcc values.
    • 64-bit only.
  • crates/drm: a no_std core on the ddi crate.
    • drm::Driver supplies the name, date, description, major/minor/patch, feature flags (MODESET, ATOMIC, RENDER), mode-setting properties for GET_CAP, and the bus ID.
    • drm::Device<D> creates card<N> and renderD<128+N>. Every open is a clone open: the clone id sits in bits 9-17, the bits illumos libdrm strips, which allows 511 files per device.
    • Per-file state DrmFile holds the authenticated flag, the master binding and the client capabilities.
    • Core ioctls: VERSION, GET_CAP, SET_CLIENT_CAP, GET_UNIQUE/SET_UNIQUE, SET_VERSION, GET_MAGIC/AUTH_MAGIC, GET_CLIENT, SET_MASTER/DROP_MASTER. They follow Linux's permission flags and semantics: per-master magic namespaces, render nodes without authentication, and a bus ID that stays empty until interface 1.1 is set.
    • drv_priv(9F) runs on the ioctl path only when a permission needs it (SET_UNIQUE, and SET_MASTER/DROP_MASTER by a file that is not a former master of the same process), as Linux calls capable() lazily.
    • GET_CAP reports ADDFB2_MODIFIERS, DUMB_PREFERRED_DEPTH and DUMB_PREFER_SHADOW as 0 until the ioctls they describe exist (R1/R3), whatever the driver's ModeConfig.
    • The ioctl path uses no slice or table indexing: get/get_mut with an errno on the unreachable branches.
    • Unknown commands fail with ENOTTY. Commands are matched on their full 32-bit value, since illumos encodes only 8 bits of the size. Core commands from 32-bit callers (ddi_model_convert_from) fail with ENOTSUP; other commands from them fail with ENOTTY.
  • examples/rdrmnull: the null driver. It registers one device (card0 and renderD128), declares MODESET, ATOMIC and RENDER, and has no hardware. It is the kernel module that is built and loaded, and it is a test module, not something to deploy. packaging/devlink.tab holds the /dev/dri rule for packages.
  • xtask: vendored from illumos-ddi eb18ab93b7 (build, link, deps, kernel-check), with a header comment that lists the changes. lint-man is not vendored (there is no -sys crate here). uapi-dump and uapi-check are new.
  • Tests and CI: tools/test/drm_r0_test.c is the libdrm test and tools/test/run-r0-test.sh the VM procedure. The two C programs are local test tools, not deliverables. .forgejo/workflows/ci.yml follows illumos-ddi's setup (hosted check, clippy, fmt, doc, tests and uapi-check; native illumos build and link of rdrmnull). CI has not run yet.

Deviations from the work item text

  1. Module structure. The core is a library linked statically into each driver module; there is no separate misc/drm kernel module.
    • Exporting Rust code to other kernel modules needs a stable, versioned C ABI. Mangled and generic Rust does not cross kmod boundaries, and designing that ABI is the R5 C-facade work.
    • misc/drm is therefore deferred to R5. Until then, static linking costs nothing, since one Rust driver comes first.
  2. Node type. The nodes use ddi_display:drm (DDI_NT_DISPLAY_DRM), not plain ddi_display.
    • The plain type triggers devfsadm misc_link's display() handler, which adds /dev/fbs/<minor> and /dev/fb<N> console framebuffer links. The VM already has /dev/fb0 -> fbs/text-0.
    • ddi_display:drm is gfx-drm's convention. OpenIndiana's driver/graphics/drm ships SUNW_drm_link, which created /dev/dri/card0 and /dev/dri/renderD128 from it, so no /etc/devlink.tab change was needed on the VM.
    • The equivalent devlink.tab rule for systems without that module is in packaging/devlink.tab.
  3. Extra ioctls.
    • SET_VERSION is added because libdrm calls it on primary nodes.
    • GET_CLIENT (index 0, as on current Linux) is an addition beyond the work item, so that the result of authentication can be observed.
  4. Layout reference headers. The VM has libdrm 2.4.109 (x11/library/libdrm) but no drm.h, drm_mode.h or virtgpu_drm.h: they belong to system/header/header-drm, which is not installed, and I installed nothing. The C layout program was therefore compiled with gcc 14.3.0 on the VM against the patched libdrm 2.4.109 headers, copied to /root/illumos-drm-test/uapi/drm. That patched header is exactly what gfx-drm compiles the installed libdrm.so against.

illumos-ddi change

The dependency is pinned to illumos-ddi branch drm-r0, rev e019d5bd3c: main plus the following. It is under review as illumos/illumos-ddi#1; once that merges, this repository pins illumos-ddi main.

  • 8463d9da92 adds CharDevice::clone_open to the module glue. The driver returns a fresh minor, and the glue stores makedevice(getmajor(dev), minor) through devp. The default calls open and keeps the device number, so existing drivers are unaffected.
  • f36d47958e documents the 32-bit dev_t limit for clone minors.
  • e019d5bd3c adds clone_open to the CharDevice description in docs/DESIGN.md §5.2.

Verification

Host: Mac, aarch64-apple-darwin, Rust 1.97.1. VM: oibldofgate, illumos-cda5b9a49f, OpenIndiana Hipster 2026.04, root, global zone.

Step Result Log
cargo check --workspace --target x86_64-unknown-illumos pass logs/oibldofgate/20260928-r0-local-checks.log
cargo clippy --workspace --target x86_64-unknown-illumos -- -D warnings pass same
cargo fmt --all -- --check pass same
cargo doc with -D warnings pass same
cargo test -p drm -p drm-uapi (host) 23 + 1 tests pass, including the 8-thread state-machine test with invariant checks after every step and a test that the privilege check is not evaluated for unprivileged operations same
cargo xtask build rdrmnull (nightly-2026-04-16, build-std) pass; archive sha256 86823733d8f9…, identical on the VM (digest in the load-test log) same
uAPI layout: gcc on the VM vs Rust (cargo xtask uapi-check) 103 sizes, 448 offsets, 115 ioctl numbers identical, 0 differences 20260928-r0-uapi-layout.log
xtask kernel-check --verify against mdb -k (ddi-sys at e019d5bd3c) sizes 53/60, offsets 185/185, symbols 983/984. No mismatch; 7 anonymous CTF unions are checked through their parents, and ddi_ncpus_expected postdates this kernel. Same as upstream's 26 Sep result 20260928-r0-kernel-check.log
ld -ztype=kmod, then each undefined symbol resolved with ::whatis 32 imported symbols, 0 unresolved (an earlier revision of this description said 33; the crgetuid import went away in 1a0f6eb) 20260928-r0-rdrmnull-load-test.log
add_drv -m '* 0666 root sys' rdrmnull, libdrm test as root and as nobody, 32-bit probe 104 PASS, 0 FAIL same
Unload refused while a file is open: drm_r0_test hold /dev/dri/card0, then modunload -i <id> refused with "Device busy"; after closing, modunload succeeds, the next open reattaches same
rem_drv rdrmnull unloaded cleanly same

The R0 structures have no kernel CTF counterpart; they are userland ABI, and the gcc comparison is their layout proof. The DDI layouts the module relies on are checked by kernel-check.

ls -l /dev/dri with the module loaded:

card0 -> ../../devices/pseudo/rdrmnull@0:card0            (crw-rw-rw- root sys 269,0)
renderD128 -> ../../devices/pseudo/rdrmnull@0:renderD128  (crw-rw-rw- root sys 269,128)

What the libdrm test showed:

  • drmOpen("rdrmnull", NULL) opened card0 as master.
  • drmGetDeviceNameFromFd mapped the clone minor 0x200 back to /dev/dri/card0.
  • drmGetVersion returned 1.0.0 "rdrmnull" "20260928".
  • drmGetCap: TIMESTAMP_MONOTONIC 1; PRIME, SYNCOBJ, SYNCOBJ_TIMELINE, DUMB_BUFFER, ADDFB2_MODIFIERS and CRTC_IN_VBLANK_EVENT 0; CURSOR_WIDTH and CURSOR_HEIGHT 64; unknown capabilities EINVAL.
  • drmSetClientCap accepted and recorded all five capabilities with the Linux rules; bad values were EINVAL.
  • drmSetInterfaceVersion(1.4) made drmGetBusid report pseudo:rdrmnull@0. drmSetBusid returned EINVAL for a foreign ID and EBUSY once set.
  • Magic handshake: a nobody client got a magic and was unauthenticated; the root master's drmAuthMagic authenticated it (checked with drmGetClient); reusing the magic was EINVAL. The client's AuthMagic, SetMaster and DropMaster were EACCES.
  • Master hand-over worked, with EBUSY and EINVAL where expected.
  • On drmOpenRender(128), as root and as nobody, version and caps work; GET_MAGIC, SET_CLIENT_CAP, GET_UNIQUE and SET_MASTER are EACCES.
  • Unknown ioctl, wrong-size VERSION and GEM_CLOSE returned ENOTTY; a bad pointer returned EFAULT.
  • Stress: 8 nobody threads did 3200 opens with 0 unexpected errors; every successful authentication (104 in the final run) was checked with drmGetClient. Meanwhile the root process dropped and retook master concurrently: 1878 retaken, 468 EBUSY. The number of authentications varies between runs with how often the master is held at the moment of AUTH_MAGIC (1025 in the run at 1a0f6eb).
  • The 511-file limit held, then EMFILE.
  • 32-bit probe: VERSION and GET_CAP were ENOTSUP; an unknown DRM ioctl was ENOTTY; isatty was false.
  • While a file was held open, modunload of rdrmnull failed with "Device busy"; it succeeded once the file was closed. rem_drv is not used for this check, because it removes the driver configuration even when the unload fails.

VM left in its prior state:

  • rdrmnull is not loaded and its files are removed from /usr/kernel/drv.
  • /dev/dri is removed again. It did not exist before, and SUNW_drm_link's remove rule does not match card0, so devfsadm -C leaves the links.
  • /etc driver and devlink files have the same SHA-256 as before (20260928-r0-vm-state-before.log and -after.log).
  • Only /root/illumos-drm-test (8.7 MB) remains; /root/illumos-ddi-test is untouched.

All log paths are under logs/oibldofgate/ in the owner's workspace (not in this repository).

Concurrency argument

All device state lives in one DeviceState behind one ddi::Mutex. Each open, close and ioctl does its permission check and state change in one critical section; user copies happen outside it; table growth with KM_SLEEP is the only blocking operation under the lock. specfs guarantees one close(9E) per clone after the last reference, never concurrent with an ioctl on the same file. The full argument is at the top of crates/drm/src/device.rs. It is tested on the host (threads with an invariant check after every step) and in the kernel (the stress run above).

Open issues

  • Linux refuses ATOMIC=1 to processes named X* (the Xorg modesetting DDX). That needs the process name, which no committed DDI interface provides; it is noted in caps.rs for R3.
  • gfx-drm's SUNW_drm_link remove rule (^dri/[a-z]D?[0-9]+$) does not match card0 or renderD128, so devfsadm -C leaves dangling /dev/dri links after rem_drv. This is a gfx-drm bug worth fixing there.
  • system/header/header-drm ships gfx-drm's old kernel drm.h, not the libdrm 2.4.109 headers libdrm is built with. Packaging should deliver the patched 2.4.109 headers.
  • For driver ioctls larger than 255 bytes (R4), libdrm's drmCommandWriteRead uses the 14-bit _IOC size field from the illumos drm.h patch, not _IOWR. The dispatch for those must accept that encoding.
  • illumos-ddi pull request illumos/illumos-ddi#1 (the drm-r0 branch) must merge before this pins illumos-ddi main.
  • illumos libdrm strips the clone bits only in drmGetDeviceNameFromFd. _sun_drm_find_device (used by drmGetDeviceNameFromFd2 and drmGetDevice2) and the stock drmGetMinorType do not, so clone fds will likely not resolve there. That matters for Mesa's loader in R4 and needs a libdrm fix in gfx-drm or a different minor layout.
  • libdrm 2.4.109's virtgpu_drm.h has no RESOURCE_CREATE_BLOB or CONTEXT_INIT (added in later libdrm), so their structures and numbers are not in drm-uapi yet; R4 must add them from a newer header with the same layout proof.
  • CI has not run on this repository yet.

Anima work item: WI-452 01a0e386-ea75-7031-82e8-cdcc4baf4ddd

Milestone R0 of the Rust DRM core (Anima WI-452): the uAPI crate, the DRM core library with card and render minors, per-file state and the core ioctls, a `Driver` trait and a null driver. It was built, loaded and tested with libdrm 2.4.109 on oibldofgate. ## What was built - **`crates/drm-uapi`**: the `#[repr(C)]` structures and constants of `drm.h`, `drm_mode.h` and `virtgpu_drm.h` from libdrm 2.4.109. - Generated with bindgen (`tools/uapi/generate.sh`, target `x86_64-pc-solaris2.11`, illumos-gate system headers) from the release gfx-drm builds (same SHA-256), with gfx-drm's illumos `drm.h` patch vendored in `tools/uapi/patches`. - The 115 `DRM_IOCTL_*` numbers are generated from the same headers in the illumos `_IOWR` encoding (`tools/uapi/gen_uapi.py`), because bindgen cannot expand function-like macros. - Static checks: bindgen's layout assertions for every structure, plus `abi.rs`, which pins 50 structures (`drm_version`, `drm_get_cap`, `drm_set_client_cap`, `drm_gem_close`, `drm_prime_handle`, all `drm_syncobj_*`, the atomic and KMS `drm_mode_*`, all `drm_virtgpu_*`) and the 11 R0 command numbers to the gcc values. - 64-bit only. - **`crates/drm`**: a `no_std` core on the `ddi` crate. - `drm::Driver` supplies the name, date, description, major/minor/patch, feature flags (`MODESET`, `ATOMIC`, `RENDER`), mode-setting properties for `GET_CAP`, and the bus ID. - `drm::Device<D>` creates `card<N>` and `renderD<128+N>`. Every open is a clone open: the clone id sits in bits 9-17, the bits illumos libdrm strips, which allows 511 files per device. - Per-file state `DrmFile` holds the authenticated flag, the master binding and the client capabilities. - Core ioctls: `VERSION`, `GET_CAP`, `SET_CLIENT_CAP`, `GET_UNIQUE`/`SET_UNIQUE`, `SET_VERSION`, `GET_MAGIC`/`AUTH_MAGIC`, `GET_CLIENT`, `SET_MASTER`/`DROP_MASTER`. They follow Linux's permission flags and semantics: per-master magic namespaces, render nodes without authentication, and a bus ID that stays empty until interface 1.1 is set. - `drv_priv(9F)` runs on the ioctl path only when a permission needs it (`SET_UNIQUE`, and `SET_MASTER`/`DROP_MASTER` by a file that is not a former master of the same process), as Linux calls `capable()` lazily. - `GET_CAP` reports `ADDFB2_MODIFIERS`, `DUMB_PREFERRED_DEPTH` and `DUMB_PREFER_SHADOW` as 0 until the ioctls they describe exist (R1/R3), whatever the driver's `ModeConfig`. - The ioctl path uses no slice or table indexing: `get`/`get_mut` with an errno on the unreachable branches. - Unknown commands fail with `ENOTTY`. Commands are matched on their full 32-bit value, since illumos encodes only 8 bits of the size. Core commands from 32-bit callers (`ddi_model_convert_from`) fail with `ENOTSUP`; other commands from them fail with `ENOTTY`. - **`examples/rdrmnull`**: the null driver. It registers one device (card0 and renderD128), declares `MODESET`, `ATOMIC` and `RENDER`, and has no hardware. It is the kernel module that is built and loaded, and it is a test module, not something to deploy. `packaging/devlink.tab` holds the `/dev/dri` rule for packages. - **`xtask`**: vendored from illumos-ddi `eb18ab93b7` (build, link, deps, kernel-check), with a header comment that lists the changes. `lint-man` is not vendored (there is no `-sys` crate here). `uapi-dump` and `uapi-check` are new. - **Tests and CI**: `tools/test/drm_r0_test.c` is the libdrm test and `tools/test/run-r0-test.sh` the VM procedure. The two C programs are local test tools, not deliverables. `.forgejo/workflows/ci.yml` follows illumos-ddi's setup (hosted check, clippy, fmt, doc, tests and uapi-check; native illumos build and link of rdrmnull). CI has not run yet. ## Deviations from the work item text 1. **Module structure.** The core is a library linked statically into each driver module; there is no separate `misc/drm` kernel module. - Exporting Rust code to other kernel modules needs a stable, versioned C ABI. Mangled and generic Rust does not cross kmod boundaries, and designing that ABI is the R5 C-facade work. - `misc/drm` is therefore deferred to R5. Until then, static linking costs nothing, since one Rust driver comes first. 2. **Node type.** The nodes use `ddi_display:drm` (`DDI_NT_DISPLAY_DRM`), not plain `ddi_display`. - The plain type triggers devfsadm `misc_link`'s `display()` handler, which adds `/dev/fbs/<minor>` and `/dev/fb<N>` console framebuffer links. The VM already has `/dev/fb0 -> fbs/text-0`. - `ddi_display:drm` is gfx-drm's convention. OpenIndiana's `driver/graphics/drm` ships `SUNW_drm_link`, which created `/dev/dri/card0` and `/dev/dri/renderD128` from it, so no `/etc/devlink.tab` change was needed on the VM. - The equivalent devlink.tab rule for systems without that module is in `packaging/devlink.tab`. 3. **Extra ioctls.** - `SET_VERSION` is added because libdrm calls it on primary nodes. - `GET_CLIENT` (index 0, as on current Linux) is an addition beyond the work item, so that the result of authentication can be observed. 4. **Layout reference headers.** The VM has libdrm 2.4.109 (`x11/library/libdrm`) but no `drm.h`, `drm_mode.h` or `virtgpu_drm.h`: they belong to `system/header/header-drm`, which is not installed, and I installed nothing. The C layout program was therefore compiled with gcc 14.3.0 on the VM against the patched libdrm 2.4.109 headers, copied to `/root/illumos-drm-test/uapi/drm`. That patched header is exactly what gfx-drm compiles the installed `libdrm.so` against. ## illumos-ddi change The dependency is pinned to illumos-ddi branch `drm-r0`, rev `e019d5bd3c`: `main` plus the following. It is under review as https://code.aopc.cloud/illumos/illumos-ddi/pulls/1; once that merges, this repository pins illumos-ddi `main`. - `8463d9da92` adds `CharDevice::clone_open` to the module glue. The driver returns a fresh minor, and the glue stores `makedevice(getmajor(dev), minor)` through `devp`. The default calls `open` and keeps the device number, so existing drivers are unaffected. - `f36d47958e` documents the 32-bit `dev_t` limit for clone minors. - `e019d5bd3c` adds `clone_open` to the `CharDevice` description in `docs/DESIGN.md` §5.2. ## Verification Host: Mac, aarch64-apple-darwin, Rust 1.97.1. VM: oibldofgate, illumos-cda5b9a49f, OpenIndiana Hipster 2026.04, root, global zone. | Step | Result | Log | | --- | --- | --- | | `cargo check --workspace --target x86_64-unknown-illumos` | pass | `logs/oibldofgate/20260928-r0-local-checks.log` | | `cargo clippy --workspace --target x86_64-unknown-illumos -- -D warnings` | pass | same | | `cargo fmt --all -- --check` | pass | same | | `cargo doc` with `-D warnings` | pass | same | | `cargo test -p drm -p drm-uapi` (host) | 23 + 1 tests pass, including the 8-thread state-machine test with invariant checks after every step and a test that the privilege check is not evaluated for unprivileged operations | same | | `cargo xtask build rdrmnull` (nightly-2026-04-16, build-std) | pass; archive sha256 `86823733d8f9…`, identical on the VM (`digest` in the load-test log) | same | | uAPI layout: gcc on the VM vs Rust (`cargo xtask uapi-check`) | 103 sizes, 448 offsets, 115 ioctl numbers identical, 0 differences | `20260928-r0-uapi-layout.log` | | `xtask kernel-check --verify` against `mdb -k` (ddi-sys at e019d5bd3c) | sizes 53/60, offsets 185/185, symbols 983/984. No mismatch; 7 anonymous CTF unions are checked through their parents, and `ddi_ncpus_expected` postdates this kernel. Same as upstream's 26 Sep result | `20260928-r0-kernel-check.log` | | `ld -ztype=kmod`, then each undefined symbol resolved with `::whatis` | 32 imported symbols, 0 unresolved (an earlier revision of this description said 33; the `crgetuid` import went away in `1a0f6eb`) | `20260928-r0-rdrmnull-load-test.log` | | `add_drv -m '* 0666 root sys' rdrmnull`, libdrm test as root and as nobody, 32-bit probe | 104 PASS, 0 FAIL | same | | Unload refused while a file is open: `drm_r0_test hold /dev/dri/card0`, then `modunload -i <id>` | refused with "Device busy"; after closing, `modunload` succeeds, the next open reattaches | same | | `rem_drv rdrmnull` | unloaded cleanly | same | The R0 structures have no kernel CTF counterpart; they are userland ABI, and the gcc comparison is their layout proof. The DDI layouts the module relies on are checked by kernel-check. `ls -l /dev/dri` with the module loaded: ``` card0 -> ../../devices/pseudo/rdrmnull@0:card0 (crw-rw-rw- root sys 269,0) renderD128 -> ../../devices/pseudo/rdrmnull@0:renderD128 (crw-rw-rw- root sys 269,128) ``` What the libdrm test showed: - `drmOpen("rdrmnull", NULL)` opened card0 as master. - `drmGetDeviceNameFromFd` mapped the clone minor `0x200` back to `/dev/dri/card0`. - `drmGetVersion` returned 1.0.0 "rdrmnull" "20260928". - `drmGetCap`: TIMESTAMP_MONOTONIC 1; PRIME, SYNCOBJ, SYNCOBJ_TIMELINE, DUMB_BUFFER, ADDFB2_MODIFIERS and CRTC_IN_VBLANK_EVENT 0; CURSOR_WIDTH and CURSOR_HEIGHT 64; unknown capabilities EINVAL. - `drmSetClientCap` accepted and recorded all five capabilities with the Linux rules; bad values were EINVAL. - `drmSetInterfaceVersion(1.4)` made `drmGetBusid` report `pseudo:rdrmnull@0`. `drmSetBusid` returned EINVAL for a foreign ID and EBUSY once set. - Magic handshake: a nobody client got a magic and was unauthenticated; the root master's `drmAuthMagic` authenticated it (checked with `drmGetClient`); reusing the magic was EINVAL. The client's AuthMagic, SetMaster and DropMaster were EACCES. - Master hand-over worked, with EBUSY and EINVAL where expected. - On `drmOpenRender(128)`, as root and as nobody, version and caps work; GET_MAGIC, SET_CLIENT_CAP, GET_UNIQUE and SET_MASTER are EACCES. - Unknown ioctl, wrong-size VERSION and GEM_CLOSE returned ENOTTY; a bad pointer returned EFAULT. - Stress: 8 nobody threads did 3200 opens with 0 unexpected errors; every successful authentication (104 in the final run) was checked with `drmGetClient`. Meanwhile the root process dropped and retook master concurrently: 1878 retaken, 468 EBUSY. The number of authentications varies between runs with how often the master is held at the moment of `AUTH_MAGIC` (1025 in the run at `1a0f6eb`). - The 511-file limit held, then EMFILE. - 32-bit probe: VERSION and GET_CAP were ENOTSUP; an unknown DRM ioctl was ENOTTY; `isatty` was false. - While a file was held open, `modunload` of rdrmnull failed with "Device busy"; it succeeded once the file was closed. rem_drv is not used for this check, because it removes the driver configuration even when the unload fails. VM left in its prior state: - rdrmnull is not loaded and its files are removed from `/usr/kernel/drv`. - `/dev/dri` is removed again. It did not exist before, and `SUNW_drm_link`'s remove rule does not match `card0`, so `devfsadm -C` leaves the links. - `/etc` driver and devlink files have the same SHA-256 as before (`20260928-r0-vm-state-before.log` and `-after.log`). - Only `/root/illumos-drm-test` (8.7 MB) remains; `/root/illumos-ddi-test` is untouched. All log paths are under `logs/oibldofgate/` in the owner's workspace (not in this repository). ## Concurrency argument All device state lives in one `DeviceState` behind one `ddi::Mutex`. Each open, close and ioctl does its permission check and state change in one critical section; user copies happen outside it; table growth with `KM_SLEEP` is the only blocking operation under the lock. specfs guarantees one `close(9E)` per clone after the last reference, never concurrent with an ioctl on the same file. The full argument is at the top of `crates/drm/src/device.rs`. It is tested on the host (threads with an invariant check after every step) and in the kernel (the stress run above). ## Open issues - Linux refuses `ATOMIC=1` to processes named `X*` (the Xorg modesetting DDX). That needs the process name, which no committed DDI interface provides; it is noted in `caps.rs` for R3. - gfx-drm's `SUNW_drm_link` remove rule (`^dri/[a-z]D?[0-9]+$`) does not match `card0` or `renderD128`, so `devfsadm -C` leaves dangling `/dev/dri` links after `rem_drv`. This is a gfx-drm bug worth fixing there. - `system/header/header-drm` ships gfx-drm's old kernel `drm.h`, not the libdrm 2.4.109 headers libdrm is built with. Packaging should deliver the patched 2.4.109 headers. - For driver ioctls larger than 255 bytes (R4), libdrm's `drmCommandWriteRead` uses the 14-bit `_IOC` size field from the illumos drm.h patch, not `_IOWR`. The dispatch for those must accept that encoding. - illumos-ddi pull request https://code.aopc.cloud/illumos/illumos-ddi/pulls/1 (the `drm-r0` branch) must merge before this pins illumos-ddi `main`. - illumos libdrm strips the clone bits only in `drmGetDeviceNameFromFd`. `_sun_drm_find_device` (used by `drmGetDeviceNameFromFd2` and `drmGetDevice2`) and the stock `drmGetMinorType` do not, so clone fds will likely not resolve there. That matters for Mesa's loader in R4 and needs a libdrm fix in gfx-drm or a different minor layout. - libdrm 2.4.109's `virtgpu_drm.h` has no `RESOURCE_CREATE_BLOB` or `CONTEXT_INIT` (added in later libdrm), so their structures and numbers are not in `drm-uapi` yet; R4 must add them from a newer header with the same layout proof. - CI has not run on this repository yet. Anima work item: WI-452 `01a0e386-ea75-7031-82e8-cdcc4baf4ddd`
drm-uapi holds the DRM userland interface as Rust types: the repr(C)
argument structures and constants of drm.h, drm_mode.h and
virtgpu_drm.h from libdrm 2.4.109, generated with bindgen for
x86_64-pc-solaris2.11 from the headers with gfx-drm's illumos patch
(64-bit drm_handle_t), and the DRM_IOCTL_* numbers in the illumos
_IOWR encoding, generated from the same headers because bindgen cannot
expand function-like macros.

tools/uapi/generate.sh fetches the release gfx-drm builds (same URL
and SHA-256), applies the vendored patch and regenerates everything.
The layout proof is mechanical: tools/uapi/drm_layout.c prints every
size, member offset and ioctl number with gcc on illumos, and
`cargo xtask uapi-check` compares that output with the Rust
definitions (103 sizes, 448 offsets, 115 ioctl numbers identical on
oibldofgate). abi.rs pins the structures the core and the planned
drivers need to the gcc values at compile time.

xtask is vendored from illumos-ddi eb18ab93b7 (build, link, deps,
kernel-check), changed to read the whole cargo metadata graph because
ddi-sys will come from a git dependency; lint-man is left out, and
uapi-dump/uapi-check are new.
The drm crate is a no_std library on the ddi crate, linked into each
driver module. A driver implements drm::Driver (name, date,
description, version, feature flags, mode-setting properties, bus ID)
and uses drm::Device as the instance of its ddi::Driver.

The device creates card<N> and renderD<128+N> with node type
ddi_display:drm and makes every open a clone open (clone id in bits
9-17, the bits illumos libdrm strips), so each file has its own state
and its own close(9E). The core answers VERSION, GET_CAP,
SET_CLIENT_CAP, GET_UNIQUE, SET_UNIQUE, SET_VERSION, GET_MAGIC,
AUTH_MAGIC, GET_CLIENT, SET_MASTER and DROP_MASTER with Linux's
permission flags and semantics (per-master magic namespaces, render
nodes without authentication). Commands are matched on their full
value; anything else is ENOTTY, and 32-bit callers get ENOTSUP.

All device state is one DeviceState behind one ddi::Mutex, with each
permission check and state change in one critical section and user
copies outside it. The state machine, capabilities, ioctl table and
minor layout are plain data with host unit tests, including an
eight-thread test that checks the invariants after every step.
Allocations go through KVec, whose growth takes the ddi context token
and reports failure instead of panicking.

ddi and ddi-sys are a git dependency pinned to illumos-ddi's drm-r0
branch (8463d9da92), which adds CharDevice::clone_open to the glue.
rdrmnull is a DRM driver without hardware: one pseudo device with
card0 and renderD128, declaring MODESET, ATOMIC and RENDER so that
every capability path of the core can be exercised. It is the kernel
module the core is tested with and the template for real drivers.

packaging/devlink.tab has the /etc/devlink.tab rule for systems
without OpenIndiana's SUNW_drm_link devfsadm module, which otherwise
links ddi_display:drm nodes as /dev/dri/<minor name>.

tools/test/drm_r0_test.c exercises the core through the installed
libdrm 2.4.109 as root and as nobody: drmOpen by name, version,
capabilities, client capabilities, interface version and bus ID, the
magic handshake between an unprivileged client and the master, master
hand-over, render nodes, error paths, eight concurrent threads, the
511-file limit and a 32-bit caller. tools/test/run-r0-test.sh links,
loads, tests and unloads the module on an illumos VM and undoes every
system change.
The README records the state of milestone R0 and the decisions behind
it: the pinned git dependency on illumos-ddi's drm-r0 branch, the
vendored xtask, how the uAPI is generated and proven against gcc, the
core as a statically linked library instead of a misc/drm module
(deferred to R5, where the C interface is designed), the
ddi_display:drm node type, the minor number layout and the ioctl
permission table.

The CI workflow follows illumos-ddi's: hosted check, clippy, fmt, doc,
host tests and the uAPI comparison, and a native illumos job that
builds and links rdrmnull.
Address review findings in the core and the kernel test
Some checks failed
ci / kmod-illumos (pull_request) Has been cancelled
ci / check-hosted (pull_request) Has been cancelled
1a0f6eba04
- UserArg::get_mut is unsafe, with the contract that handlers only
  assign fields, which is what keeps padding bytes from carrying kernel
  stack contents to the caller.
- SET_CLIENT_CAP accepts ATOMIC = 2 as Linux does; the Xorg process-name
  rule needs a private interface and is noted for R3.
- 32-bit callers get ENOTSUP only for core commands (matched by group
  and number) and ENOTTY for everything else, so isatty() and other
  generic ioctls behave normally.
- GET_CAP answers EOPNOTSUPP for every non-generic capability of a
  driver without MODESET, as Linux does.
- GET_CLIENT reports the caller's pid and the anonymous uid, as Linux
  does, instead of the opener's identity; the per-file uid is gone.
- Magic numbers skip every number an open file still reports, so a
  consumed magic is never reissued after the counter wraps (new test).
- The kernel stress test runs eight unprivileged threads that open,
  authenticate through the inherited master file and check the result,
  while the root process drops and retakes master concurrently.
- illumos-ddi is pinned to f36d47958e (drm-r0 plus a note on the
  32-bit dev_t limit for clone minors).
- drv_priv(9F) runs on the ioctl path only when a permission needs it:
  ROOT_ONLY ioctls, and SET_MASTER/DROP_MASTER by a file that is not a
  former master of the same process, as Linux calls capable() lazily.
  permit, set_master and drop_master take the check as a closure; a new
  test asserts it is not evaluated otherwise.
- GET_CAP reports ADDFB2_MODIFIERS, DUMB_PREFERRED_DEPTH and
  DUMB_PREFER_SHADOW as 0 until the ioctls they describe exist (R1/R3);
  ModeConfig documents when its fields take effect.
- The state table, master reference counts, SET_UNIQUE's buffer and the
  VERSION string copy use get/get_mut instead of indexing, with an errno
  (and the master binding undone) on the unreachable branches, so the
  absence of panics on the ioctl path does not rest on invariants.
Test unload refusal, log the archive digest, pin illumos-ddi e019d5bd3c
Some checks failed
ci / kmod-illumos (pull_request) Successful in 9m20s
ci / check-hosted (pull_request) Failing after 11m44s
13d9998dee
- run-r0-test.sh records the SHA-256 of the archive and the linked
  module, and checks with a file held open (drm_r0_test hold) that
  modunload is refused, then succeeds once the file is closed, before
  the final rem_drv. rem_drv is not used for the refusal because it
  removes the driver configuration even when the unload fails.
- README: GET_CLIENT is marked as an addition beyond the work item, the
  32-bit sentence says core commands only (ENOTTY otherwise), and the
  capability and privilege rules follow the code.
- illumos-ddi is pinned to e019d5bd3c (drm-r0: clone_open described in
  docs/DESIGN.md), under review as illumos-ddi pull request 1.
forge merged commit 29209d042f into main 2026-09-28 18:11:01 +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!1
No description provided.