establishment: block stops being a name, and enumerate learns to page
P2 of docs/establishment-planes-plan.md. usb-storage serves nameless — one process per stick cannot share an exclusive bind, and a second stick used to die silently on -EBUSY before ever helloing. Its one hello now moves both directions at once: the block-serving endpoint up, its controller channel down. fat finds its volume through the manager — a new `.consumer` role asks for the channel of the driver BOUND TO a device (distinct from the device's reporter), found by enumerating the tree for the mass-storage identity. fat stays single-volume; the boot-volume-by-content choice is M21. The conversion immediately caught a live truncation of exactly the audit's shape: ChildEntry grew to 32 bytes, one enumerate reply holds ~7, and a real tree carries a dozen ACPI nodes before the first USB child — the storage entry silently never fit (the protocol comment already said "paging joins the protocol if a tree ever outgrows one packet"). enumerate is now paged: Header.target is the start cursor, a short page is the end; device-list's page-0 read is unchanged. Grant rows move with the code: the block bind and fat's block open die, fat gains open device-manager. Gate: 18 cases green including the registry trio, device-list, and both IOMMU storage variants.
This commit is contained in:
@@ -99,10 +99,19 @@ fn identityFromReport(report: device_manager_protocol.ChildAdded) registry.Ident
|
||||
/// Whether some driver entry already serves registered device `device_id` —
|
||||
/// a re-report after a bus restart must not spawn a second instance.
|
||||
fn driverForDevice(device_id: u64) bool {
|
||||
return driverEntryForDevice(device_id) != null;
|
||||
}
|
||||
|
||||
/// The driver entry BOUND TO a device — matched and spawned for it. Distinct
|
||||
/// from the device's reporter: a mouse's provider is the bus that reported it
|
||||
/// (lineage via `children[].reporter`), while a volume's provider is the
|
||||
/// storage driver spawned FOR it — which is what a `.consumer` hello asks for.
|
||||
fn driverEntryForDevice(device_id: u64) ?*Driver {
|
||||
if (device_id == device_manager_protocol.no_device) return null;
|
||||
for (&drivers) |*driver| {
|
||||
if (driver.used and driver.device_id == device_id) return true;
|
||||
if (driver.used and driver.device_id == device_id) return driver;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- supervision -------------------------------------------------------------
|
||||
@@ -509,6 +518,24 @@ fn onHello(_: void, invocation: Invocation(device_manager_protocol.Hello), _: An
|
||||
std.log.info("refused hello (version {d}) from process {d}", .{ invocation.request.version, invocation.sender });
|
||||
return -envelope.EPROTO;
|
||||
}
|
||||
// A consumer is not a spawned driver: no entry, no deadline, no state —
|
||||
// just establishment. It asks for the channel of the driver BOUND TO its
|
||||
// target (fat asking for its volume's block provider). No channel is a
|
||||
// retryable ack, exactly as for a device-role consumer.
|
||||
//
|
||||
// The residual, stated plainly: any process granted `open device-manager`
|
||||
// can ask. The grant rows are the gate today, as they were when the block
|
||||
// name was open-granted; a finer per-channel policy belongs to the same
|
||||
// future as the spawn capability (device-authority.md).
|
||||
if (invocation.request.role == @intFromEnum(device_manager_protocol.Role.consumer)) {
|
||||
if (invocation.request.wants_channel != 0) {
|
||||
if (driverEntryForDevice(invocation.target)) |provider| {
|
||||
if (provider.endpoint) |serving| service.replyWithCapability(serving);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const driver = driverByProcess(invocation.sender) orelse {
|
||||
std.log.info("hello from unknown process {d}", .{invocation.sender});
|
||||
return -envelope.EPERM;
|
||||
@@ -664,14 +691,24 @@ fn onChildRemoved(_: void, invocation: Invocation(device_manager_protocol.ChildR
|
||||
/// The reserved `enumerate` verb: the mirror, one `ChildEntry` per known child,
|
||||
/// packed into the reply's tail. How many arrived is the reply's own length —
|
||||
/// `Status.len` — so no count header is spent saying it twice.
|
||||
fn onEnumerate(_: void, _: Invocation(void), answer: Answer(void)) isize {
|
||||
fn onEnumerate(_: void, invocation: Invocation(void), answer: Answer(void)) isize {
|
||||
const entry_size = @sizeOf(device_manager_protocol.ChildEntry);
|
||||
const tail = answer.tail();
|
||||
// `target` is the page cursor: skip that many known children first. One
|
||||
// reply holds only a handful of entries, and a real tree (a dozen ACPI
|
||||
// nodes before the first USB child) outgrew one packet — the storage
|
||||
// child silently never fit, which is precisely the truncation shape the
|
||||
// bounds audit exists to forbid. A caller pages until a short page.
|
||||
var skip = invocation.target;
|
||||
var written: usize = 0;
|
||||
for (&children) |*child| {
|
||||
if (!child.used) continue;
|
||||
if (skip > 0) {
|
||||
skip -= 1;
|
||||
continue;
|
||||
}
|
||||
if (written + entry_size > tail.len) break;
|
||||
const entry = device_manager_protocol.ChildEntry{ .parent = child.parent, .bus_address = child.bus_address, .identity = child.identity };
|
||||
const entry = device_manager_protocol.ChildEntry{ .parent = child.parent, .bus_address = child.bus_address, .identity = child.identity, .device_id = child.device_id };
|
||||
@memcpy(tail[written..][0..entry_size], std.mem.asBytes(&entry));
|
||||
written += entry_size;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user