//! Block-device client: the helper a filesystem uses to read and write a block //! device (a USB stick, via usb-storage) without hand-rolling the block-protocol //! IPC. Layered over `ipc` and the shared `block-protocol` wire format, like //! `runtime.usb` over the transfer protocol. //! //! Transfers name a caller-owned DMA buffer by physical address (from //! `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; pub const Geometry = struct { block_size: u32, block_count: u64 }; pub const Device = struct { endpoint: ipc.Handle, /// The device's block size and total block count. pub fn geometry(self: Device) ?Geometry { var reply: [block_protocol.message_maximum]u8 = undefined; const answered = self.call(.geometry, {}, null, &reply) orelse return null; const result = Protocol.decodeReply(.geometry, answered) orelse return null; return .{ .block_size = result.block_size, .block_count = result.block_count }; } /// Hand the block server a DMA-region capability (`handle` — from a `shareable` /// dma_alloc) so it forwards it to the controller and the buffer's physical /// addresses become reachable by the device. Call once per buffer before naming it /// in `read`/`write`. Harmless success when no IOMMU is enforcing. pub fn attach(self: Device, handle: ipc.Handle) bool { var reply: [block_protocol.message_maximum]u8 = undefined; return self.call(.attach, {}, handle, &reply) != null; } /// Read `count` blocks starting at `lba` into the DMA buffer at `physical`. pub fn read(self: Device, lba: u64, count: u32, physical: u64) bool { var reply: [block_protocol.message_maximum]u8 = undefined; return self.call(.read, .{ .lba = lba, .count = count, .physical = physical }, null, &reply) != null; } /// Write `count` blocks starting at `lba` from the DMA buffer at `physical`. pub fn write(self: Device, lba: u64, count: u32, physical: u64) bool { var reply: [block_protocol.message_maximum]u8 = undefined; return self.call(.write, .{ .lba = lba, .count = count, .physical = physical }, null, &reply) != null; } /// Commit any device write cache to stable media (SCSI SYNCHRONIZE CACHE), so /// prior writes survive a power-off. A filesystem calls this before the machine /// goes down; no data transfer, so the buffer arguments are unused. pub fn flush(self: Device) bool { var reply: [block_protocol.message_maximum]u8 = undefined; return self.call(.flush, {}, null, &reply) != null; } /// One request at the driver. `target` is always 0: one endpoint per device, so /// there is no object within the peer to address. fn call( self: Device, comptime operation: Protocol.Operation, request: Protocol.RequestOf(operation), capability: ?ipc.Handle, reply: []u8, ) ?[]u8 { var packet: [block_protocol.message_maximum]u8 = undefined; const framed = Protocol.encodeRequest(operation, 0, request, &.{}, &packet) orelse return null; const answer = ipc.callCap(self.endpoint, framed, reply, capability) catch return null; const status = envelope.statusOf(reply[0..answer.len]) orelse return null; if (status.status != 0) return null; return reply[0..answer.len]; } }; /// 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; }