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

@ -36,6 +36,13 @@ struct blkdev_ops {
int blkdev_register(const char *name, const struct blkdev_ops *ops,
void *priv, uint64 nsectors, uint32 secsize);
// The disk has gone -- a USB stick pulled out. Removes it and every partition
// on it, and drops whatever the buffer cache still holds for them, so that a
// different disk given the same number later is not served the old one's
// blocks. A filesystem still mounted from it fails on its next access.
void blkdev_unregister(int dev);
void binval(uint dev); // kernel/dev/bio.c
int blkdev_present(uint dev);
void blkdev_rw(struct buf *b);
int blkdev_flush(uint dev);

View file

@ -178,6 +178,9 @@ struct usb_hub_desc {
#define USB_HID_SET_IDLE 0x0A
#define USB_HID_SET_PROTOCOL 0x0B
#define USB_HID_PROTOCOL_BOOT 0
#define USB_HID_PROTOCOL_REPORT 1
#define USB_DT_HID 0x21
#define USB_DT_REPORT 0x22
// ---------------------------------------------------------------------------
// The interface between the USB core and a host controller
@ -208,9 +211,12 @@ struct usb_hc_ops {
// know: a hub is scheduled differently from anything else, and devices
// behind it are addressed relative to it.
int (*set_hub)(struct usb_dev *, int nports, int mtt);
// Whatever is on a hub's downstream port has gone: tear it down, and
// everything behind it if it was a hub too.
void (*detach)(struct usb_dev *hub, int hub_port);
};
#define USB_MAXCONF 256 // bytes of configuration descriptor we keep
#define USB_MAXCONF 512 // bytes of configuration descriptor we keep
struct usb_dev {
const struct usb_hc_ops *ops;
@ -236,6 +242,12 @@ struct usb_dev {
int tt_slot, tt_port;
int in_use;
// A device with several interfaces is one usb_dev per interface, so that
// each can have its own driver -- the keyboard and the mouse on one
// wireless receiver are interfaces 0 and 1. `base` is the device's own
// usb_dev (the one the controller addressed) for every interface after the
// first, and 0 for the first, which is the device itself.
struct usb_dev *base;
struct usb_device_desc desc;
uchar conf[USB_MAXCONF]; // the configuration descriptor and everything
@ -251,6 +263,7 @@ struct usb_dev {
// Filled in by whichever class driver claimed the device.
const char *driver;
const struct usb_driver *drv; // the same, for calling disconnect
void *drvdata;
void (*complete)(struct usb_dev *, void *buf, int len);
};
@ -266,10 +279,24 @@ struct usb_driver {
// of the device, the descriptor only says what the device claims to be.
enum dev_class devclass;
int (*probe)(struct usb_dev *);
// The device has been unplugged. Stop using it; the controller frees its
// endpoints after this returns. May be 0 for a driver with nothing to undo.
void (*disconnect)(struct usb_dev *);
};
// Called by a host controller once a device is addressed and its device
// descriptor has been read. The core reads the configuration, selects it, and
// offers the device to the class drivers.
void usb_attach(struct usb_dev *dev);
// The device, and every interface of it, is gone. Calls each bound driver's
// disconnect. From the controller, before it frees the slot.
void usb_detach(struct usb_dev *dev);
// Re-check every hub's downstream ports, for devices plugged into or pulled
// out of one. From the hot-plug thread; see kernel/drivers/usb/hub/.
void usb_hub_poll(void);
// The hot-plug thread. After sinit(); see kernel/drivers/bus/xhci/xhci.c.
void usb_hotplug_start(void);
// Key repeat for USB keyboards, which do not repeat by themselves. Called
// every tick from the hot-plug thread; see kernel/drivers/usb/hid/.
void usb_hid_tick(void);