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
|
|
@ -10,6 +10,7 @@
|
|||
#include "irq.h"
|
||||
#include "dma.h"
|
||||
#include "klog.h"
|
||||
#include "usb.h"
|
||||
|
||||
static void *klog_base; // the persistent log, just above the kernel image
|
||||
|
||||
|
|
@ -365,6 +366,9 @@ kmain(uint32 fb_addr)
|
|||
// For the same reason: a kernel thread. See irq_poll_thread() for the
|
||||
// lines it is there for, which on real hardware is most PCI INTx.
|
||||
irq_poll_start();
|
||||
// After the poller, which is what keeps a USB controller whose INTx
|
||||
// never arrives working, and after the scheduler, which it sleeps in.
|
||||
usb_hotplug_start();
|
||||
// The debugger's own thread, for the same reason netinit's comes after
|
||||
// sinit: kthread_create needs a usable process table. From here on Ctrl-G
|
||||
// on the console answers, with no filesystem mounted and nothing in
|
||||
|
|
|
|||
|
|
@ -114,6 +114,21 @@ bdevflush(uint dev)
|
|||
// Look through buffer cache for block on device dev.
|
||||
// If not found, allocate a buffer.
|
||||
// In either case, return locked buffer.
|
||||
// Forget every cached block of a device that is no longer there: clean and
|
||||
// invalid, so it is neither served to a reader nor written back on eviction.
|
||||
// A buffer somebody is holding is left to them -- they are about to find out.
|
||||
void
|
||||
binval(uint dev)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
acquire(&bcache.lock);
|
||||
for(b = bcache.head.next; b != &bcache.head; b = b->next)
|
||||
if(b->dev == dev && b->refcnt == 0)
|
||||
b->flags = 0;
|
||||
release(&bcache.lock);
|
||||
}
|
||||
|
||||
static struct buf*
|
||||
bget(uint dev, uint64 blockno)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -222,6 +222,27 @@ blkdev_register(const char *name, const struct blkdev_ops *ops, void *priv,
|
|||
return dev;
|
||||
}
|
||||
|
||||
void
|
||||
blkdev_unregister(int dev)
|
||||
{
|
||||
struct blkdev *d = lookup(dev);
|
||||
if (!d)
|
||||
return;
|
||||
int disk = dev - BLKDEV_FIRST;
|
||||
|
||||
acquire(&blklock);
|
||||
for (int i = 0; i < NBLKDEV; i++) {
|
||||
if (devs[i].used && (i == disk || devs[i].parent == disk)) {
|
||||
devs[i].used = 0;
|
||||
kinfo("blkdev: %s removed\n", devs[i].name);
|
||||
release(&blklock);
|
||||
binval(BLKDEV_FIRST + i);
|
||||
acquire(&blklock);
|
||||
}
|
||||
}
|
||||
release(&blklock);
|
||||
}
|
||||
|
||||
// --- I/O ---------------------------------------------------------------------
|
||||
|
||||
int
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
// USB HID, boot protocol: keyboards and mice.
|
||||
// USB HID: keyboards, mice and absolute pointers.
|
||||
//
|
||||
// Boot protocol is the reason a USB keyboard works in a BIOS setup screen
|
||||
// before any operating system exists. A HID device normally describes its
|
||||
// reports with a report descriptor -- a small stack language that has to be
|
||||
// parsed to know which bit means which key -- but a device claiming the boot
|
||||
// subclass also promises to support one fixed layout on request. Eight bytes
|
||||
// for a keyboard, three for a mouse, defined in the HID specification and
|
||||
// identical on every device.
|
||||
// Keyboards are driven in boot protocol, which is the reason a USB keyboard
|
||||
// works in a BIOS setup screen before any operating system exists. A HID
|
||||
// device normally describes its reports with a report descriptor -- a small
|
||||
// stack language that has to be parsed to know which bit means which key --
|
||||
// but a device claiming the boot subclass also promises one fixed layout on
|
||||
// request: eight bytes, defined in the HID specification and identical on
|
||||
// every keyboard. Nothing a keyboard sends beyond that is worth the parser.
|
||||
//
|
||||
// That is worth taking. A full report-descriptor parser is a real piece of
|
||||
// work and buys nothing for a keyboard; it is what a game controller or a
|
||||
// touchscreen will need, and it can arrive with them.
|
||||
// Pointers are not. The boot mouse report is three bytes -- buttons and an
|
||||
// 8-bit delta on each axis -- and has no wheel, which every mouse sold this
|
||||
// century has. So a pointer is driven in report protocol, from its report
|
||||
// descriptor: where its buttons, X, Y and wheel are, how wide, and whether
|
||||
// they are relative (a mouse) or absolute (a tablet, a touchscreen, a VM's
|
||||
// pointer). Only the fields a pointer needs are looked for; everything else
|
||||
// in the descriptor is stepped over. A boot mouse whose descriptor makes no
|
||||
// sense is still driven, in boot protocol, as before.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
|
|
@ -21,12 +26,28 @@
|
|||
#include "usb.h"
|
||||
#include "input.h"
|
||||
|
||||
#define NHID 4
|
||||
#define NHID 8
|
||||
|
||||
// One field of a report, as the report descriptor placed it.
|
||||
struct hfield {
|
||||
int ok;
|
||||
int bit, size; // offset and width in bits, after any ID byte
|
||||
int lmin, lmax; // logical range; lmin < 0 means signed
|
||||
int rel; // relative (a delta) rather than absolute
|
||||
};
|
||||
|
||||
#define HID_NBTN 5
|
||||
|
||||
struct hid {
|
||||
int in_use;
|
||||
int proto; // USB_HID_PROTO_KEYBOARD or _MOUSE
|
||||
int proto; // USB_HID_PROTO_KEYBOARD, _MOUSE or _TABLET
|
||||
uchar prev[8]; // the previous report, for edge detection
|
||||
|
||||
// A pointer in report protocol. `boot` instead means the three-byte boot
|
||||
// report, for a mouse whose descriptor could not be used.
|
||||
int boot;
|
||||
int report_id; // 0 when reports carry no ID byte
|
||||
struct hfield x, y, wheel, btn[HID_NBTN];
|
||||
};
|
||||
|
||||
static struct hid hids[NHID];
|
||||
|
|
@ -78,19 +99,42 @@ static struct {
|
|||
uint r, w;
|
||||
} kq;
|
||||
|
||||
// The queue is filled from the interrupt handler and, for key repeat, from
|
||||
// the hot-plug thread; consoleintr() drains it from either.
|
||||
static struct spinlock kqlock;
|
||||
static int kqinit;
|
||||
|
||||
// Software key repeat. A USB keyboard does not repeat: a held key is simply
|
||||
// still in the report, and the next report comes only when something
|
||||
// changes. The PS/2 keyboard repeats in its own firmware, so without this a
|
||||
// held key auto-repeats on one keyboard and not the other.
|
||||
#define REPEAT_DELAY (HZ / 2) // 500 ms, the PC default
|
||||
#define REPEAT_EVERY 3 // ticks: about 33 a second
|
||||
static struct {
|
||||
int c; // the character to repeat, 0 for none
|
||||
uchar usage; // the key producing it
|
||||
uint at; // tick of the next repeat
|
||||
} krep;
|
||||
|
||||
static void
|
||||
kq_put(int c)
|
||||
{
|
||||
acquire(&kqlock);
|
||||
if(kq.w - kq.r < KQSIZE)
|
||||
kq.buf[kq.w++ % KQSIZE] = c;
|
||||
release(&kqlock);
|
||||
}
|
||||
|
||||
static int
|
||||
usb_kbd_getc(void)
|
||||
{
|
||||
if(kq.r == kq.w)
|
||||
return -1;
|
||||
return kq.buf[kq.r++ % KQSIZE];
|
||||
int c = -1;
|
||||
|
||||
acquire(&kqlock);
|
||||
if(kq.r != kq.w)
|
||||
c = kq.buf[kq.r++ % KQSIZE];
|
||||
release(&kqlock);
|
||||
return c;
|
||||
}
|
||||
|
||||
// Was this usage in that report?
|
||||
|
|
@ -187,68 +231,250 @@ keyboard_report(struct hid *h, const uchar *rep, int len)
|
|||
continue;
|
||||
}
|
||||
kq_put(c);
|
||||
|
||||
// The newest key is the one that repeats, as on any keyboard.
|
||||
acquire(&kqlock);
|
||||
krep.c = c;
|
||||
krep.usage = usage;
|
||||
krep.at = ticks + REPEAT_DELAY;
|
||||
release(&kqlock);
|
||||
}
|
||||
|
||||
// Released, or another key's report without it: no more repeats.
|
||||
acquire(&kqlock);
|
||||
if(krep.c && !was_down(rep, krep.usage))
|
||||
krep.c = 0;
|
||||
release(&kqlock);
|
||||
|
||||
memmove(h->prev, rep, 8);
|
||||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
void
|
||||
usb_hid_tick(void)
|
||||
{
|
||||
int c = 0;
|
||||
|
||||
if(!kqinit)
|
||||
return;
|
||||
acquire(&kqlock);
|
||||
if(krep.c && (int)(ticks - krep.at) >= 0){
|
||||
c = krep.c;
|
||||
krep.at = ticks + REPEAT_EVERY;
|
||||
if(kq.w - kq.r < KQSIZE)
|
||||
kq.buf[kq.w++ % KQSIZE] = c;
|
||||
}
|
||||
release(&kqlock);
|
||||
|
||||
if(c && !input_is_grabbed(INPUT_KBD))
|
||||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mouse
|
||||
// Report descriptors
|
||||
//
|
||||
// A report descriptor is a sequence of items, each a prefix byte -- size in
|
||||
// bits 1:0 (3 meaning four bytes), type in 3:2, tag in 7:4 -- and that many
|
||||
// bytes of data. Global items (usage page, logical range, report size and
|
||||
// count, report ID) persist until changed; local items (usages) apply to the
|
||||
// next main item only. A main Input item lays down `count` fields of `size`
|
||||
// bits each, taking usages in order, and advances the position in that
|
||||
// report. Push, pop, delimiters and designators are not used by anything
|
||||
// that matters here and are passed over.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// QEMU's usb-tablet, and absolute pointers generally.
|
||||
//
|
||||
// Report: one byte of buttons, then X and Y as 16-bit little-endian values
|
||||
// over the device's logical range, then a wheel byte. The range is 0..0x7fff
|
||||
// and is *not* read from the report descriptor -- parsing one is a real piece
|
||||
// of work, and this decodes exactly the fixed layout the tablet declares.
|
||||
// A device whose report is a different length is left alone rather than
|
||||
// guessed at.
|
||||
//
|
||||
// Scaling happens here because this is the only place that knows the logical
|
||||
// range; everything above deals in pixels.
|
||||
#define TABLET_LOGICAL_MAX 0x7fff
|
||||
#define PAGE_DESKTOP 0x01
|
||||
#define PAGE_BUTTON 0x09
|
||||
#define U_X 0x30
|
||||
#define U_Y 0x31
|
||||
#define U_WHEEL 0x38
|
||||
|
||||
static void
|
||||
tablet_report(struct hid *h, const uchar *rep, int len)
|
||||
take_field(struct hid *h, uint32 usage, int bit, int size, int lmin, int lmax,
|
||||
int rel, int id, int *target)
|
||||
{
|
||||
(void)h;
|
||||
if(len < 5)
|
||||
struct hfield *f = 0;
|
||||
int page = usage >> 16, u = usage & 0xFFFF;
|
||||
|
||||
if(page == PAGE_DESKTOP && u == U_X) f = &h->x;
|
||||
else if(page == PAGE_DESKTOP && u == U_Y) f = &h->y;
|
||||
else if(page == PAGE_DESKTOP && u == U_WHEEL) f = &h->wheel;
|
||||
else if(page == PAGE_BUTTON && u >= 1 && u <= HID_NBTN)
|
||||
f = &h->btn[u - 1];
|
||||
if(f == 0 || f->ok || size < 1 || size > 32)
|
||||
return;
|
||||
|
||||
int rawx = rep[1] | (rep[2] << 8);
|
||||
int rawy = rep[3] | (rep[4] << 8);
|
||||
// Everything comes from one report: the first one with any of this in it.
|
||||
// A keyboard-and-mouse receiver describes both in one descriptor, and a
|
||||
// button bit from the keyboard's report would be nonsense in the mouse's.
|
||||
if(*target < 0)
|
||||
*target = id;
|
||||
if(id != *target)
|
||||
return;
|
||||
|
||||
int w = (int)gfx_width();
|
||||
int h_px = (int)gfx_height();
|
||||
if(w <= 0) w = 800;
|
||||
if(h_px <= 0) h_px = 600;
|
||||
f->ok = 1;
|
||||
f->bit = bit;
|
||||
f->size = size;
|
||||
f->lmin = lmin;
|
||||
f->lmax = lmax;
|
||||
f->rel = rel;
|
||||
}
|
||||
|
||||
// Scale in 64-bit-free arithmetic: 0x7fff * 4096 still fits in an int.
|
||||
int x = (int)(((long long)rawx * (w - 1)) / TABLET_LOGICAL_MAX);
|
||||
int y = (int)(((long long)rawy * (h_px - 1)) / TABLET_LOGICAL_MAX);
|
||||
// Returns 0 if a pointer was found: an X and a Y in the same report.
|
||||
static int
|
||||
parse_report(struct hid *h, const uchar *d, int n)
|
||||
{
|
||||
uint32 page = 0, rsize = 0, rcount = 0, umin = 0, umax = 0;
|
||||
uint32 usage[16];
|
||||
int lmin = 0, lmax = 0, id = 0, nusage = 0, range = 0, target = -1;
|
||||
int off[16];
|
||||
int i = 0, k;
|
||||
|
||||
mouse_set_abs(x, y, rep[0] & 0x07);
|
||||
memset(off, 0, sizeof(off));
|
||||
while(i < n){
|
||||
uchar b = d[i++];
|
||||
int sz = b & 3, type = (b >> 2) & 3, tag = b >> 4;
|
||||
uint32 v = 0;
|
||||
int sv;
|
||||
|
||||
if(b == 0xFE){ // long item: size, tag, data
|
||||
if(i >= n)
|
||||
break;
|
||||
i += 2 + d[i];
|
||||
continue;
|
||||
}
|
||||
if(sz == 3)
|
||||
sz = 4;
|
||||
if(i + sz > n)
|
||||
break;
|
||||
for(k = 0; k < sz; k++)
|
||||
v |= (uint32)d[i + k] << (8 * k);
|
||||
i += sz;
|
||||
sv = sz == 1 ? (signed char)v : sz == 2 ? (short)v : (int)v;
|
||||
|
||||
if(type == 1){ // global
|
||||
switch(tag){
|
||||
case 0: page = v; break;
|
||||
case 1: lmin = sv; break;
|
||||
// Signed or not depends on the minimum: 0..255 in one byte is 255,
|
||||
// not -1.
|
||||
case 2: lmax = sv < lmin ? (int)v : sv; break;
|
||||
case 7: rsize = v; break;
|
||||
case 8: id = v; break;
|
||||
case 9: rcount = v; break;
|
||||
}
|
||||
} else if(type == 2){ // local
|
||||
uint32 full = sz == 4 ? v : (page << 16) | v;
|
||||
switch(tag){
|
||||
case 0: if(nusage < 16) usage[nusage++] = full; break;
|
||||
case 1: umin = full; range = 1; break;
|
||||
case 2: umax = full; break;
|
||||
}
|
||||
} else if(type == 0){ // main
|
||||
if(tag == 8 && id < 16 && rsize <= 32 && rcount <= 256){
|
||||
for(k = 0; k < (int)rcount; k++){
|
||||
uint32 u = 0;
|
||||
if(k < nusage)
|
||||
u = usage[k];
|
||||
else if(range && umin + (k - nusage) <= umax)
|
||||
u = umin + (k - nusage);
|
||||
else if(nusage > 0)
|
||||
u = usage[nusage - 1];
|
||||
// Bit 0 set is a constant field: padding, there to be stepped over.
|
||||
if(!(v & 1))
|
||||
take_field(h, u, off[id], rsize, lmin, lmax, (v & 4) != 0, id,
|
||||
&target);
|
||||
off[id] += rsize;
|
||||
}
|
||||
}
|
||||
nusage = 0;
|
||||
range = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(!h->x.ok || !h->y.ok)
|
||||
return -1;
|
||||
h->report_id = target;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
field(const uchar *p, int len, const struct hfield *f)
|
||||
{
|
||||
uint32 v = 0;
|
||||
int i;
|
||||
|
||||
if(!f->ok || (f->bit + f->size + 7) / 8 > len)
|
||||
return 0;
|
||||
for(i = 0; i < f->size; i++){
|
||||
int b = f->bit + i;
|
||||
if((p[b >> 3] >> (b & 7)) & 1)
|
||||
v |= 1u << i;
|
||||
}
|
||||
if(f->lmin < 0 && f->size < 32 && ((v >> (f->size - 1)) & 1))
|
||||
v |= ~0u << f->size; // sign-extend
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pointers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
scale(int v, const struct hfield *f, int pixels)
|
||||
{
|
||||
long long range = (long long)f->lmax - f->lmin;
|
||||
if(range <= 0)
|
||||
return 0;
|
||||
return (int)(((long long)(v - f->lmin) * (pixels - 1)) / range);
|
||||
}
|
||||
|
||||
static void
|
||||
mouse_report(struct hid *h, const uchar *rep, int len)
|
||||
pointer_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
int dx, dy;
|
||||
int buttons = 0, i, wheel;
|
||||
|
||||
(void)h;
|
||||
if(len < 3)
|
||||
if(h->boot){
|
||||
// Boot protocol: buttons, then signed 8-bit X and Y. The deltas are
|
||||
// signed. Reading them as unsigned is the classic way to get a pointer
|
||||
// that only travels right and down.
|
||||
if(len < 3)
|
||||
return;
|
||||
mouse_inject((signed char)rep[1], (signed char)rep[2], rep[0] & 0x07);
|
||||
return;
|
||||
}
|
||||
|
||||
// The deltas are signed bytes. Reading them as unsigned is the classic way
|
||||
// to get a pointer that only travels right and down.
|
||||
dx = (signed char)rep[1];
|
||||
dy = (signed char)rep[2];
|
||||
if(h->report_id){
|
||||
if(len < 1 || rep[0] != h->report_id)
|
||||
return; // some other report of the device's
|
||||
rep++;
|
||||
len--;
|
||||
}
|
||||
|
||||
for(i = 0; i < HID_NBTN; i++)
|
||||
if(field(rep, len, &h->btn[i]))
|
||||
buttons |= 1 << i;
|
||||
|
||||
// A wheel step is reported before the motion it came with, so that both
|
||||
// reach a reader in one batch: mouse_inject() is what ends the batch.
|
||||
wheel = field(rep, len, &h->wheel);
|
||||
if(wheel && h->wheel.rel)
|
||||
input_report(INPUT_MOUSE, EV_REL, REL_WHEEL, wheel);
|
||||
|
||||
// HID's Y axis points down, and so does the screen's. The PS/2 protocol's
|
||||
// points up, which is why mouseintr subtracts where this adds.
|
||||
mouse_inject(dx, dy, rep[0] & 0x07);
|
||||
if(h->x.rel){
|
||||
mouse_inject(field(rep, len, &h->x), field(rep, len, &h->y),
|
||||
buttons & 0x07);
|
||||
} else {
|
||||
// Scaled here because this is the only place that knows the logical
|
||||
// range; everything above deals in pixels.
|
||||
int w = (int)gfx_width(), hp = (int)gfx_height();
|
||||
if(w <= 0) w = 800;
|
||||
if(hp <= 0) hp = 600;
|
||||
mouse_set_abs(scale(field(rep, len, &h->x), &h->x, w),
|
||||
scale(field(rep, len, &h->y), &h->y, hp), buttons & 0x07);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -260,12 +486,10 @@ hid_complete(struct usb_dev *dev, void *buf, int len)
|
|||
|
||||
if(h == 0)
|
||||
return;
|
||||
if(h->proto == USB_HID_PROTO_TABLET)
|
||||
tablet_report(h, buf, len);
|
||||
else if(h->proto == USB_HID_PROTO_KEYBOARD)
|
||||
if(h->proto == USB_HID_PROTO_KEYBOARD)
|
||||
keyboard_report(h, buf, len);
|
||||
else
|
||||
mouse_report(h, buf, len);
|
||||
pointer_report(h, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
@ -281,37 +505,110 @@ hid_request(struct usb_dev *dev, int request, int value)
|
|||
return dev->ops->control(dev, &s, 0, 0);
|
||||
}
|
||||
|
||||
// The report descriptor's length is in the HID descriptor, which sits in the
|
||||
// configuration right after this interface's own descriptor.
|
||||
static int
|
||||
report_desc_len(struct usb_dev *dev)
|
||||
{
|
||||
int off = 0, mine = 0;
|
||||
|
||||
while(off + 2 <= dev->conflen){
|
||||
int len = dev->conf[off], type = dev->conf[off + 1];
|
||||
if(len < 2 || off + len > dev->conflen)
|
||||
break;
|
||||
if(type == USB_DT_INTERFACE && len >= 4)
|
||||
mine = dev->conf[off + 2] == dev->iface.bInterfaceNumber &&
|
||||
dev->conf[off + 3] == 0;
|
||||
else if(mine && type == USB_DT_HID && len >= 9 &&
|
||||
dev->conf[off + 6] == USB_DT_REPORT)
|
||||
return dev->conf[off + 7] | (dev->conf[off + 8] << 8);
|
||||
off += len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
read_report_desc(struct usb_dev *dev, struct hid *h)
|
||||
{
|
||||
struct usb_setup s;
|
||||
int len = report_desc_len(dev), rc;
|
||||
uchar *buf;
|
||||
|
||||
if(len <= 0)
|
||||
return -1;
|
||||
if(len > 1024)
|
||||
len = 1024;
|
||||
buf = dma_alloc(len, 64);
|
||||
if(buf == 0)
|
||||
return -1;
|
||||
|
||||
s.bmRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE;
|
||||
s.bRequest = USB_REQ_GET_DESCRIPTOR;
|
||||
s.wValue = USB_DT_REPORT << 8;
|
||||
s.wIndex = dev->iface.bInterfaceNumber;
|
||||
s.wLength = len;
|
||||
memset(buf, 0, len);
|
||||
rc = dev->ops->control(dev, &s, buf, len);
|
||||
if(rc >= 0)
|
||||
rc = parse_report(h, buf, len);
|
||||
dma_free(buf, len);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
hid_probe(struct usb_dev *dev)
|
||||
{
|
||||
struct hid *h = 0;
|
||||
int i, proto = dev->iface.bInterfaceProtocol;
|
||||
int i;
|
||||
int boot = dev->iface.bInterfaceSubClass == USB_HID_SUBCLASS_BOOT;
|
||||
int proto = dev->iface.bInterfaceProtocol;
|
||||
|
||||
if(!dev->has_intr_in){
|
||||
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
|
||||
return -1;
|
||||
}
|
||||
if(!kqinit){
|
||||
initlock(&kqlock, "usbkbd");
|
||||
kqinit = 1;
|
||||
}
|
||||
|
||||
for(i = 0; i < NHID; i++)
|
||||
if(!hids[i].in_use){ h = &hids[i]; break; }
|
||||
if(h == 0)
|
||||
if(h == 0){
|
||||
cprintf("usb-hid: %d devices already; this one is not used\n", NHID);
|
||||
return -1;
|
||||
}
|
||||
memset(h, 0, sizeof(*h));
|
||||
|
||||
// A HID device that claims no boot protocol is taken to be an absolute
|
||||
// pointer -- in practice a tablet, which is what a VM offers so that its
|
||||
// cursor and the host's can agree. It must not be asked for boot protocol:
|
||||
// it has none, the request fails, and refusing on that basis is what kept
|
||||
// usb-tablet from binding at all.
|
||||
if(proto != USB_HID_PROTO_KEYBOARD && proto != USB_HID_PROTO_MOUSE)
|
||||
proto = USB_HID_PROTO_TABLET;
|
||||
|
||||
// Ask for the fixed layout explicitly. A device that supports the boot
|
||||
// subclass still starts in report protocol, so without this the reports
|
||||
// arrive in whatever shape its report descriptor declares -- which we have
|
||||
// not read and cannot interpret.
|
||||
if(proto != USB_HID_PROTO_TABLET &&
|
||||
hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
if(boot && proto == USB_HID_PROTO_KEYBOARD){
|
||||
// Ask for the fixed layout explicitly. A device that supports the boot
|
||||
// subclass still starts in report protocol, so without this the reports
|
||||
// arrive in whatever shape its report descriptor declares.
|
||||
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
return -1;
|
||||
}
|
||||
h->proto = USB_HID_PROTO_KEYBOARD;
|
||||
} else if(read_report_desc(dev, h) == 0){
|
||||
// Report protocol is the default after a reset, but a firmware that used
|
||||
// the mouse may have left it in boot protocol, and a port reset is not
|
||||
// always a device reset. Only a boot device knows the request.
|
||||
if(boot)
|
||||
hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_REPORT);
|
||||
h->proto = h->x.rel ? USB_HID_PROTO_MOUSE : USB_HID_PROTO_TABLET;
|
||||
} else if(boot && proto == USB_HID_PROTO_MOUSE){
|
||||
memset(h, 0, sizeof(*h));
|
||||
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
return -1;
|
||||
}
|
||||
h->proto = USB_HID_PROTO_MOUSE;
|
||||
h->boot = 1;
|
||||
} else {
|
||||
// A consumer-control interface (volume keys), a game pad, a UPS: HID,
|
||||
// and not something this driver has a use for.
|
||||
cprintf("usb-hid: interface %d is neither a keyboard nor a pointer\n",
|
||||
dev->iface.bInterfaceNumber);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -324,32 +621,52 @@ hid_probe(struct usb_dev *dev)
|
|||
return -1;
|
||||
|
||||
h->in_use = 1;
|
||||
h->proto = proto;
|
||||
dev->drvdata = h;
|
||||
dev->complete = hid_complete;
|
||||
|
||||
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
|
||||
// has nowhere to put a report, so nothing would ever arrive.
|
||||
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
|
||||
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0){
|
||||
h->in_use = 0;
|
||||
dev->drvdata = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
cprintf("usb-hid: %s on slot %d, endpoint 0x%x every %d ms\n",
|
||||
proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
|
||||
proto == USB_HID_PROTO_MOUSE ? "mouse" :
|
||||
proto == USB_HID_PROTO_TABLET ? "tablet (absolute)" : "device",
|
||||
cprintf("usb-hid: %s%s on slot %d, endpoint 0x%x every %d ms\n",
|
||||
h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
|
||||
h->proto == USB_HID_PROTO_MOUSE ? "mouse" : "absolute pointer",
|
||||
h->proto == USB_HID_PROTO_KEYBOARD || h->boot ? "" :
|
||||
h->wheel.ok ? " with wheel" : "",
|
||||
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Any HID interface, not just the boot subclass.
|
||||
//
|
||||
// It was boot-only, on the reasoning that a device without boot protocol needs
|
||||
// its report descriptor parsed and there would be nothing useful to do with it.
|
||||
// That is right in general and wrong for the one device that matters here:
|
||||
// usb-tablet declares subclass 0, so it was refused at the door -- and its
|
||||
// report layout is fixed and known, so there is something useful to do with it
|
||||
// after all. hid_probe decides what a given interface actually is; anything it
|
||||
// cannot make sense of is still declined there.
|
||||
// Unplugged. A key held at that moment is released, so that a reader of
|
||||
// /dev/input does not see it held for ever, and the slot is given back.
|
||||
static void
|
||||
hid_disconnect(struct usb_dev *dev)
|
||||
{
|
||||
struct hid *h = dev->drvdata;
|
||||
static const uchar none[8];
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
if(h->proto == USB_HID_PROTO_KEYBOARD){
|
||||
keyboard_events(h->prev, none);
|
||||
acquire(&kqlock);
|
||||
krep.c = 0;
|
||||
release(&kqlock);
|
||||
}
|
||||
cprintf("usb-hid: %s on slot %d unplugged\n",
|
||||
h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" : "pointer",
|
||||
dev->slot);
|
||||
h->in_use = 0;
|
||||
dev->drvdata = 0;
|
||||
dev->complete = 0;
|
||||
}
|
||||
|
||||
// Any HID interface, not just the boot subclass: hid_probe decides what a
|
||||
// given interface actually is, and declines anything it cannot make sense of.
|
||||
const struct usb_driver usb_hid_driver = {
|
||||
.name = "usb-hid",
|
||||
.class = USB_CLASS_HID,
|
||||
|
|
@ -357,4 +674,5 @@ const struct usb_driver usb_hid_driver = {
|
|||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_INPUT,
|
||||
.probe = hid_probe,
|
||||
.disconnect = hid_disconnect,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -61,9 +61,12 @@ struct csw {
|
|||
|
||||
#define SECTOR_SIZE 512
|
||||
|
||||
static struct {
|
||||
// One per stick. A desk with a stick in each port is a reasonable thing to
|
||||
// have, and the first one to arrive is the one the fixed USBDISK_DEV means.
|
||||
struct ums {
|
||||
struct usb_dev *dev;
|
||||
int present;
|
||||
int gone; // unplugged; fail everything
|
||||
uint32 nsectors;
|
||||
uint32 sector_size;
|
||||
uint32 tag;
|
||||
|
|
@ -71,7 +74,23 @@ static struct {
|
|||
struct cbw *cbw; // allocated once, not per command
|
||||
struct csw *csw;
|
||||
struct sleeplock lock; // one command at a time
|
||||
} ums;
|
||||
char name[8]; // usb0, usb1, ...
|
||||
int blkdev; // its number in the registry, or -1
|
||||
};
|
||||
|
||||
#define NUMS 4
|
||||
static struct ums disks[NUMS];
|
||||
|
||||
// What USBDISK_DEV (3) means: the first stick still present. It is the whole
|
||||
// medium, which is how a stick holding a bare SchFS volume is mounted.
|
||||
static struct ums *
|
||||
legacy(void)
|
||||
{
|
||||
for(int i = 0; i < NUMS; i++)
|
||||
if(disks[i].present && !disks[i].gone)
|
||||
return &disks[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// SCSI is big-endian, everywhere, for everything. x86 is not.
|
||||
static void
|
||||
|
|
@ -92,12 +111,13 @@ rd_be32(const uint8 *p)
|
|||
// `buf`, when there is data, must be DMA memory -- it is handed to the
|
||||
// controller as a physical address.
|
||||
static int
|
||||
bot_command(const uint8 *cdb, int cdb_len, void *buf, int len, int in)
|
||||
bot_command(struct ums *u, const uint8 *cdb, int cdb_len, void *buf, int len,
|
||||
int in)
|
||||
{
|
||||
struct usb_dev *dev = ums.dev;
|
||||
struct cbw *cbw = ums.cbw;
|
||||
struct csw *csw = ums.csw;
|
||||
uint32 tag = ++ums.tag;
|
||||
struct usb_dev *dev = u->dev;
|
||||
struct cbw *cbw = u->cbw;
|
||||
struct csw *csw = u->csw;
|
||||
uint32 tag = ++u->tag;
|
||||
int i, data_ok = 1;
|
||||
|
||||
memset(cbw, 0, sizeof(*cbw));
|
||||
|
|
@ -160,13 +180,13 @@ bot_command(const uint8 *cdb, int cdb_len, void *buf, int len, int in)
|
|||
}
|
||||
|
||||
static int
|
||||
scsi_inquiry(char *vendor, char *product)
|
||||
scsi_inquiry(struct ums *u, char *vendor, char *product)
|
||||
{
|
||||
uint8 cdb[6] = { SCSI_INQUIRY, 0, 0, 0, 36, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
uint8 *r = u->xfer;
|
||||
int i;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 36, 1) < 0)
|
||||
if(bot_command(u, cdb, sizeof(cdb), r, 36, 1) < 0)
|
||||
return -1;
|
||||
|
||||
// Bytes 8-15 are the vendor, 16-31 the product, space padded and not
|
||||
|
|
@ -181,22 +201,22 @@ scsi_inquiry(char *vendor, char *product)
|
|||
}
|
||||
|
||||
static int
|
||||
scsi_read_capacity(void)
|
||||
scsi_read_capacity(struct ums *u)
|
||||
{
|
||||
uint8 cdb[10] = { SCSI_READ_CAPACITY10, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
uint8 *r = u->xfer;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 8, 1) < 0)
|
||||
if(bot_command(u, cdb, sizeof(cdb), r, 8, 1) < 0)
|
||||
return -1;
|
||||
|
||||
// The first word is the address of the *last* sector, not the count.
|
||||
ums.nsectors = rd_be32(r) + 1;
|
||||
ums.sector_size = rd_be32(r + 4);
|
||||
u->nsectors = rd_be32(r) + 1;
|
||||
u->sector_size = rd_be32(r + 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_rw(uint32 lba, int count, void *buf, int write)
|
||||
scsi_rw(struct ums *u, uint32 lba, int count, void *buf, int write)
|
||||
{
|
||||
uint8 cdb[10];
|
||||
|
||||
|
|
@ -206,7 +226,7 @@ scsi_rw(uint32 lba, int count, void *buf, int write)
|
|||
cdb[7] = (count >> 8) & 0xFF;
|
||||
cdb[8] = count & 0xFF;
|
||||
|
||||
return bot_command(cdb, sizeof(cdb), buf, count * SECTOR_SIZE, !write);
|
||||
return bot_command(u, cdb, sizeof(cdb), buf, count * SECTOR_SIZE, !write);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -216,7 +236,7 @@ scsi_rw(uint32 lba, int count, void *buf, int write)
|
|||
int
|
||||
usb_storage_present(void)
|
||||
{
|
||||
return ums.present;
|
||||
return legacy() != 0;
|
||||
}
|
||||
|
||||
// Sync one buffer-cache block with the stick. Same contract as iderw() and
|
||||
|
|
@ -229,32 +249,33 @@ usb_storage_rw(struct buf *b)
|
|||
uint32 lba = b->blockno * per_block;
|
||||
int write = (b->flags & B_DIRTY) != 0;
|
||||
int i, n = -1;
|
||||
struct ums *u = legacy();
|
||||
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("usb_storage_rw: buf not locked");
|
||||
if(!ums.present)
|
||||
if(u == 0)
|
||||
panic("usb_storage_rw: no USB disk");
|
||||
if(lba + per_block > ums.nsectors)
|
||||
if(lba + per_block > u->nsectors)
|
||||
panic("usb_storage_rw: block out of range");
|
||||
|
||||
// One command at a time. Bulk-only transport has no tagging beyond the CBW
|
||||
// tag and no queueing: a second command sent while the first is mid-phase
|
||||
// desynchronises the endpoint and every command after it fails.
|
||||
acquiresleep(&ums.lock);
|
||||
acquiresleep(&u->lock);
|
||||
|
||||
// Removable media fails transiently in ways a fixed disk does not, so a
|
||||
// couple of retries are worth having before giving up.
|
||||
for(i = 0; i < 3; i++){
|
||||
if(write)
|
||||
memmove(ums.xfer, b->data, BSIZE);
|
||||
n = scsi_rw(lba, per_block, ums.xfer, write);
|
||||
memmove(u->xfer, b->data, BSIZE);
|
||||
n = scsi_rw(u, lba, per_block, u->xfer, write);
|
||||
if(n == BSIZE)
|
||||
break;
|
||||
cprintf("usb-storage: %s of block %d failed, retrying\n",
|
||||
write ? "write" : "read", b->blockno);
|
||||
}
|
||||
|
||||
releasesleep(&ums.lock);
|
||||
releasesleep(&u->lock);
|
||||
|
||||
// A read that failed must not return quietly. bread() hands the buffer
|
||||
// straight back to its caller, which has no way to ask whether it worked --
|
||||
|
|
@ -265,7 +286,7 @@ usb_storage_rw(struct buf *b)
|
|||
panic("usb_storage_rw: transfer failed");
|
||||
|
||||
if(!write)
|
||||
memmove(b->data, ums.xfer, BSIZE);
|
||||
memmove(b->data, u->xfer, BSIZE);
|
||||
b->flags |= B_VALID;
|
||||
b->flags &= ~B_DIRTY;
|
||||
}
|
||||
|
|
@ -280,21 +301,18 @@ usb_storage_rw(struct buf *b)
|
|||
// than ignored, because the caller is about to write an uberblock on the
|
||||
// strength of it -- and a barrier that silently does nothing is worse than no
|
||||
// barrier at all, since it makes an unsafe volume look safe.
|
||||
int
|
||||
usb_storage_flush(void)
|
||||
static int
|
||||
ums_flush(struct ums *u)
|
||||
{
|
||||
uint8 cdb[10];
|
||||
int r;
|
||||
|
||||
if(!ums.present)
|
||||
return 0;
|
||||
|
||||
memset(cdb, 0, sizeof(cdb));
|
||||
cdb[0] = SCSI_SYNC_CACHE10; // LBA 0, count 0: the entire medium
|
||||
|
||||
acquiresleep(&ums.lock);
|
||||
r = bot_command(cdb, sizeof(cdb), 0, 0, 0);
|
||||
releasesleep(&ums.lock);
|
||||
acquiresleep(&u->lock);
|
||||
r = u->gone ? -1 : bot_command(u, cdb, sizeof(cdb), 0, 0, 0);
|
||||
releasesleep(&u->lock);
|
||||
|
||||
if(r < 0){
|
||||
cprintf("usb-storage: SYNCHRONIZE CACHE failed; writes are not durable\n");
|
||||
|
|
@ -303,6 +321,13 @@ usb_storage_flush(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
usb_storage_flush(void)
|
||||
{
|
||||
struct ums *u = legacy();
|
||||
return u ? ums_flush(u) : 0;
|
||||
}
|
||||
|
||||
// Read block 1 through the ordinary buffer cache and report what is there.
|
||||
//
|
||||
// This is the only test that proves the whole path rather than a piece of it:
|
||||
|
|
@ -317,14 +342,15 @@ usb_storage_dump(void)
|
|||
{
|
||||
struct buf *b;
|
||||
struct superblock *sb;
|
||||
struct ums *u = legacy();
|
||||
|
||||
if(!ums.present){
|
||||
if(u == 0){
|
||||
cprintf("usb-storage: no disk\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cprintf("usb-storage: %d sectors of %d bytes\n", ums.nsectors,
|
||||
ums.sector_size);
|
||||
cprintf("usb-storage: %d sectors of %d bytes\n", u->nsectors,
|
||||
u->sector_size);
|
||||
|
||||
b = bread(USBDISK_DEV, 1);
|
||||
sb = (struct superblock*)b->data;
|
||||
|
|
@ -341,25 +367,28 @@ usb_storage_dump(void)
|
|||
static int
|
||||
ums_blk_rw(void *priv, uint64 lba, uint32 count, void *buf, int write)
|
||||
{
|
||||
struct ums *u = priv;
|
||||
int n;
|
||||
(void)priv;
|
||||
if(count * SECTOR_SIZE > BSIZE || lba + count > ums.nsectors)
|
||||
if(count * SECTOR_SIZE > BSIZE || lba + count > u->nsectors)
|
||||
return -1;
|
||||
acquiresleep(&ums.lock);
|
||||
acquiresleep(&u->lock);
|
||||
if(u->gone){
|
||||
releasesleep(&u->lock);
|
||||
return -1;
|
||||
}
|
||||
if(write)
|
||||
memmove(ums.xfer, buf, count * SECTOR_SIZE);
|
||||
n = scsi_rw((uint32)lba, count, ums.xfer, write);
|
||||
memmove(u->xfer, buf, count * SECTOR_SIZE);
|
||||
n = scsi_rw(u, (uint32)lba, count, u->xfer, write);
|
||||
if(n == (int)(count * SECTOR_SIZE) && !write)
|
||||
memmove(buf, ums.xfer, count * SECTOR_SIZE);
|
||||
releasesleep(&ums.lock);
|
||||
memmove(buf, u->xfer, count * SECTOR_SIZE);
|
||||
releasesleep(&u->lock);
|
||||
return n == (int)(count * SECTOR_SIZE) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
ums_blk_flush(void *priv)
|
||||
{
|
||||
(void)priv;
|
||||
return usb_storage_flush();
|
||||
return ums_flush(priv);
|
||||
}
|
||||
|
||||
static const struct blkdev_ops ums_blk_ops = {
|
||||
|
|
@ -373,28 +402,40 @@ static int
|
|||
storage_probe(struct usb_dev *dev)
|
||||
{
|
||||
char vendor[9], product[17];
|
||||
struct ums *u = 0;
|
||||
int i;
|
||||
|
||||
if(ums.present)
|
||||
return -1; // one disk is enough for now
|
||||
for(i = 0; i < NUMS; i++)
|
||||
if(!disks[i].present && disks[i].dev == 0){ u = &disks[i]; break; }
|
||||
if(u == 0){
|
||||
cprintf("usb-storage: %d sticks already; this one is not used\n", NUMS);
|
||||
return -1;
|
||||
}
|
||||
if(!dev->has_bulk_in || !dev->has_bulk_out){
|
||||
cprintf("usb-storage: needs a bulk endpoint in each direction\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
initsleeplock(&ums.lock, "usbms");
|
||||
ums.dev = dev;
|
||||
initsleeplock(&u->lock, "usbms");
|
||||
u->dev = dev;
|
||||
u->gone = 0;
|
||||
u->blkdev = -1;
|
||||
// The name is the slot's, not a count: pull usb0 out and put it back and
|
||||
// it comes back as usb0.
|
||||
safestrcpy(u->name, "usb0", sizeof(u->name));
|
||||
u->name[3] = '0' + (u - disks);
|
||||
|
||||
// One block, used for every transfer. bread() hands us b->data, which lives
|
||||
// in the buffer cache rather than in DMA memory, so everything bounces
|
||||
// through here. The wrappers are allocated once too: they are used on every
|
||||
// single I/O, and churning the DMA extent list twice per block read would
|
||||
// fragment an arena that has no business fragmenting.
|
||||
ums.xfer = dma_alloc(BSIZE, 4096);
|
||||
ums.cbw = dma_alloc(sizeof(struct cbw), 64);
|
||||
ums.csw = dma_alloc(sizeof(struct csw), 64);
|
||||
if(ums.xfer == 0 || ums.cbw == 0 || ums.csw == 0)
|
||||
u->xfer = dma_alloc(BSIZE, 4096);
|
||||
u->cbw = dma_alloc(sizeof(struct cbw), 64);
|
||||
u->csw = dma_alloc(sizeof(struct csw), 64);
|
||||
if(u->xfer == 0 || u->cbw == 0 || u->csw == 0)
|
||||
goto fail;
|
||||
dev->drvdata = u;
|
||||
|
||||
// Deliberately OUT first, which is the order that used to break.
|
||||
//
|
||||
|
|
@ -418,46 +459,82 @@ storage_probe(struct usb_dev *dev)
|
|||
// first command or two while it spins up or reads its own media.
|
||||
for(i = 0; i < 5; i++){
|
||||
uint8 cdb[6] = { SCSI_TEST_UNIT_READY, 0, 0, 0, 0, 0 };
|
||||
if(bot_command(cdb, sizeof(cdb), 0, 0, 0) >= 0)
|
||||
if(bot_command(u, cdb, sizeof(cdb), 0, 0, 0) >= 0)
|
||||
break;
|
||||
microdelay(100000);
|
||||
}
|
||||
|
||||
if(scsi_inquiry(vendor, product) < 0)
|
||||
if(scsi_inquiry(u, vendor, product) < 0)
|
||||
goto fail;
|
||||
if(scsi_read_capacity() < 0)
|
||||
if(scsi_read_capacity(u) < 0)
|
||||
goto fail;
|
||||
|
||||
if(ums.sector_size != SECTOR_SIZE){
|
||||
if(u->sector_size != SECTOR_SIZE){
|
||||
cprintf("usb-storage: %d-byte sectors are not supported\n",
|
||||
ums.sector_size);
|
||||
u->sector_size);
|
||||
goto fail;
|
||||
}
|
||||
if(ums.nsectors == 0){
|
||||
if(u->nsectors == 0){
|
||||
cprintf("usb-storage: device reports no capacity\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ums.present = 1;
|
||||
cprintf("usb-storage: %s %s, %d sectors (%d MB)\n",
|
||||
vendor, product, ums.nsectors,
|
||||
(int)((uint64)ums.nsectors * SECTOR_SIZE / (1024 * 1024)));
|
||||
blkdev_register("usb0", &ums_blk_ops, 0, ums.nsectors, SECTOR_SIZE);
|
||||
u->present = 1;
|
||||
cprintf("usb-storage: %s: %s %s, %d sectors (%d MB)\n", u->name,
|
||||
vendor, product, u->nsectors,
|
||||
(int)((uint64)u->nsectors * SECTOR_SIZE / (1024 * 1024)));
|
||||
u->blkdev = blkdev_register(u->name, &ums_blk_ops, u, u->nsectors,
|
||||
SECTOR_SIZE);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
// Give the memory back and forget the device. Leaving ums.dev set would let
|
||||
// Give the memory back and forget the device. Leaving u->dev set would let
|
||||
// a later block read reach a device we decided we could not drive.
|
||||
if(ums.xfer) dma_free(ums.xfer, BSIZE);
|
||||
if(ums.cbw) dma_free(ums.cbw, sizeof(struct cbw));
|
||||
if(ums.csw) dma_free(ums.csw, sizeof(struct csw));
|
||||
ums.xfer = 0;
|
||||
ums.cbw = 0;
|
||||
ums.csw = 0;
|
||||
ums.dev = 0;
|
||||
if(u->xfer) dma_free(u->xfer, BSIZE);
|
||||
if(u->cbw) dma_free(u->cbw, sizeof(struct cbw));
|
||||
if(u->csw) dma_free(u->csw, sizeof(struct csw));
|
||||
u->xfer = 0;
|
||||
u->cbw = 0;
|
||||
u->csw = 0;
|
||||
u->dev = 0;
|
||||
dev->drvdata = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Pulled out. The controller has already failed whatever transfer was in
|
||||
// flight, so the lock comes free; from then on every request fails at once,
|
||||
// and the registry forgets the disk and its partitions. Mounted from, it
|
||||
// fails the filesystem's next read -- which panics, by the rule in
|
||||
// blkdev_rw(), because a filesystem that carried on would be guessing.
|
||||
static void
|
||||
storage_disconnect(struct usb_dev *dev)
|
||||
{
|
||||
struct ums *u = dev->drvdata;
|
||||
|
||||
if(u == 0)
|
||||
return;
|
||||
acquiresleep(&u->lock);
|
||||
u->gone = 1;
|
||||
releasesleep(&u->lock);
|
||||
|
||||
cprintf("usb-storage: %s unplugged\n", u->name);
|
||||
if(u->blkdev >= 0)
|
||||
blkdev_unregister(u->blkdev);
|
||||
// USBDISK_DEV may have been this stick; nothing it cached is true now.
|
||||
binval(USBDISK_DEV);
|
||||
|
||||
dma_free(u->xfer, BSIZE);
|
||||
dma_free(u->cbw, sizeof(struct cbw));
|
||||
dma_free(u->csw, sizeof(struct csw));
|
||||
u->xfer = 0;
|
||||
u->cbw = 0;
|
||||
u->csw = 0;
|
||||
u->present = 0;
|
||||
u->blkdev = -1;
|
||||
u->dev = 0;
|
||||
dev->drvdata = 0;
|
||||
}
|
||||
|
||||
const struct usb_driver usb_storage_driver = {
|
||||
.name = "usb-storage",
|
||||
.class = USB_CLASS_MASS_STORAGE,
|
||||
|
|
@ -465,4 +542,5 @@ const struct usb_driver usb_storage_driver = {
|
|||
.protocol = USB_MSC_PROTO_BOT,
|
||||
.devclass = DEVCLASS_STORAGE,
|
||||
.probe = storage_probe,
|
||||
.disconnect = storage_disconnect,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue