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
|
|
@ -83,6 +83,11 @@ struct xhci_ep {
|
|||
volatile int done;
|
||||
int cc;
|
||||
int residue;
|
||||
|
||||
// Whose endpoint this is: the usb_dev of the interface that opened it,
|
||||
// which for a composite device is not the slot's own. Completions are
|
||||
// delivered to its `complete`.
|
||||
struct usb_dev *owner;
|
||||
};
|
||||
|
||||
struct xhci {
|
||||
|
|
@ -111,6 +116,20 @@ struct xhci {
|
|||
// has to poll instead. See xhci_bulk().
|
||||
int can_sleep;
|
||||
|
||||
// Set with can_sleep, once interrupts are on. From then the interrupt
|
||||
// handler is the event ring's only reader, and a command or a control
|
||||
// transfer issued later -- by the hot-plug thread -- cannot pop its own
|
||||
// completion. The handler files it here instead; see wait_flag().
|
||||
int running;
|
||||
volatile int cmd_done;
|
||||
struct trb cmd_ev;
|
||||
volatile int port_event; // a Port Status Change arrived
|
||||
|
||||
// What is on each root port, and ports whose device would not enumerate
|
||||
// (left alone until it is unplugged, rather than retried every scan).
|
||||
uchar root_slot[256];
|
||||
uchar port_failed[256];
|
||||
|
||||
// Per-slot state. Slot 0 is not a device slot; index by slot ID directly.
|
||||
struct {
|
||||
void *devctx;
|
||||
|
|
@ -122,12 +141,19 @@ struct xhci {
|
|||
int port;
|
||||
int speed;
|
||||
struct usb_dev dev;
|
||||
int used;
|
||||
int parent; // the hub's slot, 0 on a root port
|
||||
int pport; // the port on that hub, or root port
|
||||
volatile int ep0_done; // see `running`
|
||||
struct trb ep0_ev;
|
||||
} slot[MAX_SLOTS + 1];
|
||||
|
||||
int ndevices;
|
||||
};
|
||||
|
||||
static void xhci_attach(struct usb_dev *hub, int hub_port, int speed);
|
||||
static void xhci_detach(struct usb_dev *hub, int hub_port);
|
||||
static void free_slot(struct xhci *x, int slot);
|
||||
|
||||
static struct xhci xhcis[2];
|
||||
static int nxhci;
|
||||
|
|
@ -270,13 +296,28 @@ event_wait(struct xhci *x, int type, struct trb *out, int ms)
|
|||
int i;
|
||||
|
||||
for(i = 0; i < ms * 1000; i++){
|
||||
while(event_pop(x, out)){
|
||||
for(;;){
|
||||
// Under the lock: with no interrupt, a bulk transfer spinning in some
|
||||
// process drains this ring too.
|
||||
acquire(&x->lock);
|
||||
int got = event_pop(x, out);
|
||||
release(&x->lock);
|
||||
if(!got)
|
||||
break;
|
||||
int t = TRB_GET_TYPE(out->control);
|
||||
if(t == type)
|
||||
return 0;
|
||||
// Port status changes arrive unbidden throughout enumeration. They are
|
||||
// not errors and must not be mistaken for the event being waited on.
|
||||
if(t != TRB_PORT_STATUS)
|
||||
//
|
||||
// A transfer on some other endpoint is not either: with no interrupt
|
||||
// the hot-plug thread is the only reader of this ring, and a keyboard
|
||||
// report it threw away here would be a lost keypress.
|
||||
if(t == TRB_XFER_EVENT && TRB_GET_EPID(out->control) != DCI_EP0){
|
||||
acquire(&x->lock);
|
||||
transfer_done(x, out);
|
||||
release(&x->lock);
|
||||
} else if(t != TRB_PORT_STATUS)
|
||||
cprintf("xhci: unexpected event type %d while waiting for %d\n",
|
||||
t, type);
|
||||
}
|
||||
|
|
@ -285,6 +326,24 @@ event_wait(struct xhci *x, int type, struct trb *out, int ms)
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Wait for the interrupt handler to file a completion. Caller holds x->lock.
|
||||
//
|
||||
// Sleeps a tick at a time rather than on a channel of its own, which is what
|
||||
// gives it a timeout -- a controller that has stopped answering costs this
|
||||
// thread a second, not the rest of its life. The handler wakes the tick
|
||||
// sleepers when it files something, so the usual wait is microseconds.
|
||||
static int
|
||||
wait_flag(struct xhci *x, volatile int *flag, int ms)
|
||||
{
|
||||
uint start = ticks;
|
||||
while(!*flag){
|
||||
if(ticks - start > (uint)(ms / MSEC_PER_TICK) + 2)
|
||||
return -1;
|
||||
sleep(&ticks, &x->lock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Submit a command and wait for its completion event. Returns the completion
|
||||
// code, or -1 on timeout.
|
||||
static int
|
||||
|
|
@ -293,6 +352,24 @@ command(struct xhci *x, uint64 param, uint32 status, uint32 control,
|
|||
{
|
||||
struct trb ev;
|
||||
|
||||
if(x->running){
|
||||
acquire(&x->lock);
|
||||
x->cmd_done = 0;
|
||||
ring_push(&x->cmd, param, status, control);
|
||||
x->db[0] = 0;
|
||||
__sync_synchronize();
|
||||
int rc = wait_flag(x, &x->cmd_done, 1000);
|
||||
ev = x->cmd_ev;
|
||||
release(&x->lock);
|
||||
if(rc < 0){
|
||||
cprintf("xhci: command %d timed out\n", TRB_GET_TYPE(control));
|
||||
return -1;
|
||||
}
|
||||
if(result)
|
||||
*result = ev;
|
||||
return TRB_CC(ev.status);
|
||||
}
|
||||
|
||||
ring_push(&x->cmd, param, status, control);
|
||||
x->db[0] = 0; // doorbell 0 = command ring
|
||||
__sync_synchronize();
|
||||
|
|
@ -390,6 +467,23 @@ control_xfer(struct xhci *x, int slot, struct usb_setup *s, void *buf, int len)
|
|||
TRB_TYPE(TRB_STATUS) | TRB_IOC |
|
||||
((len == 0 || !in) ? DIR_IN : 0));
|
||||
|
||||
if(x->running){
|
||||
// Pushed above without the lock: EP0 of one slot has one user at a time,
|
||||
// the hot-plug thread. The doorbell and the wait are under it, because the
|
||||
// handler writes ep0_done.
|
||||
acquire(&x->lock);
|
||||
x->slot[slot].ep0_done = 0;
|
||||
x->db[slot] = DCI_EP0;
|
||||
__sync_synchronize();
|
||||
int rc = wait_flag(x, &x->slot[slot].ep0_done, 1000);
|
||||
ev = x->slot[slot].ep0_ev;
|
||||
release(&x->lock);
|
||||
if(rc < 0){
|
||||
cprintf("xhci: control transfer timed out (slot %d, request 0x%x)\n",
|
||||
slot, s->bRequest);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
x->db[slot] = DCI_EP0;
|
||||
__sync_synchronize();
|
||||
|
||||
|
|
@ -398,6 +492,7 @@ control_xfer(struct xhci *x, int slot, struct usb_setup *s, void *buf, int len)
|
|||
slot, s->bRequest);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// A short packet is not a failure: it is the device saying "that is all I
|
||||
// have", which is the normal reply to asking for more than it holds.
|
||||
|
|
@ -615,6 +710,7 @@ xhci_open_ep(struct usb_dev *dev, const struct usb_endpoint_desc *epd)
|
|||
ep->addr = epd->bEndpointAddress;
|
||||
ep->type = xfer;
|
||||
ep->len = mps;
|
||||
ep->owner = dev;
|
||||
|
||||
// An interrupt endpoint owns a standing buffer, because it is re-armed
|
||||
// immediately after every report and there is nobody to supply one. A bulk
|
||||
|
|
@ -651,8 +747,15 @@ xhci_open_ep(struct usb_dev *dev, const struct usb_endpoint_desc *epd)
|
|||
if(dci > x->slot[slot].max_dci)
|
||||
x->slot[slot].max_dci = dci;
|
||||
|
||||
sc[0] = ((uint32)dev->speed << 20) | ((uint32)x->slot[slot].max_dci << 27);
|
||||
sc[1] = (uint32)dev->port << 16;
|
||||
// Everything else in the slot context is copied from what the controller
|
||||
// holds now. Building it afresh, as this used to, wrote a route string of
|
||||
// zero, no transaction translator and no hub bit -- harmless on a root port
|
||||
// and wrong for anything behind a hub.
|
||||
{
|
||||
uint32 *cur = ctx_at(x, x->slot[slot].devctx, 0);
|
||||
sc[0] = cur[0]; sc[1] = cur[1]; sc[2] = cur[2]; sc[3] = 0;
|
||||
}
|
||||
sc[0] = (sc[0] & ~(0x1Fu << 27)) | ((uint32)x->slot[slot].max_dci << 27);
|
||||
|
||||
if(xfer == USB_EP_INTERRUPT)
|
||||
type = EPTYPE_INT_IN;
|
||||
|
|
@ -816,6 +919,7 @@ static const struct usb_hc_ops xhci_ops = {
|
|||
.bulk = xhci_bulk,
|
||||
.attach = xhci_attach,
|
||||
.set_hub = xhci_set_hub,
|
||||
.detach = xhci_detach,
|
||||
};
|
||||
|
||||
static const char *
|
||||
|
|
@ -838,8 +942,14 @@ class_name(int cls)
|
|||
// translator serves it. Everything past that is the same whether it is
|
||||
// plugged into the machine or four hubs away, which is the point of splitting
|
||||
// it out of the root-port scan.
|
||||
static void
|
||||
attach_device(struct xhci *x, const struct usb_dev *proto)
|
||||
//
|
||||
// `parent` and `pport` say what it is plugged into -- a hub's slot and port,
|
||||
// or 0 and the root port -- which is what finds it again when it goes.
|
||||
// Returns the slot, or -1. A device that fails part way is torn down again,
|
||||
// slot and all, rather than left holding one of the sixteen.
|
||||
static int
|
||||
attach_device(struct xhci *x, const struct usb_dev *proto, int parent,
|
||||
int pport)
|
||||
{
|
||||
struct trb ev;
|
||||
struct usb_device_desc *desc;
|
||||
|
|
@ -851,24 +961,28 @@ attach_device(struct xhci *x, const struct usb_dev *proto)
|
|||
if(cc != CC_SUCCESS){
|
||||
cprintf("xhci: Enable Slot failed on port %d, completion code %d\n",
|
||||
proto->port, cc);
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
slot = TRB_GET_SLOT(ev.control);
|
||||
if(slot <= 0 || slot > MAX_SLOTS){
|
||||
cprintf("xhci: controller returned slot %d, out of range\n", slot);
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
x->slot[slot].used = 1;
|
||||
x->slot[slot].parent = parent;
|
||||
x->slot[slot].pport = pport;
|
||||
x->ndevices++;
|
||||
|
||||
// The device context is the controller's; we allocate it and hand over the
|
||||
// address, and from then on it writes there and we only read.
|
||||
x->slot[slot].devctx = dma_alloc(x->ctxsize * 32, 64);
|
||||
if(x->slot[slot].devctx == 0)
|
||||
return;
|
||||
goto fail;
|
||||
x->slot[slot].devctx_phys = dma_phys(x->slot[slot].devctx);
|
||||
x->dcbaa[slot] = x->slot[slot].devctx_phys;
|
||||
|
||||
if(ring_init(&x->slot[slot].ep0) < 0)
|
||||
return;
|
||||
goto fail;
|
||||
|
||||
x->slot[slot].port = proto->port;
|
||||
x->slot[slot].speed = speed;
|
||||
|
|
@ -883,18 +997,18 @@ attach_device(struct xhci *x, const struct usb_dev *proto)
|
|||
dev->in_use = 1;
|
||||
|
||||
if(address_device(x, slot, dev) < 0)
|
||||
return;
|
||||
goto fail;
|
||||
|
||||
desc = dma_alloc(sizeof(*desc), 64);
|
||||
if(desc == 0)
|
||||
return;
|
||||
goto fail;
|
||||
|
||||
// Ask for eight bytes first. That is all a full-speed device is guaranteed
|
||||
// to manage in one packet, and byte 7 is the max packet size that tells us
|
||||
// how much it can manage from here on.
|
||||
if(get_descriptor(x, slot, USB_DT_DEVICE, 0, desc, 8) < 0){
|
||||
dma_free(desc, sizeof(*desc));
|
||||
return;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(desc->bMaxPacketSize0 != default_mps0(speed) &&
|
||||
|
|
@ -903,7 +1017,7 @@ attach_device(struct xhci *x, const struct usb_dev *proto)
|
|||
|
||||
if(get_descriptor(x, slot, USB_DT_DEVICE, 0, desc, sizeof(*desc)) < 0){
|
||||
dma_free(desc, sizeof(*desc));
|
||||
return;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// cprintf understands %d %u %l %x %p %b %s and nothing else -- no field
|
||||
|
|
@ -917,13 +1031,80 @@ attach_device(struct xhci *x, const struct usb_dev *proto)
|
|||
desc->bNumConfigurations,
|
||||
desc->bNumConfigurations == 1 ? "" : "s");
|
||||
|
||||
x->ndevices++;
|
||||
dev->desc = *desc;
|
||||
dma_free(desc, sizeof(*desc));
|
||||
|
||||
// Hand it to the core, which knows about configurations and class drivers
|
||||
// and nothing about TRBs.
|
||||
usb_attach(dev);
|
||||
return slot;
|
||||
|
||||
fail:
|
||||
free_slot(x, slot);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Everything about one slot, gone: the devices behind it if it is a hub, its
|
||||
// drivers, the slot itself and the memory the controller was using for it.
|
||||
//
|
||||
// The order matters. Endpoints are closed first, and a bulk transfer asleep
|
||||
// on one is woken with a failure -- an unplugged device will never complete
|
||||
// it, and the storage driver's disconnect waits for that transfer to let go
|
||||
// of its lock. Only then are the drivers told, and only after Disable Slot
|
||||
// has told the controller to stop is any of its memory handed back.
|
||||
static void
|
||||
free_slot(struct xhci *x, int slot)
|
||||
{
|
||||
struct usb_dev *dev = &x->slot[slot].dev;
|
||||
int i;
|
||||
|
||||
if(!x->slot[slot].used)
|
||||
return;
|
||||
|
||||
for(i = 1; i <= MAX_SLOTS; i++)
|
||||
if(i != slot && x->slot[i].used && x->slot[i].parent == slot)
|
||||
free_slot(x, i);
|
||||
|
||||
acquire(&x->lock);
|
||||
for(i = 0; i < x->slot[slot].nep; i++){
|
||||
struct xhci_ep *ep = &x->slot[slot].ep[i];
|
||||
ep->open = 0;
|
||||
if(ep->type == USB_EP_BULK){
|
||||
ep->cc = 0; // not CC_SUCCESS: the caller fails
|
||||
ep->done = 1;
|
||||
if(x->can_sleep)
|
||||
wakeup(ep);
|
||||
}
|
||||
}
|
||||
release(&x->lock);
|
||||
|
||||
if(dev->in_use)
|
||||
usb_detach(dev);
|
||||
|
||||
// Every slot here came from Enable Slot, so there is always one to disable.
|
||||
command(x, 0, 0, TRB_TYPE(TRB_DISABLE_SLOT) | TRB_SLOT(slot), 0);
|
||||
x->dcbaa[slot] = 0;
|
||||
|
||||
for(i = 0; i < x->slot[slot].nep; i++){
|
||||
struct xhci_ep *ep = &x->slot[slot].ep[i];
|
||||
if(ep->ring.trb)
|
||||
dma_free((void*)ep->ring.trb, RING_BYTES);
|
||||
if(ep->type == USB_EP_INTERRUPT && ep->buf)
|
||||
dma_free(ep->buf, ep->len);
|
||||
}
|
||||
if(x->slot[slot].ep0.trb)
|
||||
dma_free((void*)x->slot[slot].ep0.trb, RING_BYTES);
|
||||
if(x->slot[slot].devctx)
|
||||
dma_free(x->slot[slot].devctx, x->ctxsize * 32);
|
||||
|
||||
for(i = 1; i <= x->maxports && i < 256; i++)
|
||||
if(x->root_slot[i] == slot)
|
||||
x->root_slot[i] = 0;
|
||||
|
||||
acquire(&x->lock);
|
||||
memset(&x->slot[slot], 0, sizeof(x->slot[slot]));
|
||||
release(&x->lock);
|
||||
x->ndevices--;
|
||||
}
|
||||
|
||||
// A device found on a downstream port of a hub.
|
||||
|
|
@ -960,18 +1141,36 @@ xhci_attach(struct usb_dev *hub, int hub_port, int speed)
|
|||
}
|
||||
}
|
||||
|
||||
attach_device(x, &proto);
|
||||
attach_device(x, &proto, hub->slot, hub_port);
|
||||
}
|
||||
|
||||
// Whatever was on a hub's downstream port has gone.
|
||||
static void
|
||||
xhci_detach(struct usb_dev *hub, int hub_port)
|
||||
{
|
||||
struct xhci *x = hub->hc;
|
||||
int i;
|
||||
|
||||
for(i = 1; i <= MAX_SLOTS; i++)
|
||||
if(x->slot[i].used && x->slot[i].parent == hub->slot &&
|
||||
x->slot[i].pport == hub_port){
|
||||
cprintf("usb: slot %d unplugged from hub slot %d port %d\n",
|
||||
i, hub->slot, hub_port);
|
||||
free_slot(x, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the slot of what is now on the port, 0 if nothing is, or -1 if
|
||||
// something is and it would not enumerate.
|
||||
static int
|
||||
enumerate_port(struct xhci *x, int port)
|
||||
{
|
||||
uint32 sc = rd32(x->op, XHCI_PORTSC(port));
|
||||
struct usb_dev proto;
|
||||
int i;
|
||||
int i, slot;
|
||||
|
||||
if(!(sc & PORT_CCS))
|
||||
return;
|
||||
return 0;
|
||||
|
||||
// USB2 ports need an explicit reset before they will enable. USB3 ports
|
||||
// train themselves and come up already enabled, so resetting one is at best
|
||||
|
|
@ -993,7 +1192,7 @@ enumerate_port(struct xhci *x, int port)
|
|||
|
||||
if(!(sc & PORT_PED)){
|
||||
cprintf("xhci: port %d did not enable after reset\n", port);
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// A root port device is tier 0: no route string, no transaction translator.
|
||||
|
|
@ -1002,7 +1201,10 @@ enumerate_port(struct xhci *x, int port)
|
|||
proto.speed = PORT_SPEED(sc);
|
||||
proto.tier = 0;
|
||||
|
||||
attach_device(x, &proto);
|
||||
slot = attach_device(x, &proto, 0, port);
|
||||
if(slot > 0 && port < 256)
|
||||
x->root_slot[port] = slot;
|
||||
return slot;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -1079,9 +1281,9 @@ transfer_done(struct xhci *x, struct trb *ev)
|
|||
if(slot <= 0 || slot > MAX_SLOTS)
|
||||
return;
|
||||
ep = find_ep(x, slot, dci);
|
||||
if(ep == 0)
|
||||
if(ep == 0 || !ep->open)
|
||||
return;
|
||||
dev = &x->slot[slot].dev;
|
||||
dev = ep->owner ? ep->owner : &x->slot[slot].dev;
|
||||
|
||||
cc = TRB_CC(ev->status);
|
||||
|
||||
|
|
@ -1111,6 +1313,42 @@ transfer_done(struct xhci *x, struct trb *ev)
|
|||
queue_in_locked(dev, ep->addr, 0, 0); // re-arm, even after an error
|
||||
}
|
||||
|
||||
// One event, from whichever reader found it. Caller holds x->lock.
|
||||
//
|
||||
// Completions of commands and of control transfers are filed for whoever is
|
||||
// waiting in command() or control_xfer(), and the tick sleepers woken so
|
||||
// that they look; a port status change is noted for the hot-plug thread.
|
||||
static void
|
||||
dispatch_event(struct xhci *x, struct trb *ev)
|
||||
{
|
||||
int slot;
|
||||
|
||||
switch(TRB_GET_TYPE(ev->control)){
|
||||
case TRB_XFER_EVENT:
|
||||
if(TRB_GET_EPID(ev->control) != DCI_EP0){
|
||||
transfer_done(x, ev);
|
||||
break;
|
||||
}
|
||||
slot = TRB_GET_SLOT(ev->control);
|
||||
if(slot > 0 && slot <= MAX_SLOTS){
|
||||
x->slot[slot].ep0_ev = *ev;
|
||||
x->slot[slot].ep0_done = 1;
|
||||
wakeup(&ticks);
|
||||
}
|
||||
break;
|
||||
case TRB_CMD_COMPLETE:
|
||||
x->cmd_ev = *ev;
|
||||
x->cmd_done = 1;
|
||||
wakeup(&ticks);
|
||||
break;
|
||||
case TRB_PORT_STATUS:
|
||||
x->port_event = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
xhci_irq(void *arg)
|
||||
{
|
||||
|
|
@ -1130,19 +1368,8 @@ xhci_irq(void *arg)
|
|||
// dump, which drains it from process context, could take an event out from
|
||||
// under this handler mid-update and leave the dequeue index wrong for good.
|
||||
acquire(&x->lock);
|
||||
while(event_pop(x, &ev)){
|
||||
switch(TRB_GET_TYPE(ev.control)){
|
||||
case TRB_XFER_EVENT:
|
||||
transfer_done(x, &ev);
|
||||
break;
|
||||
case TRB_PORT_STATUS:
|
||||
// Hotplug. Acknowledging is enough for now; enumerating from interrupt
|
||||
// context would need the control transfers to become asynchronous.
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
while(event_pop(x, &ev))
|
||||
dispatch_event(x, &ev);
|
||||
release(&x->lock);
|
||||
}
|
||||
|
||||
|
|
@ -1196,7 +1423,9 @@ xhci_dump(void)
|
|||
TRB_GET_TYPE(ev.control), TRB_CC(ev.status),
|
||||
TRB_GET_SLOT(ev.control), TRB_GET_EPID(ev.control),
|
||||
ev.status & 0xFFFFFF);
|
||||
if(TRB_GET_TYPE(ev.control) == TRB_XFER_EVENT)
|
||||
if(x->running)
|
||||
dispatch_event(x, &ev);
|
||||
else if(TRB_GET_TYPE(ev.control) == TRB_XFER_EVENT)
|
||||
transfer_done(x, &ev);
|
||||
if(++n > 16)
|
||||
break;
|
||||
|
|
@ -1207,6 +1436,67 @@ xhci_dump(void)
|
|||
}
|
||||
}
|
||||
|
||||
// Take the controller from the firmware: set OS Owned in USB Legacy Support
|
||||
// and wait for it to clear BIOS Owned, then turn off every SMI it had
|
||||
// enabled. A firmware that has not let go within a second is overruled; one
|
||||
// that never answers is broken, and waiting longer will not mend it.
|
||||
static void
|
||||
bios_handoff(struct xhci *x)
|
||||
{
|
||||
int off = HCC1_XECP(rd32(x->cap, XHCI_HCCPARAMS1)) << 2;
|
||||
int i, n;
|
||||
|
||||
for(n = 0; off != 0 && n < 64; n++){
|
||||
uint32 v = rd32(x->cap, off);
|
||||
if(XECP_ID(v) == XECP_LEGACY){
|
||||
if(v & LEGSUP_BIOS_OWNED){
|
||||
wr32(x->cap, off, v | LEGSUP_OS_OWNED);
|
||||
for(i = 0; i < 1000 && (rd32(x->cap, off) & LEGSUP_BIOS_OWNED); i++)
|
||||
microdelay(1000);
|
||||
if(rd32(x->cap, off) & LEGSUP_BIOS_OWNED){
|
||||
cprintf("xhci: firmware would not hand over; taking it anyway\n");
|
||||
wr32(x->cap, off,
|
||||
(rd32(x->cap, off) & ~LEGSUP_BIOS_OWNED) | LEGSUP_OS_OWNED);
|
||||
} else
|
||||
kinfo("xhci: firmware handed the controller over\n");
|
||||
} else
|
||||
wr32(x->cap, off, v | LEGSUP_OS_OWNED);
|
||||
|
||||
v = rd32(x->cap, off + 4);
|
||||
wr32(x->cap, off + 4, (v & LEGCTL_KEEP) | LEGCTL_EVENTS);
|
||||
return;
|
||||
}
|
||||
off = XECP_NEXT(v) ? off + (XECP_NEXT(v) << 2) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Intel's 7, 8 and 9 series chipsets wire every USB port to the EHCI
|
||||
// controllers at power-on and to the xHCI only when told. A firmware that
|
||||
// leaves them there -- many do, for an OS that has no xHCI driver -- leaves
|
||||
// this controller with no ports and the keyboard on a controller nobody here
|
||||
// drives. The masks say which ports may be switched; switch all of them,
|
||||
// SuperSpeed first, the way Linux does.
|
||||
static void
|
||||
intel_route_ports(struct pci_dev *d)
|
||||
{
|
||||
static const uint16 ids[] = { 0x1E31, 0x8C31, 0x9C31, 0x9CB1, 0x8CB1, 0 };
|
||||
uint32 mask;
|
||||
int i;
|
||||
|
||||
if(d->vendor != 0x8086)
|
||||
return;
|
||||
for(i = 0; ids[i] && ids[i] != d->device; i++)
|
||||
;
|
||||
if(ids[i] == 0)
|
||||
return;
|
||||
|
||||
mask = pci_read32(d->bus, d->slot, d->func, 0xDC); // USB3PRM
|
||||
pci_write32(d->bus, d->slot, d->func, 0xD8, mask); // USB3_PSSEN
|
||||
mask = pci_read32(d->bus, d->slot, d->func, 0xD4); // USB2PRM
|
||||
pci_write32(d->bus, d->slot, d->func, 0xD0, mask); // XUSB2PR
|
||||
kinfo("xhci: ports switched over from EHCI (mask %x)\n", mask);
|
||||
}
|
||||
|
||||
static int
|
||||
xhci_probe(struct pci_dev *d)
|
||||
{
|
||||
|
|
@ -1260,6 +1550,12 @@ xhci_probe(struct pci_dev *d)
|
|||
x->maxports, x->maxslots, x->ctxsize);
|
||||
}
|
||||
|
||||
// The firmware has been driving this controller to give the boot menu a
|
||||
// keyboard. Until it is asked to stop, it goes on doing so from SMM --
|
||||
// behind our back, on the same registers -- so ask before the reset.
|
||||
bios_handoff(x);
|
||||
intel_route_ports(d);
|
||||
|
||||
if(reset_controller(x) < 0)
|
||||
return -1;
|
||||
|
||||
|
|
@ -1323,7 +1619,17 @@ xhci_probe(struct pci_dev *d)
|
|||
}
|
||||
|
||||
for(port = 1; port <= x->maxports; port++)
|
||||
enumerate_port(x, port);
|
||||
if(enumerate_port(x, port) < 0 && port < 256)
|
||||
x->port_failed[port] = 1;
|
||||
|
||||
// Acknowledge every change bit left from enumeration, so that the hot-plug
|
||||
// thread's first look does not take a device that was always there for one
|
||||
// that has just been plugged in again.
|
||||
for(port = 1; port <= x->maxports; port++){
|
||||
uint32 sc = rd32(x->op, XHCI_PORTSC(port));
|
||||
if(sc & PORT_CHANGES)
|
||||
wr32(x->op, XHCI_PORTSC(port), (sc & ~PORT_RW1C) | (sc & PORT_CHANGES));
|
||||
}
|
||||
|
||||
if(x->ndevices == 0)
|
||||
cprintf("xhci: no devices attached\n");
|
||||
|
|
@ -1348,6 +1654,7 @@ xhci_probe(struct pci_dev *d)
|
|||
cprintf("xhci: no interrupt; transfers will be polled\n");
|
||||
} else {
|
||||
x->can_sleep = 1;
|
||||
x->running = 1;
|
||||
|
||||
wr32(x->op, XHCI_USBSTS, STS_EINT);
|
||||
wr32(x->rt, XHCI_IR0 + IR_IMAN, IMAN_IP | IMAN_IE);
|
||||
|
|
@ -1358,6 +1665,124 @@ xhci_probe(struct pci_dev *d)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hot-plug
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
sleep_ticks(uint n)
|
||||
{
|
||||
acquire(&tickslock);
|
||||
uint start = ticks;
|
||||
while(ticks - start < n)
|
||||
sleep(&ticks, &tickslock);
|
||||
release(&tickslock);
|
||||
}
|
||||
|
||||
// Look at every root port and bring what is on it into line with what is
|
||||
// there: tear down what has gone, enumerate what has arrived.
|
||||
//
|
||||
// A change bit with the device still present is a device pulled out and put
|
||||
// back between two looks; the slot it had is for the old one, so it goes too.
|
||||
static void
|
||||
scan_ports(struct xhci *x)
|
||||
{
|
||||
int port;
|
||||
|
||||
for(port = 1; port <= x->maxports && port < 256; port++){
|
||||
uint32 sc = rd32(x->op, XHCI_PORTSC(port));
|
||||
uint32 chg = sc & PORT_CHANGES;
|
||||
int slot = x->root_slot[port];
|
||||
|
||||
// PED is write-one-to-clear as well, and writing it back would disable
|
||||
// the port -- so the change bits are acknowledged on their own.
|
||||
if(chg)
|
||||
wr32(x->op, XHCI_PORTSC(port), (sc & ~PORT_RW1C) | chg);
|
||||
|
||||
if(!(sc & PORT_CCS)){
|
||||
x->port_failed[port] = 0;
|
||||
if(slot){
|
||||
cprintf("usb: port %d: unplugged\n", port);
|
||||
free_slot(x, slot);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(slot && (chg & PORT_CSC)){
|
||||
cprintf("usb: port %d: replugged\n", port);
|
||||
free_slot(x, slot);
|
||||
slot = 0;
|
||||
}
|
||||
if(slot || x->port_failed[port])
|
||||
continue;
|
||||
|
||||
// USB 2.0 7.1.7.3: give the contacts 100 ms to stop bouncing before
|
||||
// believing in the connection.
|
||||
sleep_ticks(100 / MSEC_PER_TICK);
|
||||
if(!(rd32(x->op, XHCI_PORTSC(port)) & PORT_CCS))
|
||||
continue;
|
||||
cprintf("usb: port %d: plugged in\n", port);
|
||||
if(enumerate_port(x, port) < 0)
|
||||
x->port_failed[port] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The hot-plug thread. It owns every enumeration after boot: devices arriving
|
||||
// on root ports and on hubs, devices leaving, and the HID key repeat, which
|
||||
// needs a clock.
|
||||
//
|
||||
// It looks at the root ports when the controller has reported a port change
|
||||
// and every quarter second regardless. The quarter-second look costs a few
|
||||
// register reads, and it is what makes a lost or never-delivered interrupt
|
||||
// cost a delay rather than a device.
|
||||
static void
|
||||
usb_hotplug_thread(void)
|
||||
{
|
||||
uint last = 0;
|
||||
int i;
|
||||
|
||||
for(;;){
|
||||
sleep_ticks(1);
|
||||
uint now = ticks;
|
||||
int due = now - last >= HZ / 4;
|
||||
|
||||
usb_hid_tick();
|
||||
|
||||
for(i = 0; i < nxhci; i++){
|
||||
struct xhci *x = &xhcis[i];
|
||||
|
||||
// With no interrupt at all, nothing else reads the event ring.
|
||||
if(!x->running){
|
||||
struct trb ev;
|
||||
for(;;){
|
||||
acquire(&x->lock);
|
||||
int got = event_pop(x, &ev);
|
||||
if(got && TRB_GET_TYPE(ev.control) == TRB_XFER_EVENT)
|
||||
transfer_done(x, &ev);
|
||||
release(&x->lock);
|
||||
if(!got)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(x->port_event || due){
|
||||
x->port_event = 0;
|
||||
scan_ports(x);
|
||||
}
|
||||
}
|
||||
usb_hub_poll();
|
||||
if(due)
|
||||
last = now;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
usb_hotplug_start(void)
|
||||
{
|
||||
if(nxhci == 0)
|
||||
return;
|
||||
if(kthread_create(usb_hotplug_thread, "usbhotplug") < 0)
|
||||
cprintf("usb: no hot-plug thread; only boot-time devices will work\n");
|
||||
}
|
||||
|
||||
// Class 0x0C subclass 0x03 prog-if 0x30 is xHCI. Matching on class rather
|
||||
// than on vendor/device is the whole point of a standardised controller: one
|
||||
// entry covers every xHCI ever made.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,18 @@
|
|||
#define HCC1_CSZ (1 << 2) // contexts are 64 bytes, not 32
|
||||
#define HCC1_XECP(x) (((x) >> 16) & 0xFFFF) // extended caps, in dwords
|
||||
|
||||
// Extended capabilities: a chain of dwords, each with an ID in bits 7:0 and
|
||||
// the distance to the next, in dwords, in bits 15:8.
|
||||
#define XECP_ID(v) ((v) & 0xFF)
|
||||
#define XECP_NEXT(v) (((v) >> 8) & 0xFF)
|
||||
#define XECP_LEGACY 1 // USB Legacy Support
|
||||
#define LEGSUP_BIOS_OWNED (1u << 16)
|
||||
#define LEGSUP_OS_OWNED (1u << 24)
|
||||
// USBLEGCTLSTS, the dword after it: the SMI enables to clear and the
|
||||
// write-one-to-clear SMI events, as xHCI 1.2 section 7.1.2 lays them out.
|
||||
#define LEGCTL_KEEP ((0x7u << 1) | (0xFFu << 5) | (0x7u << 17))
|
||||
#define LEGCTL_EVENTS (0x7u << 29)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Operational registers -- at BAR0 + CAPLENGTH
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -71,6 +83,7 @@
|
|||
#define PORT_CSC (1 << 17)
|
||||
#define PORT_PEC (1 << 18)
|
||||
#define PORT_PRC (1 << 21)
|
||||
#define PORT_CHANGES 0x00FE0000u // every change bit, 17..23
|
||||
#define PORT_RW1C 0x00FE0002u // change bits 17..23, plus PED
|
||||
|
||||
// PORTSC speed encoding. These are the same numbers as USB_SPEED_* in usb.h,
|
||||
|
|
|
|||
Loading…
Reference in a new issue