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:
@@ -7,10 +7,8 @@
|
||||
//! `runtime.dma.alloc`), so whole sectors move without crossing the IPC size
|
||||
//! limit — the same handoff usb-storage uses toward the controller.
|
||||
|
||||
const channel = @import("channel");
|
||||
const envelope = @import("envelope");
|
||||
const ipc = @import("ipc");
|
||||
const time = @import("time");
|
||||
const block_protocol = @import("block-protocol");
|
||||
|
||||
const Protocol = block_protocol.Protocol;
|
||||
@@ -75,26 +73,7 @@ pub const Device = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// One open attempt, no waiting — for a server that retries on its own
|
||||
/// timer (the fat service) instead of blocking its harness in here.
|
||||
pub fn tryOpen() ?Device {
|
||||
if (channel.openEndpoint("block")) |handle| return .{ .endpoint = handle };
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Open `/protocol/block`, retrying generously while the USB storage chain
|
||||
/// (controller reset, enumeration, mass-storage bring-up) comes up.
|
||||
pub fn open() ?Device {
|
||||
// Patient: the whole USB storage chain (firmware discovery, xHCI reset and
|
||||
// enumeration, mass-storage bring-up) must complete first, which can take
|
||||
// tens of seconds under emulation.
|
||||
var attempts: usize = 0;
|
||||
// 30 s covers the slowest observed healthy chain (a flaky QEMU enumeration
|
||||
// completed at ~24 s); a machine whose stick genuinely failed setup should
|
||||
// not sit a further minute pretending otherwise.
|
||||
while (attempts < 600) : (attempts += 1) {
|
||||
if (channel.openEndpoint("block")) |handle| return .{ .endpoint = handle };
|
||||
time.sleepMillis(50);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// There is deliberately no open-by-name here: `block` is not a registry name.
|
||||
// One storage process serves each volume, and a consumer receives its volume's
|
||||
// channel from the device manager (establishment by lineage, communication.md
|
||||
// "Establishment: two planes"), then wraps it: `block.Device{ .endpoint = c }`.
|
||||
|
||||
@@ -270,18 +270,21 @@ pub const Exchange = struct {
|
||||
};
|
||||
|
||||
/// A consumer's whole establishment step: hello until the channel to this
|
||||
/// device's provider arrives. The manager acks a hello whose provider is not
|
||||
/// there yet (mid-restart, re-report on the way) with no channel — retryable
|
||||
/// by design — so this re-hellos on the ONE manager handle, on the same
|
||||
/// cadence the old name lookup used, and gives up on a refusal or a vanished
|
||||
/// manager. Re-hello is benign: the manager just re-marks the entry running.
|
||||
pub fn helloForChannel(role: Role, device_id: u64) ?ipc.Handle {
|
||||
const first = helloExchange(role, device_id, null, true) orelse return null;
|
||||
/// device's provider arrives. `serving` (a provider-and-consumer like
|
||||
/// usb-storage: block endpoint up, bus channel down) rides the FIRST exchange
|
||||
/// only — the manager keeps it, so retries need not resend it. The manager
|
||||
/// acks a hello whose provider is not there yet (mid-restart, re-report on
|
||||
/// the way) with no channel — retryable by design — so this re-hellos on the
|
||||
/// ONE manager handle, on the same cadence the old name lookup used, and
|
||||
/// gives up on a refusal or a vanished manager. Re-hello is benign: the
|
||||
/// manager just re-marks the entry running.
|
||||
pub fn helloForChannel(role: Role, device_id: u64, serving: ?ipc.Handle) ?ipc.Handle {
|
||||
const first = helloExchange(role, device_id, serving, true) orelse return null;
|
||||
if (first.channel) |bus| return bus;
|
||||
var attempts: u32 = 0;
|
||||
while (attempts < lookup_attempts) : (attempts += 1) {
|
||||
time.sleepMillis(lookup_pause_ms);
|
||||
const again = exchangeOn(first.manager, role, device_id, null, true) orelse return null;
|
||||
const again = helloOn(first.manager, role, device_id, null, true) orelse return null;
|
||||
if (again.channel) |bus| return bus;
|
||||
}
|
||||
std.log.info("no provider channel for device {d}", .{device_id});
|
||||
@@ -303,12 +306,14 @@ pub fn helloExchange(role: Role, device_id: u64, serving: ?ipc.Handle, want_chan
|
||||
std.log.info("no device manager to hello", .{});
|
||||
return null;
|
||||
};
|
||||
return exchangeOn(manager, role, device_id, serving, want_channel);
|
||||
return helloOn(manager, role, device_id, serving, want_channel);
|
||||
}
|
||||
|
||||
/// One hello on an already-open manager handle — the exchange without the
|
||||
/// lookup, so a retry loop never spends a handle-table slot per attempt.
|
||||
fn exchangeOn(manager: ipc.Handle, role: Role, device_id: u64, serving: ?ipc.Handle, want_channel: bool) ?Exchange {
|
||||
/// Public for parties that keep their own manager handle across a long retry
|
||||
/// cadence (fat polls for its volume on a timer).
|
||||
pub fn helloOn(manager: ipc.Handle, role: Role, device_id: u64, serving: ?ipc.Handle, want_channel: bool) ?Exchange {
|
||||
var packet: [device_manager_protocol.message_maximum]u8 = undefined;
|
||||
const framed = device_manager_protocol.Protocol.encodeRequest(
|
||||
.hello,
|
||||
|
||||
Reference in New Issue
Block a user