The xHCI driver scans its root-hub ports and reports the tree (M18.2)
child_added/child_removed join the device-manager protocol. The driver maps its register BAR (resource 0 is the ECAM config space; the walk starts at 1), reads CAPLENGTH and HCSPARAMS1, and reads one PORTSC per port: the connect bit and speed class come straight from hardware, no rings needed to see the devices. The manager mirrors reported children keyed by (parent, port), remembers which instance reported each, and prunes a dead reporter's children before deciding the restart — the children describe protocol state that died with the process. The usb-report scenario drives the whole loop: two QEMU devices reported, reporter killed, children pruned, driver respawned with backoff, and the new instance re-claims, re-scans, and re-reports.
This commit is contained in:
@@ -4,11 +4,14 @@
|
||||
//! argv[1]; this instance claims that device and no other, so multiple
|
||||
//! instances never fight over hardware.
|
||||
//!
|
||||
//! M18.1 (this increment): a harness service and the first conforming driver of
|
||||
//! the device-manager protocol — claim the controller, `hello` the manager
|
||||
//! (role, version, assignment) inside its deadline, then serve. Controller
|
||||
//! bring-up (map the MMIO window, reset, port scan) and tree reports
|
||||
//! (`child_added` for each connected port) land in M18.2.
|
||||
//! M18.2 (this increment): after the hello, real hardware — map the xHC's
|
||||
//! register window (the first memory BAR; resource 0 is the ECAM config
|
||||
//! space), read the capability registers, and walk the root-hub ports: one
|
||||
//! `child_added` report to the manager per connected port, carrying the port
|
||||
//! number and the PORTSC speed class as identity. No transfer rings yet —
|
||||
//! descriptors and USB class matching are the USB track; the connect bit and
|
||||
//! speed come straight from PORTSC, which reflects hardware state whether or
|
||||
//! not the controller is running.
|
||||
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
@@ -47,12 +50,16 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
// 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;
|
||||
// The xHC's registers live behind the first memory BAR. Resource 0 is the
|
||||
// function's ECAM configuration space (M15), so the walk starts at 1.
|
||||
var register_index: u64 = 0;
|
||||
const register_window = for (descriptor.resources[1..@intCast(descriptor.resource_count)], 1..) |resource, index| {
|
||||
if (resource.kind == @intFromEnum(device.ResourceKind.memory)) {
|
||||
register_index = index;
|
||||
break resource;
|
||||
}
|
||||
} else {
|
||||
writeLine("usb-xhci-bus: controller device {d} has no MMIO window\n", .{controller_id});
|
||||
writeLine("usb-xhci-bus: controller device {d} has no register BAR\n", .{controller_id});
|
||||
return false;
|
||||
};
|
||||
writeLine("usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{
|
||||
@@ -60,6 +67,10 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
register_window.start,
|
||||
register_window.len,
|
||||
});
|
||||
register_base = device.mmioMap(controller_id, register_index) orelse {
|
||||
_ = runtime.system.write("usb-xhci-bus: mmio_map failed\n");
|
||||
return false;
|
||||
};
|
||||
|
||||
// The handshake: role, protocol version, assignment — inside the manager's
|
||||
// deadline (the lookup retries cover the manager still registering).
|
||||
@@ -84,9 +95,56 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
return false;
|
||||
}
|
||||
_ = runtime.system.write("usb-xhci-bus: hello acknowledged\n");
|
||||
|
||||
scanPorts(h);
|
||||
return true;
|
||||
}
|
||||
|
||||
var register_base: usize = 0;
|
||||
|
||||
/// One 32-bit volatile register read at `offset` from the mapped window.
|
||||
fn readRegister(offset: usize) u32 {
|
||||
const register: *volatile u32 = @ptrFromInt(register_base + offset);
|
||||
return register.*;
|
||||
}
|
||||
|
||||
/// The root-hub port scan: read the capability registers for the port count
|
||||
/// and the operational-register offset, then one PORTSC per port. The connect
|
||||
/// bit (CCS) and the speed field reflect hardware state directly — no
|
||||
/// controller reset or run needed to *see* the devices; driving them needs the
|
||||
/// rings (the USB track).
|
||||
fn scanPorts(manager: runtime.ipc.Handle) void {
|
||||
// Capability registers: CAPLENGTH is byte 0 of the first dword; HCSPARAMS1
|
||||
// carries MaxPorts in bits 31:24.
|
||||
const capability_length = readRegister(0) & 0xFF;
|
||||
const structural = readRegister(0x04);
|
||||
const maximum_ports: u32 = structural >> 24;
|
||||
writeLine("usb-xhci-bus: {d} root-hub ports\n", .{maximum_ports});
|
||||
|
||||
// PORTSC registers: operational base + 0x400 + 0x10 per port (1-based).
|
||||
var port: u32 = 1;
|
||||
var connected: u32 = 0;
|
||||
while (port <= maximum_ports) : (port += 1) {
|
||||
const port_status = readRegister(capability_length + 0x400 + 0x10 * (port - 1));
|
||||
if (port_status & 1 == 0) continue; // CCS: nothing connected
|
||||
connected += 1;
|
||||
const speed = (port_status >> 10) & 0xF; // the PORTSC port-speed class
|
||||
writeLine("usb-xhci-bus: port {d} connected (speed class {d})\n", .{ port, speed });
|
||||
|
||||
const report = protocol.ChildAdded{
|
||||
.parent = controller_id,
|
||||
.bus_address = port,
|
||||
.identity = speed,
|
||||
};
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
_ = runtime.ipc.call(manager, std.mem.asBytes(&report), &reply) catch {
|
||||
writeLine("usb-xhci-bus: child report for port {d} failed\n", .{port});
|
||||
continue;
|
||||
};
|
||||
}
|
||||
if (connected == 0) _ = runtime.system.write("usb-xhci-bus: no devices connected\n");
|
||||
}
|
||||
|
||||
/// No bus protocol to serve yet — transfer requests arrive with the USB track.
|
||||
fn onMessage(message: []const u8, reply: []u8, sender: u32) usize {
|
||||
_ = message;
|
||||
|
||||
Reference in New Issue
Block a user