Allocate device resources only from the driver's own node (OwnNode) #2
Loading…
Reference in a new issue
No description provided.
Delete branch "dma-own-node"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Stacked on #1 (
drm-r0, clone opens and devmap): the base isdrm-r0, so this diff shows only the own-node change. Merge #1 first.The panic this prevents
On 2026-09-28 a test kernel (illumos
cda5b9a49f) panicked withBAD TRAP: type=e (#pf Page fault) ... addr=0 ... NULL pointer dereference,pc=0, insideddi_dma_addr_bind_handle. The illumos-drmrdrmnullDMA drill had allocated its DMA handles againstddi_root_node(9F)instead of its own node. Rootnex accepts the root as its own requester, so the allocation succeeds. The bind then calls the root node'sdevi_bus_dma_bindfunc, which is never filled because the root has no parent.ddi_dma_alloc_handle(9F)requires "the device's dev_info structure" and says "a DMA handle must only be used by the device for which it was allocated".notes/illumos-drm-dma-panic-20260928.mdThe safe layer reached the root node only through
unsafe { DevInfo::from_raw(...) }. However,DevInfoisCopy + Send + Syncand names no owner, so safe code could still pass another instance's node, and no documentation stated the own-node rule.The rule (docs/DESIGN.md, new section 5.12 "Panics become types")
Every kernel panic traced to DDI misuse must become impossible in the safe layer. Each such panic gets four things, made together:
unsafecompile_faildoctest (error code pinned, with a compiling twin) and anrselftestcheck that cannot panic even if the guard is missingThis panic is the first row.
API change
OwnNode(crates/ddi/src/device.rs) is the capability for the driver's own attached node.CopynorClone. It derefs toDevInfofor queries.module!glue creates it, inattach(9E)withDDI_ATTACH. The glue also recordsddi_driver_majorthere, for the guard.unsafe OwnNode::from_raw, whose contract names the rule. Its only legitimate use is testing the guard.Driver::attach(node: OwnNode, ctx)replacesattach(dev: DevInfo, ctx). A driver keepsnodein its instance state if it allocates device resources later, as the DRM core does.rnullandrselftestare adapted.OwnNode::is_plausibly_own(), crate-private, refuses a node without a parent (the root, detected withddi_get_parent). It also refuses a node whoseddi_driver_majordiffers from the major recorded at attach.ddi, so the value is per module.DmaHandle::new/try_newtake&OwnNode.DmaError::NotOwnNode.BadAttr, which is observed behaviour.impl From<DmaError> for Errnois new:NotOwnNode→ENXIOBadAttrand the other invalid requests →EINVALNoResourcesandTooBig→ENOMEMNoMapping→EFAULTInUseandLocked→EBUSYEIO-sysitems:ddi_get_parent,ddi_driver_majorandddi_root_nodewere already bound.Per-API decisions
DmaHandle::new,try_new&OwnNode+ guard (NotOwnNode)Interrupts::alloc,try_alloc&OwnNode+ guard (IntrError::Invalid)ddi_intr_alloc→i_ddi_intr_opsdereferences the node's parent (NEXUS_HAS_INTR_OP(NULL)), so the root node panics too. The vectors belong to the requester.RegMap::map,RegBlock::map&OwnNode+ guard (DdiError)ddi_regs_map_setup→ddi_mapdereferencesdevi_parent, so the root node panics. A foreign node would map another device's registers.PciConfig::setup&OwnNode+ guard (DdiError)pci_config_setupisddi_regs_map_setup(dip, 0, ...), the same path.intr_types,intr_count,intr_available,nregs,regsize, props, names,instance, loggingDevInfoDevInfostill needsunsafe DevInfo::from_raw, whose contract is unchanged.SoftInterrupt::addDevInfoddi_intr_add_softintuses the node only for its name (add_avsoftintr) and does not go through the parent.TaskQueue::newDevInfoddi_taskq_createuses the node only to build the queue name.LdiIdent::from_dipDevInfoDevInfo::create_minor_nodeDevInfokstat_createtakes module name and instance.devmap_umem_setup(9F)takes a dip but is called raw by drivers (illumos-drm). Follow-up below.Tests and evidence
Logs are in the workspace under
logs/hephaistos/illumos-drm/20260928-ddi-own-node-*.cargo check,clippy -D warnings,fmt --checkanddoc -D warningsonx86_64-unknown-illumos: clean.cargo test -p ddi --no-default-features --lib: 6 passed (guard decision, errno mapping, attribute checks).x86_64-unknown-illumos: 5 passed.cargo +nightly-2026-04-16 test -p ddi --no-default-features --doc: 4compile_failpassed with their error codes enforced (E0308DevInfoto DMA, E0507 copy, E0277 clone, E0133 safefrom_raw), plus the compiling twin.node.clone()cloned the reference), and that test was fixed.cargo test -p ddi --no-default-features --liband the illumos-target doctests. No CI step ran tests before this. The--librun on the Linux runner has not been verified yet.cargo xtask lint-man --coverage(gate0371167eed): 0 errors, 1183/1183.cargo xtask kernel-checkagainst the guest kernel (cda5b9a49f): no size, offset or symbol disagreed.ddi-sys983/984 symbols, with the knownddi_ncpus_expectednote.ddi-gld-sysis 14/14 oncemisc/gldis loaded.ddi-ktest-syssymbols.drv/ktestis not installed on the image, as in section 8.1.cda5b9a49f): 38/38 checks at attach and 7/7 LDI checks, 0 failed, no panic, cleanrem_drv.newandtry_new)b24c307, tested on the same guest type):dma-mode="none"and"self"both gave R0 105, R1 153 and probes 5 and 4 PASS with no test FAIL.selfleaves the knownpath_to_instresidue.panic-drillbuild withroot-nodestill panics with the same panic string and root-node address, and savecore keeps the dump.root-nodeat attach without a panic.dba0697) changes docs only.Follow-ups
LdiIdent::from_dip,DevInfo::create_minor_nodeand property updates could move toOwnNode. They tie identity, minor nodes or properties to a node, but no panic is known, so they were left out to keep this change small.devmap_umem_setup(9F)anddevmap_devmem_setup(9F)behindOwnNode, since drivers call them raw today.compile_failerror codes are only enforced on nightly. The CI's native illumos job has the pinned nightly and could run the doctests.Driver::Instance: 'static(section 5.12).ddi_dma_addr_bind_handlecould refuse a handle whoserdipis the root instead of calling through NULL. That is a separate, local-only gate patch under the owner's rule and is not part of this PR.