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:
Nexoniarz 2026-10-22 23:47:17 +02:00
commit 27bf91cfe0
3 changed files with 177 additions and 11 deletions

View file

@ -48,6 +48,17 @@ struct hid {
int boot;
int report_id; // 0 when reports carry no ID byte
struct hfield x, y, wheel, btn[HID_NBTN];
// A keyboard in report protocol: where its keys are, so that each report
// can be rewritten into the boot layout and go through the same path. Not
// every keyboard interface offers the boot subclass -- the second
// interface of a gaming keyboard, the macro keys of a mouse, an NKRO mode
// that reports a bitmap instead of six slots.
int rkbd; // set when any of the below was found
int kbd_id; // their report ID, 0 for none
int mod_bit[8]; // E0-E7, -1 where the report has none
int arr_bit, arr_n, arr_size; // an array of usages, arr_n slots
int map_bit, map_first, map_n; // a bitmap: usage map_first + i
};
static struct hid hids[NHID];
@ -284,6 +295,7 @@ usb_hid_tick(void)
// ---------------------------------------------------------------------------
#define PAGE_DESKTOP 0x01
#define PAGE_KEYBOARD 0x07
#define PAGE_BUTTON 0x09
#define U_X 0x30
#define U_Y 0x31
@ -320,7 +332,49 @@ take_field(struct hid *h, uint32 usage, int bit, int size, int lmin, int lmax,
f->rel = rel;
}
// Returns 0 if a pointer was found: an X and a Y in the same report.
// One Input item on the keyboard page. Everything is taken from the first
// report ID that has any of it, for the same reason as take_field's.
static void
take_keys(struct hid *h, int variable, int bit, int size, int count,
uint32 first, int id)
{
int k;
if(h->rkbd && id != h->kbd_id)
return;
if(!variable){
// An array: `count` slots, each holding the usage of a key that is
// down. The usage range only says which keys it can name.
if(h->arr_n || size < 1 || size > 16)
return;
h->arr_bit = bit;
h->arr_n = count;
h->arr_size = size;
} else {
// One bit per key, the usage stepping up from `first`. Modifiers are
// the common case; a whole keyboard's worth is NKRO.
if(size != 1)
return;
for(k = 0; k < count; k++){
uint32 u = (first & 0xFFFF) + k;
if(u >= 0xE0 && u <= 0xE7)
h->mod_bit[u - 0xE0] = bit + k;
else if(h->map_n == 0){
h->map_bit = bit + k;
h->map_first = u;
h->map_n = 1;
} else if(bit + k == h->map_bit + h->map_n &&
(int)u == h->map_first + h->map_n)
h->map_n++;
}
}
h->rkbd = 1;
h->kbd_id = id;
}
// Returns 0 if a pointer was found -- an X and a Y in the same report -- or a
// keyboard.
static int
parse_report(struct hid *h, const uchar *d, int n)
{
@ -331,6 +385,8 @@ parse_report(struct hid *h, const uchar *d, int n)
int i = 0, k;
memset(off, 0, sizeof(off));
for(k = 0; k < 8; k++)
h->mod_bit[k] = -1;
while(i < n){
uchar b = d[i++];
int sz = b & 3, type = (b >> 2) & 3, tag = b >> 4;
@ -371,7 +427,13 @@ parse_report(struct hid *h, const uchar *d, int n)
case 2: umax = full; break;
}
} else if(type == 0){ // main
if(tag == 8 && id < 16 && rsize <= 32 && rcount <= 256){
if(tag == 8 && id < 16 && rsize <= 32 && rcount <= 256 &&
page == PAGE_KEYBOARD){
uint32 first = range ? umin : nusage ? usage[0] : 0;
if(!(v & 1))
take_keys(h, (v & 2) != 0, off[id], rsize, rcount, first, id);
off[id] += rsize * rcount;
} else if(tag == 8 && id < 16 && rsize <= 32 && rcount <= 256){
for(k = 0; k < (int)rcount; k++){
uint32 u = 0;
if(k < nusage)
@ -392,12 +454,65 @@ parse_report(struct hid *h, const uchar *d, int n)
}
}
if(!h->x.ok || !h->y.ok)
return -1;
if(!h->x.ok || !h->y.ok){
memset(&h->x, 0, sizeof(h->x)); // half a pointer is no pointer
memset(&h->y, 0, sizeof(h->y));
return h->rkbd && (h->arr_n || h->map_n) ? 0 : -1;
}
h->report_id = target;
return 0;
}
static uint32
bits(const uchar *p, int len, int bit, int size)
{
uint32 v = 0;
int i;
if((bit + size + 7) / 8 > len)
return 0;
for(i = 0; i < size; i++, bit++)
if((p[bit >> 3] >> (bit & 7)) & 1)
v |= 1u << i;
return v;
}
// A report-protocol keyboard report, rewritten as a boot one: modifiers, a
// reserved byte, six usages. Six is a limit only of the rewrite; the seventh
// key held at once is dropped, which is what a boot keyboard does too.
static void
report_keyboard(struct hid *h, const uchar *rep, int len)
{
uchar boot[8];
int i, n = 0;
if(h->kbd_id){
if(len < 1 || rep[0] != h->kbd_id)
return;
rep++;
len--;
}
memset(boot, 0, sizeof(boot));
for(i = 0; i < 8; i++)
if(h->mod_bit[i] >= 0 && bits(rep, len, h->mod_bit[i], 1))
boot[0] |= 1 << i;
for(i = 0; i < h->arr_n; i++){
uint32 u = bits(rep, len, h->arr_bit + i * h->arr_size, h->arr_size);
if(u >= 0xE0 && u <= 0xE7)
boot[0] |= 1 << (u - 0xE0);
else if(u >= 4 && u < 0xE0 && n < 6) // 1-3 are error codes
boot[2 + n++] = u;
}
for(i = 0; i < h->map_n; i++){
uint32 u = h->map_first + i;
if(u < 4 || u >= 0xE0 || !bits(rep, len, h->map_bit + i, 1))
continue;
if(n < 6)
boot[2 + n++] = u;
}
keyboard_report(h, boot, sizeof(boot));
}
static int
field(const uchar *p, int len, const struct hfield *f)
{
@ -486,9 +601,13 @@ hid_complete(struct usb_dev *dev, void *buf, int len)
if(h == 0)
return;
if(h->proto == USB_HID_PROTO_KEYBOARD)
// One interface can be both: a wireless receiver describes its keyboard
// and its mouse as two report IDs of one descriptor.
if(h->rkbd)
report_keyboard(h, buf, len);
if(h->proto == USB_HID_PROTO_KEYBOARD && !h->rkbd)
keyboard_report(h, buf, len);
else
else if(h->x.ok || h->boot)
pointer_report(h, buf, len);
}
@ -595,7 +714,8 @@ hid_probe(struct usb_dev *dev)
// always a device reset. Only a boot device knows the request.
if(boot)
hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_REPORT);
h->proto = h->x.rel ? USB_HID_PROTO_MOUSE : USB_HID_PROTO_TABLET;
h->proto = !h->x.ok ? USB_HID_PROTO_KEYBOARD :
h->x.rel ? USB_HID_PROTO_MOUSE : USB_HID_PROTO_TABLET;
} else if(boot && proto == USB_HID_PROTO_MOUSE){
memset(h, 0, sizeof(*h));
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
@ -635,7 +755,10 @@ hid_probe(struct usb_dev *dev)
cprintf("usb-hid: %s%s on slot %d, endpoint 0x%x every %d ms\n",
h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
h->proto == USB_HID_PROTO_MOUSE ? "mouse" : "absolute pointer",
h->proto == USB_HID_PROTO_KEYBOARD || h->boot ? "" :
h->proto == USB_HID_PROTO_KEYBOARD ?
(h->rkbd ? " (report protocol)" : "") :
h->boot ? "" :
h->rkbd ? " and keyboard" :
h->wheel.ok ? " with wheel" : "",
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
return 0;
@ -651,7 +774,7 @@ hid_disconnect(struct usb_dev *dev)
if(h == 0)
return;
if(h->proto == USB_HID_PROTO_KEYBOARD){
if(h->proto == USB_HID_PROTO_KEYBOARD || h->rkbd){
keyboard_events(h->prev, none);
acquire(&kqlock);
krep.c = 0;