Every supervised driver owes the device manager a hello at startup, and the retry-lookup-call-check for it had been copied into five drivers: usb.helloManager (misfiled in the USB client) plus hand-rolled twins in display, virtio-gpu, pci-bus, and usb-xhci-bus. Extract it once as a runtime client, runtime.device_manager.hello(role, device_id) ?Handle — returns the manager endpoint (bus drivers keep it to report children through), null when there is no manager or it refused the handshake, and logs the outcome itself so each call site is one line. Also correct two roles while collapsing their calls: virtio-gpu and the display driver each claim one PCI function and report no children, so they are Role.device, not Role.bus. The manager ignores role today, so this is cosmetic, but it matches the protocol's own definition (bus = reports children via child_added). Behavior-preserving otherwise: pci-bus and usb-xhci-bus move their hello logging from raw serial writes to std.log, which the kernel renders with the same "<path>: " prefix, so driver-restart still matches "usb-xhci-bus: hello acknowledged". zig build clean; 8 QEMU cases pass (driver-restart, pci-scan, usb-hid, usb-storage, virtio-gpu, display-reattach, device-list, device-manager).
128 lines
5.3 KiB
Zig
128 lines
5.3 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;
|
|
|
|
// 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 {
|
|
std.log.info("malformed device id '{s}'", .{argument});
|
|
return;
|
|
};
|
|
|
|
if (runtime.device_manager.hello(.device, device_id) == null) return;
|
|
var device = runtime.usb.open(device_id) orelse {
|
|
std.log.info("could not open device {d}", .{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);
|
|
std.log.info("ok (device {d}, interface {d})", .{ 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,
|
|
});
|
|
}
|
|
}
|
|
}
|