chpoll(9E) in the CharDevice glue with pinned pollheads #4

Merged
toasterson merged 3 commits from r3-kms-glue into main 2026-10-05 16:33:01 +00:00
Contributor

Adds chpoll(9E) to the CharDevice glue, for the illumos-drm R3 event queue (read(2)/poll(2) on DRM nodes).

What

  • ddi::poll: PollEvents, PollHead (!Unpin, destructor calls pollhead_clean(9F), wakeup wraps pollwakeup(9F), clean for reused heads), PollHeads (a kmem_zalloc(9F) array of pinned heads that lives as long as the instance), PollRegistration<'a>.
  • CharDevice::chpoll<'a>(&'a self, minor, events, anyyet) -> Result<(PollEvents, PollRegistration<'a>), Errno>; default ENXIO (as nochpoll(9F)). Both ops: [char] forms install the glue as cb_chpoll, so existing drivers are unchanged.
  • The glue, not the driver, decides when the kernel receives the pollhead (nothing ready and anyyet zero, or POLLET), and masks the reported events to the requested ones plus POLLERR/POLLHUP.
  • KBox::pin.
  • docs/DESIGN.md §5.2 describes the rules.

Type rules

The kernel keeps pointers into a pollhead after chpoll(9E) returns, until pollhead_clean(9F). A head that moves, is freed, or lives on the stack of chpoll leaves dangling pointers for pollwakeup(9F) and the next poll(2). In safe code that cannot be written: a registration needs a Pin<&'a PollHead> whose lifetime is the instance's (compile_fail doctests: E0277 for an unpinned head, E0515 for a stack head), and dropping a head cleans it. No panic happened; these are preventive rules, so there is no row in the "Panics prevented" table and no rselftest check yet.

Base

Stacked on loose-ends-20260928 (449bfe7, PR 3), which is not merged yet; the diff against main shows those commits too until it is. Own commits: 6c1d738, 5d64181, 5c792b3.

Verification

cargo test -p ddi --no-default-features --lib (9), cargo +nightly-2026-04-16 test -p ddi --no-default-features --doc --target x86_64-unknown-illumos (15, including both compile_fail), clippy -D warnings, fmt, rustdoc -D warnings. In the kernel through illumos-drm R3 on illumos cda5b9a49f: poll/read tests, a close-while-pending churn and modetest -v (select-driven page flips at 60 Hz) pass; see the illumos-drm PR.

Reviewed locally against the Anima review bar in two rounds (round 1 found the missing lifetime on PollRegistration; fixed in 5c792b3).

Adds `chpoll(9E)` to the `CharDevice` glue, for the illumos-drm R3 event queue (`read(2)`/`poll(2)` on DRM nodes). ## What - `ddi::poll`: `PollEvents`, `PollHead` (`!Unpin`, destructor calls `pollhead_clean(9F)`, `wakeup` wraps `pollwakeup(9F)`, `clean` for reused heads), `PollHeads` (a `kmem_zalloc(9F)` array of pinned heads that lives as long as the instance), `PollRegistration<'a>`. - `CharDevice::chpoll<'a>(&'a self, minor, events, anyyet) -> Result<(PollEvents, PollRegistration<'a>), Errno>`; default `ENXIO` (as `nochpoll(9F)`). Both `ops: [char]` forms install the glue as `cb_chpoll`, so existing drivers are unchanged. - The glue, not the driver, decides when the kernel receives the pollhead (nothing ready and `anyyet` zero, or `POLLET`), and masks the reported events to the requested ones plus `POLLERR`/`POLLHUP`. - `KBox::pin`. - `docs/DESIGN.md` §5.2 describes the rules. ## Type rules The kernel keeps pointers into a pollhead after `chpoll(9E)` returns, until `pollhead_clean(9F)`. A head that moves, is freed, or lives on the stack of `chpoll` leaves dangling pointers for `pollwakeup(9F)` and the next `poll(2)`. In safe code that cannot be written: a registration needs a `Pin<&'a PollHead>` whose lifetime is the instance's (`compile_fail` doctests: E0277 for an unpinned head, E0515 for a stack head), and dropping a head cleans it. No panic happened; these are preventive rules, so there is no row in the "Panics prevented" table and no rselftest check yet. ## Base Stacked on `loose-ends-20260928` (`449bfe7`, PR 3), which is not merged yet; the diff against `main` shows those commits too until it is. Own commits: `6c1d738`, `5d64181`, `5c792b3`. ## Verification `cargo test -p ddi --no-default-features --lib` (9), `cargo +nightly-2026-04-16 test -p ddi --no-default-features --doc --target x86_64-unknown-illumos` (15, including both `compile_fail`), clippy `-D warnings`, fmt, rustdoc `-D warnings`. In the kernel through illumos-drm R3 on illumos `cda5b9a49f`: poll/read tests, a close-while-pending churn and `modetest -v` (select-driven page flips at 60 Hz) pass; see the illumos-drm PR. Reviewed locally against the Anima review bar in two rounds (round 1 found the missing lifetime on `PollRegistration`; fixed in `5c792b3`).
The DRM core lives in illumos-drm. R0 and R1 needed only the public
tier; PRIME, sync_file, virtio-gpu, the console hand-over and
write-combining page pools need illumos-kernel-private-sys, which
therefore moves ahead of the other planned crates.
The kernel ties each of these to the node and its driver: detach_node
(os/devcfg.c) removes a node's minor nodes and driver properties when
that node's driver detaches, a minor node takes the major number of the
node's driver, and ldi_ident_from_dip keeps the node without a hold for
the layered usage walker (os/driver_lyr.c).

- OwnNode::create_minor_node replaces DevInfo::create_minor_node.
- Props takes the access as a type parameter: DevInfo::props gives a
  ReadOnly view for lookups, OwnNode::props a ReadWrite view that also
  updates and removes. remove and remove_all now return a Result.
- LdiIdent::from_dip takes &OwnNode.
- Each applies the own-node guard: DdiError, PropError::NotOwnNode and
  ENXIO before the kernel is called.
- DevmapHandle documents that the devmap setup functions need the own
  node (devmem_setup maps through the parent nexus; umem_setup ignores
  the node); they stay unwrapped.

compile_fail doctests cover each type rule, rselftest checks each guard
against the root node, and DESIGN.md section 5.12 records the decision
for every interface that takes a node.
Stable rustdoc does not check the error code of a compile_fail doctest,
so a type rule could fail to compile for the wrong reason unnoticed. The
check-hosted job now runs the doctests again with nightly-2026-04-16,
which checks the codes.
Record the rselftest run of the own-node guard checks
All checks were successful
ci / check-hosted (pull_request) Successful in 4m10s
ci / kmod-illumos (pull_request) Successful in 5m24s
eb0150cadc
All 49 checks (42 at attach, 7 LDI) passed on a fresh Hephaistos guest
with illumos cda5b9a49f, including the guard checks for minor nodes,
property updates and LDI identifiers.
Gate drivers write properties of other nodes: nexus drivers on children
(lofi, fcoe, npe_misc, mpt_sas) and a few leaf drivers on their parent
(sil3xxx, dnet). The own-node view cannot express that; say so in
DESIGN.md sections 5.8 and 5.12 and in the prop module, and name the
raw ddi::sys functions as the way until a child-node capability exists.
Also reflow an over-long doc line on OwnNode.
Make DmaError::check public for drivers that call raw DMA functions
All checks were successful
ci / check-hosted (pull_request) Successful in 2m40s
ci / kmod-illumos (pull_request) Successful in 2m44s
449bfe73e9
It decodes all ten DDI_DMA_* failure codes the way the safe layer does,
so a driver that binds through the raw bindings (illumos-drm binds
ddi_umem memory with ddi_dma_addr_bind_handle) decodes them
consistently. A host test covers every code.
The kernel keeps pointers into a driver's pollhead after chpoll(9E)
returns, until pollhead_clean(9F). PollHead is !Unpin, a registration
can only be taken from a pinned head, and its destructor calls
pollhead_clean(9F). CharDevice::chpoll always returns the registration;
the glue hands it to the kernel exactly when chpoll(9E) requires it and
masks the reported events. KBox::pin allocates a pinned value.
A driver that wakes pollers from interrupt context can race with the
close of the file being woken. PollHeads keeps one head per minor in a
kmem_zalloc(9F) allocation that never moves; PollHead::clean detaches a
closed file's pollers without freeing the head.
Tie a PollRegistration to the lifetime of the instance
All checks were successful
ci / check-hosted (pull_request) Successful in 3m0s
ci / kmod-illumos (pull_request) Successful in 2m59s
5c792b3229
A registration without a lifetime let safe code register a head on the
stack of chpoll, which is dropped (and cleaned) before the glue hands its
address to the kernel. PollRegistration<'a> borrows the head for the
lifetime of the &self that CharDevice::chpoll receives; a compile_fail
doctest checks the stack case. PollHeads::new reports a size overflow as
ENOMEM instead of saturating into a KM_SLEEP allocation, and the wakeup
documentation states chpoll(9E)'s lock rule.
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-ddi!4
No description provided.