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

@ -76,11 +76,15 @@ set_configuration(struct usb_dev *dev, int value)
// middle (HID's own, class-specific ones) and skipping them by size is the
// only way through. A parser that assumed a fixed order would work on one
// device and fail on the next.
static void
parse_config(struct usb_dev *dev)
//
// `nth` picks the interface: the nth one at alternate setting 0, counted from
// zero. Returns 0 if there is one, -1 when there are no more.
static int
parse_config(struct usb_dev *dev, int nth)
{
int off = 0;
int seen_iface = 0;
int count = 0;
dev->has_intr_in = dev->has_bulk_in = dev->has_bulk_out = 0;
@ -92,10 +96,15 @@ parse_config(struct usb_dev *dev)
break; // malformed: stop rather than guess
if(type == USB_DT_INTERFACE && len >= (int)sizeof(dev->iface)){
struct usb_interface_desc *id = (struct usb_interface_desc *)&dev->conf[off];
if(seen_iface)
break; // first interface only, for now
memmove(&dev->iface, &dev->conf[off], sizeof(dev->iface));
seen_iface = 1;
break; // the next interface: this one is done
// Alternate settings are the same interface again with other
// endpoints. Setting 0 is the one in force after SET_CONFIGURATION.
if(id->bAlternateSetting == 0 && count++ == nth){
memmove(&dev->iface, id, sizeof(dev->iface));
seen_iface = 1;
}
} else if(type == USB_DT_ENDPOINT && seen_iface &&
len >= (int)sizeof(struct usb_endpoint_desc)){
struct usb_endpoint_desc ep;
@ -116,8 +125,15 @@ parse_config(struct usb_dev *dev)
}
off += len;
}
return seen_iface ? 0 : -1;
}
// One usb_dev for every interface after the first. See `base` in
// include/usb.h. A fixed pool, like everything else here: sixteen is more
// interfaces than a desk of keyboards, mice and receivers adds up to.
#define NIFDEV 16
static struct usb_dev ifdevs[NIFDEV];
// ---------------------------------------------------------------------------
// Class drivers
// ---------------------------------------------------------------------------
@ -146,6 +162,7 @@ usb_offer(const struct usb_driver *drv, struct usb_dev *dev)
if(drv->probe(dev) != 0)
return 0;
dev->driver = drv->name;
dev->drv = drv;
return 1;
}
@ -185,8 +202,6 @@ usb_attach(struct usb_dev *dev)
memmove(dev->conf, buf, total);
dev->conflen = total;
parse_config(dev);
// A device is not obliged to do anything at all until it has been
// configured -- before this, endpoints other than 0 do not exist.
if(set_configuration(dev, cd->bConfigurationValue) < 0){
@ -194,16 +209,67 @@ usb_attach(struct usb_dev *dev)
return;
}
cprintf("usb: interface class %d.%d protocol %d, %d endpoint%s%s%s%s\n",
dev->iface.bInterfaceClass, dev->iface.bInterfaceSubClass,
dev->iface.bInterfaceProtocol, dev->iface.bNumEndpoints,
dev->iface.bNumEndpoints == 1 ? "" : "s",
dev->has_intr_in ? ", interrupt IN" : "",
dev->has_bulk_in ? ", bulk IN" : "",
dev->has_bulk_out ? ", bulk OUT" : "");
// Every interface, each offered to the class drivers on its own. This used
// to stop after the first, which is how a wireless receiver's mouse --
// interface 1, behind the keyboard on 0 -- was never heard from.
for(int n = 0; ; n++){
struct usb_dev *d = dev;
usb_bind_all(dev);
if(n > 0){
d = 0;
for(int i = 0; i < NIFDEV; i++)
if(!ifdevs[i].in_use){ d = &ifdevs[i]; break; }
if(d == 0){
cprintf("usb: out of interface slots; the rest are not used\n");
break;
}
*d = *dev;
d->base = dev;
d->driver = 0;
d->drv = 0;
d->drvdata = 0;
d->complete = 0;
}
if(parse_config(d, n) < 0){
if(n > 0)
d->in_use = 0;
break;
}
if(dev->driver == 0)
cprintf("usb: no driver for this device\n");
cprintf("usb: interface %d: class %d.%d protocol %d, %d endpoint%s%s%s%s\n",
d->iface.bInterfaceNumber,
d->iface.bInterfaceClass, d->iface.bInterfaceSubClass,
d->iface.bInterfaceProtocol, d->iface.bNumEndpoints,
d->iface.bNumEndpoints == 1 ? "" : "s",
d->has_intr_in ? ", interrupt IN" : "",
d->has_bulk_in ? ", bulk IN" : "",
d->has_bulk_out ? ", bulk OUT" : "");
usb_bind_all(d);
if(d->driver == 0){
cprintf("usb: no driver for this interface\n");
if(n > 0)
d->in_use = 0; // nothing will ever use it; give it back
}
}
}
// Tell every driver bound to any interface of `dev` that it is gone, and give
// the interface slots back. The controller frees the endpoints afterwards.
void
usb_detach(struct usb_dev *dev)
{
for(int i = 0; i < NIFDEV; i++){
struct usb_dev *d = &ifdevs[i];
if(!d->in_use || d->base != dev)
continue;
if(d->drv && d->drv->disconnect)
d->drv->disconnect(d);
d->in_use = 0;
}
if(dev->drv && dev->drv->disconnect)
dev->drv->disconnect(dev);
dev->drv = 0;
dev->driver = 0;
}