usb: plug and play -- hot-plug, unplug, every interface, real mice

Devices can now arrive and leave after boot. A "usbhotplug" kernel thread
handles root-port changes, which it is told about by Port Status Change
events and also checks for every quarter second. It also handles changes
behind hubs, which each hub reports on its status-change endpoint. An
unplugged device has its endpoints closed and any transfer in flight
failed. Then its drivers' disconnect hooks run, the slot is disabled and
its memory is freed. Everything behind a hub that goes goes with it. This
needed the interrupt handler to file command and endpoint-0 completions
for their waiters, so that enumeration can run while keyboards interrupt.

On real hardware: the controller is taken from the firmware through USB
Legacy Support before reset, with SMIs off. On Intel 7/8/9 series parts
the ports are switched over from EHCI, which is where firmware leaves
them.

Every interface of a device is offered to the class drivers, not just
the first, so a receiver's mouse on interface 1 gets a driver. Configure
Endpoint now copies the slot context from the controller. Before, it
rebuilt it and lost the route string and TT fields of devices behind
hubs.

HID: pointers are read in report protocol from their report descriptor.
That covers buttons, X/Y and wheel, relative or absolute, and report IDs.
Boot protocol is kept as the fallback for mice and for keyboards.
Non-pointer HID interfaces are declined rather than treated as tablets.
USB keyboards get software key repeat. Mass storage handles up to four
sticks, each unregistered from the block layer when it is pulled out,
along with its cached blocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
This commit is contained in:
Nexoniarz 2026-10-22 11:38:13 +02:00
commit fa7669254e
13 changed files with 1367 additions and 240 deletions

View file

