usb: correct EP0's max packet size from the device; sample speed after reset
Two real-hardware correctness fixes from the M20 list, both invisible to QEMU's forgiving controller: refreshMaxPacketSize0 existed only as a comment. The EP0 context kept the SPEED-DEFAULT max packet size (full-speed: 8) even when the device declares 16/32/64 — real controllers fault the very first full descriptor read on the mismatch, which is the standing suspect for full-speed mice dying on the user's PC. Enumeration now probes the device descriptor's first 8 bytes (deliverable at any legal MPS0), and issues Evaluate Context to correct EP0 before any longer transfer, naming the failure and codes if the controller refuses. And a USB2 port's PORTSC speed field is only meaningful once the port reset ENABLES the port: the speed (and the MPS0 default derived from it) is now sampled after the reset instead of trusting the connect-time read.
This commit is contained in:
@@ -643,10 +643,18 @@ pub const Controller = struct {
|
|||||||
// Only a not-yet-enabled port — every USB2 device, or a stuck SS link —
|
// Only a not-yet-enabled port — every USB2 device, or a stuck SS link —
|
||||||
// needs the reset to enable.
|
// needs the reset to enable.
|
||||||
const already_enabled = speed >= 4 and self.portStatus(port) & portsc_enabled != 0;
|
const already_enabled = speed >= 4 and self.portStatus(port) & portsc_enabled != 0;
|
||||||
if (!already_enabled and !self.resetPort(port)) {
|
var effective_speed = speed;
|
||||||
|
if (!already_enabled) {
|
||||||
|
if (!self.resetPort(port)) {
|
||||||
std.log.info("port {d} setup: port reset failed (PORTSC 0x{x:0>8})", .{ port, self.portStatus(port) });
|
std.log.info("port {d} setup: port reset failed (PORTSC 0x{x:0>8})", .{ port, self.portStatus(port) });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// A USB2 port's PORTSC speed field is only meaningful once the port
|
||||||
|
// is enabled by the reset — sample it NOW, not at connect time
|
||||||
|
// (pre-reset reads misreport on real controllers; M20).
|
||||||
|
effective_speed = (self.portStatus(port) >> 10) & 0xF;
|
||||||
|
if (effective_speed == 0) effective_speed = speed; // defensive: keep the caller's read
|
||||||
|
}
|
||||||
const slot_id = self.enableSlot() orelse {
|
const slot_id = self.enableSlot() orelse {
|
||||||
std.log.info("port {d} setup: Enable Slot failed", .{port});
|
std.log.info("port {d} setup: Enable Slot failed", .{port});
|
||||||
return null;
|
return null;
|
||||||
@@ -659,8 +667,8 @@ pub const Controller = struct {
|
|||||||
.used = true,
|
.used = true,
|
||||||
.slot_id = slot_id,
|
.slot_id = slot_id,
|
||||||
.port = port,
|
.port = port,
|
||||||
.speed = speed,
|
.speed = effective_speed,
|
||||||
.max_packet_size_0 = defaultMaxPacketSize0(speed),
|
.max_packet_size_0 = defaultMaxPacketSize0(effective_speed),
|
||||||
};
|
};
|
||||||
device.input_context = dma.alloc(page_size, dma.coherent) orelse return self.abandon(device);
|
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);
|
device.device_context = dma.alloc(page_size, dma.coherent) orelse return self.abandon(device);
|
||||||
@@ -768,7 +776,47 @@ pub const Controller = struct {
|
|||||||
/// endpoints into `device`, and select the configuration. After this the
|
/// endpoints into `device`, and select the configuration. After this the
|
||||||
/// device is in the configured state and its interfaces are ready to match a
|
/// device is in the configured state and its interfaces are ready to match a
|
||||||
/// class driver. Returns false on any control-transfer failure.
|
/// class driver. Returns false on any control-transfer failure.
|
||||||
|
/// Correct EP0's max packet size from the device itself. The context starts
|
||||||
|
/// with the SPEED-DEFAULT (full-speed: 8, but the true value may be 8/16/32/
|
||||||
|
/// 64 — byte 7 of the device descriptor). Read just the descriptor's first
|
||||||
|
/// 8 bytes (always deliverable at any legal MPS0), and when the device
|
||||||
|
/// disagrees with the context, issue Evaluate Context to fix EP0 before any
|
||||||
|
/// longer transfer. Real controllers fault the full 18-byte read on a wrong
|
||||||
|
/// MPS0; QEMU forgives it — the classic full-speed-mouse-on-real-hardware
|
||||||
|
/// failure (M20).
|
||||||
|
fn refreshMaxPacketSize0(self: *Controller, device: *Device) bool {
|
||||||
|
var head: [8]u8 = undefined;
|
||||||
|
const request = usb_abi.getDescriptor(.device, 0, 0, 8);
|
||||||
|
if (!self.controlTransfer(device, request, head[0..], true)) return false;
|
||||||
|
const actual: u32 = head[7];
|
||||||
|
if (actual == 0 or actual == device.max_packet_size_0) return true;
|
||||||
|
|
||||||
|
// Input Control Context: add-flag A1 (EP0 only); EP0 context rebuilt
|
||||||
|
// with the corrected MPS. Fields not being changed stay zero (the
|
||||||
|
// controller evaluates only the added context).
|
||||||
|
const cs = self.context_size;
|
||||||
|
const base = device.input_context.virtual;
|
||||||
|
@memset(@as([*]u8, @ptrFromInt(base))[0 .. 3 * cs], 0);
|
||||||
|
contextDword(base, 0, 1, cs).* = 0b10; // A1
|
||||||
|
contextDword(base, 2, 1, cs).* = (@as(u32, 3) << 1) | (@as(u32, 4) << 3) | (actual << 16);
|
||||||
|
const physical = self.submitCommand(.{
|
||||||
|
.parameter = device.input_context.physical,
|
||||||
|
.control = trbControl(.evaluate_context, @as(u32, device.slot_id) << 24),
|
||||||
|
});
|
||||||
|
const code = self.awaitCommand(physical) orelse {
|
||||||
|
std.log.info("slot {d}: Evaluate Context (MPS0 {d} -> {d}) timed out", .{ device.slot_id, device.max_packet_size_0, actual });
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (code != @intFromEnum(CompletionCode.success)) {
|
||||||
|
std.log.info("slot {d}: Evaluate Context (MPS0 {d} -> {d}) completion code {d}", .{ device.slot_id, device.max_packet_size_0, actual, code });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
device.max_packet_size_0 = actual;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enumerate(self: *Controller, device: *Device) bool {
|
pub fn enumerate(self: *Controller, device: *Device) bool {
|
||||||
|
if (!self.refreshMaxPacketSize0(device)) return false;
|
||||||
device.device_descriptor = self.getDeviceDescriptor(device) orelse return false;
|
device.device_descriptor = self.getDeviceDescriptor(device) orelse return false;
|
||||||
|
|
||||||
// The configuration descriptor's own 9 bytes carry the total length of
|
// The configuration descriptor's own 9 bytes carry the total length of
|
||||||
|
|||||||
Reference in New Issue
Block a user