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
275 lines
8.9 KiB
C
275 lines
8.9 KiB
C
// The USB core: everything between "the controller can talk to a device" and
|
|
// "a driver knows what the device is".
|
|
//
|
|
// The split matters. xhci.c deals in TRBs, slot IDs and doorbells; nothing
|
|
// here knows any of those words. A class driver deals in reports and
|
|
// commands; nothing here knows those either. What lives in the middle is the
|
|
// part of USB that is the same for every device ever made: read the
|
|
// configuration, choose one, look at the interface, find someone who wants it.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "mmu.h"
|
|
#include "spinlock.h"
|
|
#include "dma.h"
|
|
#include "usb.h"
|
|
|
|
// A staging buffer for control transfers.
|
|
//
|
|
// It exists because a descriptor has to be read into memory the controller
|
|
// can reach and that does not straddle a 64 KB boundary -- a rule that holds
|
|
// for xHCI data stages and that a static buffer inside some other structure
|
|
// satisfies only by luck. One page, allocated once, copied out of afterwards.
|
|
static void *scratch;
|
|
|
|
static void *
|
|
get_scratch(void)
|
|
{
|
|
if(scratch == 0)
|
|
scratch = dma_alloc(4096, 4096);
|
|
return scratch;
|
|
}
|
|
|
|
static int
|
|
get_descriptor(struct usb_dev *dev, int type, int index, void *buf, int len)
|
|
{
|
|
struct usb_setup s;
|
|
|
|
// Clear first. The scratch buffer is shared by every device on the bus, and
|
|
// a control transfer reports success without saying how many bytes it
|
|
// actually moved -- so a device that answers short leaves the previous
|
|
// device's descriptor visible past the end of its own, and the parser
|
|
// happily walks into it. Zeroed bytes stop the walk instead, because a
|
|
// descriptor whose length is 0 is rejected.
|
|
memset(buf, 0, len);
|
|
|
|
s.bmRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
|
|
s.bRequest = USB_REQ_GET_DESCRIPTOR;
|
|
s.wValue = (type << 8) | index;
|
|
s.wIndex = 0;
|
|
s.wLength = len;
|
|
return dev->ops->control(dev, &s, buf, len);
|
|
}
|
|
|
|
static int
|
|
set_configuration(struct usb_dev *dev, int value)
|
|
{
|
|
struct usb_setup s;
|
|
|
|
s.bmRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
|
|
s.bRequest = USB_REQ_SET_CONFIG;
|
|
s.wValue = value;
|
|
s.wIndex = 0;
|
|
s.wLength = 0;
|
|
return dev->ops->control(dev, &s, 0, 0);
|
|
}
|
|
|
|
// Walk the configuration descriptor.
|
|
//
|
|
// What comes back from GET_DESCRIPTOR(CONFIG) is not one descriptor: it is
|
|
// the configuration descriptor followed by every interface and endpoint
|
|
// descriptor belonging to it, packed end to end, each one starting with its
|
|
// own length. That length is the only thing that can be relied on to find the
|
|
// next -- descriptor types this kernel has never heard of appear in the
|
|
// 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.
|
|
//
|
|
// `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;
|
|
|
|
while(off + 2 <= dev->conflen){
|
|
uint8 len = dev->conf[off];
|
|
uint8 type = dev->conf[off + 1];
|
|
|
|
if(len < 2 || off + len > dev->conflen)
|
|
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; // 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;
|
|
int in;
|
|
|
|
memmove(&ep, &dev->conf[off], sizeof(ep));
|
|
in = USB_EP_IS_IN(ep.bEndpointAddress);
|
|
|
|
switch(USB_EP_XFERTYPE(ep.bmAttributes)){
|
|
case USB_EP_INTERRUPT:
|
|
if(in && !dev->has_intr_in){ dev->intr_in = ep; dev->has_intr_in = 1; }
|
|
break;
|
|
case USB_EP_BULK:
|
|
if(in && !dev->has_bulk_in){ dev->bulk_in = ep; dev->has_bulk_in = 1; }
|
|
if(!in && !dev->has_bulk_out){ dev->bulk_out = ep; dev->has_bulk_out = 1; }
|
|
break;
|
|
}
|
|
}
|
|
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
|
|
// ---------------------------------------------------------------------------
|
|
|
|
extern const struct usb_driver usb_hid_driver;
|
|
void usb_bind_all(struct usb_dev *dev);
|
|
|
|
static int
|
|
matches(const struct usb_driver *drv, struct usb_dev *dev)
|
|
{
|
|
if(drv->class != 0xFF && drv->class != dev->iface.bInterfaceClass)
|
|
return 0;
|
|
if(drv->subclass != 0xFF && drv->subclass != dev->iface.bInterfaceSubClass)
|
|
return 0;
|
|
if(drv->protocol != 0xFF && drv->protocol != dev->iface.bInterfaceProtocol)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
// Offer a device to one driver. Returns 1 if it was claimed.
|
|
int
|
|
usb_offer(const struct usb_driver *drv, struct usb_dev *dev)
|
|
{
|
|
if(dev->driver || !matches(drv, dev))
|
|
return 0;
|
|
if(drv->probe(dev) != 0)
|
|
return 0;
|
|
dev->driver = drv->name;
|
|
dev->drv = drv;
|
|
return 1;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void
|
|
usb_attach(struct usb_dev *dev)
|
|
{
|
|
struct usb_config_desc *cd;
|
|
void *buf = get_scratch();
|
|
int total;
|
|
|
|
if(buf == 0)
|
|
return;
|
|
|
|
// Nine bytes first, because wTotalLength is in them and there is no other
|
|
// way to know how much to ask for.
|
|
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, 9) < 0){
|
|
cprintf("usb: slot %d would not give up its configuration\n", dev->slot);
|
|
return;
|
|
}
|
|
cd = buf;
|
|
total = cd->wTotalLength;
|
|
|
|
// A configuration that does not contain its own descriptor is not one we
|
|
// can do anything with, and reading it as a length would walk off the end.
|
|
if(cd->bDescriptorType != USB_DT_CONFIG || total < (int)sizeof(*cd)){
|
|
cprintf("usb: slot %d returned a malformed configuration descriptor\n",
|
|
dev->slot);
|
|
return;
|
|
}
|
|
if(total > USB_MAXCONF)
|
|
total = USB_MAXCONF;
|
|
|
|
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, total) < 0)
|
|
return;
|
|
memmove(dev->conf, buf, total);
|
|
dev->conflen = total;
|
|
|
|
// 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){
|
|
cprintf("usb: slot %d refused SET_CONFIGURATION\n", dev->slot);
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|