danos/library/runtime/block.zig

70 lines
3.3 KiB
Zig

//! 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;
}