kernel: maximum_children_per_parent is gone

The second invented ceiling. It was written to stop a driver looping
device_register and exhausting a shared table — but there is no shared table
to exhaust any more, and each registrar already has its own allowance, so a
runaway costs only itself.

It never bounded a determined caller in the first place: 16 children per
parent, and nothing stopped it claiming more parents. What it reliably did
was refuse a real PCI bus with more than 16 functions, which is how an AMD
Ryzen booted with a working display, no USB and no storage.

The constant, its check, and the now-unused childCount all go. TooManyChildren
survives with one meaning instead of two: the caller is at its per-registrar
allowance.

This was unblocked from the moment D9 landed. The plan said so — "once the
quota exists the per-parent cap is redundant whether or not D6 has landed" —
in the same edit that left the step tagged "blocked on D6". Three iterations
were then spent re-reading that tag instead of the sentence beside it.

The containment test now asserts 64 children under one parent, four times
the old ceiling; restoring the cap fails it.

Suite 118/118.
This commit is contained in:
Daniel Samson
2026-08-08 21:12:20 +01:00
parent 3ae541214f
commit 7d8aa51234
3 changed files with 54 additions and 75 deletions
+1 -26
View File
@@ -53,22 +53,6 @@ const heap = @import("heap.zig");
/// found against registered)
const maximum_devices_per_registrar = 4096;
/// Cap on children a single parent may have. A zero-resource child (legal — a USB
/// device is addressed through its controller, not by MMIO) sidesteps the containment
/// check, so without a bound a process that claimed one device could loop
/// `device_register` and exhaust the whole table, permanently denying it to every other
/// driver. This bounds the blast radius of one claim; a real quota (and a
/// `device_release` to reclaim on exit) is future work — see docs/driver-model.md.
///
/// bound: children one claimed parent may register — in practice every PCI function on
/// the machine, since pci-bus registers them all under the one host bridge
/// decided-by: hardware
/// protects: the shared device table, against a driver looping device_register — but
/// it is a proxy for an authorisation the kernel does not perform, since any
/// process may claim any unclaimed device (docs/bounds-track-plan.md phase 2)
/// at-limit: refuse — ECHILDREN, distinct from a full table
/// observed-by: pci-bus logs the reason per refused function, and warns at end of scan
const maximum_children_per_parent = 16;
/// The table, grown on demand from the kernel heap. **There is no ceiling**: how many
/// devices a machine has is the machine's business, and no specification bounds it, so
@@ -368,7 +352,7 @@ pub const RegisterError = error{
NoSuchParent, // no device with that id
NotYourParent, // that device exists but this task has not claimed it
TooManyResources, // the descriptor declares more resources than one device may hold
TooManyChildren, // this parent is at maximum_children_per_parent
TooManyChildren, // the caller is at its per-registrar allowance
NotContained, // a child resource escapes its parent's window
};
@@ -459,14 +443,6 @@ fn existingChild(parent_id: u64, descriptor: *const device_abi.DeviceDescriptor)
return null;
}
/// Number of devices currently recorded with `parent_id` as their parent.
fn childCount(parent_id: u64) usize {
var n: usize = 0;
for (devices[0..count]) |d| {
if (d.parent == parent_id) n += 1;
}
return n;
}
/// Publish `descriptor` as a child of `parent_id`, on behalf of `owner`. Returns the new
/// device id. The child is left **unclaimed**, so another process (a class driver)
@@ -497,7 +473,6 @@ pub fn register(parent_id: u64, owner: u32, descriptor: *const device_abi.Device
// below).
if (existingChild(parent_id, descriptor)) |existing_id| return existing_id;
if (childCount(parent_id) >= maximum_children_per_parent) return error.TooManyChildren;
// The allowance is charged to whoever is registering, so a driver in a loop
// exhausts its own and every other driver carries on. There is no machine-wide
// ceiling any more: the table grows.