usb: recognize and configure hubs, power downstream ports (B4a)
First slice of hub support (docs/usb-hub.md), handled IN the xhci-bus because a device behind a hub is reached by the CONTROLLER via a route string in its slot context — topology only exists inside the driver that owns the controller. When the scan enumerates a class-9 device it now calls setupHub: read the hub descriptor for the downstream port count and TT arrangement, tell the controller the slot is a hub (Configure Endpoint with the slot add-flag sets the Hub bit, Number of Ports, and MTT/TT-Think-Time per xHCI 4.6.6), SET_HUB_DEPTH for a SuperSpeed hub so it can compose route strings, and SET_FEATURE PORT_POWER every downstream port. The Device gains topology fields (route string, root port, parent hub slot/port for the TT) and buildAddressInputContext now fills them, so the Address Device path is ready for downstream devices — those are enumerated in B4b. A root-port device gets route 0 and its own port as the chain root, unchanged behavior. Verified in QEMU with a hub on a second controller and a device behind it (a usb-hub QEMU case, since the boot controller's auto-assigned devices collide on the low ports): 'hub slot 1: 8 downstream ports powered (USB2 single-TT)'. Full suite 89/89. The user's SuperSpeed Genesys hub is the real-hardware target, flagged separately (QEMU's USB2 hub doesn't model the compound USB3 hub).
This commit is contained in:
@@ -280,6 +280,18 @@ pub const Device = struct {
|
||||
// Transfer rings configured for this device's interrupt/bulk endpoints.
|
||||
endpoint_ring_count: u8 = 0,
|
||||
endpoint_rings: [max_configured_endpoints]ConfiguredEndpoint = [_]ConfiguredEndpoint{.{}} ** max_configured_endpoints,
|
||||
|
||||
// Hub topology (docs/usb-hub.md). A device behind a hub is addressed with a
|
||||
// route string; these carry the fields buildAddressInputContext needs.
|
||||
route: u32 = 0, // Slot Context route string (5 tiers x 4 bits); 0 = on a root port
|
||||
root_port: u32 = 0, // the ROOT-hub port the chain hangs off (inherited down a chain)
|
||||
parent_slot: u8 = 0, // the parent hub's slot id (0 = on a root port) — the TT hub
|
||||
parent_port: u8 = 0, // the parent hub's downstream port this device sits on
|
||||
|
||||
// Set when this device IS a hub, after setupHub configures it.
|
||||
is_hub: bool = false,
|
||||
hub_ports: u8 = 0, // downstream port count from the hub descriptor
|
||||
hub_multi_tt: bool = false,
|
||||
};
|
||||
|
||||
// A standing interrupt-IN subscription: the endpoint's ring is kept armed with a
|
||||
@@ -315,6 +327,101 @@ const max_devices = 8;
|
||||
const max_subscriptions = 8;
|
||||
const report_queue_capacity = 16;
|
||||
|
||||
// --- USB hub class requests + constants (docs/usb-hub.md) ------------------
|
||||
//
|
||||
// A hub is bus infrastructure the CONTROLLER driver handles in-process: the
|
||||
// route strings and slot contexts a downstream device needs only exist here.
|
||||
// These are the class-specific control requests to a hub device.
|
||||
|
||||
fn routeDepth(route: u32) u16 {
|
||||
// Tiers used by a route string: each nonzero 4-bit nibble is one tier.
|
||||
var depth: u16 = 0;
|
||||
var r = route;
|
||||
while (r != 0) : (r >>= 4) {
|
||||
if (r & 0xF != 0) depth += 1;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
const hub = struct {
|
||||
// Hub descriptor types (GET_DESCRIPTOR value high byte).
|
||||
const descriptor_usb2: u8 = 0x29;
|
||||
const descriptor_usb3: u8 = 0x2A;
|
||||
|
||||
// Hub/port feature selectors (SET_FEATURE / CLEAR_FEATURE value).
|
||||
const feature_port_reset: u16 = 4;
|
||||
const feature_port_power: u16 = 8;
|
||||
const feature_c_port_connection: u16 = 16;
|
||||
const feature_c_port_reset: u16 = 20;
|
||||
const feature_c_port_link_state: u16 = 25; // SS
|
||||
const feature_bh_port_reset: u16 = 28; // SS
|
||||
const feature_c_bh_port_reset: u16 = 29; // SS
|
||||
|
||||
// Port status (wPortStatus, first 16 bits of the 4-byte GET_STATUS result).
|
||||
const status_connection: u16 = 1 << 0;
|
||||
const status_enable: u16 = 1 << 1;
|
||||
const status_reset: u16 = 1 << 4;
|
||||
// Port-status change bits (wPortChange, the high 16 bits).
|
||||
const change_connection: u16 = 1 << 0;
|
||||
const change_reset: u16 = 1 << 4;
|
||||
|
||||
// wHubCharacteristics bit 7: multiple transaction translators.
|
||||
const characteristics_multi_tt: u16 = 1 << 7;
|
||||
|
||||
// SET_HUB_DEPTH (SuperSpeed hubs, so they can compose route strings).
|
||||
const request_set_hub_depth: u8 = 12;
|
||||
|
||||
fn getDescriptor(kind: u8, length: u16) usb_abi.Request {
|
||||
return .{
|
||||
.request_type = .{ .recipient = .device, .kind = .class, .direction = .device_to_host },
|
||||
.request_code = .get_descriptor,
|
||||
.value = @as(u16, kind) << 8,
|
||||
.index = 0,
|
||||
.length = length,
|
||||
};
|
||||
}
|
||||
|
||||
fn setPortFeature(feature: u16, port: u16) usb_abi.Request {
|
||||
return .{
|
||||
.request_type = .{ .recipient = .other, .kind = .class, .direction = .host_to_device },
|
||||
.request_code = .set_feature,
|
||||
.value = feature,
|
||||
.index = port,
|
||||
.length = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn clearPortFeature(feature: u16, port: u16) usb_abi.Request {
|
||||
return .{
|
||||
.request_type = .{ .recipient = .other, .kind = .class, .direction = .host_to_device },
|
||||
.request_code = .clear_feature,
|
||||
.value = feature,
|
||||
.index = port,
|
||||
.length = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn getPortStatus(port: u16) usb_abi.Request {
|
||||
return .{
|
||||
.request_type = .{ .recipient = .other, .kind = .class, .direction = .device_to_host },
|
||||
.request_code = .get_status,
|
||||
.value = 0,
|
||||
.index = port,
|
||||
.length = 4,
|
||||
};
|
||||
}
|
||||
|
||||
fn setHubDepth(depth: u16) usb_abi.Request {
|
||||
return .{
|
||||
.request_type = .{ .recipient = .device, .kind = .class, .direction = .host_to_device },
|
||||
.request_code = @enumFromInt(request_set_hub_depth),
|
||||
.value = depth,
|
||||
.index = 0,
|
||||
.length = 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Controller = struct {
|
||||
register_base: usize,
|
||||
op_base: usize,
|
||||
@@ -623,10 +730,19 @@ pub const Controller = struct {
|
||||
const base = device.input_context.virtual;
|
||||
// Input Control Context (index 0): Add flags in dword 1 = A0 | A1.
|
||||
contextDword(base, 0, 1, cs).* = 0b11;
|
||||
// Slot Context (index 1): speed[23:20], Context Entries[31:27] = 1.
|
||||
contextDword(base, 1, 0, cs).* = (device.speed << 20) | (@as(u32, 1) << 27);
|
||||
// Root Hub Port Number[23:16].
|
||||
contextDword(base, 1, 1, cs).* = device.port << 16;
|
||||
// Slot Context dword 0: Route String[19:0], Speed[23:20], Context
|
||||
// Entries[31:27] = 1.
|
||||
contextDword(base, 1, 0, cs).* = (device.route & 0xFFFFF) | (device.speed << 20) | (@as(u32, 1) << 27);
|
||||
// Slot Context dword 1: Root Hub Port Number[23:16] — the ROOT port the
|
||||
// hub chain hangs off (inherited down a chain), not the device's own
|
||||
// downstream hub port.
|
||||
contextDword(base, 1, 1, cs).* = device.root_port << 16;
|
||||
// Slot Context dword 2: the transaction translator — a full/low-speed
|
||||
// device behind a high-speed hub routes split transactions through the
|
||||
// parent hub's TT. Parent Hub Slot ID[7:0], Parent Port Number[13:8].
|
||||
if (device.parent_slot != 0 and device.speed < 3) {
|
||||
contextDword(base, 1, 2, cs).* = @as(u32, device.parent_slot) | (@as(u32, device.parent_port) << 8);
|
||||
}
|
||||
// EP0 Context (index 2): CErr[2:1]=3, EP Type[5:3]=Control(4), MPS[31:16].
|
||||
contextDword(base, 2, 1, cs).* = (@as(u32, 3) << 1) | (@as(u32, 4) << 3) | (device.max_packet_size_0 << 16);
|
||||
// TR Dequeue Pointer (dwords 2:3) with Dequeue Cycle State = 1.
|
||||
@@ -691,6 +807,7 @@ pub const Controller = struct {
|
||||
.port = port,
|
||||
.speed = effective_speed,
|
||||
.max_packet_size_0 = defaultMaxPacketSize0(effective_speed),
|
||||
.root_port = port, // a root-port device: the chain root IS this port
|
||||
};
|
||||
device.input_context = dma.alloc(page_size, dma.coherent) orelse return self.abandon(device);
|
||||
device.device_context = dma.alloc(page_size, dma.coherent) orelse return self.abandon(device);
|
||||
@@ -706,6 +823,76 @@ pub const Controller = struct {
|
||||
}
|
||||
|
||||
|
||||
/// Configure an enumerated class-9 device as a hub (docs/usb-hub.md): read
|
||||
/// the hub descriptor for the downstream port count, tell the controller the
|
||||
/// slot is a hub (so it routes downstream traffic), SET_HUB_DEPTH for a
|
||||
/// SuperSpeed hub, and power every downstream port. Downstream enumeration
|
||||
/// (status-change handling) is B4b. Returns false on a control-transfer
|
||||
/// failure; the hub is still registered, just inert.
|
||||
pub fn setupHub(self: *Controller, device: *Device) bool {
|
||||
const is_usb3 = device.speed >= 4;
|
||||
var descriptor: [16]u8 = undefined;
|
||||
const kind: u8 = if (is_usb3) hub.descriptor_usb3 else hub.descriptor_usb2;
|
||||
if (!self.controlTransfer(device, hub.getDescriptor(kind, descriptor.len), descriptor[0..], true)) {
|
||||
std.log.info("hub slot {d}: hub descriptor read failed", .{device.slot_id});
|
||||
return false;
|
||||
}
|
||||
device.is_hub = true;
|
||||
device.hub_ports = descriptor[2]; // bNbrPorts
|
||||
const characteristics = @as(u16, descriptor[3]) | (@as(u16, descriptor[4]) << 8);
|
||||
device.hub_multi_tt = !is_usb3 and (characteristics & hub.characteristics_multi_tt != 0);
|
||||
|
||||
// A SuperSpeed hub needs its depth (tiers from the root) to compose the
|
||||
// route strings of devices below it.
|
||||
if (is_usb3) {
|
||||
const depth = routeDepth(device.route);
|
||||
_ = self.controlTransfer(device, hub.setHubDepth(depth), &.{}, false);
|
||||
}
|
||||
|
||||
// Tell the controller the slot is a hub — Hub bit, Number of Ports, and
|
||||
// (for a USB2 multi-TT hub) MTT + TT Think Time. Configure Endpoint with
|
||||
// only the slot add-flag (A0) evaluates these (xHCI 4.6.6).
|
||||
if (!self.configureSlotAsHub(device)) {
|
||||
std.log.info("hub slot {d}: could not configure slot as a hub", .{device.slot_id});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Power every downstream port.
|
||||
var port: u16 = 1;
|
||||
while (port <= device.hub_ports) : (port += 1) {
|
||||
_ = self.controlTransfer(device, hub.setPortFeature(hub.feature_port_power, port), &.{}, false);
|
||||
}
|
||||
std.log.info("hub slot {d}: {d} downstream ports powered ({s})", .{
|
||||
device.slot_id,
|
||||
device.hub_ports,
|
||||
if (is_usb3) "SuperSpeed" else if (device.hub_multi_tt) "USB2 multi-TT" else "USB2 single-TT",
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Configure Endpoint with only A0 (slot) set: rebuild the slot context with
|
||||
/// the Hub bit, Number of Ports, and MTT/TT-Think-Time, so the controller
|
||||
/// treats this slot as a hub.
|
||||
fn configureSlotAsHub(self: *Controller, device: *Device) bool {
|
||||
const cs = self.context_size;
|
||||
const base = device.input_context.virtual;
|
||||
@memset(@as([*]u8, @ptrFromInt(base))[0 .. 2 * cs], 0);
|
||||
contextDword(base, 0, 1, cs).* = 0b1; // Input Control Context add flags: A0 (slot)
|
||||
// Slot Context dword 0: route, speed, context entries, plus Hub[26] and
|
||||
// (USB2 multi-TT) MTT[25].
|
||||
var dword0: u32 = (device.route & 0xFFFFF) | (device.speed << 20) | (@as(u32, 1) << 27) | (@as(u32, 1) << 26);
|
||||
if (device.hub_multi_tt) dword0 |= (@as(u32, 1) << 25);
|
||||
contextDword(base, 1, 0, cs).* = dword0;
|
||||
// Slot Context dword 1: Root Hub Port Number[23:16], Number of Ports[31:24].
|
||||
contextDword(base, 1, 1, cs).* = (device.root_port << 16) | (@as(u32, device.hub_ports) << 24);
|
||||
// Slot Context dword 2: TT Think Time[17:16] = 0 (8 FS bit times); the
|
||||
// parent-TT fields (if this hub is itself behind a hub) carry over.
|
||||
if (device.parent_slot != 0 and device.speed < 3) {
|
||||
contextDword(base, 1, 2, cs).* = @as(u32, device.parent_slot) | (@as(u32, device.parent_port) << 8);
|
||||
}
|
||||
return self.configureEndpointCommand(device);
|
||||
}
|
||||
|
||||
fn abandon(self: *Controller, device: *Device) ?*Device {
|
||||
_ = self;
|
||||
device.used = false;
|
||||
|
||||
Reference in New Issue
Block a user