192 lines
8.4 KiB
Zig
192 lines
8.4 KiB
Zig
//! /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("system/drivers/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("system/drivers/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("system/drivers/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("system/drivers/usb-xhci-bus: controller device {d} has no register BAR\n", .{controller_id});
|
|
return false;
|
|
};
|
|
writeLine("system/drivers/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("system/drivers/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("system/drivers/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("system/drivers/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("system/drivers/usb-xhci-bus: hello refused\n");
|
|
return false;
|
|
}
|
|
_ = runtime.system.write("system/drivers/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 xHCI default Protocol Speed IDs (the PORTSC port-speed field, bits 13:10)
|
|
/// decoded to human names — the boot-log breadcrumb for what actually enumerated on
|
|
/// a port, the USB analog of the pci-bus class-code line. A controller may redefine
|
|
/// these through its Supported Protocol capability, but the defaults cover every
|
|
/// speed QEMU and real hardware report at this (pre-descriptor) stage.
|
|
fn speedName(speed: u32) []const u8 {
|
|
return switch (speed) {
|
|
1 => "Full-speed (USB 2.0, 12 Mb/s)",
|
|
2 => "Low-speed (USB 2.0, 1.5 Mb/s)",
|
|
3 => "High-speed (USB 2.0, 480 Mb/s)",
|
|
4 => "SuperSpeed (USB 3.0, 5 Gb/s)",
|
|
5 => "SuperSpeedPlus (USB 3.1, 10 Gb/s)",
|
|
else => "unknown speed",
|
|
};
|
|
}
|
|
|
|
/// 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("system/drivers/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("system/drivers/usb-xhci-bus: port {d} connected — {s} (speed class {d})\n", .{ port, speedName(speed), 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("system/drivers/usb-xhci-bus: child report for port {d} failed\n", .{port});
|
|
continue;
|
|
};
|
|
}
|
|
if (connected == 0) _ = runtime.system.write("system/drivers/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("system/drivers/usb-xhci-bus: missing controller device id (argv[1])\n");
|
|
return;
|
|
};
|
|
controller_id = std.fmt.parseInt(u64, argument, 10) catch {
|
|
writeLine("system/drivers/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
|
|
}
|