Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77901bbba6 | ||
|
|
78582d24d2 | ||
|
|
1cdffe21b1 | ||
|
|
4df90bc212 | ||
|
|
713e77354b | ||
|
|
abb7b1b634 | ||
|
|
f5f0e15769 |
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
*.zig text eol=lf
|
||||
@@ -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_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 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");
|
||||
// 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.
|
||||
@@ -360,6 +361,8 @@ pub fn build(b: *std.Build) void {
|
||||
mk_run.addFileArg(ps2_keyboard_exe.getEmittedBin());
|
||||
mk_run.addArg("ps2-mouse");
|
||||
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.addFileArg(device_manager_exe.getEmittedBin());
|
||||
mk_run.addArg("input");
|
||||
@@ -384,6 +387,7 @@ pub fn build(b: *std.Build) void {
|
||||
.{ ps2_bus_exe, "system/drivers" },
|
||||
.{ ps2_keyboard_exe, "system/drivers" },
|
||||
.{ ps2_mouse_exe, "system/drivers" },
|
||||
.{ usb_xhci_bus_exe, "system/drivers" },
|
||||
}) |entry| {
|
||||
const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } });
|
||||
b.getInstallStep().dependOn(&step.step);
|
||||
@@ -526,6 +530,8 @@ pub fn build(b: *std.Build) void {
|
||||
"system/devices/device-abi.zig",
|
||||
"system/devices/pci-class.zig", // class/subclass/prog-IF 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
|
||||
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
|
||||
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
|
||||
|
||||
+13
-2
@@ -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
|
||||
supervision link as the kill authority, and child-exit notifications over the
|
||||
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
|
||||
asynchronous `ipc_send` primitive built to fix it, and the per-device subscribe/publish
|
||||
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.
|
||||
|
||||
Start with the north star:
|
||||
|
||||
@@ -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
|
||||
is a clipped form of?* If yes, write the word. If it's an initialism standing in for a
|
||||
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.
|
||||
|
||||
@@ -37,6 +37,10 @@ pub fn mmioMap(device_id: u64, resource_index: u64) ?usize {
|
||||
/// `DeviceDescriptor.parent` for a device with 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.
|
||||
/// 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.
|
||||
|
||||
@@ -37,7 +37,8 @@ pub const MouseEventKind = protocol.MouseEventKind;
|
||||
pub const JoystickEventKind = protocol.JoystickEventKind;
|
||||
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_mouse = protocol.device_mouse;
|
||||
pub const device_joystick = protocol.device_joystick;
|
||||
|
||||
@@ -19,34 +19,49 @@ pub inline fn systemCall0(n: SystemCall) usize {
|
||||
pub inline fn systemCall1(n: SystemCall, a0: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0),
|
||||
: [n] "{rax}" (@intFromEnum(n)),
|
||||
[a0] "{rdi}" (a0),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
}
|
||||
|
||||
pub inline fn systemCall2(n: SystemCall, a0: usize, a1: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [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 });
|
||||
}
|
||||
|
||||
pub inline fn systemCall3(n: SystemCall, a0: usize, a1: usize, a2: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [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 });
|
||||
}
|
||||
|
||||
pub inline fn systemCall4(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [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 });
|
||||
}
|
||||
|
||||
pub inline fn systemCall5(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [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 });
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ pub const PowerInformation = struct {
|
||||
reset: RegisterAccess = .{},
|
||||
reset_value: u8 = 0,
|
||||
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,
|
||||
s3: ?aml.SleepType = null,
|
||||
};
|
||||
@@ -198,7 +199,8 @@ const ExtendedSystemDescriptorPointer = extern struct {
|
||||
root_system_description_table_address: u32 align(1),
|
||||
/// The size of the RSDP.
|
||||
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),
|
||||
/// A checksum used for the entire table.
|
||||
extended_checksum: u8,
|
||||
|
||||
@@ -126,17 +126,22 @@ test "parses a nested namespace and finds the sleep package" {
|
||||
// Scope(\_SB) packagelen=0x27
|
||||
0x10, 0x27, 0x5C, 0x5F, 0x53, 0x42, 0x5F,
|
||||
// Device(PCI0) packagelen=0x1F
|
||||
0x5B, 0x82, 0x1F, 0x50, 0x43, 0x49, 0x30,
|
||||
0x5B, 0x82, 0x1F, 0x50, 0x43,
|
||||
0x49, 0x30,
|
||||
// Name(_HID, 0x11)
|
||||
0x08, 0x5F, 0x48, 0x49, 0x44, 0x0A, 0x11,
|
||||
// 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
|
||||
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)
|
||||
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
|
||||
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);
|
||||
|
||||
@@ -56,6 +56,10 @@ pub const maximum_device_resources = 8;
|
||||
/// `DeviceDescriptor.parent` for a device with no parent — a root of the device tree.
|
||||
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
|
||||
/// these to find the hardware it owns, claims it, and maps its MMIO.
|
||||
///
|
||||
@@ -69,6 +73,11 @@ pub const DeviceDescriptor = extern struct {
|
||||
id: u64,
|
||||
parent: u64, // a device id, or `no_parent`
|
||||
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,
|
||||
resource_count: u64,
|
||||
hid: [8]u8,
|
||||
|
||||
@@ -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
|
||||
else
|
||||
std.fmt.bufPrint(buffer[indent..], "{s} [{s}] hid={s}\n", .{ device.name(), @tagName(device.class), device.hid() }) catch return;
|
||||
} else
|
||||
std.fmt.bufPrint(buffer[indent..], "{s} [{s}]\n", .{ device.name(), @tagName(device.class) }) catch return;
|
||||
} else std.fmt.bufPrint(buffer[indent..], "{s} [{s}]\n", .{ device.name(), @tagName(device.class) }) catch return;
|
||||
emit(buffer[0 .. indent + body.len]);
|
||||
|
||||
// For a PCI function, decode its class code — the (class / subclass / prog-IF)
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -100,6 +100,7 @@ pub fn main() void {
|
||||
while (n < n_children) : (n += 1) {
|
||||
var child = std.mem.zeroes(device.DeviceDescriptor);
|
||||
child.class = @intFromEnum(device.DeviceClass.timer);
|
||||
child.pci_class = device.no_pci_class;
|
||||
child.hid_len = 6;
|
||||
child.hid[0..6].* = "hpet-t".*;
|
||||
child.resource_count = 1;
|
||||
|
||||
@@ -118,8 +118,8 @@ pub fn main() void {
|
||||
maybe_controller = controller;
|
||||
maybe_interrupt_index = findInterruptResourceIndex(controller_device_descriptor);
|
||||
|
||||
controller.disablePort(.One);
|
||||
controller.disablePort(.Two);
|
||||
controller.disablePort(.one);
|
||||
controller.disablePort(.two);
|
||||
controller.flushOutputBuffer();
|
||||
|
||||
const current = controller.readConfigurationByte() orelse {
|
||||
@@ -136,7 +136,7 @@ pub fn main() void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller.performSelfTest()) | reply | {
|
||||
if (controller.performSelfTest()) |reply| {
|
||||
if (reply != ps2.response_controller_test_passed) {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: perform controller self test failed\n");
|
||||
return;
|
||||
@@ -154,20 +154,20 @@ pub fn main() void {
|
||||
if (has_two_channels) {
|
||||
_ = 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
|
||||
controller.disablePort(.Two);
|
||||
controller.disablePort(.two);
|
||||
} else {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: has one channel\n");
|
||||
}
|
||||
|
||||
// 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");
|
||||
return;
|
||||
}) == ps2.response_port_test_passed;
|
||||
|
||||
var port_two_works = false;
|
||||
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");
|
||||
return;
|
||||
}) == 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
|
||||
// below — reset and identify use polled reads, which must never race the
|
||||
// interrupt-driven drain loop for bytes.
|
||||
controller.enablePort(.One);
|
||||
if (port_two_works) controller.enablePort(.Two);
|
||||
controller.enablePort(.one);
|
||||
if (port_two_works) controller.enablePort(.two);
|
||||
|
||||
// reset each working device; a failing device is logged but does not
|
||||
// abort bring-up of the other one
|
||||
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");
|
||||
} else {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset timed out\n");
|
||||
}
|
||||
}
|
||||
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");
|
||||
} else {
|
||||
_ = 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
|
||||
// that matches what it reported — a port is not assumed to be a keyboard
|
||||
// or a mouse by its number.
|
||||
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_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);
|
||||
} else {
|
||||
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n");
|
||||
return;
|
||||
@@ -243,7 +243,7 @@ pub fn main() void {
|
||||
// claim that node too and route its IRQ to the same endpoint. The IRQ belongs
|
||||
// to the *port*, whatever device identify found on it.
|
||||
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 (findInterruptResourceIndex(descriptor)) |auxiliary_index| {
|
||||
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");
|
||||
return;
|
||||
};
|
||||
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 (port_device_types[@intFromEnum(ps2.Port.one)] != null) configuration |= ps2.Port.one.interruptBit();
|
||||
if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.two.interruptBit();
|
||||
_ = controller.writeConfigurationByte(configuration);
|
||||
|
||||
_ = 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);
|
||||
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 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| {
|
||||
const forwarded = ps2.ForwardedByte{ .port = @intFromEnum(port), .byte = byte };
|
||||
_ = ipc.send(child, std.mem.asBytes(&forwarded));
|
||||
|
||||
@@ -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 {
|
||||
// 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;
|
||||
return device.ioWrite(id, data_index, 0, 1, byte);
|
||||
}
|
||||
|
||||
pub const Port = enum(u2) {
|
||||
One,
|
||||
Two,
|
||||
one,
|
||||
two,
|
||||
|
||||
/// Command register byte that disables this port.
|
||||
fn disableCommand(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => cmd_disable_first_port,
|
||||
.Two => cmd_disable_second_port,
|
||||
.one => cmd_disable_first_port,
|
||||
.two => cmd_disable_second_port,
|
||||
};
|
||||
}
|
||||
|
||||
/// Command register byte that enables this port (and its clock).
|
||||
fn enableCommand(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => cmd_enable_first_port,
|
||||
.Two => cmd_enable_second_port,
|
||||
.one => cmd_enable_first_port,
|
||||
.two => cmd_enable_second_port,
|
||||
};
|
||||
}
|
||||
|
||||
/// Command register byte that runs this port's interface test.
|
||||
fn testCommand(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => cmd_test_first_port,
|
||||
.Two => cmd_test_second_port,
|
||||
.one => cmd_test_first_port,
|
||||
.two => cmd_test_second_port,
|
||||
};
|
||||
}
|
||||
|
||||
/// Configuration-byte bit that, when set, disables this port's clock.
|
||||
pub fn clockDisabledBit(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => configuration_first_port_clock_disabled,
|
||||
.Two => configuration_second_port_clock_disabled,
|
||||
.one => configuration_first_port_clock_disabled,
|
||||
.two => configuration_second_port_clock_disabled,
|
||||
};
|
||||
}
|
||||
|
||||
/// Configuration-byte bit that, when set, enables this port's interrupt.
|
||||
pub fn interruptBit(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => configuration_first_port_interrupt,
|
||||
.Two => configuration_second_port_interrupt,
|
||||
.one => configuration_first_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).
|
||||
pub fn writeOutputBufferCommand(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => cmd_write_first_port_output,
|
||||
.Two => cmd_write_second_port_output,
|
||||
.one => cmd_write_first_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.
|
||||
pub fn deviceInputCommand(self: Port) ?u8 {
|
||||
return switch (self) {
|
||||
.One => null,
|
||||
.Two => cmd_write_second_port_input,
|
||||
.one => null,
|
||||
.two => cmd_write_second_port_input,
|
||||
};
|
||||
}
|
||||
|
||||
/// Controller output-port bit driving this port's clock line.
|
||||
pub fn outputPortClockBit(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => output_port_first_port_clock,
|
||||
.Two => output_port_second_port_clock,
|
||||
.one => output_port_first_port_clock,
|
||||
.two => output_port_second_port_clock,
|
||||
};
|
||||
}
|
||||
|
||||
/// Controller output-port bit driving this port's data line.
|
||||
pub fn outputPortDataBit(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => output_port_first_port_data,
|
||||
.Two => output_port_second_port_data,
|
||||
.one => output_port_first_port_data,
|
||||
.two => output_port_second_port_data,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -234,8 +235,8 @@ pub const Port = enum(u2) {
|
||||
/// (wired to the port's IRQ line).
|
||||
pub fn outputPortBufferFullBit(self: Port) u8 {
|
||||
return switch (self) {
|
||||
.One => output_port_first_port_output_full,
|
||||
.Two => output_port_second_port_output_full,
|
||||
.one => output_port_first_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
|
||||
/// device bring-up.
|
||||
pub fn hasTwoChannels(self: Controller) ?bool {
|
||||
self.enablePort(.Two);
|
||||
self.enablePort(.two);
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -490,8 +490,7 @@ pub fn saveInterrupts() u64 {
|
||||
\\cli
|
||||
: [f] "=r" (flags),
|
||||
:
|
||||
: .{ .memory = true }
|
||||
);
|
||||
: .{ .memory = true });
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
@@ -166,8 +166,7 @@ pub fn init(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot
|
||||
asm volatile ("mov %[pml4], %%cr3"
|
||||
:
|
||||
: [pml4] "r" (pml4),
|
||||
: .{ .memory = true }
|
||||
);
|
||||
: .{ .memory = true });
|
||||
on_own_tables = true; // now on the kernel's physmap (covers all RAM)
|
||||
init_done = true; // the kernel half is fixed from here
|
||||
}
|
||||
@@ -409,6 +408,5 @@ fn invalidate(virtual: u64) void {
|
||||
\\invlpg (%%rax)
|
||||
:
|
||||
: [v] "r" (virtual),
|
||||
: .{ .rax = true, .memory = true }
|
||||
);
|
||||
: .{ .rax = true, .memory = true });
|
||||
}
|
||||
|
||||
@@ -154,5 +154,3 @@ pub const Console = struct {
|
||||
while (x < self.fb.width) : (x += 1) destination[x] = source[x];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ fn record(node: *platform.Device, parent_id: u64) u64 {
|
||||
d.id = count;
|
||||
d.parent = parent_id;
|
||||
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();
|
||||
d.hid_len = @min(h.len, 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.parent = parent_id;
|
||||
d.class = descriptor.class;
|
||||
d.pci_class = descriptor.pci_class;
|
||||
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)]);
|
||||
d.resource_count = descriptor.resource_count;
|
||||
|
||||
@@ -87,7 +87,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
||||
log.print(" pitch : {d} bytes\n", .{fb.pitch});
|
||||
log.print(" format : {s}\n", .{@tagName(fb.format)});
|
||||
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
|
||||
// own MemoryRegion, so this is a plain slice — no firmware layout in sight.
|
||||
|
||||
@@ -2023,7 +2023,10 @@ fn faultNull() void {
|
||||
// access). `allowzero` skips the same null check on the cast. The write then
|
||||
// hits the unmapped page 0 and takes a real hardware #PF.
|
||||
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);
|
||||
p.* = 1;
|
||||
}
|
||||
@@ -2049,8 +2052,7 @@ fn faultDoubleFault() void {
|
||||
\\ud2
|
||||
:
|
||||
: [sp] "r" (bad_sp),
|
||||
: .{ .memory = true }
|
||||
);
|
||||
: .{ .memory = true });
|
||||
bad_sp += 0;
|
||||
}
|
||||
|
||||
@@ -2069,8 +2071,7 @@ fn apDoubleFaultTask() void {
|
||||
\\ud2
|
||||
:
|
||||
: [sp] "r" (bad_sp),
|
||||
: .{ .memory = true }
|
||||
);
|
||||
: .{ .memory = true });
|
||||
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 {
|
||||
// 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;
|
||||
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;
|
||||
matched += 1;
|
||||
if (!system.isProcessRunning(driver_name)) {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
//! spellings (`stat`, `O_CREAT`, ...) live only in the POSIX layer
|
||||
//! (library/posix/unistd.zig), which translates to these.
|
||||
//!
|
||||
//! This is user-space only — the kernel knows nothing of files or paths; it only
|
||||
//! moves the bytes. Shared by library/posix/unistd.zig (client) and system/services/vfs/vfs.zig (server).
|
||||
//! This is user-space only — the kernel knows nothing of files or paths; it only moves the bytes.
|
||||
//! Shared by library/posix/unistd.zig (client) and system/services/vfs/vfs.zig (server).
|
||||
|
||||
pub const Operation = enum(u32) {
|
||||
open, // open(path) -> node id
|
||||
|
||||
Executable
+152
@@ -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()
|
||||
Reference in New Issue
Block a user