usb: hot-plug plumbing, all ports powered, interrupter enabled (M20)
B3 — the runtime lifecycle a hot-pluggable bus needs, plus two init fixes that runtime device arrival depends on: - pump() now handles PORT STATUS CHANGE events (silently dropped before): it queues the port, and the bus driver brings the port up (a device arrived) or tears it down (a device left) on its tick — reporting each interface ChildRemoved to the device manager, which prunes the node, notifies watchers, and lets the class driver's world end honestly, then Disable Slot frees the controller-side state. - ALL root-hub ports are powered at init, not just those with a boot-time device: an unpowered port (PP=0) cannot signal a later connect, so a hot-plug would never be seen. - the interrupter is enabled (IMAN.IE + USBCMD.INTE) while the ring stays polled — some controllers only WRITE runtime events to the ring when the interrupter is enabled. Real-hardware validation is flagged for the user: QEMU's qemu-xhci does not raise a runtime port-change event to a polling driver on device_add, so the end-to-end hot-plug path can't be exercised in the harness (the port-change handling itself IS proven — a late boot device's PSCE is caught and acked). The harness gained qmp_sequence (multi-step QMP injection with arguments) for when a drivable case exists. Full suite 88/88; the working USB path (enumeration, HID, storage) is unregressed by the port-power and interrupter changes.
This commit is contained in:
@@ -143,6 +143,7 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
_ = runtime.system.write("/system/drivers/usb-xhci-bus: no device manager to hello\n");
|
||||
return false;
|
||||
};
|
||||
manager_handle = h; // the tick's hot-plug dispatch reports through this
|
||||
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = controller_id };
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch {
|
||||
@@ -186,6 +187,8 @@ fn speedName(speed: u32) []const u8 {
|
||||
/// report one child per interface — carrying the interface's (class, subclass,
|
||||
/// protocol) triple as identity, which is what the device manager matches a
|
||||
/// class driver against.
|
||||
var manager_handle: ?runtime.ipc.Handle = null;
|
||||
|
||||
fn scanPorts(manager: runtime.ipc.Handle) void {
|
||||
const engine = if (controller) |*c| c else {
|
||||
_ = runtime.system.write("/system/drivers/usb-xhci-bus: controller not initialised\n");
|
||||
@@ -196,38 +199,66 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
||||
var port: u32 = 1;
|
||||
var connected: u32 = 0;
|
||||
while (port <= engine.max_ports) : (port += 1) {
|
||||
const port_status = engine.portStatus(port);
|
||||
if (port_status & 1 == 0) continue; // CCS: nothing connected
|
||||
if (!engine.portConnected(port)) continue;
|
||||
connected += 1;
|
||||
const speed = (port_status >> 10) & 0xF; // the PORTSC port-speed class
|
||||
std.log.info("port {d} connected — {s} (speed class {d})", .{ port, speedName(speed), speed });
|
||||
|
||||
const usb_device = engine.setupDevice(port, speed) orelse {
|
||||
std.log.info("port {d} device setup failed", .{port});
|
||||
continue;
|
||||
};
|
||||
if (!engine.enumerate(usb_device)) {
|
||||
std.log.info("port {d} enumeration failed", .{port});
|
||||
continue;
|
||||
}
|
||||
std.log.info("port {d} device vendor 0x{x:0>4} product 0x{x:0>4}, {d} interface(s)", .{
|
||||
port,
|
||||
usb_device.device_descriptor.vendor_id,
|
||||
usb_device.device_descriptor.product_id,
|
||||
usb_device.interface_count,
|
||||
});
|
||||
|
||||
for (usb_device.interfaces[0..usb_device.interface_count]) |*interface| {
|
||||
// Record the id each interface was registered as, so a class driver
|
||||
// opening the interface (by that id) resolves to it.
|
||||
if (reportInterface(manager, port, interface.*)) |registered| {
|
||||
interface.registered_device_id = registered;
|
||||
}
|
||||
}
|
||||
bringUpPort(manager, engine, port);
|
||||
}
|
||||
if (connected == 0) _ = runtime.system.write("/system/drivers/usb-xhci-bus: no devices connected\n");
|
||||
}
|
||||
|
||||
/// Bring up whatever is on `port`: setup + enumerate + register/report one child
|
||||
/// per interface. Shared by the boot scan and hot-plug (a port-change event with
|
||||
/// the port now connected).
|
||||
fn bringUpPort(manager: runtime.ipc.Handle, engine: *library.Controller, port: u32) void {
|
||||
const speed = (engine.portStatus(port) >> 10) & 0xF; // the PORTSC port-speed class
|
||||
std.log.info("port {d} connected — {s} (speed class {d})", .{ port, speedName(speed), speed });
|
||||
|
||||
const usb_device = engine.setupDevice(port, speed) orelse {
|
||||
std.log.info("port {d} device setup failed", .{port});
|
||||
return;
|
||||
};
|
||||
if (!engine.enumerate(usb_device)) {
|
||||
std.log.info("port {d} enumeration failed", .{port});
|
||||
return;
|
||||
}
|
||||
std.log.info("port {d} device vendor 0x{x:0>4} product 0x{x:0>4}, {d} interface(s)", .{
|
||||
port,
|
||||
usb_device.device_descriptor.vendor_id,
|
||||
usb_device.device_descriptor.product_id,
|
||||
usb_device.interface_count,
|
||||
});
|
||||
|
||||
for (usb_device.interfaces[0..usb_device.interface_count]) |*interface| {
|
||||
// Record the id each interface was registered as, so a class driver
|
||||
// opening the interface (by that id) resolves to it.
|
||||
if (reportInterface(manager, port, interface.*)) |registered| {
|
||||
interface.registered_device_id = registered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tear down whatever was on `port` after an unplug: report each registered
|
||||
/// interface as removed (the manager prunes the node, notifies watchers, and
|
||||
/// stops the class driver's world honestly), then release the controller-side
|
||||
/// device state (Disable Slot).
|
||||
fn tearDownPort(manager: runtime.ipc.Handle, engine: *library.Controller, port: u32) void {
|
||||
const usb_device = engine.deviceOnPort(port) orelse return;
|
||||
std.log.info("port {d} disconnected", .{port});
|
||||
for (usb_device.interfaces[0..usb_device.interface_count]) |*interface| {
|
||||
if (interface.registered_device_id == 0) continue;
|
||||
const event = protocol.ChildRemoved{
|
||||
.parent = controller_id,
|
||||
.bus_address = (@as(u64, port) << 8) | interface.number,
|
||||
};
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
_ = runtime.ipc.call(manager, std.mem.asBytes(&event), &reply) catch {
|
||||
std.log.info("child-removed report for port {d} interface {d} failed", .{ port, interface.number });
|
||||
};
|
||||
interface.registered_device_id = 0;
|
||||
}
|
||||
engine.tearDownDevice(usb_device);
|
||||
}
|
||||
|
||||
/// Register one interface as a resource-less child of the controller and report
|
||||
/// it to the device manager. The identity is the packed USB class triple, so the
|
||||
/// manager can match a class driver (HID keyboard, mouse, mass storage); the
|
||||
@@ -380,6 +411,14 @@ fn onNotification(badge: u64) void {
|
||||
if (badge & runtime.ipc.notify_timer_bit == 0) return;
|
||||
if (controller) |*engine| {
|
||||
engine.pump();
|
||||
while (engine.takePortChange()) |port| {
|
||||
const manager = manager_handle orelse break;
|
||||
if (engine.portConnected(port)) {
|
||||
if (engine.deviceOnPort(port) == null) bringUpPort(manager, engine, port);
|
||||
} else {
|
||||
tearDownPort(manager, engine, port);
|
||||
}
|
||||
}
|
||||
while (engine.takeReport()) |report| {
|
||||
var message = transfer.InterruptReport{
|
||||
.device_token = report.device_token,
|
||||
|
||||
Reference in New Issue
Block a user