7 Commits
Author SHA1 Message Date
Daniel Samson 77901bbba6 WIP: USB 2026-07-12 22:24:47 +01:00
Daniel Samson 78582d24d2 code lint 2026-07-12 19:36:10 +01:00
Daniel Samson 1cdffe21b1 fixing comments 2026-07-12 16:19:09 +01:00
Daniel Samson 4df90bc212 add tools/rewrap-comments.py 2026-07-12 16:18:56 +01:00
Daniel Samson 713e77354b gitattributes 2026-07-12 16:09:18 +01:00
Daniel Samson abb7b1b634 editorconfig 2026-07-12 16:09:11 +01:00
Daniel Samson f5f0e15769 zig fmt 2026-07-12 16:04:58 +01:00
31 changed files with 1560 additions and 97 deletions
+16
View File
@@ -0,0 +1,16 @@
# EditorConfig: https://editorconfig.org/
# Follows the Zig style guide: https://ziglang.org/documentation/0.16.0/#Style-Guide
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.zig]
# "Line length: aim for 100; use common sense."
max_line_length = 100
+1
View File
@@ -0,0 +1 @@
*.zig text eol=lf
+6
View File
@@ -331,6 +331,7 @@ pub fn build(b: *std.Build) void {
const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-bus", "system/drivers/ps2-bus/ps2-bus.zig"); const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-bus", "system/drivers/ps2-bus/ps2-bus.zig");
const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig"); const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig");
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig"); const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
const usb_xhci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-xhci-bus", "system/drivers/usb-xhci-bus/usb-xhci-bus.zig");
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig"); const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
// The input service and its exercisers: the fan-out server, a hardware-free synthetic // The input service and its exercisers: the fan-out server, a hardware-free synthetic
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md. // source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
@@ -360,6 +361,8 @@ pub fn build(b: *std.Build) void {
mk_run.addFileArg(ps2_keyboard_exe.getEmittedBin()); mk_run.addFileArg(ps2_keyboard_exe.getEmittedBin());
mk_run.addArg("ps2-mouse"); mk_run.addArg("ps2-mouse");
mk_run.addFileArg(ps2_mouse_exe.getEmittedBin()); mk_run.addFileArg(ps2_mouse_exe.getEmittedBin());
mk_run.addArg("usb-xhci-bus");
mk_run.addFileArg(usb_xhci_bus_exe.getEmittedBin());
mk_run.addArg("device-manager"); mk_run.addArg("device-manager");
mk_run.addFileArg(device_manager_exe.getEmittedBin()); mk_run.addFileArg(device_manager_exe.getEmittedBin());
mk_run.addArg("input"); mk_run.addArg("input");
@@ -384,6 +387,7 @@ pub fn build(b: *std.Build) void {
.{ ps2_bus_exe, "system/drivers" }, .{ ps2_bus_exe, "system/drivers" },
.{ ps2_keyboard_exe, "system/drivers" }, .{ ps2_keyboard_exe, "system/drivers" },
.{ ps2_mouse_exe, "system/drivers" }, .{ ps2_mouse_exe, "system/drivers" },
.{ usb_xhci_bus_exe, "system/drivers" },
}) |entry| { }) |entry| {
const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } }); const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } });
b.getInstallStep().dependOn(&step.step); b.getInstallStep().dependOn(&step.step);
@@ -526,6 +530,8 @@ pub fn build(b: *std.Build) void {
"system/devices/device-abi.zig", "system/devices/device-abi.zig",
"system/devices/pci-class.zig", // class/subclass/prog-IF name decoding "system/devices/pci-class.zig", // class/subclass/prog-IF name decoding
"system/devices/acpi-ids.zig", // _HID name decoding "system/devices/acpi-ids.zig", // _HID name decoding
"system/devices/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
"system/devices/usb-ids.zig", // class/subclass/protocol code assignments
"library/mmio/mmio.zig", // barriers assemble + registers round-trip "library/mmio/mmio.zig", // barriers assemble + registers round-trip
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine "system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly "system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
+13 -2
View File
@@ -52,11 +52,22 @@ rather than restate it. Roughly in the order things happen at runtime:
microkernel's `ps`/`kill`/SIGCHLD: enumerate as a table snapshot, the microkernel's `ps`/`kill`/SIGCHLD: enumerate as a table snapshot, the
supervision link as the kill authority, and child-exit notifications over the supervision link as the kill authority, and child-exit notifications over the
same endpoints IRQs arrive on. same endpoints IRQs arrive on.
16. **[input.md](input.md) — the input module.** Broadcasting input events (keyboard, 16. **[process-lifecycle.md](process-lifecycle.md) — the process lifecycle.** Design:
signals over IPC as the one lifecycle vocabulary every process speaks — the
POSIX.1-1990 words with message delivery instead of stack hijack, the stable
`runtime.process` interface, exit reasons, published exit events any stateful
service can subscribe to (the VFS releasing dead clients' handles), and the two
iron rules (cleanup is the kernel's job; kill is not a signal).
17. **[device-manager.md](device-manager.md) — the device manager.** Design: the
tree, the matcher, and the supervisor. Tree structure lives in the manager,
authority stays in the kernel; bus drivers report what they see; drivers are
restarted through the lifecycle vocabulary — the plan that turns
[resilience.md](resilience.md)'s restart goal into increments.
18. **[input.md](input.md) — the input module.** Broadcasting input events (keyboard,
mouse, joystick): why a synchronous rendezvous can't fan out to many listeners, the mouse, joystick): why a synchronous rendezvous can't fan out to many listeners, the
asynchronous `ipc_send` primitive built to fix it, and the per-device subscribe/publish asynchronous `ipc_send` primitive built to fix it, and the per-device subscribe/publish
service layered on top. service layered on top.
17. **[halting.md](halting.md) — halting.** Why a kernel can't just "exit", and 19. **[halting.md](halting.md) — halting.** Why a kernel can't just "exit", and
how `while (true) hlt` parks the CPU safely once there's nothing left to do. how `while (true) hlt` parks the CPU safely once there's nothing left to do.
Start with the north star: Start with the north star:
+16
View File
@@ -150,3 +150,19 @@ input output" in code — that expansion is what the acronym *is for*. But `msg`
test for "is this an abbreviation I must expand" is simply: *is there a longer word this test for "is this an abbreviation I must expand" is simply: *is there a longer word this
is a clipped form of?* If yes, write the word. If it's an initialism standing in for a is a clipped form of?* If yes, write the word. If it's an initialism standing in for a
phrase, leave it. phrase, leave it.
## Zen of Zig
* Communicate intent precisely.
* Edge cases matter.
* Favor reading code over writing code.
* Only one obvious way to do things.
* Runtime crashes are better than bugs.
* Compile errors are better than runtime crashes.
* Incremental improvements.
* Avoid local maximums.
* Reduce the amount one must remember.
* Focus on code rather than style.
* Resource allocation may fail; resource deallocation must succeed.
* Memory is a resource.
* Together we serve the users.
+4
View File
@@ -37,6 +37,10 @@ pub fn mmioMap(device_id: u64, resource_index: u64) ?usize {
/// `DeviceDescriptor.parent` for a device with no parent. /// `DeviceDescriptor.parent` for a device with no parent.
pub const no_parent = device_abi.no_parent; pub const no_parent = device_abi.no_parent;
/// `DeviceDescriptor.pci_class` for a device that is not a PCI function. Set this on
/// descriptors passed to `register` unless the child really is one.
pub const no_pci_class = device_abi.no_pci_class;
/// Publish `descriptor` as a child of `parent_id`, which this process must have claimed. /// Publish `descriptor` as a child of `parent_id`, which this process must have claimed.
/// Returns the new device id. The child is left unclaimed, so whichever driver owns /// Returns the new device id. The child is left unclaimed, so whichever driver owns
/// that class of device can `claim` it — that is how a bus hands off a device. /// that class of device can `claim` it — that is how a bus hands off a device.
+2 -1
View File
@@ -37,7 +37,8 @@ pub const MouseEventKind = protocol.MouseEventKind;
pub const JoystickEventKind = protocol.JoystickEventKind; pub const JoystickEventKind = protocol.JoystickEventKind;
pub const Keycode = protocol.Keycode; pub const Keycode = protocol.Keycode;
/// Interest masks re-exported so a caller can `subscribe(input.device_keyboard | input.device_mouse)`. /// Interest masks re-exported so a caller can `subscribe(input.device_keyboard |
/// input.device_mouse)`.
pub const device_keyboard = protocol.device_keyboard; pub const device_keyboard = protocol.device_keyboard;
pub const device_mouse = protocol.device_mouse; pub const device_mouse = protocol.device_mouse;
pub const device_joystick = protocol.device_joystick; pub const device_joystick = protocol.device_joystick;
+20 -5
View File
@@ -19,34 +19,49 @@ pub inline fn systemCall0(n: SystemCall) usize {
pub inline fn systemCall1(n: SystemCall, a0: usize) usize { pub inline fn systemCall1(n: SystemCall, a0: usize) usize {
return asm volatile ("syscall" return asm volatile ("syscall"
: [ret] "={rax}" (-> usize), : [ret] "={rax}" (-> usize),
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), : [n] "{rax}" (@intFromEnum(n)),
[a0] "{rdi}" (a0),
: .{ .rcx = true, .r11 = true, .memory = true }); : .{ .rcx = true, .r11 = true, .memory = true });
} }
pub inline fn systemCall2(n: SystemCall, a0: usize, a1: usize) usize { pub inline fn systemCall2(n: SystemCall, a0: usize, a1: usize) usize {
return asm volatile ("syscall" return asm volatile ("syscall"
: [ret] "={rax}" (-> usize), : [ret] "={rax}" (-> usize),
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1), : [n] "{rax}" (@intFromEnum(n)),
[a0] "{rdi}" (a0),
[a1] "{rsi}" (a1),
: .{ .rcx = true, .r11 = true, .memory = true }); : .{ .rcx = true, .r11 = true, .memory = true });
} }
pub inline fn systemCall3(n: SystemCall, a0: usize, a1: usize, a2: usize) usize { pub inline fn systemCall3(n: SystemCall, a0: usize, a1: usize, a2: usize) usize {
return asm volatile ("syscall" return asm volatile ("syscall"
: [ret] "={rax}" (-> usize), : [ret] "={rax}" (-> usize),
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1), [a2] "{rdx}" (a2), : [n] "{rax}" (@intFromEnum(n)),
[a0] "{rdi}" (a0),
[a1] "{rsi}" (a1),
[a2] "{rdx}" (a2),
: .{ .rcx = true, .r11 = true, .memory = true }); : .{ .rcx = true, .r11 = true, .memory = true });
} }
pub inline fn systemCall4(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize) usize { pub inline fn systemCall4(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize) usize {
return asm volatile ("syscall" return asm volatile ("syscall"
: [ret] "={rax}" (-> usize), : [ret] "={rax}" (-> usize),
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1), [a2] "{rdx}" (a2), [a3] "{r10}" (a3), : [n] "{rax}" (@intFromEnum(n)),
[a0] "{rdi}" (a0),
[a1] "{rsi}" (a1),
[a2] "{rdx}" (a2),
[a3] "{r10}" (a3),
: .{ .rcx = true, .r11 = true, .memory = true }); : .{ .rcx = true, .r11 = true, .memory = true });
} }
pub inline fn systemCall5(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) usize { pub inline fn systemCall5(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) usize {
return asm volatile ("syscall" return asm volatile ("syscall"
: [ret] "={rax}" (-> usize), : [ret] "={rax}" (-> usize),
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1), [a2] "{rdx}" (a2), [a3] "{r10}" (a3), [a4] "{r8}" (a4), : [n] "{rax}" (@intFromEnum(n)),
[a0] "{rdi}" (a0),
[a1] "{rsi}" (a1),
[a2] "{rdx}" (a2),
[a3] "{r10}" (a3),
[a4] "{r8}" (a4),
: .{ .rcx = true, .r11 = true, .memory = true }); : .{ .rcx = true, .r11 = true, .memory = true });
} }
+4 -2
View File
@@ -52,7 +52,8 @@ pub const PowerInformation = struct {
reset: RegisterAccess = .{}, reset: RegisterAccess = .{},
reset_value: u8 = 0, reset_value: u8 = 0,
reset_supported: bool = false, reset_supported: bool = false,
/// SLP_TYP values for S5 (soft off) and S3 (suspend), from the AML sleep-state (`_Sx`) packages. /// SLP_TYP values for S5 (soft off) and S3 (suspend), from the AML sleep-state (`_Sx`)
/// packages.
s5: ?aml.SleepType = null, s5: ?aml.SleepType = null,
s3: ?aml.SleepType = null, s3: ?aml.SleepType = null,
}; };
@@ -198,7 +199,8 @@ const ExtendedSystemDescriptorPointer = extern struct {
root_system_description_table_address: u32 align(1), root_system_description_table_address: u32 align(1),
/// The size of the RSDP. /// The size of the RSDP.
length: u32 align(1), length: u32 align(1),
/// A 64-bit physical address pointing to the XSDT. If the revision is at least 2, the XSDT should be used regardless of architecture, as the RSDT was deprecated. /// A 64-bit physical address pointing to the XSDT. If the revision is at least 2, the XSDT
/// should be used regardless of architecture, as the RSDT was deprecated.
extended_system_descriptor_table_address: u64 align(1), extended_system_descriptor_table_address: u64 align(1),
/// A checksum used for the entire table. /// A checksum used for the entire table.
extended_checksum: u8, extended_checksum: u8,
+10 -5
View File
@@ -126,17 +126,22 @@ test "parses a nested namespace and finds the sleep package" {
// Scope(\_SB) packagelen=0x27 // Scope(\_SB) packagelen=0x27
0x10, 0x27, 0x5C, 0x5F, 0x53, 0x42, 0x5F, 0x10, 0x27, 0x5C, 0x5F, 0x53, 0x42, 0x5F,
// Device(PCI0) packagelen=0x1F // Device(PCI0) packagelen=0x1F
0x5B, 0x82, 0x1F, 0x50, 0x43, 0x49, 0x30, 0x5B, 0x82, 0x1F, 0x50, 0x43,
0x49, 0x30,
// Name(_HID, 0x11) // Name(_HID, 0x11)
0x08, 0x5F, 0x48, 0x49, 0x44, 0x0A, 0x11, 0x08, 0x5F, 0x48, 0x49, 0x44, 0x0A, 0x11,
// Method(MTHD, flags=1) empty, packagelen=0x06 // Method(MTHD, flags=1) empty, packagelen=0x06
0x14, 0x06, 0x4D, 0x54, 0x48, 0x44, 0x01, 0x14, 0x06, 0x4D,
0x54, 0x48, 0x44, 0x01,
// Method(CALL, flags=0) { MTHD(Zero) }, packagelen=0x0B // Method(CALL, flags=0) { MTHD(Zero) }, packagelen=0x0B
0x14, 0x0B, 0x43, 0x41, 0x4C, 0x4C, 0x00, 0x4D, 0x54, 0x48, 0x44, 0x00, 0x14, 0x0B, 0x43, 0x41, 0x4C, 0x4C, 0x00, 0x4D,
0x54, 0x48, 0x44, 0x00,
// OperationRegion(DBG0, SystemIO, Word 0x0402, Byte 1) // OperationRegion(DBG0, SystemIO, Word 0x0402, Byte 1)
0x5B, 0x80, 0x44, 0x42, 0x47, 0x30, 0x01, 0x0B, 0x02, 0x04, 0x0A, 0x01, 0x5B, 0x80, 0x44, 0x42, 0x47, 0x30, 0x01, 0x0B,
0x02, 0x04, 0x0A, 0x01,
// Field(DBG0, flags=1) { DBGB, 8 }, packagelen=0x0B // Field(DBG0, flags=1) { DBGB, 8 }, packagelen=0x0B
0x5B, 0x81, 0x0B, 0x44, 0x42, 0x47, 0x30, 0x01, 0x44, 0x42, 0x47, 0x42, 0x08, 0x5B, 0x81, 0x0B, 0x44, 0x42, 0x47, 0x30, 0x01,
0x44, 0x42, 0x47, 0x42, 0x08,
}; };
var arena = std.heap.ArenaAllocator.init(std.testing.allocator); var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+9
View File
@@ -56,6 +56,10 @@ pub const maximum_device_resources = 8;
/// `DeviceDescriptor.parent` for a device with no parent — a root of the device tree. /// `DeviceDescriptor.parent` for a device with no parent — a root of the device tree.
pub const no_parent: u64 = ~@as(u64, 0); pub const no_parent: u64 = ~@as(u64, 0);
/// `DeviceDescriptor.pci_class` for a device that is not a PCI function. (Zero would be
/// ambiguous: 0x000000 is a real class code, "unclassified device".)
pub const no_pci_class: u64 = ~@as(u64, 0);
/// A device, as snapshotted for user space by `device_enumerate`. A driver scans /// A device, as snapshotted for user space by `device_enumerate`. A driver scans
/// these to find the hardware it owns, claims it, and maps its MMIO. /// these to find the hardware it owns, claims it, and maps its MMIO.
/// ///
@@ -69,6 +73,11 @@ pub const DeviceDescriptor = extern struct {
id: u64, id: u64,
parent: u64, // a device id, or `no_parent` parent: u64, // a device id, or `no_parent`
class: u64, // a DeviceClass value class: u64, // a DeviceClass value
// The PCI class/subclass/prog-IF triple packed as 0xCCSSPP when this device is a PCI
// function, or `no_pci_class` otherwise. This is how a manager tells *what* a
// `pci_device` is (an xHCI controller, an AHCI controller) — decode the triple into
// names with the pci-class module.
pci_class: u64,
hid_len: u64, hid_len: u64,
resource_count: u64, resource_count: u64,
hid: [8]u8, hid: [8]u8,
+1 -2
View File
@@ -198,8 +198,7 @@ fn dumpNode(device: *const Device, depth: usize, emit: *const fn ([]const u8) vo
std.fmt.bufPrint(buffer[indent..], "{s} [{s}] hid={s} ({s})\n", .{ device.name(), @tagName(device.class), device.hid(), desc }) catch return std.fmt.bufPrint(buffer[indent..], "{s} [{s}] hid={s} ({s})\n", .{ device.name(), @tagName(device.class), device.hid(), desc }) catch return
else else
std.fmt.bufPrint(buffer[indent..], "{s} [{s}] hid={s}\n", .{ device.name(), @tagName(device.class), device.hid() }) catch return; std.fmt.bufPrint(buffer[indent..], "{s} [{s}] hid={s}\n", .{ device.name(), @tagName(device.class), device.hid() }) catch return;
} else } else std.fmt.bufPrint(buffer[indent..], "{s} [{s}]\n", .{ device.name(), @tagName(device.class) }) catch return;
std.fmt.bufPrint(buffer[indent..], "{s} [{s}]\n", .{ device.name(), @tagName(device.class) }) catch return;
emit(buffer[0 .. indent + body.len]); emit(buffer[0 .. indent + body.len]);
// For a PCI function, decode its class code — the (class / subclass / prog-IF) // For a PCI function, decode its class code — the (class / subclass / prog-IF)
+857
View File
@@ -0,0 +1,857 @@
//! USB device-framework wire ABI: the set-up packets, standard requests, and standard
//! descriptors every USB device speaks over its default control pipe, as defined by chapter 9
//! of the USB 2.0 specification (see https://wiki.osdev.org/Universal_Serial_Bus). Pure data
//! definitions — no hardware access — shared by the host-controller bus drivers (which build
//! the requests) and anything that parses what devices return (device naming, driver
//! matching, configuration). The structs mirror the wire byte-for-byte: multi-byte fields are
//! little-endian and align(1), so a descriptor can be bit-cast straight out of a transfer
//! buffer at any offset, and bitmap bytes are packed structs so no caller ever needs a magic
//! mask. Class, subclass, and protocol code tables live in usb-ids.zig.
const DeviceState = enum(u8) {
// Immediately after the USB device is attached to the USB system, it is in this state.
// The USB specifications do not define the state of a USB device that is detached from
// a USB system.
attached,
// A device is in this state after it has both been attached to the bus, and the VBUS line is
// applied to the device (the host controller drives the VBUS at +5V, however this is only
// particularly important for hardware developers). In this state, the device must not respond
// to any bus transactions. The USB specification recognizes three potential scenarios with
// respect to how a device draws power:
// - Self-Powered Devices draw power from an external power source (e.g, a USB printer plugs
// into the wall as well as a USB port). Although the device may be considered
// technically "powered" even before attachment to the USB, it is still only considered
// powered after the VBUS line is applied to the device.
// - Bus-Powered Devices draw power solely from the USB up to 100mA.
// - Self- or Bus-Powered Devices may draw power from either the bus or an external power
// source, depending on the configuration. These devices may change power source at any
// time. If a device is currently self-powered and requires more than 100mA of power, but
// switches to being bus-powered, then the device must return to the Address state.
powered,
// A device in the powered state enters the default state after receiving a bus reset. In this
// state, the device is addressable at the default, reserved address of 0. At this point, the
// device is operating at the correct speed. The host is expected to allow 10 milliseconds
// before expecting the device to respond to data transfers after reset.
default,
// A device enters this state after the host assigns it an address via the default control pipe,
// which is always accessible whether the device's address has been set or not.
address,
// A device is in this state after the host examines its possible configurations and selects
// one. All endpoint's data toggle bits are initialized to zero when a device enters this state.
configured,
// When no traffic is observed on the bus for a period of 1 millisecond, a USB device enters
// this state, characterized by its low power consumption. The device's address and
// configuration settings are maintained while suspended. A device exits the suspended state as
// soon as it begins seeing bus activity again. The host is expected to allow 10 milliseconds
// before expecting the device to respond to data transfers after resume.
suspended,
};
const RequestCode = enum(u8) {
get_status = 0,
clear_feature = 1,
set_feature = 3,
set_address = 5,
get_descriptor = 6,
set_descriptor = 7,
get_configuration = 8,
set_configuration = 9,
get_interface = 10,
set_interface = 11,
sync_frame = 12,
};
// Direction of an endpoint, from the host's point of view
const EndpointDirection = enum(u1) {
out = 0,
in = 1,
};
// Identifier newtypes: distinct wire-sized types for values that identify something on the
// device rather than count something. Each is a non-exhaustive enum whose values originate
// in the descriptors below and flow, still typed, into the standard request constructors —
// so an interface number can never be passed where a configuration value is expected.
// The bus address of a device, assigned by the host with SET_ADDRESS. Addresses are 7 bits
// wide.
const DeviceAddress = enum(u7) {
// The default address every device answers at after a reset, until SET_ADDRESS
// completes
default = 0,
_,
};
// Identifies a configuration; from ConfigurationDescriptor.configuration_value.
const ConfigurationValue = enum(u8) {
// Not configured: returned by GET_CONFIGURATION while the device is in the address
// state, and passed to SET_CONFIGURATION to return a configured device to the address
// state
none = 0,
_,
};
// Identifies an interface within a configuration; from
// InterfaceDescriptor.interface_number.
const InterfaceNumber = enum(u8) { _ };
// Selects between the alternate settings of one interface; from
// InterfaceDescriptor.alternate_setting.
const AlternateSetting = enum(u8) {
// The default setting of an interface
default = 0,
_,
};
// The number of an endpoint within a device, 4 bits wide. The direction bit carried
// alongside it tells the two endpoints sharing a number apart.
const EndpointNumber = enum(u4) {
// Endpoint zero: the default control pipe every device provides
default_control = 0,
_,
};
// Index of a STRING descriptor, stored in descriptors that reference a string and passed to
// GET_DESCRIPTOR to read it.
const StringIndex = enum(u8) {
// The device has no string descriptor for this field
none = 0,
_,
};
// Characteristics of a device request (the bmRequestType field of a set-up packet). Fields are
// declared least-significant first: recipient occupies bits 4...0, kind bits 6...5, and
// direction bit 7.
const RequestType = packed struct(u8) {
// The recipient of the request (values 4...31 are reserved)
recipient: Recipient,
// The type of the request
kind: Kind,
// Data transfer direction. The value of this bit is ignored when length is zero.
direction: Direction,
const Recipient = enum(u5) {
device = 0,
interface = 1,
endpoint = 2,
other = 3,
};
const Kind = enum(u2) {
standard = 0,
class = 1,
vendor = 2,
reserved = 3,
};
const Direction = enum(u1) {
host_to_device = 0,
device_to_host = 1,
};
};
const Request = extern struct {
// Characteristics of the request
request_type: RequestType,
// Specific request
request_code: RequestCode,
// Word-sized field that may (or may not) serve as a parameter to the request, depending
// on the specific request. For GET_DESCRIPTOR and SET_DESCRIPTOR, bit-cast a
// DescriptorValue into this field.
value: u16 align(1),
// Word-sized field that may (or may not) serve as a parameter to the request, depending
// on the specific request. Typically this field holds an index or an offset value. When
// request_type specifies an endpoint or an interface as the recipient, bit-cast an
// EndpointIndex or an InterfaceIndex into this field.
index: u16 align(1),
// Number of bytes to transfer if there is a DATA stage.
// - If this field is non-zero, and request_type indicates a transfer from
// device-to-host, then the device must never return more than length bytes of data.
// However, a device may return less.
// - If this field is non-zero, and request_type indicates a transfer from
// host-to-device, then the host must send exactly length bytes of data. If the host
// sends more than length bytes, the behavior of the device is undefined.
length: u16 align(1),
// The format of the index field when request_type specifies an endpoint as the
// recipient. The host should always set the direction bit to zero (but the device
// should accept either value) when the endpoint is part of a control pipe.
const EndpointIndex = packed struct(u16) {
// Endpoint number
number: EndpointNumber,
// Reserved (reset to zero)
reserved: u3 = 0,
// Selects the OUT or the IN endpoint with the specified endpoint number
direction: EndpointDirection,
// Reserved (reset to zero)
reserved_high: u8 = 0,
};
// The format of the index field when request_type specifies an interface as the
// recipient.
const InterfaceIndex = packed struct(u16) {
// Interface number
number: u8,
// Reserved (reset to zero)
reserved: u8 = 0,
};
// The format of the value field of GET_DESCRIPTOR and SET_DESCRIPTOR requests: the
// descriptor type in the high byte, and the descriptor index in the low byte. The index
// is used to select a specific descriptor (only for CONFIGURATION and STRING
// descriptors) when several descriptors of that type are implemented by a device.
const DescriptorValue = packed struct(u16) {
// Descriptor index
index: u8 = 0,
// Descriptor type
kind: DescriptorType,
};
};
// Feature selectors, used as the value field of CLEAR_FEATURE and SET_FEATURE requests. The
// comment on each value notes the recipient the selector applies to.
const FeatureSelector = enum(u16) {
// Halts an endpoint (recipient: endpoint)
endpoint_halt = 0,
// Enables or disables the device's remote wakeup capability (recipient: device)
device_remote_wakeup = 1,
// Puts a hi-speed device into a test mode, selected by a TestMode value in the high
// byte of the index field (recipient: device)
test_mode = 2,
};
// Test mode selectors, passed in the high byte of the index field of a SET_FEATURE request
// with the test_mode feature selector. Values 06h...3Fh are reserved for standard test
// selectors and C0h...FFh for vendor-specific test modes; all other unlisted values are
// reserved.
const TestMode = enum(u8) {
test_j = 0x01,
test_k = 0x02,
test_se0_nak = 0x03,
test_packet = 0x04,
test_force_enable = 0x05,
_,
};
// The two bytes returned by a GET_STATUS request directed at a device. Fields are declared
// least-significant first.
const DeviceStatus = packed struct(u16) {
// Whether the device is currently self-powered (as opposed to bus-powered). This bit
// cannot be changed with the SET_FEATURE or CLEAR_FEATURE requests.
self_powered: bool,
// Whether the device is currently enabled to request remote wakeup. Changed with the
// SET_FEATURE and CLEAR_FEATURE requests using the device_remote_wakeup feature
// selector.
remote_wakeup: bool,
// Reserved (reset to zero)
reserved: u14,
};
// The two bytes returned by a GET_STATUS request directed at an endpoint. (A GET_STATUS
// request directed at an interface returns two bytes that are entirely reserved.)
const EndpointStatus = packed struct(u16) {
// Whether the endpoint is currently halted. Set with the SET_FEATURE request using the
// endpoint_halt feature selector, and cleared with CLEAR_FEATURE.
halted: bool,
// Reserved (reset to zero)
reserved: u15,
};
// A target for the standard requests that may be directed at the device, an interface, or
// an endpoint.
const Target = union(enum) {
device,
interface: InterfaceNumber,
endpoint: Request.EndpointIndex,
fn recipient(target: Target) RequestType.Recipient {
return switch (target) {
.device => .device,
.interface => .interface,
.endpoint => .endpoint,
};
}
fn index(target: Target) u16 {
return switch (target) {
.device => 0,
.interface => |number| @intFromEnum(number),
.endpoint => |endpoint| @bitCast(endpoint),
};
}
};
// Constructors for the standard device requests, one per RequestCode. Each returns a
// ready-to-send set-up packet with the request_type, value, index, and length fields the
// specification prescribes for that request.
// Reads the status of the given target: bit-cast the two bytes the device returns into a
// DeviceStatus or an EndpointStatus. (The two bytes returned for an interface are entirely
// reserved.)
fn getStatus(target: Target) Request {
return .{
.request_type = .{
.recipient = target.recipient(),
.kind = .standard,
.direction = .device_to_host,
},
.request_code = .get_status,
.value = 0,
.index = target.index(),
.length = 2,
};
}
// Clears or disables the given feature. A device cannot be taken out of a test mode with
// this request; test_mode is only cleared by cycling power.
fn clearFeature(feature: FeatureSelector, target: Target) Request {
return .{
.request_type = .{
.recipient = target.recipient(),
.kind = .standard,
.direction = .host_to_device,
},
.request_code = .clear_feature,
.value = @intFromEnum(feature),
.index = target.index(),
.length = 0,
};
}
// Sets or enables the given feature. For the test_mode feature selector, use setTestMode
// instead: the test selector rides in the high byte of the index field.
fn setFeature(feature: FeatureSelector, target: Target) Request {
return .{
.request_type = .{
.recipient = target.recipient(),
.kind = .standard,
.direction = .host_to_device,
},
.request_code = .set_feature,
.value = @intFromEnum(feature),
.index = target.index(),
.length = 0,
};
}
// Puts a hi-speed device into the given test mode: a SET_FEATURE request with the test_mode
// feature selector and the test selector in the high byte of the index field.
fn setTestMode(mode: TestMode) Request {
return .{
.request_type = .{
.recipient = .device,
.kind = .standard,
.direction = .host_to_device,
},
.request_code = .set_feature,
.value = @intFromEnum(FeatureSelector.test_mode),
.index = @as(u16, @intFromEnum(mode)) << 8,
.length = 0,
};
}
// Assigns the device its bus address, moving it from the default state to the address
// state. The device does not answer at the new address until the status stage of this
// request completes.
fn setAddress(address: DeviceAddress) Request {
return .{
.request_type = .{
.recipient = .device,
.kind = .standard,
.direction = .host_to_device,
},
.request_code = .set_address,
.value = @intFromEnum(address),
.index = 0,
.length = 0,
};
}
// Reads a descriptor from the device.
// - descriptor_index selects among descriptors of the same type, and is only used for
// configuration and string descriptors.
// - language_id selects the language of a string descriptor, and is zero otherwise.
// - length is the number of bytes to read; a device never returns more than length bytes,
// but may return less if the descriptor is shorter.
fn getDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
return .{
.request_type = .{
.recipient = .device,
.kind = .standard,
.direction = .device_to_host,
},
.request_code = .get_descriptor,
.value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }),
.index = language_id,
.length = length,
};
}
// Updates an existing descriptor or adds a new one (optional; many devices do not support
// this request). The parameters mirror getDescriptor; the descriptor itself is sent in the
// DATA stage.
fn setDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
return .{
.request_type = .{
.recipient = .device,
.kind = .standard,
.direction = .host_to_device,
},
.request_code = .set_descriptor,
.value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }),
.index = language_id,
.length = length,
};
}
// Reads the currently active configuration: @enumFromInt the byte the device returns into a
// ConfigurationValue, which is none while the device is not configured.
fn getConfiguration() Request {
return .{
.request_type = .{
.recipient = .device,
.kind = .standard,
.direction = .device_to_host,
},
.request_code = .get_configuration,
.value = 0,
.index = 0,
.length = 1,
};
}
// Selects the configuration with the given configuration_value (from
// ConfigurationDescriptor.configuration_value), moving the device from the address state to
// the configured state. Selecting none returns the device to the address state.
fn setConfiguration(configuration_value: ConfigurationValue) Request {
return .{
.request_type = .{
.recipient = .device,
.kind = .standard,
.direction = .host_to_device,
},
.request_code = .set_configuration,
.value = @intFromEnum(configuration_value),
.index = 0,
.length = 0,
};
}
// Reads the alternate setting currently selected for the given interface: @enumFromInt the
// byte the device returns into an AlternateSetting.
fn getInterface(interface: InterfaceNumber) Request {
return .{
.request_type = .{
.recipient = .interface,
.kind = .standard,
.direction = .device_to_host,
},
.request_code = .get_interface,
.value = 0,
.index = @intFromEnum(interface),
.length = 1,
};
}
// Selects an alternate setting (from InterfaceDescriptor.alternate_setting) for the given
// interface.
fn setInterface(interface: InterfaceNumber, alternate_setting: AlternateSetting) Request {
return .{
.request_type = .{
.recipient = .interface,
.kind = .standard,
.direction = .host_to_device,
},
.request_code = .set_interface,
.value = @intFromEnum(alternate_setting),
.index = @intFromEnum(interface),
.length = 0,
};
}
// Reads the two-byte number of the frame in which the given isochronous endpoint's
// repeating pattern of transfers begins.
fn syncFrame(endpoint: Request.EndpointIndex) Request {
return .{
.request_type = .{
.recipient = .endpoint,
.kind = .standard,
.direction = .device_to_host,
},
.request_code = .sync_frame,
.value = 0,
.index = @bitCast(endpoint),
.length = 2,
};
}
const DescriptorType = enum(u8) {
device = 1,
configuration = 2,
string = 3,
interface = 4,
endpoint = 5,
device_qualifier = 6,
other_speed_configuration = 7,
interface_power = 8,
_,
};
const DeviceDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// DEVICE Descriptor Type
descriptor_type: DescriptorType,
// USB Specification Release Number in Binary-Coded Decimal (i.e, 2.10 is expressed as 210h).
// Identifies the release of the USB Specification with with the device and its
// descriptors are compliant.
bcd_usb: u16 align(1),
// Class code (assigned by the USB-IF)
// - This field is reset to zero if each interface within a configuration specifies its own
// class information and the various interfaces operate independently.
// - A value of FFh in this field indicates the device class is vendor-specific.
device_class: u8,
// Subclass Code (assigned by the USB-IF)
// - The subclass code of a device is qualified by the class code of that device.
// - If device_class is reset to zero, then this field must also be reset to zero.
// - When device_class is not set to FFh, then all values for this field are reserved for
// assignment by the USB-IF.
device_subclass: u8,
// Protocol code (assigned by the USB-IF)
// - The protocol code of a device is qualified by both the class and subclass codes of
// that device.
// - A value of 00h in this field means that the device may specify class-specific
// protocols on an interface basis, though this is not a requirement.
// - If this field is set to FFh, then the device uses a vendor-specific protocol.
device_protocol: u8,
// Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options)
max_packet_size_0: u8,
// Vendor ID (assigned by the USB-IF)
vendor_id: u16 align(1),
// Product ID (assigned by the USB-IF)
product_id: u16 align(1),
// Device release number in binary-coded decimal
bcd_device: u16 align(1),
// Index of STRING descriptor describing manufacturer
manufacturer_index: StringIndex,
// Index of STRING descriptor describing product
product_index: StringIndex,
// Index of STRING descriptor describing the device's serial number
serial_number_index: StringIndex,
// Number of possible configurations
configuration_count: u8,
};
const DeviceQualifierDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// DEVICE_QUALIFIER Descriptor Type
descriptor_type: DescriptorType,
// USB Specification Release Number in Binary-Coded Decimal (i.e, 2.00 is expressed as 200h).
// Identifies the release of the USB Specification with with the device and its
// descriptors are compliant. This field must be at least 0200h.
bcd_usb: u16 align(1),
// Class code (assigned by the USB-IF)
device_class: u8,
// Subclass Code (assigned by the USB-IF)
device_subclass: u8,
// Protocol code (assigned by the USB-IF)
device_protocol: u8,
// Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options)
max_packet_size_0: u8,
// Number of possible configurations
configuration_count: u8,
// Reserved for future uses, must be zero.
reserved: u8,
};
const ConfigurationDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// CONFIGURATION Descriptor Type
descriptor_type: DescriptorType,
// The total combined length in bytes of all the descriptors returned with the request for
// this CONFIGURATION descriptor (including CONFIGURATION, INTERFACE, ENDPOINT, class- and
// vendor-specific descriptors).
total_length: u16 align(1),
// Number of interfaces supported by this configuration
interface_count: u8,
// Value which when used as an argument in the SET_CONFIGURATION request, causes the device
// to assume the configuration described by this descriptor.
configuration_value: ConfigurationValue,
// Index of STRING descriptor describing this configuration.
configuration_index: StringIndex,
// Configuration Characteristics
attributes: Attributes,
// Maximum power consumption of this device from the bus when fully operational and using
// this configuration. Expressed in units of 2mA (i.e., a value of 50 in this field
// indicates 100mA).
// - A device reports with the attributes field whether the configuration is bus- or
// self-powered, but the device status (retrieved with a GET_STATUS request) reports
// whether the device is currently self-powered.
// - If a device is disconnected from an external power source, it may not draw more
// power from the bus than specified in this field.
max_power: u8,
// Configuration characteristics. Fields are declared least-significant first.
const Attributes = packed struct(u8) {
// Reserved, reset to zero (D4...0)
reserved: u5,
// Whether Remote Wakeup is supported by this configuration (D5)
remote_wakeup: bool,
// Self-Powered (D6)
// - false: Device runs on power supplied by the bus
// - true: Device provides a local power source; if max_power is non-zero, the
// device also may use bus power.
self_powered: bool,
// Reserved, must be set to one for historical reasons (D7)
reserved_one: u1,
};
};
// This descriptor describes the configuration of a high-speed device if it were operating at
// its alternative speed. The structure of the OTHER_SPEED_CONFIGURATION is identical to that
// of the CONFIGURATION descriptor; the only difference is that the descriptor_type field
// reflects that the descriptor is an OTHER_SPEED_CONFIGURATION descriptor.
const OtherSpeedConfigurationDescriptor = ConfigurationDescriptor;
const InterfaceDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// INTERFACE Descriptor Type
descriptor_type: DescriptorType,
// Number of this interface. Zero-based value which identifies the index of this interface
// in the array of interfaces supported within a configuration.
interface_number: InterfaceNumber,
// Value used to select the alternate settings described by this INTERFACE descriptor for
// the interface with the interface_number in the previous field. This value is zero if
// this descriptor describes the default settings for a particular interface.
alternate_setting: AlternateSetting,
// Number of endpoints used by this interface, not including endpoint zero.
endpoint_count: u8,
// Class code (assigned by the USB-IF)
// - A value of zero here is reserved for future standardization.
// - If this value is FFh, the interface class is vendor-specific.
// - All other values are reserved for assignment by the USB-IF.
interface_class: u8,
// Subclass code (assigned by the USB-IF)
// - The subclass code in this field is qualified by the value of the interface_class
// field.
// - If interface_class is reset to zero, then this field must also be reset to zero.
// - If interface_class is not set to the value of FFh, then all values of this field are
// reserved for assignment by the USB-IF.
interface_subclass: u8,
// Protocol code (assigned by the USB-IF)
// - The protocol code in this field is qualified by the values of the interface_class
// and interface_subclass fields.
// - If an interface supports class-specific requests, then this field identifies the
// protocols that the device uses as defined by the specifications of the device class.
// - If this field is reset to zero, then the device does not use a class-specific
// protocol on this interface.
// - If this field is set to FFh, then the device uses a vendor-specific protocol on
// this interface.
interface_protocol: u8,
// Index of STRING descriptor describing this interface
interface_index: StringIndex,
};
const EndpointDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// ENDPOINT Descriptor Type
descriptor_type: DescriptorType,
// The address of the endpoint on the USB device described by this descriptor
endpoint_address: Address,
// The endpoint's attributes
attributes: Attributes,
// Maximum packet size that this endpoint is capable of sending or receiving. For
// isochronous endpoints, this value is used to reserve bus time; the pipe, however, may
// not always use all of the reserved bus time.
max_packet_size: MaxPacketSize align(1),
// Interval for polling a device during a data transfer, expressed in units of microframes
// for high-speed devices, and frames for low- and full-speed devices. The exact meaning of
// the value in this field depends on the endpoint type and the operating speed of the
// device:
// - Full- and High-speed isochronous endpoints, and high-speed interrupt endpoints:
// This field must be in the range from 1 to 16, and is used to calculate the period
// as 2^(interval - 1). That is, a value of 4 calculates to 2^(4 - 1) = 2^3 = 8.
// - Full- and Low-speed interrupt endpoints: This field must be in the range from
// 1 to 255.
// - High-speed bulk and control OUT endpoints: This field must be in the range from
// 0 to 255, and specifies the maximum NAK rate of the endpoint. A value of zero
// indicates that the endpoint never NAKs; other values indicate at most 1 NAK each
// interval number of microframes.
interval: u8,
// The address of an endpoint. Fields are declared least-significant first.
const Address = packed struct(u8) {
// Endpoint Number (D3...0)
number: EndpointNumber,
// Reserved, reset to zero (D6...4)
reserved: u3,
// Direction, ignored for control endpoints (D7)
direction: EndpointDirection,
};
// An endpoint's attributes. Fields are declared least-significant first.
const Attributes = packed struct(u8) {
// Transfer Type (D1...0)
transfer_type: TransferType,
// Synchronization Type; isochronous endpoints only, reserved and reset to zero for
// other endpoint types (D3...2)
synchronization: Synchronization,
// Usage Type; isochronous endpoints only, reserved and reset to zero for other
// endpoints (D5...4)
usage: Usage,
// Reserved, reset to zero (D7...6)
reserved: u2,
};
const TransferType = enum(u2) {
control = 0,
isochronous = 1,
bulk = 2,
interrupt = 3,
};
const Synchronization = enum(u2) {
none = 0,
asynchronous = 1,
adaptive = 2,
synchronous = 3,
};
const Usage = enum(u2) {
data = 0,
feedback = 1,
implicit_feedback_data = 2,
_,
};
// The maximum packet size of an endpoint. Fields are declared least-significant first.
const MaxPacketSize = packed struct(u16) {
// Maximum packet size in bytes (bits 10...0)
size: u11,
// Number of additional transaction opportunities per microframe, for high-speed
// isochronous and interrupt endpoints; reserved and reset to zero for other
// endpoints (bits 12...11)
additional_transactions: AdditionalTransactions,
// Reserved, must be reset to zero (bits 15...13)
reserved: u3,
};
const AdditionalTransactions = enum(u2) {
// None (1 transaction per microframe)
none = 0,
// 1 additional (2 transactions per microframe)
one = 1,
// 2 additional (3 transactions per microframe)
two = 2,
_,
};
};
// A STRING descriptor at index zero returns the list of LANGID codes supported by the
// device; all other indices return a Unicode string. Both forms start with this two-byte
// header, followed by the variable-length payload:
// - index 0: an array of two-byte LANGID codes (wLangID[0] through wLangID[x])
// - other indices: a Unicode string of N bytes
const StringDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// STRING Descriptor Type
descriptor_type: DescriptorType,
};
const std = @import("std");
test "wire sizes and offsets match the specification" {
const expectEqual = std.testing.expectEqual;
try expectEqual(8, @sizeOf(Request));
try expectEqual(18, @sizeOf(DeviceDescriptor));
try expectEqual(10, @sizeOf(DeviceQualifierDescriptor));
try expectEqual(9, @sizeOf(ConfigurationDescriptor));
try expectEqual(9, @sizeOf(InterfaceDescriptor));
try expectEqual(7, @sizeOf(EndpointDescriptor));
try expectEqual(2, @sizeOf(StringDescriptor));
try expectEqual(2, @offsetOf(DeviceDescriptor, "bcd_usb"));
try expectEqual(8, @offsetOf(DeviceDescriptor, "vendor_id"));
try expectEqual(17, @offsetOf(DeviceDescriptor, "configuration_count"));
try expectEqual(2, @offsetOf(ConfigurationDescriptor, "total_length"));
try expectEqual(4, @offsetOf(EndpointDescriptor, "max_packet_size"));
}
test "bitmap packings match the specification" {
const expectEqual = std.testing.expectEqual;
const expect = std.testing.expect;
// bmRequestType for GET_DESCRIPTOR: device-to-host | standard | device = 80h
const request_type = RequestType{
.recipient = .device,
.kind = .standard,
.direction = .device_to_host,
};
try expectEqual(0x80, @as(u8, @bitCast(request_type)));
// wValue for GET_DESCRIPTOR(CONFIGURATION, index 0) = 0200h
const descriptor_value = Request.DescriptorValue{ .kind = .configuration };
try expectEqual(0x0200, @as(u16, @bitCast(descriptor_value)));
// wIndex for the IN endpoint 1 = 0081h
const endpoint_index = Request.EndpointIndex{ .number = @enumFromInt(1), .direction = .in };
try expectEqual(0x0081, @as(u16, @bitCast(endpoint_index)));
// Endpoint address 81h = IN endpoint 1
const address: EndpointDescriptor.Address = @bitCast(@as(u8, 0x81));
try expectEqual(1, @intFromEnum(address.number));
try expectEqual(.in, address.direction);
// Endpoint attributes 03h = interrupt transfer
const attributes: EndpointDescriptor.Attributes = @bitCast(@as(u8, 0x03));
try expectEqual(.interrupt, attributes.transfer_type);
// wMaxPacketSize 0008h = 8 bytes, no additional transactions
const max_packet_size: EndpointDescriptor.MaxPacketSize = @bitCast(@as(u16, 0x0008));
try expectEqual(8, max_packet_size.size);
try expectEqual(.none, max_packet_size.additional_transactions);
// Configuration attributes C0h = self-powered, with the historical D7 bit set
const configuration_attributes: ConfigurationDescriptor.Attributes = @bitCast(@as(u8, 0xC0));
try expect(configuration_attributes.self_powered);
try expect(!configuration_attributes.remote_wakeup);
try expectEqual(1, configuration_attributes.reserved_one);
// GET_STATUS words: device 0001h = self-powered; endpoint 0001h = halted
const device_status: DeviceStatus = @bitCast(@as(u16, 0x0001));
try expect(device_status.self_powered and !device_status.remote_wakeup);
const endpoint_status: EndpointStatus = @bitCast(@as(u16, 0x0001));
try expect(endpoint_status.halted);
// DescriptorType is non-exhaustive: class-specific values (HID = 21h) pass through
const hid_type: DescriptorType = @enumFromInt(0x21);
try expectEqual(0x21, @intFromEnum(hid_type));
try expect(hid_type != .device);
}
fn expectRequestBytes(request: Request, expected: [8]u8) !void {
try std.testing.expectEqualSlices(u8, &expected, std.mem.asBytes(&request));
}
test "standard request constructors encode the specification's set-up packets" {
try expectRequestBytes(getStatus(.device), .{ 0x80, 0, 0, 0, 0, 0, 2, 0 });
try expectRequestBytes(getStatus(.{ .interface = @enumFromInt(3) }), .{ 0x81, 0, 0, 0, 3, 0, 2, 0 });
try expectRequestBytes(getStatus(.{ .endpoint = .{ .number = @enumFromInt(2), .direction = .in } }), .{ 0x82, 0, 0, 0, 0x82, 0, 2, 0 });
try expectRequestBytes(clearFeature(.endpoint_halt, .{ .endpoint = .{ .number = @enumFromInt(1), .direction = .out } }), .{ 0x02, 1, 0, 0, 0x01, 0, 0, 0 });
try expectRequestBytes(setFeature(.device_remote_wakeup, .device), .{ 0x00, 3, 1, 0, 0, 0, 0, 0 });
try expectRequestBytes(setTestMode(.test_packet), .{ 0x00, 3, 2, 0, 0, 0x04, 0, 0 });
try expectRequestBytes(setAddress(@enumFromInt(5)), .{ 0x00, 5, 5, 0, 0, 0, 0, 0 });
try expectRequestBytes(getDescriptor(.device, 0, 0, 18), .{ 0x80, 6, 0, 1, 0, 0, 18, 0 });
try expectRequestBytes(getDescriptor(.string, 2, 0x0409, 255), .{ 0x80, 6, 2, 3, 0x09, 0x04, 255, 0 });
try expectRequestBytes(setDescriptor(.string, 2, 0x0409, 16), .{ 0x00, 7, 2, 3, 0x09, 0x04, 16, 0 });
try expectRequestBytes(getConfiguration(), .{ 0x80, 8, 0, 0, 0, 0, 1, 0 });
try expectRequestBytes(setConfiguration(@enumFromInt(1)), .{ 0x00, 9, 1, 0, 0, 0, 0, 0 });
try expectRequestBytes(getInterface(@enumFromInt(2)), .{ 0x81, 10, 0, 0, 2, 0, 1, 0 });
try expectRequestBytes(setInterface(@enumFromInt(2), @enumFromInt(1)), .{ 0x01, 11, 1, 0, 2, 0, 0, 0 });
try expectRequestBytes(syncFrame(.{ .number = @enumFromInt(3), .direction = .in }), .{ 0x82, 12, 0, 0, 0x83, 0, 2, 0 });
}
+264
View File
@@ -0,0 +1,264 @@
//! USB class-code decoding: turn the (class, subclass, protocol) triple a USB device or
//! interface reports in its descriptors into typed values. The device descriptor carries one
//! triple for the whole device, and each interface descriptor carries its own; a class code
//! of zero at the device level defers entirely to the interfaces. Subclass and protocol
//! codes are qualified by the class code — the same value means different things under
//! different classes — so there is no single SubClass or Protocol enum: each class with
//! spec-defined codes gets its own namespace below. Pure reference data (from the USB-IF
//! defined class codes; see https://www.usb.org/defined-class-codes) — no hardware access —
//! so it is shared by kernel discovery and any user-space tool (device naming, driver
//! matching).
// Base class codes (assigned by the USB-IF). The comment on each value notes where the code
// may legally appear: in the device descriptor, in interface descriptors, or both.
const Class = enum(u8) {
// Use class information in the interface descriptors (device descriptor only). Each
// interface within a configuration specifies its own class information and the various
// interfaces operate independently.
per_interface = 0x00,
// Audio: speakers, microphones, sound cards (interface)
audio = 0x01,
// Communications and CDC control: modems, network adapters (both)
communications = 0x02,
// Human Interface Device: keyboards, mice, game controllers (interface)
hid = 0x03,
// Physical: force-feedback devices (interface)
physical = 0x05,
// Image: still-imaging cameras, scanners (interface)
image = 0x06,
// Printer (interface)
printer = 0x07,
// Mass storage: flash drives, external disks, card readers (interface)
mass_storage = 0x08,
// Hub (device descriptor only)
hub = 0x09,
// CDC-Data: the data interfaces paired with a communications control interface
// (interface)
cdc_data = 0x0A,
// Smart card readers (interface)
smart_card = 0x0B,
// Content security (interface)
content_security = 0x0D,
// Video: webcams (interface)
video = 0x0E,
// Personal healthcare devices (interface)
personal_healthcare = 0x0F,
// Audio/Video devices (interface)
audio_video = 0x10,
// Billboard: describes alternate modes a USB Type-C device supports (device descriptor
// only)
billboard = 0x11,
// USB Type-C bridge (interface)
type_c_bridge = 0x12,
// USB Bulk Display Protocol devices (interface)
bulk_display = 0x13,
// MCTP over USB protocol endpoint devices (interface)
mctp = 0x14,
// I3C devices (interface)
i3c = 0x3C,
// Diagnostic devices (both)
diagnostic = 0xDC,
// Wireless controllers: Bluetooth adapters (interface)
wireless_controller = 0xE0,
// Miscellaneous (both)
miscellaneous = 0xEF,
// Application-specific: firmware upgrade, IrDA bridges, test and measurement
// (interface)
application_specific = 0xFE,
// Vendor-specific (both)
vendor_specific = 0xFF,
_,
};
// Subclass and protocol codes qualified by Class.hub. Hubs have no subclass codes; the
// protocol distinguishes the hub's transaction-translator arrangement.
const hub = struct {
const Protocol = enum(u8) {
// Full-speed hub
full_speed = 0x00,
// Hi-speed hub with a single transaction translator
hi_speed_single_tt = 0x01,
// Hi-speed hub with multiple transaction translators
hi_speed_multi_tt = 0x02,
// SuperSpeed hub (USB 3)
super_speed = 0x03,
_,
};
};
// Subclass and protocol codes qualified by Class.hid.
const hid = struct {
const SubClass = enum(u8) {
// No subclass
none = 0x00,
// Boot interface: the device also supports the simplified boot protocol, usable by
// firmware before a full HID report-descriptor parser is available
boot = 0x01,
_,
};
// Only meaningful when the subclass is boot
const Protocol = enum(u8) {
none = 0x00,
keyboard = 0x01,
mouse = 0x02,
_,
};
};
// Subclass and protocol codes qualified by Class.mass_storage. The subclass identifies the
// command set the device understands; the protocol identifies the transport used to carry
// commands, data, and status over the bus.
const mass_storage = struct {
const SubClass = enum(u8) {
// SCSI command set not reported; de facto, treat as scsi
not_reported = 0x00,
// Reduced Block Commands: typically flash devices
rbc = 0x01,
// MMC-5 (ATAPI): CD and DVD drives
atapi = 0x02,
// QIC-157 tape drives (obsolete)
qic_157 = 0x03,
// UFI: floppy disk drives
ufi = 0x04,
// SFF-8070i (obsolete)
sff_8070i = 0x05,
// Transparent SCSI command set: the common case for flash drives and disks
scsi = 0x06,
// LSD FS: negotiated access to large storage devices
lsd_fs = 0x07,
// IEEE 1667
ieee_1667 = 0x08,
// Vendor-specific
vendor_specific = 0xFF,
_,
};
const Protocol = enum(u8) {
// Control/Bulk/Interrupt with command completion interrupt
cbi_completion_interrupt = 0x00,
// Control/Bulk/Interrupt without command completion interrupt
cbi = 0x01,
// Bulk-only transport: the common case for flash drives and disks
bulk_only = 0x50,
// USB attached SCSI
uas = 0x62,
// Vendor-specific
vendor_specific = 0xFF,
_,
};
};
// Subclass and protocol codes qualified by Class.communications (CDC). The protocol codes
// are model-specific; the useful invariant is the subclass, which selects the control model
// the interface implements.
const communications = struct {
const SubClass = enum(u8) {
// Direct line control model
direct_line = 0x01,
// Abstract control model: USB modems and serial adapters
abstract_control = 0x02,
// Telephone control model
telephone = 0x03,
// Multi-channel control model
multi_channel = 0x04,
// CAPI control model
capi = 0x05,
// Ethernet networking control model
ethernet = 0x06,
// ATM networking control model
atm = 0x07,
// Wireless handset control model
wireless_handset = 0x08,
// Device management
device_management = 0x09,
// Mobile direct line model
mobile_direct_line = 0x0A,
// OBEX
obex = 0x0B,
// Ethernet emulation model
ethernet_emulation = 0x0C,
// Network control model
network_control = 0x0D,
_,
};
};
// Subclass and protocol codes qualified by Class.wireless_controller.
const wireless_controller = struct {
const SubClass = enum(u8) {
// Radio frequency controllers
radio_frequency = 0x01,
_,
};
// Only meaningful when the subclass is radio_frequency
const Protocol = enum(u8) {
// Bluetooth programming interface
bluetooth = 0x01,
// Ultra-wideband radio control
ultra_wideband = 0x02,
// Remote NDIS
remote_ndis = 0x03,
// Bluetooth AMP controller
bluetooth_amp = 0x04,
_,
};
};
// Subclass and protocol codes qualified by Class.miscellaneous.
const miscellaneous = struct {
const SubClass = enum(u8) {
// Common class
common = 0x02,
_,
};
// Only meaningful when the subclass is common
const Protocol = enum(u8) {
// Interface association descriptor: at the device level, announces that the
// configuration groups interfaces into functions with IADs
interface_association = 0x01,
_,
};
};
// Subclass and protocol codes qualified by Class.application_specific.
const application_specific = struct {
const SubClass = enum(u8) {
// Device firmware upgrade
firmware_upgrade = 0x01,
// IrDA bridge
irda_bridge = 0x02,
// Test and measurement
test_and_measurement = 0x03,
_,
};
};
test "class codes match the USB-IF assignments" {
const std = @import("std");
const expectEqual = std.testing.expectEqual;
try expectEqual(0x03, @intFromEnum(Class.hid));
try expectEqual(0x09, @intFromEnum(Class.hub));
try expectEqual(0xFF, @intFromEnum(Class.vendor_specific));
// A typical flash drive: mass storage, transparent SCSI, bulk-only transport.
try expectEqual(0x06, @intFromEnum(mass_storage.SubClass.scsi));
try expectEqual(0x50, @intFromEnum(mass_storage.Protocol.bulk_only));
// A boot keyboard: HID, boot subclass, keyboard protocol.
try expectEqual(0x01, @intFromEnum(hid.SubClass.boot));
try expectEqual(0x01, @intFromEnum(hid.Protocol.keyboard));
// Class codes are non-exhaustive: unlisted values pass through undamaged.
const unknown: Class = @enumFromInt(0x42);
try expectEqual(0x42, @intFromEnum(unknown));
_ = hub.Protocol.hi_speed_multi_tt;
_ = communications.SubClass.abstract_control;
_ = wireless_controller.Protocol.bluetooth;
_ = miscellaneous.Protocol.interface_association;
_ = application_specific.SubClass.firmware_upgrade;
}
+1
View File
@@ -100,6 +100,7 @@ pub fn main() void {
while (n < n_children) : (n += 1) { while (n < n_children) : (n += 1) {
var child = std.mem.zeroes(device.DeviceDescriptor); var child = std.mem.zeroes(device.DeviceDescriptor);
child.class = @intFromEnum(device.DeviceClass.timer); child.class = @intFromEnum(device.DeviceClass.timer);
child.pci_class = device.no_pci_class;
child.hid_len = 6; child.hid_len = 6;
child.hid[0..6].* = "hpet-t".*; child.hid[0..6].* = "hpet-t".*;
child.resource_count = 1; child.resource_count = 1;
+16 -16
View File
@@ -118,8 +118,8 @@ pub fn main() void {
maybe_controller = controller; maybe_controller = controller;
maybe_interrupt_index = findInterruptResourceIndex(controller_device_descriptor); maybe_interrupt_index = findInterruptResourceIndex(controller_device_descriptor);
controller.disablePort(.One); controller.disablePort(.one);
controller.disablePort(.Two); controller.disablePort(.two);
controller.flushOutputBuffer(); controller.flushOutputBuffer();
const current = controller.readConfigurationByte() orelse { const current = controller.readConfigurationByte() orelse {
@@ -136,7 +136,7 @@ pub fn main() void {
return; return;
} }
if (controller.performSelfTest()) | reply | { if (controller.performSelfTest()) |reply| {
if (reply != ps2.response_controller_test_passed) { if (reply != ps2.response_controller_test_passed) {
_ = runtime.system.write("system/drivers/ps2-bus: perform controller self test failed\n"); _ = runtime.system.write("system/drivers/ps2-bus: perform controller self test failed\n");
return; return;
@@ -154,20 +154,20 @@ pub fn main() void {
if (has_two_channels) { if (has_two_channels) {
_ = runtime.system.write("system/drivers/ps2-bus: has two channels\n"); _ = runtime.system.write("system/drivers/ps2-bus: has two channels\n");
// keep the bus quiet until we have tested the ports and are ready to use them // keep the bus quiet until we have tested the ports and are ready to use them
controller.disablePort(.Two); controller.disablePort(.two);
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: has one channel\n"); _ = runtime.system.write("system/drivers/ps2-bus: has one channel\n");
} }
// interface tests: always test port 1, test port 2 only if it exists // interface tests: always test port 1, test port 2 only if it exists
const port_one_works = (controller.testPort(.One) orelse { const port_one_works = (controller.testPort(.one) orelse {
_ = runtime.system.write("system/drivers/ps2-bus: port 1 test timed out\n"); _ = runtime.system.write("system/drivers/ps2-bus: port 1 test timed out\n");
return; return;
}) == ps2.response_port_test_passed; }) == ps2.response_port_test_passed;
var port_two_works = false; var port_two_works = false;
if (has_two_channels) { if (has_two_channels) {
port_two_works = (controller.testPort(.Two) orelse { port_two_works = (controller.testPort(.two) orelse {
_ = runtime.system.write("system/drivers/ps2-bus: port 2 test timed out\n"); _ = runtime.system.write("system/drivers/ps2-bus: port 2 test timed out\n");
return; return;
}) == ps2.response_port_test_passed; }) == ps2.response_port_test_passed;
@@ -181,20 +181,20 @@ pub fn main() void {
// Enable the working ports. Their interrupts stay off until IRQ1 is bound // Enable the working ports. Their interrupts stay off until IRQ1 is bound
// below — reset and identify use polled reads, which must never race the // below — reset and identify use polled reads, which must never race the
// interrupt-driven drain loop for bytes. // interrupt-driven drain loop for bytes.
controller.enablePort(.One); controller.enablePort(.one);
if (port_two_works) controller.enablePort(.Two); if (port_two_works) controller.enablePort(.two);
// reset each working device; a failing device is logged but does not // reset each working device; a failing device is logged but does not
// abort bring-up of the other one // abort bring-up of the other one
if (port_one_works) { if (port_one_works) {
if (controller.resetDevice(.One)) |passed| { if (controller.resetDevice(.one)) |passed| {
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset failed\n"); if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset failed\n");
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset timed out\n"); _ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset timed out\n");
} }
} }
if (port_two_works) { if (port_two_works) {
if (controller.resetDevice(.Two)) |passed| { if (controller.resetDevice(.two)) |passed| {
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset failed\n"); if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset failed\n");
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset timed out\n"); _ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset timed out\n");
@@ -204,8 +204,8 @@ pub fn main() void {
// Identify the device on each working port and hand it off to the driver // Identify the device on each working port and hand it off to the driver
// that matches what it reported — a port is not assumed to be a keyboard // that matches what it reported — a port is not assumed to be a keyboard
// or a mouse by its number. // or a mouse by its number.
if (port_one_works) port_device_types[@intFromEnum(ps2.Port.One)] = spawnIdentifiedDriver(controller, .One); if (port_one_works) port_device_types[@intFromEnum(ps2.Port.one)] = spawnIdentifiedDriver(controller, .one);
if (port_two_works) port_device_types[@intFromEnum(ps2.Port.Two)] = spawnIdentifiedDriver(controller, .Two); if (port_two_works) port_device_types[@intFromEnum(ps2.Port.two)] = spawnIdentifiedDriver(controller, .two);
} else { } else {
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n"); _ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n");
return; return;
@@ -243,7 +243,7 @@ pub fn main() void {
// claim that node too and route its IRQ to the same endpoint. The IRQ belongs // claim that node too and route its IRQ to the same endpoint. The IRQ belongs
// to the *port*, whatever device identify found on it. // to the *port*, whatever device identify found on it.
var maybe_auxiliary_interrupt: ?struct { device_id: u64, interrupt_index: u64, gsi: u64 } = null; var maybe_auxiliary_interrupt: ?struct { device_id: u64, interrupt_index: u64, gsi: u64 } = null;
if (port_device_types[@intFromEnum(ps2.Port.Two)] != null) { if (port_device_types[@intFromEnum(ps2.Port.two)] != null) {
if (device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_mouse.hid())) |descriptor| { if (device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_mouse.hid())) |descriptor| {
if (findInterruptResourceIndex(descriptor)) |auxiliary_index| { if (findInterruptResourceIndex(descriptor)) |auxiliary_index| {
if (device.claim(descriptor.id) and device.irqBind(descriptor.id, auxiliary_index, endpoint)) { if (device.claim(descriptor.id) and device.irqBind(descriptor.id, auxiliary_index, endpoint)) {
@@ -263,8 +263,8 @@ pub fn main() void {
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n"); _ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
return; return;
}; };
if (port_device_types[@intFromEnum(ps2.Port.One)] != null) configuration |= ps2.Port.One.interruptBit(); if (port_device_types[@intFromEnum(ps2.Port.one)] != null) configuration |= ps2.Port.one.interruptBit();
if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.Two.interruptBit(); if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.two.interruptBit();
_ = controller.writeConfigurationByte(configuration); _ = controller.writeConfigurationByte(configuration);
_ = runtime.system.write("system/drivers/ps2-bus: ok\n"); _ = runtime.system.write("system/drivers/ps2-bus: ok\n");
@@ -284,7 +284,7 @@ pub fn main() void {
const current_status = ps2.status(controller.device_id, controller.status_index); const current_status = ps2.status(controller.device_id, controller.status_index);
if (current_status & ps2.status_output_buffer_full == 0) break; if (current_status & ps2.status_output_buffer_full == 0) break;
const byte = device.ioRead(controller.device_id, controller.data_index, 0, 1) orelse break; const byte = device.ioRead(controller.device_id, controller.data_index, 0, 1) orelse break;
const port: ps2.Port = if (current_status & ps2.status_auxiliary_output != 0) .Two else .One; const port: ps2.Port = if (current_status & ps2.status_auxiliary_output != 0) .two else .one;
if (port_endpoints[@intFromEnum(port)]) |child| { if (port_endpoints[@intFromEnum(port)]) |child| {
const forwarded = ps2.ForwardedByte{ .port = @intFromEnum(port), .byte = byte }; const forwarded = ps2.ForwardedByte{ .port = @intFromEnum(port), .byte = byte };
_ = ipc.send(child, std.mem.asBytes(&forwarded)); _ = ipc.send(child, std.mem.asBytes(&forwarded));
+26 -25
View File
@@ -146,52 +146,53 @@ pub fn readData(id: u64, status_index: u64, data_index: u64, timeout_nanoseconds
} }
pub fn writeData(id: u64, status_index: u64, data_index: u64, byte: u8, timeout_nanoseconds: u64) bool { pub fn writeData(id: u64, status_index: u64, data_index: u64, byte: u8, timeout_nanoseconds: u64) bool {
// IBF lives in the status register (0x64); wait for it to clear there, then write the data port (0x60) // IBF lives in the status register (0x64); wait for it to clear there, then write the data port
// (0x60)
if (!waitWritable(id, status_index, timeout_nanoseconds)) return false; if (!waitWritable(id, status_index, timeout_nanoseconds)) return false;
return device.ioWrite(id, data_index, 0, 1, byte); return device.ioWrite(id, data_index, 0, 1, byte);
} }
pub const Port = enum(u2) { pub const Port = enum(u2) {
One, one,
Two, two,
/// Command register byte that disables this port. /// Command register byte that disables this port.
fn disableCommand(self: Port) u8 { fn disableCommand(self: Port) u8 {
return switch (self) { return switch (self) {
.One => cmd_disable_first_port, .one => cmd_disable_first_port,
.Two => cmd_disable_second_port, .two => cmd_disable_second_port,
}; };
} }
/// Command register byte that enables this port (and its clock). /// Command register byte that enables this port (and its clock).
fn enableCommand(self: Port) u8 { fn enableCommand(self: Port) u8 {
return switch (self) { return switch (self) {
.One => cmd_enable_first_port, .one => cmd_enable_first_port,
.Two => cmd_enable_second_port, .two => cmd_enable_second_port,
}; };
} }
/// Command register byte that runs this port's interface test. /// Command register byte that runs this port's interface test.
fn testCommand(self: Port) u8 { fn testCommand(self: Port) u8 {
return switch (self) { return switch (self) {
.One => cmd_test_first_port, .one => cmd_test_first_port,
.Two => cmd_test_second_port, .two => cmd_test_second_port,
}; };
} }
/// Configuration-byte bit that, when set, disables this port's clock. /// Configuration-byte bit that, when set, disables this port's clock.
pub fn clockDisabledBit(self: Port) u8 { pub fn clockDisabledBit(self: Port) u8 {
return switch (self) { return switch (self) {
.One => configuration_first_port_clock_disabled, .one => configuration_first_port_clock_disabled,
.Two => configuration_second_port_clock_disabled, .two => configuration_second_port_clock_disabled,
}; };
} }
/// Configuration-byte bit that, when set, enables this port's interrupt. /// Configuration-byte bit that, when set, enables this port's interrupt.
pub fn interruptBit(self: Port) u8 { pub fn interruptBit(self: Port) u8 {
return switch (self) { return switch (self) {
.One => configuration_first_port_interrupt, .one => configuration_first_port_interrupt,
.Two => configuration_second_port_interrupt, .two => configuration_second_port_interrupt,
}; };
} }
@@ -199,8 +200,8 @@ pub const Port = enum(u2) {
/// output buffer (makes a byte appear as if it came from the device). /// output buffer (makes a byte appear as if it came from the device).
pub fn writeOutputBufferCommand(self: Port) u8 { pub fn writeOutputBufferCommand(self: Port) u8 {
return switch (self) { return switch (self) {
.One => cmd_write_first_port_output, .one => cmd_write_first_port_output,
.Two => cmd_write_second_port_output, .two => cmd_write_second_port_output,
}; };
} }
@@ -209,24 +210,24 @@ pub const Port = enum(u2) {
/// prefix (null); port 2 requires the "write second port input" command. /// prefix (null); port 2 requires the "write second port input" command.
pub fn deviceInputCommand(self: Port) ?u8 { pub fn deviceInputCommand(self: Port) ?u8 {
return switch (self) { return switch (self) {
.One => null, .one => null,
.Two => cmd_write_second_port_input, .two => cmd_write_second_port_input,
}; };
} }
/// Controller output-port bit driving this port's clock line. /// Controller output-port bit driving this port's clock line.
pub fn outputPortClockBit(self: Port) u8 { pub fn outputPortClockBit(self: Port) u8 {
return switch (self) { return switch (self) {
.One => output_port_first_port_clock, .one => output_port_first_port_clock,
.Two => output_port_second_port_clock, .two => output_port_second_port_clock,
}; };
} }
/// Controller output-port bit driving this port's data line. /// Controller output-port bit driving this port's data line.
pub fn outputPortDataBit(self: Port) u8 { pub fn outputPortDataBit(self: Port) u8 {
return switch (self) { return switch (self) {
.One => output_port_first_port_data, .one => output_port_first_port_data,
.Two => output_port_second_port_data, .two => output_port_second_port_data,
}; };
} }
@@ -234,8 +235,8 @@ pub const Port = enum(u2) {
/// (wired to the port's IRQ line). /// (wired to the port's IRQ line).
pub fn outputPortBufferFullBit(self: Port) u8 { pub fn outputPortBufferFullBit(self: Port) u8 {
return switch (self) { return switch (self) {
.One => output_port_first_port_output_full, .one => output_port_first_port_output_full,
.Two => output_port_second_port_output_full, .two => output_port_second_port_output_full,
}; };
} }
}; };
@@ -392,9 +393,9 @@ pub const Controller = struct {
/// enabled; the caller should disable it again to keep the bus quiet until /// enabled; the caller should disable it again to keep the bus quiet until
/// device bring-up. /// device bring-up.
pub fn hasTwoChannels(self: Controller) ?bool { pub fn hasTwoChannels(self: Controller) ?bool {
self.enablePort(.Two); self.enablePort(.two);
const configuration = self.readConfigurationByte() orelse return null; const configuration = self.readConfigurationByte() orelse return null;
return (configuration & Port.Two.clockDisabledBit()) == 0; return (configuration & Port.two.clockDisabledBit()) == 0;
} }
/// Reset the device attached to `port` (device command 0xFF) and wait for /// Reset the device attached to `port` (device command 0xFF) and wait for
@@ -0,0 +1,71 @@
//! /system/drivers/usb-xhci-bus — the xHCI (USB 3) host-controller bus driver.
//! The device manager spawns **one instance per controller** it discovers (a machine
//! can carry several), passing the controller's device-tree id as argv[1]; this
//! instance claims that device and no other, so multiple instances never fight over
//! hardware. This increment proves the plumbing: parse the id, claim the controller,
//! and report its MMIO window. The next increments map the registers and bring the
//! controller up (reset, rings, port scan), then enumerate the USB devices on the
//! bus with the usb-abi request builders and publish each with `device_register`.
const std = @import("std");
const runtime = @import("runtime");
const device = runtime.device;
/// Format one whole log line and emit it in a single `debug_write`, so concurrent
/// instances (one per controller) can never interleave mid-line.
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);
}
pub fn main(init: runtime.process.Init) void {
const argument = init.arguments.get(1) orelse {
_ = runtime.system.write("usb-xhci-bus: missing controller device id (argv[1])\n");
return;
};
const controller_id = std.fmt.parseInt(u64, argument, 10) catch {
writeLine("usb-xhci-bus: malformed controller device id '{s}'\n", .{argument});
return;
};
if (!device.claim(controller_id)) {
writeLine("usb-xhci-bus: unable to claim controller device {d}\n", .{controller_id});
return;
}
// Fetch our own descriptor back for the controller's resources.
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("usb-xhci-bus: out of memory\n");
return;
};
const total = device.enumerate(buffer);
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
if (d.id == controller_id) break d;
} else {
writeLine("usb-xhci-bus: device {d} not in the device tree\n", .{controller_id});
return;
};
// The controller's operational registers live behind BAR0, enumerated as the
// device's first memory resource.
const register_window = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| {
if (resource.kind == @intFromEnum(device.ResourceKind.memory)) break resource;
} else {
writeLine("usb-xhci-bus: controller device {d} has no MMIO window\n", .{controller_id});
return;
};
writeLine("usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{
controller_id,
register_window.start,
register_window.len,
});
// Controller bring-up (map the window, reset, rings, port scan) is the next
// increment; stay resident as the bus's supervisor in the meantime.
while (true) runtime.system.sleep(1000);
}
pub const panic = runtime.panic;
comptime {
_ = &runtime.start._start; // pull the runtime entry shim into the image
}
+1 -2
View File
@@ -490,8 +490,7 @@ pub fn saveInterrupts() u64 {
\\cli \\cli
: [f] "=r" (flags), : [f] "=r" (flags),
: :
: .{ .memory = true } : .{ .memory = true });
);
return flags; return flags;
} }
+2 -4
View File
@@ -166,8 +166,7 @@ pub fn init(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot
asm volatile ("mov %[pml4], %%cr3" asm volatile ("mov %[pml4], %%cr3"
: :
: [pml4] "r" (pml4), : [pml4] "r" (pml4),
: .{ .memory = true } : .{ .memory = true });
);
on_own_tables = true; // now on the kernel's physmap (covers all RAM) on_own_tables = true; // now on the kernel's physmap (covers all RAM)
init_done = true; // the kernel half is fixed from here init_done = true; // the kernel half is fixed from here
} }
@@ -409,6 +408,5 @@ fn invalidate(virtual: u64) void {
\\invlpg (%%rax) \\invlpg (%%rax)
: :
: [v] "r" (virtual), : [v] "r" (virtual),
: .{ .rax = true, .memory = true } : .{ .rax = true, .memory = true });
);
} }
-2
View File
@@ -154,5 +154,3 @@ pub const Console = struct {
while (x < self.fb.width) : (x += 1) destination[x] = source[x]; while (x < self.fb.width) : (x += 1) destination[x] = source[x];
} }
}; };
+2
View File
@@ -66,6 +66,7 @@ fn record(node: *platform.Device, parent_id: u64) u64 {
d.id = count; d.id = count;
d.parent = parent_id; d.parent = parent_id;
d.class = @intFromEnum(node.class); d.class = @intFromEnum(node.class);
d.pci_class = if (node.ids.pci_class) |code| code else device_abi.no_pci_class;
const h = node.hid(); const h = node.hid();
d.hid_len = @min(h.len, d.hid.len); d.hid_len = @min(h.len, d.hid.len);
@memcpy(d.hid[0..d.hid_len], h[0..d.hid_len]); @memcpy(d.hid[0..d.hid_len], h[0..d.hid_len]);
@@ -170,6 +171,7 @@ pub fn register(parent_id: u64, owner: u32, descriptor: *const device_abi.Device
d.id = count; d.id = count;
d.parent = parent_id; d.parent = parent_id;
d.class = descriptor.class; d.class = descriptor.class;
d.pci_class = descriptor.pci_class;
d.hid_len = @min(descriptor.hid_len, d.hid.len); d.hid_len = @min(descriptor.hid_len, d.hid.len);
@memcpy(d.hid[0..@intCast(d.hid_len)], descriptor.hid[0..@intCast(d.hid_len)]); @memcpy(d.hid[0..@intCast(d.hid_len)], descriptor.hid[0..@intCast(d.hid_len)]);
d.resource_count = descriptor.resource_count; d.resource_count = descriptor.resource_count;
+1 -1
View File
@@ -87,7 +87,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
log.print(" pitch : {d} bytes\n", .{fb.pitch}); log.print(" pitch : {d} bytes\n", .{fb.pitch});
log.print(" format : {s}\n", .{@tagName(fb.format)}); log.print(" format : {s}\n", .{@tagName(fb.format)});
log.print(" framebuffer: 0x{x:0>16}\n", .{fb.base}); log.print(" framebuffer: 0x{x:0>16}\n", .{fb.base});
log.print (" footprint : {d} MiB\n", .{(fb.pitch * fb.height) / (1024 * 1024)}); log.print(" footprint : {d} MiB\n", .{(fb.pitch * fb.height) / (1024 * 1024)});
// Summarise the physical memory the loader handed us. The array is danos's // Summarise the physical memory the loader handed us. The array is danos's
// own MemoryRegion, so this is a plain slice — no firmware layout in sight. // own MemoryRegion, so this is a plain slice — no firmware layout in sight.
+6 -5
View File
@@ -2023,7 +2023,10 @@ fn faultNull() void {
// access). `allowzero` skips the same null check on the cast. The write then // access). `allowzero` skips the same null check on the cast. The write then
// hits the unmapped page 0 and takes a real hardware #PF. // hits the unmapped page 0 and takes a real hardware #PF.
var address: u64 = 0; var address: u64 = 0;
address = asm ("" : [ret] "=r" (-> u64) : [in] "0" (address)); address = asm (""
: [ret] "=r" (-> u64),
: [in] "0" (address),
);
const p: *allowzero volatile u64 = @ptrFromInt(address); const p: *allowzero volatile u64 = @ptrFromInt(address);
p.* = 1; p.* = 1;
} }
@@ -2049,8 +2052,7 @@ fn faultDoubleFault() void {
\\ud2 \\ud2
: :
: [sp] "r" (bad_sp), : [sp] "r" (bad_sp),
: .{ .memory = true } : .{ .memory = true });
);
bad_sp += 0; bad_sp += 0;
} }
@@ -2069,8 +2071,7 @@ fn apDoubleFaultTask() void {
\\ud2 \\ud2
: :
: [sp] "r" (bad_sp), : [sp] "r" (bad_sp),
: .{ .memory = true } : .{ .memory = true });
);
bad_sp += 0; bad_sp += 0;
} }
@@ -42,6 +42,35 @@ fn driverFor(d: device.DeviceDescriptor) ?[]const u8 {
}; };
} }
/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller:
/// Serial Bus Controller (0x0C) / USB Controller (0x03) / XHCI (0x30) — the names
/// pci-class.zig decodes.
const xhci_pci_class: u64 = 0x0C_03_30;
/// The bus driver that serves a PCI function, or null. Unlike the singleton drivers
/// in `driverFor`, a machine can carry several identical controllers — so the caller
/// spawns one driver instance *per device*, passing the device id as argv[1] for the
/// instance to claim.
fn pciDriverFor(d: device.DeviceDescriptor) ?[]const u8 {
if (d.class != @intFromEnum(device.DeviceClass.pci_device)) return null;
return switch (d.pci_class) {
xhci_pci_class => "usb-xhci-bus",
else => null,
};
}
/// Spawn one instance of `driver_name` to serve the specific device `id` — the id
/// arrives as argv[1]. No isProcessRunning gate here: the name alone cannot tell two
/// instances apart, and this manager is the sole spawner of drivers.
fn spawnForDevice(driver_name: []const u8, id: u64) void {
var text: [20]u8 = undefined;
const id_text = std.fmt.bufPrint(&text, "{d}", .{id}) catch return;
if (system.spawnWithArguments(driver_name, &.{id_text}) != null) {
writeLine("device-manager: spawned {s} for device {d}\n", .{ driver_name, id });
} else {
writeLine("device-manager: failed to spawn {s} for device {d}\n", .{ driver_name, id });
}
}
pub fn main() void { pub fn main() void {
// Enumerate into a heap buffer (too big for the one-page user stack). // Enumerate into a heap buffer (too big for the one-page user stack).
@@ -54,6 +83,11 @@ pub fn main() void {
var matched: usize = 0; var matched: usize = 0;
for (buffer[0..count]) |descriptor| { for (buffer[0..count]) |descriptor| {
if (pciDriverFor(descriptor)) |driver_name| {
matched += 1;
spawnForDevice(driver_name, descriptor.id);
continue;
}
const driver_name = driverFor(descriptor) orelse continue; const driver_name = driverFor(descriptor) orelse continue;
matched += 1; matched += 1;
if (!system.isProcessRunning(driver_name)) { if (!system.isProcessRunning(driver_name)) {
+2 -2
View File
@@ -8,8 +8,8 @@
//! spellings (`stat`, `O_CREAT`, ...) live only in the POSIX layer //! spellings (`stat`, `O_CREAT`, ...) live only in the POSIX layer
//! (library/posix/unistd.zig), which translates to these. //! (library/posix/unistd.zig), which translates to these.
//! //!
//! This is user-space only — the kernel knows nothing of files or paths; it only //! This is user-space only — the kernel knows nothing of files or paths; it only moves the bytes.
//! moves the bytes. Shared by library/posix/unistd.zig (client) and system/services/vfs/vfs.zig (server). //! Shared by library/posix/unistd.zig (client) and system/services/vfs/vfs.zig (server).
pub const Operation = enum(u32) { pub const Operation = enum(u32) {
open, // open(path) -> node id open, // open(path) -> node id
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Rewrap over-long Zig comment lines at a column limit (default 100).
Rules:
- Only comment-only lines are touched; trailing comments after code are left alone.
- Consecutive comment lines with the same indentation and marker (`//`, `///`, `//!`)
form a block. Blank comment lines separate paragraphs within a block.
- A line whose text starts with `- ` begins a bullet paragraph; its continuation
lines are the ones indented to align under the bullet's text (bullet lead + 2).
- Plain paragraphs join consecutive lines with the same text indentation;
wrapped lines align where the first line's text begins.
- A paragraph is rewrapped only if at least one of its lines exceeds the limit,
so deliberate short line breaks elsewhere are preserved.
"""
import argparse
import difflib
import re
import subprocess
import sys
import textwrap
LIMIT = 100
COMMENT_RE = re.compile(r"^(\s*)(//[/!]?)(?:\s(.*))?$")
BULLET_RE = re.compile(r"^(\s*)- (.*)$")
def split_paragraphs(texts):
"""texts: list of comment text (None for a bare marker line).
Returns paragraphs: dicts with lead/hang/bullet/texts/lines(indices)."""
paragraphs = []
current = None
for index, text in enumerate(texts):
if text is None or text.strip() == "":
paragraphs.append({"literal": True, "lines": [index]})
current = None
continue
lead = len(text) - len(text.lstrip(" "))
bullet = BULLET_RE.match(text)
if bullet:
current = {
"lead": len(bullet.group(1)),
"hang": len(bullet.group(1)) + 2,
"bullet": True,
"texts": [bullet.group(2)],
"lines": [index],
}
paragraphs.append(current)
elif current is not None and lead == current["hang"]:
current["texts"].append(text)
current["lines"].append(index)
else:
current = {
"lead": lead,
"hang": lead,
"bullet": False,
"texts": [text],
"lines": [index],
}
paragraphs.append(current)
return paragraphs
def wrap_paragraph(paragraph, prefix):
joined = re.sub(r"\s+", " ", " ".join(t.strip() for t in paragraph["texts"]))
if paragraph["bullet"]:
initial = prefix + " " * paragraph["lead"] + "- "
else:
initial = prefix + " " * paragraph["lead"]
subsequent = prefix + " " * paragraph["hang"]
return textwrap.wrap(
joined,
width=LIMIT,
initial_indent=initial,
subsequent_indent=subsequent,
break_long_words=False,
break_on_hyphens=False,
)
def rewrap_block(original_lines, indent, marker, texts):
prefix = indent + marker + " "
output = []
for paragraph in split_paragraphs(texts):
block_originals = [original_lines[i] for i in paragraph["lines"]]
if paragraph.get("literal") or all(len(l) <= LIMIT for l in block_originals):
output.extend(block_originals)
else:
output.extend(wrap_paragraph(paragraph, prefix))
return output
def process(source):
lines = source.split("\n")
result = []
i = 0
while i < len(lines):
match = COMMENT_RE.match(lines[i])
if not match:
result.append(lines[i])
i += 1
continue
indent, marker = match.group(1), match.group(2)
block_lines, texts = [], []
while i < len(lines):
m = COMMENT_RE.match(lines[i])
if not m or m.group(1) != indent or m.group(2) != marker:
break
block_lines.append(lines[i])
texts.append(m.group(3))
i += 1
result.extend(rewrap_block(block_lines, indent, marker, texts))
return "\n".join(result)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--write", action="store_true", help="apply changes (default: diff only)")
parser.add_argument("files", nargs="*", help="files to process (default: git ls-files '*.zig')")
args = parser.parse_args()
files = args.files or subprocess.run(
["git", "ls-files", "*.zig"], capture_output=True, text=True, check=True
).stdout.split()
changed = 0
for path in files:
with open(path, encoding="utf-8") as f:
source = f.read()
rewrapped = process(source)
if rewrapped == source:
continue
changed += 1
if args.write:
with open(path, "w", encoding="utf-8") as f:
f.write(rewrapped)
print(f"rewrapped {path}")
else:
sys.stdout.writelines(
difflib.unified_diff(
source.splitlines(keepends=True),
rewrapped.splitlines(keepends=True),
fromfile=path,
tofile=path,
)
)
if not changed:
print("no comments over the limit")
if __name__ == "__main__":
main()