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.
74 lines
2.5 KiB
Zig
74 lines
2.5 KiB
Zig
//! USB Mass Storage Bulk-Only Transport (BOT) wire structures — the Command and
|
|
//! Command Status Wrappers that bracket every command (USB MSC BOT §5). Pure data
|
|
//! definitions, host-testable in isolation. The command inside the CBW is a SCSI
|
|
//! CDB (see scsi.zig); the transport here just carries it and reports status.
|
|
//!
|
|
//! One command is three bulk transfers: CBW out, an optional data stage, CSW in.
|
|
|
|
const std = @import("std");
|
|
|
|
/// "USBC" — the signature at the head of every Command Block Wrapper.
|
|
pub const cbw_signature: u32 = 0x43425355;
|
|
/// "USBS" — the signature at the head of every Command Status Wrapper.
|
|
pub const csw_signature: u32 = 0x53425355;
|
|
|
|
/// CBW `flags`: set for a device-to-host (IN) data stage, clear for OUT.
|
|
pub const flag_data_in: u8 = 0x80;
|
|
|
|
/// The 31-byte Command Block Wrapper, sent on the bulk-OUT endpoint.
|
|
pub const CommandBlockWrapper = extern struct {
|
|
signature: u32 align(1) = cbw_signature,
|
|
tag: u32 align(1),
|
|
data_transfer_length: u32 align(1),
|
|
flags: u8,
|
|
lun: u8,
|
|
cdb_length: u8,
|
|
cdb: [16]u8 = [_]u8{0} ** 16,
|
|
};
|
|
|
|
/// A device's answer to a command (the CSW `status` byte).
|
|
pub const CommandStatus = enum(u8) {
|
|
passed = 0,
|
|
failed = 1,
|
|
phase_error = 2,
|
|
_,
|
|
};
|
|
|
|
/// The 13-byte Command Status Wrapper, read from the bulk-IN endpoint.
|
|
pub const CommandStatusWrapper = extern struct {
|
|
signature: u32 align(1) = csw_signature,
|
|
tag: u32 align(1),
|
|
data_residue: u32 align(1),
|
|
status: u8,
|
|
};
|
|
|
|
comptime {
|
|
std.debug.assert(@sizeOf(CommandBlockWrapper) == 31);
|
|
std.debug.assert(@sizeOf(CommandStatusWrapper) == 13);
|
|
}
|
|
|
|
test "wrapper sizes and signatures match the specification" {
|
|
const cbw = CommandBlockWrapper{
|
|
.tag = 0x11223344,
|
|
.data_transfer_length = 512,
|
|
.flags = flag_data_in,
|
|
.lun = 0,
|
|
.cdb_length = 10,
|
|
};
|
|
const bytes = std.mem.asBytes(&cbw);
|
|
try std.testing.expectEqual(@as(usize, 31), bytes.len);
|
|
// "USBC" little-endian.
|
|
try std.testing.expectEqualSlices(u8, "USBC", bytes[0..4]);
|
|
try std.testing.expectEqual(flag_data_in, bytes[12]);
|
|
|
|
const csw = std.mem.bytesToValue(CommandStatusWrapper, &[_]u8{
|
|
0x55, 0x53, 0x42, 0x53, // "USBS"
|
|
0x44, 0x33, 0x22, 0x11, // tag
|
|
0x00, 0x00, 0x00, 0x00, // residue
|
|
0x00, // passed
|
|
});
|
|
try std.testing.expectEqual(csw_signature, csw.signature);
|
|
try std.testing.expectEqual(@as(u32, 0x11223344), csw.tag);
|
|
try std.testing.expectEqual(@as(u8, @intFromEnum(CommandStatus.passed)), csw.status);
|
|
}
|