Flesh out the xHCI host-controller driver into a full transfer engine and build the three USB class drivers on top, all verified end to end under QEMU. - xHCI engine (usb-xhci-library.zig): controller reset, command/event rings with cycle-bit bookkeeping (gated on a No-Op-command proof), device slots, Address Device, control transfers, full chapter-9 enumeration, Configure Endpoint, and interrupt/bulk transfers. Each interface is device_registered with its (class,subclass,protocol) identity, unique per (port,interface). - Bus<->class transfer protocol (usb-transfer-protocol.zig + runtime.usb): open / control / interrupt-subscribe (async report pump on a poll timer) / bulk-by- physical-address, so sector data never crosses the 256-byte IPC limit. - USB HID keyboard + mouse (usb-hid/): decode boot-protocol reports and publish to the input service. A USB usage is already the input protocol's keycode. - USB mass storage (usb-storage/): Bulk-Only Transport + transparent SCSI, serving a block device under the new .block service id (block-protocol). - device-manager matches USB interfaces to class drivers (usbDriverForIdentity). - usb-abi / usb-ids made importable modules; add HID and mass-storage class requests, packTriple, and a usb_device DeviceClass. - Fix test/qemu_test.py on macOS: the QMP unix-socket path was built from the deep worktree path and exceeded the 104-byte sun_path limit, so QEMU exited before booting. It now lives under a short temp path. Tests: usb-report, usb-hid, usb-storage pass under python3 test/qemu_test.py; host units (usb-abi, usb-ids, hid-report, bulk-only-transport, scsi) green.
87 lines
3.3 KiB
Zig
87 lines
3.3 KiB
Zig
//! 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;
|
|
|
|
/// 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;
|
|
}
|
|
|
|
/// 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);
|
|
}
|