Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
185
kernel/drivers/usb/hub/usb_hub.c
Normal file
185
kernel/drivers/usb/hub/usb_hub.c
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// 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, but that is only needed for
|
||||
// hotplug -- at boot it is enough to walk every port and look.
|
||||
//
|
||||
// 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 "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define MAX_PORTS 15 // per hub; more than any real bus hub has
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
dma_free(scratch, 64);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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,
|
||||
};
|
||||
Loading…
Reference in a new issue