//! The SCSI command descriptor blocks a transparent-SCSI (subclass 0x06) mass //! storage device understands, and the parsers for what they return. Pure data — //! host-testable. These CDBs go inside a Bulk-Only-Transport CBW (see //! bulk-only-transport.zig). //! //! Every multi-byte SCSI field is **big-endian** — the opposite of the USB wire //! ABI — so the LBA and transfer-length encodings are the load-bearing detail. const std = @import("std"); // SCSI operation codes. const op_test_unit_ready: u8 = 0x00; const op_request_sense: u8 = 0x03; const op_inquiry: u8 = 0x12; const op_read_capacity_10: u8 = 0x25; const op_read_10: u8 = 0x28; const op_write_10: u8 = 0x2A; const op_synchronize_cache_10: u8 = 0x35; /// INQUIRY: standard device data (36 bytes: peripheral type, removable, vendor /// and product strings). pub fn inquiry(allocation_length: u8) [6]u8 { return .{ op_inquiry, 0, 0, 0, allocation_length, 0 }; } /// TEST UNIT READY: no data; success (CSW passed) means the unit is ready. pub fn testUnitReady() [6]u8 { return .{ op_test_unit_ready, 0, 0, 0, 0, 0 }; } /// REQUEST SENSE: 18 bytes of sense data (sense key + ASC/ASCQ) explaining the /// previous failure. pub fn requestSense(allocation_length: u8) [6]u8 { return .{ op_request_sense, 0, 0, 0, allocation_length, 0 }; } /// READ CAPACITY(10): 8 bytes back — the last LBA and the block size, both u32 /// big-endian. Block count is last_lba + 1. pub fn readCapacity10() [10]u8 { return .{ op_read_capacity_10, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; } /// READ(10): read `blocks` logical blocks starting at `lba` into the data stage. pub fn read10(lba: u32, blocks: u16) [10]u8 { var cdb = [_]u8{0} ** 10; cdb[0] = op_read_10; std.mem.writeInt(u32, cdb[2..6], lba, .big); std.mem.writeInt(u16, cdb[7..9], blocks, .big); return cdb; } /// WRITE(10): write `blocks` logical blocks starting at `lba` from the data stage. pub fn write10(lba: u32, blocks: u16) [10]u8 { var cdb = [_]u8{0} ** 10; cdb[0] = op_write_10; std.mem.writeInt(u32, cdb[2..6], lba, .big); std.mem.writeInt(u16, cdb[7..9], blocks, .big); return cdb; } /// SYNCHRONIZE CACHE(10): commit the device's write cache to stable media. LBA 0 /// and block count 0 mean "the whole medium". No data stage. Without this a write /// can sit in the USB flash controller's cache and be lost if power is cut right /// after — which is exactly what a shutdown-time log flush hits on real hardware. pub fn synchronizeCache10() [10]u8 { var cdb = [_]u8{0} ** 10; cdb[0] = op_synchronize_cache_10; return cdb; } /// Decode an 8-byte READ CAPACITY(10) reply. pub fn parseCapacity(bytes: [8]u8) struct { last_lba: u32, block_size: u32 } { return .{ .last_lba = std.mem.readInt(u32, bytes[0..4], .big), .block_size = std.mem.readInt(u32, bytes[4..8], .big), }; } test "read/write CDBs encode the LBA and length big-endian" { const read = read10(0x01020304, 8); try std.testing.expectEqualSlices(u8, &.{ 0x28, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x08, 0x00 }, &read); const write = write10(0xAABBCCDD, 1); try std.testing.expectEqualSlices(u8, &.{ 0x2A, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0x00, 0x00, 0x01, 0x00 }, &write); try std.testing.expectEqual(@as(u8, 0x25), readCapacity10()[0]); try std.testing.expectEqual(@as(u8, 0x12), inquiry(36)[0]); try std.testing.expectEqual(@as(u8, 36), inquiry(36)[4]); try std.testing.expectEqual(@as(u8, 0x00), testUnitReady()[0]); } test "read capacity parses last LBA and block size" { // last_lba = 0x0003FFFF (262144 blocks), block_size = 512. const capacity = parseCapacity(.{ 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x02, 0x00 }); try std.testing.expectEqual(@as(u32, 0x0003FFFF), capacity.last_lba); try std.testing.expectEqual(@as(u32, 512), capacity.block_size); }