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:
parent
c1e52e172f
commit
fa7669254e
13 changed files with 1367 additions and 240 deletions
|
|
@ -4,8 +4,9 @@
|
|||
// nothing about driving one is special: every operation is an ordinary
|
||||
// control request sent to the hub as a device. "Reset port 3" is a
|
||||
// SET_FEATURE(PORT_RESET, wIndex=3). The hub has an interrupt endpoint too,
|
||||
// which reports which ports have changed, but that is only needed for
|
||||
// hotplug -- at boot it is enough to walk every port and look.
|
||||
// which reports which ports have changed. At boot it is enough to walk every
|
||||
// port and look; after that, the endpoint is how a device plugged into the
|
||||
// hub, or pulled out of it, is noticed at all. See usb_hub_poll().
|
||||
//
|
||||
// What *is* special is what the host controller has to be told. xHCI does not
|
||||
// discover topology; it is informed of it. A device three hubs deep is
|
||||
|
|
@ -22,6 +23,21 @@
|
|||
#include "usb.h"
|
||||
|
||||
#define MAX_PORTS 15 // per hub; more than any real bus hub has
|
||||
#define NHUB 8
|
||||
|
||||
// Port change bits, the second half of GET_STATUS(port).
|
||||
#define PORTCHG_CONNECTION (1 << 0)
|
||||
#define PORTCHG_OTHERS 0x1E // enable, suspend, over-current, reset
|
||||
|
||||
struct hub {
|
||||
int in_use;
|
||||
struct usb_dev *dev;
|
||||
int nports;
|
||||
void *scratch; // DMA, for GET_STATUS
|
||||
volatile uint32 change; // bit n: port n has changed; 0 is hub
|
||||
};
|
||||
|
||||
static struct hub hubs[NHUB];
|
||||
|
||||
static int
|
||||
hub_request(struct usb_dev *dev, int type, int req, int value, int index,
|
||||
|
|
@ -64,6 +80,19 @@ port_status(struct usb_dev *dev, int port, void *scratch)
|
|||
return st[0];
|
||||
}
|
||||
|
||||
// The other half: what has changed since the bits were last cleared.
|
||||
static int
|
||||
port_change(struct usb_dev *dev, int port, void *scratch, int *status)
|
||||
{
|
||||
uint16 *st = scratch;
|
||||
|
||||
if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_GET_STATUS, 0, port, scratch, 4) < 0)
|
||||
return -1;
|
||||
*status = st[0];
|
||||
return st[1];
|
||||
}
|
||||
|
||||
// Bring up whatever is plugged into one downstream port.
|
||||
static void
|
||||
hub_port(struct usb_dev *hub, int port, void *scratch)
|
||||
|
|
@ -117,6 +146,23 @@ hub_port(struct usb_dev *hub, int port, void *scratch)
|
|||
hub->ops->attach(hub, port, speed);
|
||||
}
|
||||
|
||||
// The status-change endpoint has spoken: a bitmap, one bit per port. From
|
||||
// interrupt context, so only note it.
|
||||
static void
|
||||
hub_complete(struct usb_dev *dev, void *buf, int len)
|
||||
{
|
||||
struct hub *h = dev->drvdata;
|
||||
uchar *b = buf;
|
||||
uint32 bits = 0;
|
||||
int i;
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
for(i = 0; i < len && i < 4; i++)
|
||||
bits |= (uint32)b[i] << (8 * i);
|
||||
__sync_fetch_and_or(&h->change, bits);
|
||||
}
|
||||
|
||||
static int
|
||||
hub_probe(struct usb_dev *dev)
|
||||
{
|
||||
|
|
@ -168,10 +214,93 @@ hub_probe(struct usb_dev *dev)
|
|||
for(i = 1; i <= nports; i++)
|
||||
hub_port(dev, i, scratch);
|
||||
|
||||
dma_free(scratch, 64);
|
||||
// From here the hub is listened to: its status-change endpoint completes
|
||||
// whenever a port changes, and usb_hub_poll() acts on it. A hub without
|
||||
// one is unheard of and could still be driven by polling, but is not.
|
||||
for(i = 0; i < NHUB; i++)
|
||||
if(!hubs[i].in_use)
|
||||
break;
|
||||
if(i == NHUB || !dev->has_intr_in){
|
||||
if(i == NHUB)
|
||||
cprintf("usb-hub: too many hubs; this one will not see hot-plug\n");
|
||||
dma_free(scratch, 64);
|
||||
return 0;
|
||||
}
|
||||
hubs[i].dev = dev;
|
||||
hubs[i].nports = nports;
|
||||
hubs[i].scratch = scratch;
|
||||
hubs[i].change = 0;
|
||||
hubs[i].in_use = 1;
|
||||
dev->drvdata = &hubs[i];
|
||||
dev->complete = hub_complete;
|
||||
if(dev->ops->open_ep(dev, &dev->intr_in) < 0 ||
|
||||
dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
|
||||
cprintf("usb-hub: status-change endpoint would not open; no hot-plug\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The hub is gone. Whatever was behind it went with it -- the controller
|
||||
// tears those down first -- so all that is left is to forget it.
|
||||
static void
|
||||
hub_disconnect(struct usb_dev *dev)
|
||||
{
|
||||
struct hub *h = dev->drvdata;
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
h->in_use = 0;
|
||||
dma_free(h->scratch, 64);
|
||||
dev->drvdata = 0;
|
||||
}
|
||||
|
||||
// Act on every port change the hubs have reported. From the hot-plug thread,
|
||||
// which is the only thing that enumerates once the machine is up.
|
||||
void
|
||||
usb_hub_poll(void)
|
||||
{
|
||||
int i, port;
|
||||
|
||||
for(i = 0; i < NHUB; i++){
|
||||
struct hub *h = &hubs[i];
|
||||
uint32 bits;
|
||||
|
||||
if(!h->in_use || h->change == 0)
|
||||
continue;
|
||||
bits = __sync_fetch_and_and(&h->change, 0);
|
||||
|
||||
for(port = 1; port <= h->nports; port++){
|
||||
int st, chg;
|
||||
|
||||
if(!(bits & (1u << port)))
|
||||
continue;
|
||||
chg = port_change(h->dev, port, h->scratch, &st);
|
||||
if(chg < 0)
|
||||
continue;
|
||||
if(chg & PORTCHG_OTHERS){
|
||||
if(chg & (1 << 1)) port_clear_feature(h->dev, port, PORT_FEAT_C_ENABLE);
|
||||
if(chg & (1 << 2)) port_clear_feature(h->dev, port, 18);
|
||||
if(chg & (1 << 3)) port_clear_feature(h->dev, port, 19);
|
||||
if(chg & (1 << 4)) port_clear_feature(h->dev, port, PORT_FEAT_C_RESET);
|
||||
}
|
||||
if(!(chg & PORTCHG_CONNECTION))
|
||||
continue;
|
||||
|
||||
// Whatever was there before is not there now, even if something is:
|
||||
// a connection change with the port still connected is a swap.
|
||||
port_clear_feature(h->dev, port, PORT_FEAT_C_CONNECTION);
|
||||
h->dev->ops->detach(h->dev, port);
|
||||
if(st & PORTSTAT_CONNECTION){
|
||||
microdelay(100000); // debounce, as on a root port
|
||||
cprintf("usb-hub: slot %d port %d: plugged in\n", h->dev->slot, port);
|
||||
hub_port(h->dev, port, h->scratch);
|
||||
}
|
||||
// The hub may have been unplugged by what was just done; stop if so.
|
||||
if(!h->in_use)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any hub, whatever its protocol: 0 is a full-speed hub, 1 single-TT, 2
|
||||
// multi-TT, and they differ only in how transactions are translated, which
|
||||
// is the controller's problem rather than ours.
|
||||
|
|
@ -182,4 +311,5 @@ const struct usb_driver usb_hub_driver = {
|
|||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_BUS,
|
||||
.probe = hub_probe,
|
||||
.disconnect = hub_disconnect,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue