volume-manager: consume medium_changed — the second removal trigger (S5)
The block client gains subscribeMedium / unsubscribeMedium / decodeMediumChanged (the reserved subscribe/unsubscribe verbs plus the MediumChanged decode), so a consumer never hand-rolls the wire format. The volume manager subscribes to each device it adopts and consumes the event through the new on_buffered_message seam — never the protocol dispatch, whose op numbers collide with the manager's own hello. A medium leaving while its device stays in the tree (a card reader, an eject) now runs the SAME kill-retire-remount path as a pulled stick: absent retires the volume, present re-probes it. That closes the "two triggers, one lifecycle" the architecture specifies — device-presence polling alone could never see a medium leave under a present device. dropDevice unsubscribes before closing so the driver's bounded subscriber table frees the slot; on a dead channel (a real pull) the call fails fast, proven by volume-removal still passing. New volume-medium-change case: eject the medium (not the device) -> "medium absent" -> the manager unmounts. Fails against a pre-S5 manager that never subscribed. Suite 132/132 (device-authority is the known child-cleanup flake, green on rerun).
This commit is contained in:
@@ -7,12 +7,25 @@
|
||||
//! `runtime.dma.alloc`), so whole sectors move without crossing the IPC size
|
||||
//! limit — the same handoff usb-storage uses toward the controller.
|
||||
|
||||
const std = @import("std");
|
||||
const envelope = @import("envelope");
|
||||
const ipc = @import("ipc");
|
||||
const block_protocol = @import("block-protocol");
|
||||
|
||||
const Protocol = block_protocol.Protocol;
|
||||
|
||||
/// The medium_changed event payload, re-exported so a consumer decodes it without
|
||||
/// reaching into the wire-format module.
|
||||
pub const MediumChanged = block_protocol.MediumChanged;
|
||||
|
||||
/// Decode a medium_changed event from a buffered-message payload a subscriber
|
||||
/// received (a `Received.isMessage` wake). Null if the bytes are too short to be
|
||||
/// one — a caller ignores anything that is not a well-formed event.
|
||||
pub fn decodeMediumChanged(payload: []const u8) ?MediumChanged {
|
||||
if (payload.len < envelope.prefix_size + @sizeOf(MediumChanged)) return null;
|
||||
return std.mem.bytesToValue(MediumChanged, payload[envelope.prefix_size..][0..@sizeOf(MediumChanged)]);
|
||||
}
|
||||
|
||||
pub const Geometry = struct { block_size: u32, block_count: u64 };
|
||||
|
||||
pub const Device = struct {
|
||||
@@ -88,6 +101,30 @@ pub const Device = struct {
|
||||
if (status.status != 0) return null;
|
||||
return reply[0..answer.len];
|
||||
}
|
||||
|
||||
/// Subscribe `subscriber` (an endpoint) to this device's medium_changed
|
||||
/// events: the reserved `subscribe` verb carries the subscriber's endpoint as
|
||||
/// the capability, and the driver then ipc.sends each medium transition to it.
|
||||
pub fn subscribeMedium(self: Device, subscriber: ipc.Handle) bool {
|
||||
var packet: [block_protocol.message_maximum]u8 = undefined;
|
||||
const framed = envelope.encodeSubscribe(0, &packet) orelse return false; // interest 0: every event (block has one)
|
||||
var reply: [block_protocol.message_maximum]u8 = undefined;
|
||||
const answer = ipc.callCap(self.endpoint, framed, &reply, subscriber) catch return false;
|
||||
const status = envelope.statusOf(reply[0..answer.len]) orelse return false;
|
||||
return status.status == 0;
|
||||
}
|
||||
|
||||
/// Unsubscribe from this device's medium_changed events. Call before closing
|
||||
/// the channel so the driver's bounded subscriber table frees the slot rather
|
||||
/// than holding a dead endpoint until an exit sweep notices.
|
||||
pub fn unsubscribeMedium(self: Device) bool {
|
||||
var packet: [block_protocol.message_maximum]u8 = undefined;
|
||||
const framed = envelope.encodeUnsubscribe(&packet) orelse return false;
|
||||
var reply: [block_protocol.message_maximum]u8 = undefined;
|
||||
const answer = ipc.callCap(self.endpoint, framed, &reply, null) catch return false;
|
||||
const status = envelope.statusOf(reply[0..answer.len]) orelse return false;
|
||||
return status.status == 0;
|
||||
}
|
||||
};
|
||||
|
||||
// There is deliberately no open-by-name here: `block` is not a registry name.
|
||||
|
||||
Reference in New Issue
Block a user