Files
danos/system/drivers/usb-hid/mouse.zig
T
Daniel Samson 3fb9d5936a USB driver stack: xHCI transfers, HID keyboard/mouse, mass storage
Flesh out the xHCI host-controller driver into a full transfer engine and build
the three USB class drivers on top, all verified end to end under QEMU.

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

Tests: usb-report, usb-hid, usb-storage pass under python3 test/qemu_test.py;
host units (usb-abi, usb-ids, hid-report, bulk-only-transport, scsi) green.
2026-07-13 14:11:00 +01:00

141 lines
5.7 KiB
Zig

//! USB HID boot mouse driver.
//!
//! Spawned by the device manager when the xHCI bus driver reports a HID / boot /
//! mouse interface (class 3, subclass 1, protocol 2); its assigned device id
//! arrives as argv[1]. Like the keyboard driver it owns no hardware: it opens its
//! device through the USB transfer protocol (`runtime.usb`), asks for the boot
//! protocol, subscribes to its interrupt-IN endpoint, and turns each 3- or 4-byte
//! boot report into input-protocol mouse events published to the input service.
//!
//! Unlike PS/2, HID reports Y in screen convention (positive = down), so motion
//! is passed straight through (the decode in hid-report.zig does not negate it).
const std = @import("std");
const runtime = @import("runtime");
const usb_abi = @import("usb-abi");
const hid = @import("hid-report.zig");
const ipc = runtime.ipc;
const process = runtime.process;
const input_protocol = runtime.input_protocol;
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);
}
// The current pressed-button bitmask in input-protocol terms.
fn buttonMask(buttons: u8) u32 {
var mask: u32 = 0;
if (buttons & hid.mouse_button_left != 0) mask |= input_protocol.mouse_button_left;
if (buttons & hid.mouse_button_right != 0) mask |= input_protocol.mouse_button_right;
if (buttons & hid.mouse_button_middle != 0) mask |= input_protocol.mouse_button_middle;
return mask;
}
pub fn main(init: runtime.process.Init) void {
const argument = init.arguments.get(1) orelse {
_ = runtime.system.write("/system/drivers/usb-hid/mouse: missing device id (argv[1])\n");
return;
};
const device_id = std.fmt.parseInt(u64, argument, 10) catch {
writeLine("/system/drivers/usb-hid/mouse: malformed device id '{s}'\n", .{argument});
return;
};
if (!runtime.usb.helloManager(device_id)) {
_ = runtime.system.write("/system/drivers/usb-hid/mouse: hello to device manager failed\n");
return;
}
var device = runtime.usb.open(device_id) orelse {
writeLine("/system/drivers/usb-hid/mouse: could not open device {d}\n", .{device_id});
return;
};
const endpoint = device.findEndpoint(runtime.usb.transfer_type_interrupt, true) orelse {
_ = runtime.system.write("/system/drivers/usb-hid/mouse: no interrupt-IN endpoint\n");
return;
};
_ = device.controlOut(@bitCast(usb_abi.setProtocol(@enumFromInt(device.interface_number), .boot)));
if (!device.subscribeInterrupt(endpoint.address, endpoint.max_packet_size)) {
_ = runtime.system.write("/system/drivers/usb-hid/mouse: interrupt subscribe failed\n");
return;
}
var source = runtime.input.connectSource() orelse {
_ = runtime.system.write("/system/drivers/usb-hid/mouse: input service unavailable\n");
return;
};
_ = process.bindSignals(device.endpoint);
writeLine("/system/drivers/usb-hid/mouse: ok (device {d}, interface {d})\n", .{ device_id, device.interface_number });
var previous_buttons: u8 = 0;
var receive: [64]u8 = undefined;
while (true) {
const got = ipc.replyWait(device.endpoint, &.{}, &receive, null);
if (!got.isNotification()) continue;
if (process.signalsFrom(got.badge)) |signals| {
if (signals.has(.terminate)) return;
continue;
}
if (!got.isMessage() or got.len < @sizeOf(runtime.usb.InterruptReport)) continue;
const message = std.mem.bytesToValue(runtime.usb.InterruptReport, receive[0..@sizeOf(runtime.usb.InterruptReport)]);
const length = @min(message.length, message.data.len);
const report = hid.parseMouse(message.data[0..length]) orelse continue;
const mask = buttonMask(report.buttons);
// Button transitions: one event per changed button bit.
const changed = report.buttons ^ previous_buttons;
inline for (.{
.{ hid.mouse_button_left, input_protocol.mouse_button_left },
.{ hid.mouse_button_right, input_protocol.mouse_button_right },
.{ hid.mouse_button_middle, input_protocol.mouse_button_middle },
}) |pair| {
if (changed & pair[0] != 0) {
_ = source.publishMouseEvent(.{
.kind = @intFromEnum(if (report.buttons & pair[0] != 0) input_protocol.MouseEventKind.button_down else input_protocol.MouseEventKind.button_up),
.button = pair[1],
.dx = 0,
.dy = 0,
.scroll_x = 0,
.scroll_y = 0,
.buttons = mask,
});
}
}
previous_buttons = report.buttons;
// Relative motion (dy straight through — HID Y is already screen convention).
if (report.dx != 0 or report.dy != 0) {
_ = source.publishMouseEvent(.{
.kind = @intFromEnum(input_protocol.MouseEventKind.motion),
.button = 0,
.dx = report.dx,
.dy = report.dy,
.scroll_x = 0,
.scroll_y = 0,
.buttons = mask,
});
}
// Wheel (4-byte reports only): positive = scroll up.
if (report.has_wheel and report.wheel != 0) {
_ = source.publishMouseEvent(.{
.kind = @intFromEnum(input_protocol.MouseEventKind.scroll),
.button = 0,
.dx = 0,
.dy = 0,
.scroll_x = 0,
.scroll_y = report.wheel,
.buttons = mask,
});
}
}
}
pub const panic = runtime.panic;
comptime {
_ = &runtime.start._start;
}