//! /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. //! //! 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"); const protocol = runtime.device_manager_protocol; 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); } var controller_id: u64 = protocol.no_device; /// Claim the assigned controller, find its register window, and hello the /// manager. Any failure returns false: the process exits cleanly, which the /// manager reads as "meant to stop" — a missing assignment is not a crash loop. fn initialise(endpoint: runtime.ipc.Handle) bool { _ = endpoint; if (!device.claim(controller_id)) { writeLine("usb-xhci-bus: unable to claim controller device {d}\n", .{controller_id}); return false; } // 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 false; }; 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 false; }; // 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 register BAR\n", .{controller_id}); return false; }; writeLine("usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{ controller_id, 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). var manager: ?runtime.ipc.Handle = null; var tries: u32 = 0; while (manager == null and tries < 100) : (tries += 1) { manager = runtime.ipc.lookup(.device_manager); if (manager == null) runtime.system.sleep(20); } const h = manager orelse { _ = runtime.system.write("usb-xhci-bus: no device manager to hello\n"); return false; }; 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 { _ = runtime.system.write("usb-xhci-bus: hello call failed\n"); return false; }; if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) { _ = runtime.system.write("usb-xhci-bus: hello refused\n"); 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, capability: ?runtime.ipc.Handle) usize { _ = message; _ = reply; _ = sender; _ = capability; return 0; } 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; }; controller_id = std.fmt.parseInt(u64, argument, 10) catch { writeLine("usb-xhci-bus: malformed controller device id '{s}'\n", .{argument}); return; }; runtime.service.run(protocol.message_maximum, .{ .init = initialise, .on_message = onMessage, }); } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; // pull the runtime entry shim into the image }