usb: readable PORTSC decode, link-state names, and device string descriptors
Make the USB diagnostic logs scannable by eye:
- usb-ids gains speedName + linkStateName (USB3 PORTSC link states: U0,
RxDetect, Polling, ...), host-tested with usb-ids.
- The PORTSC dump decodes the register instead of printing hex flags:
PORTSC[3] 0x00021203: connected, enabled, link=U0, power=on, SuperSpeed
PORTSC[5] 0x00020ee1: connected, disabled, link=Polling, power=on, High-speed
(the raw word stays for reference). A USB2 device reads connected+
disabled+Polling until reset — so the user's next log shows at a glance
whether the companion port ever reaches that state.
- The library reads STRING descriptors (readString, UTF-16LE -> ASCII,
English langid), and the device line now names the maker + product:
port 5 device: Hub "Genesys Logic USB3.0 Hub" (0x05e3:0x0626), 1 interface(s)
instead of a bare vendor/product id pair.
Full USB case set green (hub regexes follow the new 'device: <class>
"<maker> <product>"' format). Branch diagnostics for the SuperSpeed
compound-hub investigation.
This commit is contained in:
@@ -103,6 +103,38 @@ pub fn className(class: u8) []const u8 {
|
||||
};
|
||||
}
|
||||
|
||||
/// The USB speed class (as xHCI reports it in PORTSC/slot contexts) named.
|
||||
pub fn speedName(speed: u32) []const u8 {
|
||||
return switch (speed) {
|
||||
1 => "Full-speed",
|
||||
2 => "Low-speed",
|
||||
3 => "High-speed",
|
||||
4 => "SuperSpeed",
|
||||
5 => "SuperSpeedPlus",
|
||||
else => "unknown-speed",
|
||||
};
|
||||
}
|
||||
|
||||
/// A USB3 Port Link State (xHCI PORTSC PLS field) named.
|
||||
pub fn linkStateName(pls: u32) []const u8 {
|
||||
return switch (pls) {
|
||||
0 => "U0",
|
||||
1 => "U1",
|
||||
2 => "U2",
|
||||
3 => "U3-suspended",
|
||||
4 => "Disabled",
|
||||
5 => "RxDetect",
|
||||
6 => "Inactive",
|
||||
7 => "Polling",
|
||||
8 => "Recovery",
|
||||
9 => "HotReset",
|
||||
10 => "Compliance",
|
||||
11 => "Test",
|
||||
15 => "Resume",
|
||||
else => "reserved",
|
||||
};
|
||||
}
|
||||
|
||||
/// A fuller name for a HID interface's (subclass, protocol): "HID boot keyboard"
|
||||
/// / "HID boot mouse", else just the class name. Used to make input devices
|
||||
/// recognizable in logs.
|
||||
|
||||
@@ -230,9 +230,15 @@ fn bringUpPort(manager: runtime.ipc.Handle, engine: *library.Controller, port: u
|
||||
std.log.info("port {d} enumeration failed", .{port});
|
||||
return;
|
||||
}
|
||||
std.log.info("port {d} device: {s} vendor 0x{x:0>4} product 0x{x:0>4}, {d} interface(s)", .{
|
||||
var maker_buffer: [64]u8 = undefined;
|
||||
var product_buffer: [64]u8 = undefined;
|
||||
const maker = engine.readString(usb_device, @intFromEnum(usb_device.device_descriptor.manufacturer_index), &maker_buffer) orelse "?";
|
||||
const product = engine.readString(usb_device, @intFromEnum(usb_device.device_descriptor.product_index), &product_buffer) orelse "?";
|
||||
std.log.info("port {d} device: {s} \"{s} {s}\" (0x{x:0>4}:0x{x:0>4}), {d} interface(s)", .{
|
||||
port,
|
||||
usb_ids.className(usb_device.device_descriptor.device_class),
|
||||
maker,
|
||||
product,
|
||||
usb_device.device_descriptor.vendor_id,
|
||||
usb_device.device_descriptor.product_id,
|
||||
usb_device.interface_count,
|
||||
@@ -280,9 +286,15 @@ fn bringUpBehindHub(manager: runtime.ipc.Handle, engine: *library.Controller, hu
|
||||
std.log.info("hub slot {d} port {d}: enumeration failed", .{ hub.slot_id, port });
|
||||
return;
|
||||
}
|
||||
std.log.info("hub slot {d} port {d} device: {s} vendor 0x{x:0>4} product 0x{x:0>4}, {d} interface(s)", .{
|
||||
var maker_buffer: [64]u8 = undefined;
|
||||
var product_buffer: [64]u8 = undefined;
|
||||
const maker = engine.readString(usb_device, @intFromEnum(usb_device.device_descriptor.manufacturer_index), &maker_buffer) orelse "?";
|
||||
const product = engine.readString(usb_device, @intFromEnum(usb_device.device_descriptor.product_index), &product_buffer) orelse "?";
|
||||
std.log.info("hub slot {d} port {d} device: {s} \"{s} {s}\" (0x{x:0>4}:0x{x:0>4}), {d} interface(s)", .{
|
||||
hub.slot_id, port,
|
||||
usb_ids.className(usb_device.device_descriptor.device_class),
|
||||
maker,
|
||||
product,
|
||||
usb_device.device_descriptor.vendor_id,
|
||||
usb_device.device_descriptor.product_id,
|
||||
usb_device.interface_count,
|
||||
|
||||
@@ -22,6 +22,7 @@ const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const mmio = @import("mmio");
|
||||
const usb_abi = @import("usb-abi");
|
||||
const usb_ids = @import("usb-ids");
|
||||
const dma = runtime.dma;
|
||||
const system = runtime.system;
|
||||
|
||||
@@ -511,12 +512,46 @@ pub const Controller = struct {
|
||||
var port: u32 = 1;
|
||||
while (port <= self.max_ports) : (port += 1) {
|
||||
const portsc = self.portStatus(port);
|
||||
std.log.info("PORTSC[{d}] = 0x{x:0>8} ccs={d} ped={d} pls={d} pp={d} speed={d}", .{
|
||||
port, portsc, portsc & 1, (portsc >> 1) & 1, (portsc >> 5) & 0xF, (portsc >> 9) & 1, (portsc >> 10) & 0xF,
|
||||
std.log.info("PORTSC[{d}] 0x{x:0>8}: {s}, {s}, link={s}, power={s}, {s}", .{
|
||||
port,
|
||||
portsc,
|
||||
if (portsc & 1 != 0) "connected" else "empty",
|
||||
if (portsc & 2 != 0) "enabled" else "disabled",
|
||||
usb_ids.linkStateName((portsc >> 5) & 0xF),
|
||||
if (portsc & (1 << 9) != 0) "on" else "off",
|
||||
usb_ids.speedName((portsc >> 10) & 0xF),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Read USB STRING descriptor `index` (English, langid 0x0409) into `out` as
|
||||
/// ASCII, returning the slice — for logging manufacturer/product names.
|
||||
/// Null for index 0 (no string) or a failed transfer. Non-ASCII code units
|
||||
/// become '?'.
|
||||
pub fn readString(self: *Controller, device: *Device, index: u8, out: []u8) ?[]const u8 {
|
||||
if (index == 0) return null;
|
||||
var raw: [256]u8 = undefined;
|
||||
const request = usb_abi.Request{
|
||||
.request_type = .{ .recipient = .device, .kind = .standard, .direction = .device_to_host },
|
||||
.request_code = .get_descriptor,
|
||||
.value = (@as(u16, 3) << 8) | index, // STRING descriptor
|
||||
.index = 0x0409, // English (US)
|
||||
.length = raw.len,
|
||||
};
|
||||
if (!self.controlTransfer(device, request, raw[0..], true)) return null;
|
||||
const length = raw[0]; // bLength; the UTF-16LE payload is bytes 2..length
|
||||
if (length < 2) return null;
|
||||
const chars = (@min(length, raw.len) - 2) / 2;
|
||||
var n: usize = 0;
|
||||
var i: usize = 0;
|
||||
while (i < chars and n < out.len) : (i += 1) {
|
||||
const unit = @as(u16, raw[2 + i * 2]) | (@as(u16, raw[2 + i * 2 + 1]) << 8);
|
||||
out[n] = if (unit >= 0x20 and unit < 0x7F) @intCast(unit) else '?';
|
||||
n += 1;
|
||||
}
|
||||
return out[0..n];
|
||||
}
|
||||
|
||||
pub fn portStatus(self: *const Controller, port: u32) u32 {
|
||||
return read32(self.op_base + op_portsc_base + op_portsc_stride * (port - 1));
|
||||
}
|
||||
|
||||
+2
-2
@@ -494,7 +494,7 @@ CASES = [
|
||||
# B4a: the hub powers its ports. B4b: the keyboard behind it enumerates
|
||||
# (route string + TT) and binds usb-hid-keyboard.
|
||||
"expect": r"(?s)(?=.*hub slot \d+: \d+ downstream ports powered)"
|
||||
r"(?=.*hub slot \d+ port \d+ device:.*vendor 0x0627)"
|
||||
r"(?=.*hub slot \d+ port \d+ device:.*0x0627)"
|
||||
r"(?=.*usb-hid-keyboard: ok \(device 3)",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
# USB mass storage end to end: the boot usb-storage device (the FAT32 image,
|
||||
@@ -676,7 +676,7 @@ CASES = [
|
||||
"-device", "usb-hub,bus=xhci2.0,port=1",
|
||||
"-device", "usb-hub,bus=xhci2.0,port=1.1",
|
||||
"-device", "usb-kbd,bus=xhci2.0,port=1.1.1"],
|
||||
"expect": r"(?s)(?=.*hub slot \d+ port \d+ device:.*vendor 0x0409)"
|
||||
"expect": r"(?s)(?=.*hub slot \d+ port \d+ device:.*0x0409)"
|
||||
r"(?=.*usb-hid-keyboard: ok \(device 3)",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
# Hub-downstream disconnect (B4c): device_del the keyboard behind the hub;
|
||||
|
||||
Reference in New Issue
Block a user