// USB hub class driver. // // A hub is what makes USB a tree rather than a handful of sockets, and // 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. 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 // addressed by a route string naming the port taken at each tier, and a // slow device behind a fast hub is scheduled against that hub's transaction // translator rather than against the device. So this driver's real job is // less "operate the hub" than "tell the controller what shape the tree is". #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "core/spinlock.h" #include "core/dma.h" #include "dev/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, void *buf, int len) { struct usb_setup s; s.bmRequestType = type; s.bRequest = req; s.wValue = value; s.wIndex = index; s.wLength = len; return dev->ops->control(dev, &s, buf, len); } static int port_set_feature(struct usb_dev *dev, int port, int feature) { return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER, USB_REQ_SET_FEATURE, feature, port, 0, 0); } static int port_clear_feature(struct usb_dev *dev, int port, int feature) { return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER, USB_REQ_CLEAR_FEATURE, feature, port, 0, 0); } // Returns wPortStatus, or -1. The change bits are in the upper half and are // deliberately dropped here -- they are acknowledged separately. static int port_status(struct usb_dev *dev, int port, void *scratch) { 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; 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) { int st, speed, i; st = port_status(hub, port, scratch); if(st < 0 || !(st & PORTSTAT_CONNECTION)) return; // Acknowledge the connect-change bit before resetting. Leaving it set is // harmless here but confuses any later attempt to use these bits for // hotplug, which is what they are actually for. port_clear_feature(hub, port, PORT_FEAT_C_CONNECTION); if(port_set_feature(hub, port, PORT_FEAT_RESET) < 0){ cprintf("usb-hub: port %d would not reset\n", port); return; } // A hub does not report reset completion by clearing the request; it sets // C_PORT_RESET and enables the port. Poll for the enable rather than // guessing at a delay, because the time depends on the device. for(i = 0; i < 200; i++){ microdelay(1000); st = port_status(hub, port, scratch); if(st >= 0 && !(st & PORTSTAT_RESET) && (st & PORTSTAT_ENABLE)) break; } port_clear_feature(hub, port, PORT_FEAT_C_RESET); st = port_status(hub, port, scratch); if(st < 0 || !(st & PORTSTAT_ENABLE)){ cprintf("usb-hub: port %d did not enable after reset\n", port); return; } // Speed comes out of two status bits rather than a field, and the absence // of both means full speed. if(st & PORTSTAT_LOWSPEED) speed = USB_SPEED_LOW; else if(st & PORTSTAT_HIGHSPEED) speed = USB_SPEED_HIGH; else speed = USB_SPEED_FULL; // A device needs time to settle after reset before it will answer on // endpoint 0. The specification calls this TRSTRCY and allows 10 ms. microdelay(20000); 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) { struct usb_hub_desc *hd; void *scratch; int nports, mtt, i; scratch = dma_alloc(64, 64); if(scratch == 0) return -1; // The hub descriptor is class-specific, so it comes from a class GET // request rather than the standard one -- same request number, different // recipient and type. if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE, USB_REQ_GET_DESCRIPTOR, USB_DT_HUB << 8, 0, scratch, sizeof(struct usb_hub_desc)) < 0){ cprintf("usb-hub: no hub descriptor\n"); dma_free(scratch, 64); return -1; } hd = scratch; nports = hd->bNbrPorts; if(nports < 1 || nports > MAX_PORTS){ cprintf("usb-hub: %d ports is not a number of ports\n", nports); dma_free(scratch, 64); return -1; } mtt = (hd->wHubCharacteristics & HUB_CHAR_TT_MASK) != 0; cprintf("usb-hub: %d ports on slot %d\n", nports, dev->slot); // Tell the controller *before* powering anything on. Addressing a device // behind a slot the controller does not know is a hub fails, and it fails // at Address Device with a completion code rather than anywhere useful. if(dev->ops->set_hub(dev, nports, mtt) < 0){ dma_free(scratch, 64); return -1; } // Ports come up unpowered. bPwrOn2PwrGood is how long the hub says it needs // after switching power on before a device behind it is worth talking to, // in 2 ms units -- a number the hub knows and we do not. for(i = 1; i <= nports; i++) port_set_feature(dev, i, PORT_FEAT_POWER); microdelay((hd->bPwrOn2PwrGood * 2 + 10) * 1000); for(i = 1; i <= nports; i++) hub_port(dev, i, scratch); // 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. const struct usb_driver usb_hub_driver = { .name = "usb-hub", .class = USB_CLASS_HUB, .subclass = 0xFF, .protocol = 0xFF, .devclass = DEVCLASS_BUS, .probe = hub_probe, .disconnect = hub_disconnect, };