diff --git a/docs/architecture/usb.md b/docs/architecture/usb.md index a56d101..91b623a 100644 --- a/docs/architecture/usb.md +++ b/docs/architecture/usb.md @@ -56,18 +56,43 @@ interrupts disabled and no scheduler, so a driver that waited for an interrupt during probe would wait forever. Interrupts are registered afterwards, for when a device is actually delivering -data. +data. From then on the interrupt handler is the event ring's only reader: +command completions and endpoint-0 completions are filed per slot, and whoever +issued them is woken to collect them. That is what lets enumeration happen +again later, from a thread, while keyboards are interrupting. + +Before reset the driver takes the controller from the firmware (USB Legacy +Support: OS Owned, wait for BIOS Owned to clear, SMIs off), and on Intel 7/8/9 +series chipsets switches the ports over from the EHCI controllers, which is +where the firmware leaves them. + +### Hot-plug + +The `usbhotplug` kernel thread owns every enumeration after boot. It looks at +the root ports whenever the controller reports a port change and every quarter +second regardless, and at hubs whenever their status-change endpoint reports +one. A device that arrives is debounced for 100 ms and enumerated exactly as at +boot; one that leaves has its endpoints closed (failing any transfer in flight), +its drivers' `disconnect` called, its slot disabled and its memory freed, and +the same for everything behind it if it was a hub. A device that will not +enumerate is left alone until it is unplugged, not retried for ever. + +The thread also drives key repeat for USB keyboards, which do not repeat by +themselves: 500 ms, then about 33 a second, as the PS/2 keyboard's firmware +does. ## Class drivers | | | |---|---| -| **HID** (`usb/hid/`) | Boot protocol keyboard and mouse, driven from the IRQ handler, feeding the same input paths as PS/2. | -| **Mass storage** (`usb/storage/`) | Bulk-only transport and SCSI, presenting a block device. A superblock reads back through `bread()`. | -| **Hub** (`usb/hub/`) | Route strings, hub slot contexts, per-port reset and power. | +| **HID** (`usb/hid/`) | Keyboards in boot protocol. Pointers in report protocol, from their report descriptor: buttons, X, Y and wheel, relative (mice) or absolute (tablets, VM pointers), with report IDs. A boot mouse whose descriptor cannot be used falls back to boot protocol. Feeds the same input paths as PS/2. | +| **Mass storage** (`usb/storage/`) | Bulk-only transport and SCSI. Up to four sticks, `usb0`..`usb3`, each registered with the block layer with its partitions; pulling one out unregisters it. | +| **Hub** (`usb/hub/`) | Route strings, hub slot contexts, per-port reset and power, and the status-change endpoint for hot-plug behind the hub. | -Class drivers are offered a device by the core once its interface descriptor has -been read, from the list in `kernel/drivers/registry.c`. Whichever claims it +Class drivers are offered **every interface** of a device, each as its own +`usb_dev`, once the configuration has been read, from the list in +`kernel/drivers/registry.c` — a wireless receiver's keyboard and mouse are +interfaces 0 and 1 and get a driver each. Whichever claims it publishes a `struct device` in the bus-agnostic list, **with the class the driver chose rather than the one the descriptor claimed** — the descriptor says what the device says it is, and the driver knows what it made of it. @@ -100,17 +125,15 @@ working one. ## What it does not do -- **A USB disk cannot be mounted or booted from.** The block device works and - `bread()` reads a superblock off it; the root device is compiled in rather than - selectable, so nothing can be told to use it. This is the largest single gap - and it is item 1 of [../plan/roadmap.md](../plan/roadmap.md). -- **Boot protocol only** for HID. No report descriptor parsing, so anything that - is not a plain keyboard or mouse is not understood. +- **Pulling out a stick a filesystem is mounted from panics** on that + filesystem's next read, by the rule in `blkdev_rw()`: a filesystem that went on + with missing blocks would be guessing. +- **HID is keyboards and pointers.** Consumer-control interfaces (volume keys), + game pads and the rest are declined. Keyboard LEDs are not driven. +- **USB 3 hubs** are not driven: the hub driver speaks the USB 2 hub protocol. - **No isochronous transfers**, so no audio and no cameras. - **No USB 3 SuperSpeed-specific features** beyond what enumeration needs — the USB2 and USB3 descriptor paths both work, the rest of the difference is unused. - **16 device slots**, compile-time. -- **No hot-unplug.** A device removed is not noticed; the device model has no - removal path either. - **One controller.** A second xHCI card would bind and then contend for state the driver keeps per-driver rather than per-instance. diff --git a/docs/plan/roadmap.md b/docs/plan/roadmap.md index c930e73..d2591ad 100644 --- a/docs/plan/roadmap.md +++ b/docs/plan/roadmap.md @@ -14,13 +14,13 @@ boxes, is now `usb`, `networking.md`, `graphics.md` and `devices.md` over there. ## 1. Storage and the boot path -**Make a USB disk mountable and bootable.** The xHCI stack, the bulk-only -transport, the SCSI layer and the block device all work — a superblock reads back -through `bread()`. What is missing is that **the root device is compiled in -rather than selectable**, so nothing can be told to mount it. +**Done: a USB disk, a SATA disk and their partitions can be booted from.** The +root is chosen at boot from what is present (`rootdev_mount()` in +`kernel/proc/proc.c`), and `make disk` builds a GPT image with an ESP and a SchFS +partition that a PC boots from a stick or a disk. -This is also the first piece of the installer, and it is the single change that -would most enlarge what SchOS can boot from. +**Next: NVMe**, through the same block registry, and an installer that writes +that layout to a disk. ## 2. Graphics diff --git a/include/blkdev.h b/include/blkdev.h index c55e188..16537ec 100644 --- a/include/blkdev.h +++ b/include/blkdev.h @@ -36,6 +36,13 @@ struct blkdev_ops { int blkdev_register(const char *name, const struct blkdev_ops *ops, void *priv, uint64 nsectors, uint32 secsize); +// The disk has gone -- a USB stick pulled out. Removes it and every partition +// on it, and drops whatever the buffer cache still holds for them, so that a +// different disk given the same number later is not served the old one's +// blocks. A filesystem still mounted from it fails on its next access. +void blkdev_unregister(int dev); +void binval(uint dev); // kernel/dev/bio.c + int blkdev_present(uint dev); void blkdev_rw(struct buf *b); int blkdev_flush(uint dev); diff --git a/include/usb.h b/include/usb.h index 7be0477..c29a086 100644 --- a/include/usb.h +++ b/include/usb.h @@ -178,6 +178,9 @@ struct usb_hub_desc { #define USB_HID_SET_IDLE 0x0A #define USB_HID_SET_PROTOCOL 0x0B #define USB_HID_PROTOCOL_BOOT 0 +#define USB_HID_PROTOCOL_REPORT 1 +#define USB_DT_HID 0x21 +#define USB_DT_REPORT 0x22 // --------------------------------------------------------------------------- // The interface between the USB core and a host controller @@ -208,9 +211,12 @@ struct usb_hc_ops { // know: a hub is scheduled differently from anything else, and devices // behind it are addressed relative to it. int (*set_hub)(struct usb_dev *, int nports, int mtt); + // Whatever is on a hub's downstream port has gone: tear it down, and + // everything behind it if it was a hub too. + void (*detach)(struct usb_dev *hub, int hub_port); }; -#define USB_MAXCONF 256 // bytes of configuration descriptor we keep +#define USB_MAXCONF 512 // bytes of configuration descriptor we keep struct usb_dev { const struct usb_hc_ops *ops; @@ -236,6 +242,12 @@ struct usb_dev { int tt_slot, tt_port; int in_use; + // A device with several interfaces is one usb_dev per interface, so that + // each can have its own driver -- the keyboard and the mouse on one + // wireless receiver are interfaces 0 and 1. `base` is the device's own + // usb_dev (the one the controller addressed) for every interface after the + // first, and 0 for the first, which is the device itself. + struct usb_dev *base; struct usb_device_desc desc; uchar conf[USB_MAXCONF]; // the configuration descriptor and everything @@ -251,6 +263,7 @@ struct usb_dev { // Filled in by whichever class driver claimed the device. const char *driver; + const struct usb_driver *drv; // the same, for calling disconnect void *drvdata; void (*complete)(struct usb_dev *, void *buf, int len); }; @@ -266,10 +279,24 @@ struct usb_driver { // of the device, the descriptor only says what the device claims to be. enum dev_class devclass; int (*probe)(struct usb_dev *); + // The device has been unplugged. Stop using it; the controller frees its + // endpoints after this returns. May be 0 for a driver with nothing to undo. + void (*disconnect)(struct usb_dev *); }; // Called by a host controller once a device is addressed and its device // descriptor has been read. The core reads the configuration, selects it, and // offers the device to the class drivers. void usb_attach(struct usb_dev *dev); +// The device, and every interface of it, is gone. Calls each bound driver's +// disconnect. From the controller, before it frees the slot. +void usb_detach(struct usb_dev *dev); +// Re-check every hub's downstream ports, for devices plugged into or pulled +// out of one. From the hot-plug thread; see kernel/drivers/usb/hub/. +void usb_hub_poll(void); +// The hot-plug thread. After sinit(); see kernel/drivers/bus/xhci/xhci.c. +void usb_hotplug_start(void); +// Key repeat for USB keyboards, which do not repeat by themselves. Called +// every tick from the hot-plug thread; see kernel/drivers/usb/hid/. +void usb_hid_tick(void); diff --git a/kernel/core/main.c b/kernel/core/main.c index 74d1ef9..029ae37 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -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 diff --git a/kernel/dev/bio.c b/kernel/dev/bio.c index c7d300b..60ef676 100644 --- a/kernel/dev/bio.c +++ b/kernel/dev/bio.c @@ -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) { diff --git a/kernel/dev/blkdev.c b/kernel/dev/blkdev.c index ac3a785..990e100 100644 --- a/kernel/dev/blkdev.c +++ b/kernel/dev/blkdev.c @@ -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 diff --git a/kernel/drivers/bus/xhci/xhci.c b/kernel/drivers/bus/xhci/xhci.c index d22ca63..f3b905d 100644 --- a/kernel/drivers/bus/xhci/xhci.c +++ b/kernel/drivers/bus/xhci/xhci.c @@ -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. diff --git a/kernel/drivers/bus/xhci/xhci.h b/kernel/drivers/bus/xhci/xhci.h index d0c5760..e433386 100644 --- a/kernel/drivers/bus/xhci/xhci.h +++ b/kernel/drivers/bus/xhci/xhci.h @@ -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, diff --git a/kernel/drivers/usb/core/usb.c b/kernel/drivers/usb/core/usb.c index 8d652d2..4f1cc97 100644 --- a/kernel/drivers/usb/core/usb.c +++ b/kernel/drivers/usb/core/usb.c @@ -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; } diff --git a/kernel/drivers/usb/hid/usb_hid.c b/kernel/drivers/usb/hid/usb_hid.c index 8e542d4..ba769de 100644 --- a/kernel/drivers/usb/hid/usb_hid.c +++ b/kernel/drivers/usb/hid/usb_hid.c @@ -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, }; diff --git a/kernel/drivers/usb/hub/usb_hub.c b/kernel/drivers/usb/hub/usb_hub.c index 659f710..8d5ce60 100644 --- a/kernel/drivers/usb/hub/usb_hub.c +++ b/kernel/drivers/usb/hub/usb_hub.c @@ -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, }; diff --git a/kernel/drivers/usb/storage/usb_storage.c b/kernel/drivers/usb/storage/usb_storage.c index 589abbc..2f1ed0f 100644 --- a/kernel/drivers/usb/storage/usb_storage.c +++ b/kernel/drivers/usb/storage/usb_storage.c @@ -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, };