@ -1,16 +1,21 @@
// USB HID, boot protocol: keyboards and mice.
// USB HID: keyboards, mice and absolute pointers.
//
// Boot protocol is the reason a USB keyboard works in a BIOS setup screen
// before any operating system exists. A HID device normally describes its
// reports with a report descriptor -- a small stack language that has to be
// parsed to know which bit means which key -- but a device claiming the boot
// subclass also promises to support one fixed layout on request. Eight bytes
// for a keyboard, three for a mouse, defined in the HID specification and
// identical on every device.
// Keyboards are driven in boot protocol, which is the reason a USB keyboard
// works in a BIOS setup screen before any operating system exists. A HID
// device normally describes its reports with a report descriptor -- a small
// stack language that has to be parsed to know which bit means which key --
// but a device claiming the boot subclass also promises one fixed layout on
// request: eight bytes, defined in the HID specification and identical on
// every keyboard. Nothing a keyboard sends beyond that is worth the parser.
//
// That is worth taking. A full report-descriptor parser is a real piece of
// work and buys nothing for a keyboard; it is what a game controller or a
// touchscreen will need, and it can arrive with them.
// Pointers are not. The boot mouse report is three bytes -- buttons and an
// 8-bit delta on each axis -- and has no wheel, which every mouse sold this
// century has. So a pointer is driven in report protocol, from its report
// descriptor: where its buttons, X, Y and wheel are, how wide, and whether
// they are relative (a mouse) or absolute (a tablet, a touchscreen, a VM's
// pointer). Only the fields a pointer needs are looked for; everything else
// in the descriptor is stepped over. A boot mouse whose descriptor makes no
// sense is still driven, in boot protocol, as before.
#include "types.h"
#include "defs.h"
@ -21,12 +26,28 @@
#include "usb.h"
#include "input.h"
#define NHID 4
#define NHID 8
// One field of a report, as the report descriptor placed it.
struct hfield {
int ok;
int bit, size; // offset and width in bits, after any ID byte
int lmin, lmax; // logical range; lmin < 0 means signed
int rel; // relative (a delta) rather than absolute
};
#define HID_NBTN 5
struct hid {
int in_use;
int proto; // USB_HID_PROTO_KEYBOARD or _MOUSE
int proto; // USB_HID_PROTO_KEYBOARD, _MOUSE or _TABLET
uchar prev[8]; // the previous report, for edge detection
// A pointer in report protocol. `boot` instead means the three-byte boot
// report, for a mouse whose descriptor could not be used.
int boot;
int report_id; // 0 when reports carry no ID byte
struct hfield x, y, wheel, btn[HID_NBTN];
};
static struct hid hids[NHID];
@ -78,19 +99,42 @@ static struct {
uint r, w;
} kq;
// The queue is filled from the interrupt handler and, for key repeat, from
// the hot-plug thread; consoleintr() drains it from either.
static struct spinlock kqlock;
static int kqinit;
// Software key repeat. A USB keyboard does not repeat: a held key is simply
// still in the report, and the next report comes only when something
// changes. The PS/2 keyboard repeats in its own firmware, so without this a
// held key auto-repeats on one keyboard and not the other.
#define REPEAT_DELAY (HZ / 2) // 500 ms, the PC default
#define REPEAT_EVERY 3 // ticks: about 33 a second
static struct {
int c; // the character to repeat, 0 for none
uchar usage; // the key producing it
uint at; // tick of the next repeat
} krep;
static void
kq_put(int c)
{
acquire(&kqlock);
if(kq.w - kq.r < KQSIZE)
kq.buf[kq.w++ % KQSIZE] = c;
release(&kqlock);
}
static int
usb_kbd_getc(void)
{
if(kq.r == kq.w)
return -1;
return kq.buf[kq.r++ % KQSIZE];
int c = -1;
acquire(&kqlock);
if(kq.r != kq.w)
c = kq.buf[kq.r++ % KQSIZE];
release(&kqlock);
return c;
}
// Was this usage in that report?
@ -187,68 +231,250 @@ keyboard_report(struct hid *h, const uchar *rep, int len)
continue;
}
kq_put(c);
// The newest key is the one that repeats, as on any keyboard.
acquire(&kqlock);
krep.c = c;
krep.usage = usage;
krep.at = ticks + REPEAT_DELAY;
release(&kqlock);
}
// Released, or another key's report without it: no more repeats.
acquire(&kqlock);
if(krep.c && !was_down(rep, krep.usage))
krep.c = 0;
release(&kqlock);
memmove(h->prev, rep, 8);
consoleintr(usb_kbd_getc);
}
void
usb_hid_tick(void)
{
int c = 0;
if(!kqinit)
return;
acquire(&kqlock);
if(krep.c && (int)(ticks - krep.at) >= 0){
c = krep.c;
krep.at = ticks + REPEAT_EVERY;
if(kq.w - kq.r < KQSIZE)
kq.buf[kq.w++ % KQSIZE] = c;
}
release(&kqlock);
if(c && !input_is_grabbed(INPUT_KBD))
consoleintr(usb_kbd_getc);
}
// ---------------------------------------------------------------------------
// Mouse
// Report descriptors
//
// A report descriptor is a sequence of items, each a prefix byte -- size in
// bits 1:0 (3 meaning four bytes), type in 3:2, tag in 7:4 -- and that many
// bytes of data. Global items (usage page, logical range, report size and
// count, report ID) persist until changed; local items (usages) apply to the
// next main item only. A main Input item lays down `count` fields of `size`
// bits each, taking usages in order, and advances the position in that
// report. Push, pop, delimiters and designators are not used by anything
// that matters here and are passed over.
// ---------------------------------------------------------------------------
// QEMU's usb-tablet, and absolute pointers generally.
//
// Report: one byte of buttons, then X and Y as 16-bit little-endian values
// over the device's logical range, then a wheel byte. The range is 0..0x7fff
// and is *not* read from the report descriptor -- parsing one is a real piece
// of work, and this decodes exactly the fixed layout the tablet declares.
// A device whose report is a different length is left alone rather than
// guessed at.
//
// Scaling happens here because this is the only place that knows the logical
// range; everything above deals in pixels.
#define TABLET_LOGICAL_MAX 0x7fff
#define PAGE_DESKTOP 0x01
#define PAGE_BUTTON 0x09
#define U_X 0x30
#define U_Y 0x31
#define U_WHEEL 0x38
static void
tablet_report(struct hid *h, const uchar *rep, int len)
take_field(struct hid *h, uint32 usage, int bit, int size, int lmin, int lmax,
int rel, int id, int *target)
{
(void)h;
if(len < 5)
struct hfield *f = 0;
int page = usage >> 16, u = usage & 0xFFFF;
if(page == PAGE_DESKTOP && u == U_X) f = &h->x;
else if(page == PAGE_DESKTOP && u == U_Y) f = &h->y;
else if(page == PAGE_DESKTOP && u == U_WHEEL) f = &h->wheel;
else if(page == PAGE_BUTTON && u >= 1 && u <= HID_NBTN)
f = &h->btn[u - 1];
if(f == 0 || f->ok || size < 1 || size > 32)
return;
int rawx = rep[1] | (rep[2] << 8);
int rawy = rep[3] | (rep[4] << 8);
// Everything comes from one report: the first one with any of this in it.
// A keyboard-and-mouse receiver describes both in one descriptor, and a
// button bit from the keyboard's report would be nonsense in the mouse's.
if(*target < 0)
*target = id;
if(id != *target)
return;
int w = (int)gfx_width();
int h_px = (int)gfx_height();
if(w <= 0) w = 800;
if(h_px <= 0) h_px = 600;
f->ok = 1;
f->bit = bit;
f->size = size;
f->lmin = lmin;
f->lmax = lmax;
f->rel = rel;
}
// Scale in 64-bit-free arithmetic: 0x7fff * 4096 still fits in an int.
int x = (int)(((long long)rawx * (w - 1)) / TABLET_LOGICAL_MAX);
int y = (int)(((long long)rawy * (h_px - 1)) / TABLET_LOGICAL_MAX);
// Returns 0 if a pointer was found: an X and a Y in the same report.
static int
parse_report(struct hid *h, const uchar *d, int n)
{
uint32 page = 0, rsize = 0, rcount = 0, umin = 0, umax = 0;
uint32 usage[16];
int lmin = 0, lmax = 0, id = 0, nusage = 0, range = 0, target = -1;
int off[16];
int i = 0, k;
mouse_set_abs(x, y, rep[0] & 0x07);
memset(off, 0, sizeof(off));
while(i < n){
uchar b = d[i++];
int sz = b & 3, type = (b >> 2) & 3, tag = b >> 4;
uint32 v = 0;
int sv;
if(b == 0xFE){ // long item: size, tag, data
if(i >= n)
break;
i += 2 + d[i];
continue;
}
if(sz == 3)
sz = 4;
if(i + sz > n)
break;
for(k = 0; k < sz; k++)
v |= (uint32)d[i + k] << (8 * k);
i += sz;
sv = sz == 1 ? (signed char)v : sz == 2 ? (short)v : (int)v;
if(type == 1){ // global
switch(tag){
case 0: page = v; break;
case 1: lmin = sv; break;
// Signed or not depends on the minimum: 0..255 in one byte is 255,
// not -1.
case 2: lmax = sv < lmin ? (int)v : sv; break;
case 7: rsize = v; break;
case 8: id = v; break;
case 9: rcount = v; break;
}
} else if(type == 2){ // local
uint32 full = sz == 4 ? v : (page << 16) | v;
switch(tag){
case 0: if(nusage < 16) usage[nusage++] = full; break;
case 1: umin = full; range = 1; break;
case 2: umax = full; break;
}
} else if(type == 0){ // main
if(tag == 8 && id < 16 && rsize <= 32 && rcount <= 256){
for(k = 0; k < (int)rcount; k++){
uint32 u = 0;
if(k < nusage)
u = usage[k];
else if(range && umin + (k - nusage) <= umax)
u = umin + (k - nusage);
else if(nusage > 0)
u = usage[nusage - 1];
// Bit 0 set is a constant field: padding, there to be stepped over.
if(!(v & 1))
take_field(h, u, off[id], rsize, lmin, lmax, (v & 4) != 0, id,
&target);
off[id] += rsize;
}
}
nusage = 0;
range = 0;
}
}
if(!h->x.ok || !h->y.ok)
return -1;
h->report_id = target;
return 0;
}
static int
field(const uchar *p, int len, const struct hfield *f)
{
uint32 v = 0;
int i;
if(!f->ok || (f->bit + f->size + 7) / 8 > len)
return 0;
for(i = 0; i < f->size; i++){
int b = f->bit + i;
if((p[b >> 3] >> (b & 7)) & 1)
v |= 1u << i;
}
if(f->lmin < 0 && f->size < 32 && ((v >> (f->size - 1)) & 1))
v |= ~0u << f->size; // sign-extend
return (int)v;
}
// ---------------------------------------------------------------------------
// Pointers
// ---------------------------------------------------------------------------
static int
scale(int v, const struct hfield *f, int pixels)
{
long long range = (long long)f->lmax - f->lmin;
if(range <= 0)
return 0;
return (int)(((long long)(v - f->lmin) * (pixels - 1)) / range);
}
static void
mouse_report(struct hid *h, const uchar *rep, int len)
pointer_report(struct hid *h, const uchar *rep, int len)
{
int dx, dy;
int buttons = 0, i, wheel;
(void)h;
if(len < 3)
if(h->boot){
// Boot protocol: buttons, then signed 8-bit X and Y. The deltas are
// signed. Reading them as unsigned is the classic way to get a pointer
// that only travels right and down.
if(len < 3)
return;
mouse_inject((signed char)rep[1], (signed char)rep[2], rep[0] & 0x07);
return;
}
// The deltas are signed bytes. Reading them as unsigned is the classic way
// to get a pointer that only travels right and down.
dx = (signed char)rep[1];
dy = (signed char)rep[2];
if(h->report_id){
if(len < 1 || rep[0] != h->report_id)
return; // some other report of the device's
rep++;
len--;
}
for(i = 0; i < HID_NBTN; i++)
if(field(rep, len, &h->btn[i]))
buttons |= 1 << i;
// A wheel step is reported before the motion it came with, so that both
// reach a reader in one batch: mouse_inject() is what ends the batch.
wheel = field(rep, len, &h->wheel);
if(wheel && h->wheel.rel)
input_report(INPUT_MOUSE, EV_REL, REL_WHEEL, wheel);
// HID's Y axis points down, and so does the screen's. The PS/2 protocol's
// points up, which is why mouseintr subtracts where this adds.
mouse_inject(dx, dy, rep[0] & 0x07);
if(h->x.rel){
mouse_inject(field(rep, len, &h->x), field(rep, len, &h->y),
buttons & 0x07);
} else {
// Scaled here because this is the only place that knows the logical
// range; everything above deals in pixels.
int w = (int)gfx_width(), hp = (int)gfx_height();
if(w <= 0) w = 800;
if(hp <= 0) hp = 600;
mouse_set_abs(scale(field(rep, len, &h->x), &h->x, w),
scale(field(rep, len, &h->y), &h->y, hp), buttons & 0x07);
}
}
// ---------------------------------------------------------------------------
@ -260,12 +486,10 @@ hid_complete(struct usb_dev *dev, void *buf, int len)
if(h == 0)
return;
if(h->proto == USB_HID_PROTO_TABLET)
tablet_report(h, buf, len);
else if(h->proto == USB_HID_PROTO_KEYBOARD)
if(h->proto == USB_HID_PROTO_KEYBOARD)
keyboard_report(h, buf, len);
else
mouse_report(h, buf, len);
pointer_report(h, buf, len);
}
static int
@ -281,37 +505,110 @@ hid_request(struct usb_dev *dev, int request, int value)
return dev->ops->control(dev, &s, 0, 0);
}
// The report descriptor's length is in the HID descriptor, which sits in the
// configuration right after this interface's own descriptor.
static int
report_desc_len(struct usb_dev *dev)
{
int off = 0, mine = 0;
while(off + 2 <= dev->conflen){
int len = dev->conf[off], type = dev->conf[off + 1];
if(len < 2 || off + len > dev->conflen)
break;
if(type == USB_DT_INTERFACE && len >= 4)
mine = dev->conf[off + 2] == dev->iface.bInterfaceNumber &&
dev->conf[off + 3] == 0;
else if(mine && type == USB_DT_HID && len >= 9 &&
dev->conf[off + 6] == USB_DT_REPORT)
return dev->conf[off + 7] | (dev->conf[off + 8] << 8);
off += len;
}
return -1;
}
static int
read_report_desc(struct usb_dev *dev, struct hid *h)
{
struct usb_setup s;
int len = report_desc_len(dev), rc;
uchar *buf;
if(len <= 0)
return -1;
if(len > 1024)
len = 1024;
buf = dma_alloc(len, 64);
if(buf == 0)
return -1;
s.bmRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE;
s.bRequest = USB_REQ_GET_DESCRIPTOR;
s.wValue = USB_DT_REPORT << 8;
s.wIndex = dev->iface.bInterfaceNumber;
s.wLength = len;
memset(buf, 0, len);
rc = dev->ops->control(dev, &s, buf, len);
if(rc >= 0)
rc = parse_report(h, buf, len);
dma_free(buf, len);
return rc;
}
static int
hid_probe(struct usb_dev *dev)
{
struct hid *h = 0;
int i, proto = dev->iface.bInterfaceProtocol;
int i;
int boot = dev->iface.bInterfaceSubClass == USB_HID_SUBCLASS_BOOT;
int proto = dev->iface.bInterfaceProtocol;
if(!dev->has_intr_in){
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
return -1;
}
if(!kqinit){
initlock(&kqlock, "usbkbd");
kqinit = 1;
}
for(i = 0; i < NHID; i++)
if(!hids[i].in_use){ h = &hids[i]; break; }
if(h == 0)
if(h == 0){
cprintf("usb-hid: %d devices already; this one is not used\n", NHID);
return -1;
}
memset(h, 0, sizeof(*h));
// A HID device that claims no boot protocol is taken to be an absolute
// pointer -- in practice a tablet, which is what a VM offers so that its
// cursor and the host's can agree. It must not be asked for boot protocol:
// it has none, the request fails, and refusing on that basis is what kept
// usb-tablet from binding at all.
if(proto != USB_HID_PROTO_KEYBOARD && proto != USB_HID_PROTO_MOUSE)
proto = USB_HID_PROTO_TABLET;
// Ask for the fixed layout explicitly. A device that supports the boot
// subclass still starts in report protocol, so without this the reports
// arrive in whatever shape its report descriptor declares -- which we have
// not read and cannot interpret.
if(proto != USB_HID_PROTO_TABLET &&
hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
if(boot && proto == USB_HID_PROTO_KEYBOARD){
// Ask for the fixed layout explicitly. A device that supports the boot
// subclass still starts in report protocol, so without this the reports
// arrive in whatever shape its report descriptor declares.
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
return -1;
}
h->proto = USB_HID_PROTO_KEYBOARD;
} else if(read_report_desc(dev, h) == 0){
// Report protocol is the default after a reset, but a firmware that used
// the mouse may have left it in boot protocol, and a port reset is not
// 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;
} 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){
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
return -1;
}
h->proto = USB_HID_PROTO_MOUSE;
h->boot = 1;
} else {
// A consumer-control interface (volume keys), a game pad, a UPS: HID,
// and not something this driver has a use for.
cprintf("usb-hid: interface %d is neither a keyboard nor a pointer\n",
dev->iface.bInterfaceNumber);
return -1;
}
@ -324,32 +621,52 @@ hid_probe(struct usb_dev *dev)
return -1;
h->in_use = 1;
h->proto = proto;
dev->drvdata = h;
dev->complete = hid_complete;
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
// has nowhere to put a report, so nothing would ever arrive.
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0){
h->in_use = 0;
dev->drvdata = 0;
return -1;
}
cprintf("usb-hid: %s on slot %d, endpoint 0x%x every %d ms\n",
proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
proto == USB_HID_PROTO_MOUSE ? "mouse" :
proto == USB_HID_PROTO_TABLET ? "tablet (absolute)" : "device",
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->wheel.ok ? " with wheel" : "",
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
return 0;
}
// Any HID interface, not just the boot subclass.
//
// It was boot-only, on the reasoning that a device without boot protocol needs
// its report descriptor parsed and there would be nothing useful to do with it.
// That is right in general and wrong for the one device that matters here:
// usb-tablet declares subclass 0, so it was refused at the door -- and its
// report layout is fixed and known, so there is something useful to do with it
// after all. hid_probe decides what a given interface actually is; anything it
// cannot make sense of is still declined there.
// Unplugged. A key held at that moment is released, so that a reader of
// /dev/input does not see it held for ever, and the slot is given back.
static void
hid_disconnect(struct usb_dev *dev)
{
struct hid *h = dev->drvdata;
static const uchar none[8];
if(h == 0)
return;
if(h->proto == USB_HID_PROTO_KEYBOARD){
keyboard_events(h->prev, none);
acquire(&kqlock);
krep.c = 0;
release(&kqlock);
}
cprintf("usb-hid: %s on slot %d unplugged\n",
h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" : "pointer",
dev->slot);
h->in_use = 0;
dev->drvdata = 0;
dev->complete = 0;
}
// Any HID interface, not just the boot subclass: hid_probe decides what a
// given interface actually is, and declines anything it cannot make sense of.
const struct usb_driver usb_hid_driver = {
.name = "usb-hid",
.class = USB_CLASS_HID,
@ -357,4 +674,5 @@ const struct usb_driver usb_hid_driver = {
.protocol = 0xFF,
.devclass = DEVCLASS_INPUT,
.probe = hid_probe,
.disconnect = hid_disconnect,
};