//! 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 std = @import("std"); const ipc = @import("ipc.zig"); const system = @import("system.zig"); const protocol = @import("block-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 request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.geometry), .lba = 0, .count = 0, .physical = 0 }; var reply: [protocol.reply_size]u8 = undefined; const n = ipc.call(self.endpoint, std.mem.asBytes(&request), &reply) catch return null; if (n < protocol.reply_size) return null; const result = std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]); if (result.status != 0) return null; return .{ .block_size = result.block_size, .block_count = result.block_count }; } /// Read `count` blocks starting at `lba` into the DMA buffer at `physical`. pub fn read(self: Device, lba: u64, count: u32, physical: u64) bool { return self.transfer(.read, lba, count, physical); } /// Write `count` blocks starting at `lba` from the DMA buffer at `physical`. pub fn write(self: Device, lba: u64, count: u32, physical: u64) bool { return self.transfer(.write, lba, count, physical); } /// 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 { return self.transfer(.flush, 0, 0, 0); } fn transfer(self: Device, operation: protocol.Operation, lba: u64, count: u32, physical: u64) bool { var request = protocol.Request{ .operation = @intFromEnum(operation), .lba = lba, .count = count, .physical = physical }; var reply: [protocol.reply_size]u8 = undefined; const n = ipc.call(self.endpoint, std.mem.asBytes(&request), &reply) catch return false; if (n < protocol.reply_size) return false; return std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status == 0; } }; /// Look up the block device, 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; while (attempts < 1200) : (attempts += 1) { if (ipc.lookup(.block)) |handle| return .{ .endpoint = handle }; system.sleep(50); } return null; }