SchOS/kernel/drivers/usb/hub/usb_hub.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

315 lines
9.9 KiB
C

// 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,
};