//! USB mass-storage class driver (Bulk-Only Transport + transparent SCSI). //! //! Spawned by the device manager when the xHCI bus driver reports a mass-storage //! / SCSI / bulk-only interface (class 8, subclass 6, protocol 0x50); its device //! id arrives as argv[1]. It owns no hardware: it opens its device through the //! USB transfer protocol (`runtime.usb`), then drives it with the BOT command //! cycle — CBW out, an optional data stage, CSW in — carrying SCSI commands //! (READ CAPACITY, READ(10), WRITE(10)). Upward it is a block device: it serves //! the block protocol under `.block`, the storage a FAT filesystem sits on. //! //! Block data never crosses IPC: read/write name a caller-owned DMA buffer by //! physical address, which the data stage DMAs straight to/from. const std = @import("std"); const runtime = @import("runtime"); const scsi = @import("scsi.zig"); const bot = @import("bulk-only-transport.zig"); const block_protocol = @import("block-protocol"); const dma = runtime.dma; fn writeLine(comptime fmt: []const u8, arguments: anytype) void { var line: [128]u8 = undefined; _ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return); } var device_id: u64 = 0; var device: runtime.usb.Device = undefined; var bulk_in: runtime.usb.Endpoint = undefined; var bulk_out: runtime.usb.Endpoint = undefined; // DMA buffers for the transport: the 31-byte CBW, the 13-byte CSW, and a page // for the small command data (INQUIRY / READ CAPACITY / the self-check sector). var command_wrapper: dma.Region = undefined; var status_wrapper: dma.Region = undefined; var command_data: dma.Region = undefined; var next_tag: u32 = 1; var block_size: u32 = 512; var block_count: u64 = 0; /// One Bulk-Only-Transport command: send the CBW, run the data stage (to/from /// `data_physical`), read and validate the CSW. Returns true on a passed status. fn transact(cdb: []const u8, direction_in: bool, data_physical: u64, data_length: u32) bool { const tag = next_tag; next_tag +%= 1; const wrapper: *bot.CommandBlockWrapper = @ptrFromInt(command_wrapper.virtual); wrapper.* = .{ .tag = tag, .data_transfer_length = data_length, .flags = if (direction_in) bot.flag_data_in else 0, .lun = 0, .cdb_length = @intCast(cdb.len), }; @memcpy(wrapper.cdb[0..cdb.len], cdb); if (device.bulk(bulk_out.address, command_wrapper.physical, @sizeOf(bot.CommandBlockWrapper)) == null) return false; if (data_length > 0) { const endpoint = if (direction_in) bulk_in.address else bulk_out.address; if (device.bulk(endpoint, data_physical, data_length) == null) return false; } if (device.bulk(bulk_in.address, status_wrapper.physical, @sizeOf(bot.CommandStatusWrapper)) == null) return false; const status: *const bot.CommandStatusWrapper = @ptrFromInt(status_wrapper.virtual); if (status.signature != bot.csw_signature or status.tag != tag) return false; return status.status == @intFromEnum(bot.CommandStatus.passed); } fn initialise(endpoint: runtime.ipc.Handle) bool { _ = endpoint; if (!runtime.usb.helloManager(device_id)) { _ = runtime.system.write("/system/drivers/usb-storage: hello to device manager failed\n"); return false; } device = runtime.usb.open(device_id) orelse { writeLine("/system/drivers/usb-storage: could not open device {d}\n", .{device_id}); return false; }; bulk_in = device.findEndpoint(runtime.usb.transfer_type_bulk, true) orelse { _ = runtime.system.write("/system/drivers/usb-storage: no bulk-IN endpoint\n"); return false; }; bulk_out = device.findEndpoint(runtime.usb.transfer_type_bulk, false) orelse { _ = runtime.system.write("/system/drivers/usb-storage: no bulk-OUT endpoint\n"); return false; }; command_wrapper = dma.alloc(4096, dma.coherent) orelse return false; status_wrapper = dma.alloc(4096, dma.coherent) orelse return false; command_data = dma.alloc(4096, dma.coherent) orelse return false; // Bring the LUN up: wait for it to be ready (clearing the initial unit-attention // with REQUEST SENSE), identify it, and read its capacity. var tries: u32 = 0; while (tries < 10) : (tries += 1) { const ready = scsi.testUnitReady(); if (transact(&ready, false, 0, 0)) break; const sense = scsi.requestSense(18); _ = transact(&sense, true, command_data.physical, 18); runtime.system.sleep(50); } const inquiry = scsi.inquiry(36); _ = transact(&inquiry, true, command_data.physical, 36); const capacity_command = scsi.readCapacity10(); if (!transact(&capacity_command, true, command_data.physical, 8)) { _ = runtime.system.write("/system/drivers/usb-storage: READ CAPACITY failed\n"); return false; } var capacity_bytes: [8]u8 = undefined; const capacity_source: [*]const u8 = @ptrFromInt(command_data.virtual); @memcpy(&capacity_bytes, capacity_source[0..8]); const capacity = scsi.parseCapacity(capacity_bytes); block_size = capacity.block_size; block_count = @as(u64, capacity.last_lba) + 1; writeLine("/system/drivers/usb-storage: ready ({d} blocks x {d} bytes)\n", .{ block_count, block_size }); // Self-check: read block 0 and log its trailing signature (0x55AA for a boot // sector) — proof READ(10) works end to end over the bulk path. const read0 = scsi.read10(0, 1); if (block_size <= 4096 and transact(&read0, true, command_data.physical, block_size)) { const sector: [*]const u8 = @ptrFromInt(command_data.virtual); writeLine("/system/drivers/usb-storage: block 0 signature 0x{x:0>2}{x:0>2}\n", .{ sector[510], sector[511] }); } return true; } /// Serve the block protocol: geometry, and whole-block read/write to/from the /// caller's DMA buffer (named by physical address). fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize { _ = sender; _ = capability; if (message.len < block_protocol.request_size) return 0; const request = std.mem.bytesToValue(block_protocol.Request, message[0..block_protocol.request_size]); switch (request.operation) { @intFromEnum(block_protocol.Operation.geometry) => { return writeReply(reply, .{ .status = 0, .block_size = block_size, .block_count = block_count }); }, @intFromEnum(block_protocol.Operation.read) => { const count: u16 = @intCast(request.count); const cdb = scsi.read10(@intCast(request.lba), count); const ok = transact(&cdb, true, request.physical, request.count * block_size); return writeReply(reply, .{ .status = if (ok) 0 else -1, .block_size = block_size, .block_count = if (ok) request.count else 0 }); }, @intFromEnum(block_protocol.Operation.write) => { const count: u16 = @intCast(request.count); const cdb = scsi.write10(@intCast(request.lba), count); const ok = transact(&cdb, false, request.physical, request.count * block_size); return writeReply(reply, .{ .status = if (ok) 0 else -1, .block_size = block_size, .block_count = if (ok) request.count else 0 }); }, @intFromEnum(block_protocol.Operation.flush) => { // SYNCHRONIZE CACHE: commit the device's write cache to flash. No data // stage. Makes prior writes durable before a caller (init at shutdown) // cuts power. A device without a volatile cache reports success anyway. const cdb = scsi.synchronizeCache10(); const ok = transact(&cdb, false, 0, 0); return writeReply(reply, .{ .status = if (ok) 0 else -1, .block_size = block_size, .block_count = 0 }); }, else => return 0, } } fn writeReply(reply: []u8, value: block_protocol.Reply) usize { const bytes = std.mem.asBytes(&value); @memcpy(reply[0..bytes.len], bytes); return bytes.len; } pub fn main(init: runtime.process.Init) void { const argument = init.arguments.get(1) orelse { _ = runtime.system.write("/system/drivers/usb-storage: missing device id (argv[1])\n"); return; }; device_id = std.fmt.parseInt(u64, argument, 10) catch { writeLine("/system/drivers/usb-storage: malformed device id '{s}'\n", .{argument}); return; }; runtime.service.run(block_protocol.message_maximum, .{ .service = .block, .init = initialise, .on_message = onMessage, }); } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; }