From ab732dc455850da9576768fb993b4a8e80365d3e Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:19:18 +0100 Subject: [PATCH] usb: readable PORTSC decode, link-state names, and device string descriptors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: " "' format). Branch diagnostics for the SuperSpeed compound-hub investigation. --- system/devices/usb-ids.zig | 32 +++++++++++++++ system/drivers/usb-xhci-bus/usb-xhci-bus.zig | 16 +++++++- .../drivers/usb-xhci-bus/usb-xhci-library.zig | 39 ++++++++++++++++++- test/qemu_test.py | 4 +- 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/system/devices/usb-ids.zig b/system/devices/usb-ids.zig index d1d907d..c086aaf 100644 --- a/system/devices/usb-ids.zig +++ b/system/devices/usb-ids.zig @@ -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. diff --git a/system/drivers/usb-xhci-bus/usb-xhci-bus.zig b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig index 767cb31..313a92c 100644 --- a/system/drivers/usb-xhci-bus/usb-xhci-bus.zig +++ b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig @@ -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, diff --git a/system/drivers/usb-xhci-bus/usb-xhci-library.zig b/system/drivers/usb-xhci-bus/usb-xhci-library.zig index 7b00547..feaef0b 100644 --- a/system/drivers/usb-xhci-bus/usb-xhci-library.zig +++ b/system/drivers/usb-xhci-bus/usb-xhci-library.zig @@ -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)); } diff --git a/test/qemu_test.py b/test/qemu_test.py index 025bd92..5fb6f9f 100644 --- a/test/qemu_test.py +++ b/test/qemu_test.py @@ -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;