usb: recover endpoint 0 after a failed request; report-protocol keyboards
From the first boot on real hardware: a Razer mouse's control transfer failed with a babble, and an ITE controller's late endpoint-0 completion was reported as an "unexpected event" during a command. A failed control transfer left endpoint 0 halted (or, on a timeout, still running with TRBs queued), so every request after it to that device failed too. It is now reset or stopped and its dequeue pointer moved past what was left on the ring. A STALL -- a device declining a request it does not implement -- is logged only on a verbose boot. HID interfaces without the boot subclass were turned away unless they had a pointer. The report descriptor's keyboard page is now read too -- modifier bits, a key array, an NKRO bitmap -- and each report rewritten into the boot layout for the existing path. Checked with QEMU's usb-kbd forced into report protocol: "echo Hi", Shift included, typed and ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
956d8de5d1
commit
27bf91cfe0
3 changed files with 177 additions and 11 deletions
|
|
@ -313,11 +313,18 @@ event_wait(struct xhci *x, int type, struct trb *out, int ms)
|
|||
// A transfer on some other endpoint is not either: with no interrupt
|
||||
// the hot-plug thread is the only reader of this ring, and a keyboard
|
||||
// report it threw away here would be a lost keypress.
|
||||
//
|
||||
// One on endpoint 0 is a straggler from a control transfer that has
|
||||
// already been given up on -- an error ends a transfer at the TRB that
|
||||
// failed, and its completion can trail the wait that returned for it.
|
||||
if(t == TRB_XFER_EVENT && TRB_GET_EPID(out->control) != DCI_EP0){
|
||||
acquire(&x->lock);
|
||||
transfer_done(x, out);
|
||||
release(&x->lock);
|
||||
} else if(t != TRB_PORT_STATUS)
|
||||
} else if(t == TRB_XFER_EVENT)
|
||||
kinfo("xhci: late completion on slot %d endpoint 0, code %d\n",
|
||||
TRB_GET_SLOT(out->control), TRB_CC(out->status));
|
||||
else if(t != TRB_PORT_STATUS)
|
||||
cprintf("xhci: unexpected event type %d while waiting for %d\n",
|
||||
t, type);
|
||||
}
|
||||
|
|
@ -431,6 +438,26 @@ speed_name(int speed)
|
|||
// Control transfers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Put endpoint 0 back in service after a transfer that did not finish.
|
||||
//
|
||||
// A STALL or a babble halts the endpoint, and a halted endpoint refuses every
|
||||
// transfer after it -- so one request a device did not like (plenty stall a
|
||||
// string descriptor or SET_IDLE they do not implement) used to cost the whole
|
||||
// device: everything that followed timed out. A timeout leaves it running
|
||||
// with TRBs still queued, which is no better. Either way it is stopped or
|
||||
// reset, and its dequeue pointer moved past whatever was left on the ring.
|
||||
static void
|
||||
ep0_recover(struct xhci *x, int slot, int halted)
|
||||
{
|
||||
struct ring *r = &x->slot[slot].ep0;
|
||||
|
||||
command(x, 0, 0, TRB_TYPE(halted ? TRB_RESET_EP : TRB_STOP_EP) |
|
||||
((uint32)DCI_EP0 << 16) | TRB_SLOT(slot), 0);
|
||||
command(x, (r->phys + (uint64)r->enq * 16) | (r->cycle ? 1 : 0), 0,
|
||||
TRB_TYPE(TRB_SET_TR_DEQ) | ((uint32)DCI_EP0 << 16) | TRB_SLOT(slot),
|
||||
0);
|
||||
}
|
||||
|
||||
// A control transfer is three stages on the endpoint ring: Setup, an optional
|
||||
// Data stage, and Status. Only the last carries IOC, so one transfer produces
|
||||
// one event.
|
||||
|
|
@ -481,6 +508,7 @@ control_xfer(struct xhci *x, int slot, struct usb_setup *s, void *buf, int len)
|
|||
if(rc < 0){
|
||||
cprintf("xhci: control transfer timed out (slot %d, request 0x%x)\n",
|
||||
slot, s->bRequest);
|
||||
ep0_recover(x, slot, 0);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -490,6 +518,7 @@ control_xfer(struct xhci *x, int slot, struct usb_setup *s, void *buf, int len)
|
|||
if(event_wait(x, TRB_XFER_EVENT, &ev, 1000) < 0){
|
||||
cprintf("xhci: control transfer timed out (slot %d, request 0x%x)\n",
|
||||
slot, s->bRequest);
|
||||
ep0_recover(x, slot, 0);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -498,7 +527,17 @@ control_xfer(struct xhci *x, int slot, struct usb_setup *s, void *buf, int len)
|
|||
// have", which is the normal reply to asking for more than it holds.
|
||||
int cc = TRB_CC(ev.status);
|
||||
if(cc != CC_SUCCESS && cc != CC_SHORT_PACKET){
|
||||
cprintf("xhci: control transfer failed, completion code %d\n", cc);
|
||||
// A stall is a device declining a request, which is its right and
|
||||
// which callers already treat as "not supported". Only say so on a
|
||||
// verbose boot; anything else is worth a line.
|
||||
if(cc == CC_STALL)
|
||||
kinfo("xhci: slot %d stalled request 0x%x (type 0x%x, value 0x%x)\n",
|
||||
slot, s->bRequest, s->bmRequestType, s->wValue);
|
||||
else
|
||||
cprintf("xhci: control transfer failed, completion code %d "
|
||||
"(slot %d, request 0x%x, value 0x%x)\n",
|
||||
cc, slot, s->bRequest, s->wValue);
|
||||
ep0_recover(x, slot, 1);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue