SchOS/kernel/drivers/usb/core/usb.c
Nexoniarz d650ff433b udev: PCI bridges as parents, USB interfaces, the controller's own address, and the attributes lsusb reads
PCI devices now nest under the bridge they sit behind (from each bridge's
secondary..subordinate window), so a controller behind a root port is where
Linux would put it. USB interfaces (1-5:1.0) are directories with class,
number, a driver link and a uevent with INTERFACE= and a MODALIAS, announced
after the drivers have been offered the device and removed before it. The xHCI
driver tells the USB core its PCI address when it comes up, replacing the guess
that bus N was the Nth controller. Devices and root hubs carry version,
bMaxPacketSize0, bNumInterfaces, bmAttributes and the rest; lsusb -t from
Alpine's usbutils now draws the tree.

Checked on q35 with an xHCI behind a pcie-root-port and a USB keyboard on each
controller. Plain lsusb still fails: libusb wants usbfs, and refuses a kernel
that calls itself 0.1.0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
2026-10-26 17:54:13 +01:00

569 lines
17 KiB
C

// The USB core: everything between "the controller can talk to a device" and
// "a driver knows what the device is".
//
// The split matters. xhci.c deals in TRBs, slot IDs and doorbells; nothing
// here knows any of those words. A class driver deals in reports and
// commands; nothing here knows those either. What lives in the middle is the
// part of USB that is the same for every device ever made: read the
// configuration, choose one, look at the interface, find someone who wants it.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "core/spinlock.h"
#include "core/dma.h"
#include "dev/usb.h"
#include "core/hwinfo.h"
// A staging buffer for control transfers.
//
// It exists because a descriptor has to be read into memory the controller
// can reach and that does not straddle a 64 KB boundary -- a rule that holds
// for xHCI data stages and that a static buffer inside some other structure
// satisfies only by luck. One page, allocated once, copied out of afterwards.
static void *scratch;
static void *
get_scratch(void)
{
if(scratch == 0)
scratch = dma_alloc(4096, 4096);
return scratch;
}
static int
get_descriptor(struct usb_dev *dev, int type, int index, void *buf, int len)
{
struct usb_setup s;
// Clear first. The scratch buffer is shared by every device on the bus, and
// a control transfer reports success without saying how many bytes it
// actually moved -- so a device that answers short leaves the previous
// device's descriptor visible past the end of its own, and the parser
// happily walks into it. Zeroed bytes stop the walk instead, because a
// descriptor whose length is 0 is rejected.
memset(buf, 0, len);
s.bmRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
s.bRequest = USB_REQ_GET_DESCRIPTOR;
s.wValue = (type << 8) | index;
s.wIndex = 0;
s.wLength = len;
return dev->ops->control(dev, &s, buf, len);
}
static int
set_configuration(struct usb_dev *dev, int value)
{
struct usb_setup s;
s.bmRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
s.bRequest = USB_REQ_SET_CONFIG;
s.wValue = value;
s.wIndex = 0;
s.wLength = 0;
return dev->ops->control(dev, &s, 0, 0);
}
// Walk the configuration descriptor.
//
// What comes back from GET_DESCRIPTOR(CONFIG) is not one descriptor: it is
// the configuration descriptor followed by every interface and endpoint
// descriptor belonging to it, packed end to end, each one starting with its
// own length. That length is the only thing that can be relied on to find the
// next -- descriptor types this kernel has never heard of appear in the
// 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.
//
// `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;
while(off + 2 <= dev->conflen){
uint8 len = dev->conf[off];
uint8 type = dev->conf[off + 1];
if(len < 2 || off + len > dev->conflen)
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; // 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;
int in;
memmove(&ep, &dev->conf[off], sizeof(ep));
in = USB_EP_IS_IN(ep.bEndpointAddress);
switch(USB_EP_XFERTYPE(ep.bmAttributes)){
case USB_EP_INTERRUPT:
if(in && !dev->has_intr_in){ dev->intr_in = ep; dev->has_intr_in = 1; }
break;
case USB_EP_BULK:
if(in && !dev->has_bulk_in){ dev->bulk_in = ep; dev->has_bulk_in = 1; }
if(!in && !dev->has_bulk_out){ dev->bulk_out = ep; dev->has_bulk_out = 1; }
break;
}
}
off += len;
}
return seen_iface ? 0 : -1;
}
// One usb_dev for every interface after the first. See `base` in
// include/dev/usb.h. A fixed pool, like everything else here. It was sixteen,
// which a dock -- hub, Ethernet, audio, card reader, each two or three
// interfaces -- and a couple of receivers already reach. 672 bytes each.
#define NIFDEV 64
static struct usb_dev ifdevs[NIFDEV];
// ---------------------------------------------------------------------------
// Class drivers
// ---------------------------------------------------------------------------
extern const struct usb_driver usb_hid_driver;
void usb_bind_all(struct usb_dev *dev);
static int
matches(const struct usb_driver *drv, struct usb_dev *dev)
{
if(drv->class != 0xFF && drv->class != dev->iface.bInterfaceClass)
return 0;
if(drv->subclass != 0xFF && drv->subclass != dev->iface.bInterfaceSubClass)
return 0;
if(drv->protocol != 0xFF && drv->protocol != dev->iface.bInterfaceProtocol)
return 0;
return 1;
}
// Offer a device to one driver. Returns 1 if it was claimed.
int
usb_offer(const struct usb_driver *drv, struct usb_dev *dev)
{
if(dev->driver || !matches(drv, dev))
return 0;
if(drv->probe(dev) != 0)
return 0;
dev->driver = drv->name;
dev->drv = drv;
return 1;
}
// ---------------------------------------------------------------------------
// What is plugged in, for /sys/bus/usb/devices.
//
// The controller owns each usb_dev; this only remembers which ones are
// attached, from usb_attach until usb_detach, and copies their identity out
// under the lock so a reader never follows a pointer the controller may be
// about to reuse. A bus number is handed to each controller the first time one
// of its devices appears, in that order, as Linux numbers usb1, usb2.
#define NPRESENT 64
#define NBUSES 8
static struct spinlock present_lock;
static int present_init;
static struct usb_dev *present[NPRESENT];
static void *buses[NBUSES];
static uint8 bus_pci[NBUSES][3]; // the controller's PCI bus, slot, function
// Give a controller its bus number if it has none. Caller holds present_lock.
// Returns the number (1-based), or 0 if there is no room.
static int
bus_number(void *hc)
{
for(int b = 0; b < NBUSES; b++){
if(buses[b] == hc) return b + 1;
if(buses[b] == 0){ buses[b] = hc; return b + 1; }
}
return 0;
}
static void
present_init_once(void)
{
if(!present_init){
initlock(&present_lock, "usbpresent");
present_init = 1;
}
}
// A controller is up: number its bus now and announce its root hub, so usbN
// is in /sys before anything is plugged into it. The loader of the driver that
// calls this runs once per controller, in PCI order -- which is what ties bus
// N to the Nth controller in sysfs.c.
void
usb_bus_add(void *hc, int pci_bus, int pci_slot, int pci_func)
{
int n;
present_init_once();
acquire(&present_lock);
n = bus_number(hc);
if(n){
bus_pci[n - 1][0] = (uint8)pci_bus;
bus_pci[n - 1][1] = (uint8)pci_slot;
bus_pci[n - 1][2] = (uint8)pci_func;
}
release(&present_lock);
if(n)
sysfs_usb_root_uevent(n, "add");
}
int
usb_bus_pci(int bus, int *pb, int *ps, int *pf)
{
int r = -1;
if(!present_init || bus < 1 || bus > NBUSES)
return -1;
acquire(&present_lock);
if(buses[bus - 1]){
*pb = bus_pci[bus - 1][0];
*ps = bus_pci[bus - 1][1];
*pf = bus_pci[bus - 1][2];
r = 0;
}
release(&present_lock);
return r;
}
// The nth interface (alternate setting 0) in `dev`'s configuration descriptor.
// Returns 0, or -1 if there are fewer. The driver is whatever is bound to an
// interface of that number, on the device itself or on one of its interface
// slots.
static int
if_desc(struct usb_dev *dev, int nth, struct usb_if_info *o)
{
int off = 0, count = 0;
while(off + 2 <= dev->conflen){
uint8 len = dev->conf[off];
uint8 type = dev->conf[off + 1];
if(len < 2 || off + len > dev->conflen)
break;
if(type == USB_DT_INTERFACE && len >= (int)sizeof(struct usb_interface_desc)){
struct usb_interface_desc *id = (struct usb_interface_desc *)&dev->conf[off];
if(id->bAlternateSetting == 0 && count++ == nth){
memset(o, 0, sizeof *o);
o->num = id->bInterfaceNumber;
o->cls = id->bInterfaceClass;
o->subcls = id->bInterfaceSubClass;
o->proto = id->bInterfaceProtocol;
o->nendp = id->bNumEndpoints;
if(dev->driver && dev->iface.bInterfaceNumber == o->num)
safestrcpy(o->driver, dev->driver, sizeof o->driver);
for(int k = 0; !o->driver[0] && k < NIFDEV; k++)
if(ifdevs[k].in_use && ifdevs[k].base == dev && ifdevs[k].driver &&
ifdevs[k].iface.bInterfaceNumber == o->num)
safestrcpy(o->driver, ifdevs[k].driver, sizeof o->driver);
return 0;
}
}
off += len;
}
return -1;
}
int
usb_if_nth(int bus, int devnum, int n, struct usb_if_info *o)
{
int r = -1;
if(!present_init)
return -1;
acquire(&present_lock);
for(int i = 0; i < NPRESENT; i++){
struct usb_dev *d = present[i];
if(d == 0 || d->slot != devnum)
continue;
int b = 1;
for(int k = 0; k < NBUSES; k++)
if(buses[k] == d->hc){ b = k + 1; break; }
if(b != bus)
continue;
r = if_desc(d, n, o);
break;
}
release(&present_lock);
return r;
}
static void
present_add(struct usb_dev *dev)
{
present_init_once();
acquire(&present_lock);
for(int i = 0; i < NPRESENT; i++)
if(present[i] == 0){ present[i] = dev; break; }
bus_number(dev->hc);
release(&present_lock);
}
static void
present_del(struct usb_dev *dev)
{
if(!present_init)
return;
acquire(&present_lock);
for(int i = 0; i < NPRESENT; i++)
if(present[i] == dev) present[i] = 0;
release(&present_lock);
}
static void
put_num(char *out, int *n, int cap, int v)
{
char t[12];
int k = 0;
do { t[k++] = '0' + v % 10; v /= 10; } while(v);
while(k && *n < cap - 1) out[(*n)++] = t[--k];
out[*n] = 0;
}
// Copy `d`'s identity out. Caller holds present_lock, or is the one thread
// that owns `d` (attach and detach).
static void
fill_info(struct usb_dev *d, struct usb_info *o)
{
memset(o, 0, sizeof *o);
o->bus = 1;
for(int b = 0; b < NBUSES; b++)
if(buses[b] == d->hc){ o->bus = b + 1; break; }
o->devnum = d->slot;
o->speed = d->speed;
o->vid = d->desc.idVendor;
o->pid = d->desc.idProduct;
o->bcd = d->desc.bcdDevice;
o->cls = d->desc.bDeviceClass;
o->subcls = d->desc.bDeviceSubClass;
o->proto = d->desc.bDeviceProtocol;
o->nconf = d->desc.bNumConfigurations;
o->bcdusb = d->desc.bcdUSB;
o->maxp0 = d->desc.bMaxPacketSize0;
if(d->conflen >= 9){
o->nif = d->conf[4];
o->confval = d->conf[5];
o->attrs = d->conf[7];
// bMaxPower counts 2 mA below SuperSpeed and 8 mA from it.
o->maxpower = (uint16)(d->conf[8] * (d->speed >= 4 ? 8 : 2));
}
// "<bus>-<root port>[.<hub port>...]": the route string holds one
// downstream port per tier below the root, a nibble each.
int n = 0, cap = (int)sizeof o->path;
put_num(o->path, &n, cap, o->bus);
o->path[n++] = '-';
put_num(o->path, &n, cap, d->port);
for(int t = 0; t < d->tier && n < cap - 3; t++){
o->path[n++] = '.';
put_num(o->path, &n, cap, (int)((d->route >> (4 * t)) & 0xF));
}
const char *drv = d->driver;
for(int k = 0; !drv && k < NIFDEV; k++)
if(ifdevs[k].in_use && ifdevs[k].base == d)
drv = ifdevs[k].driver;
if(drv)
safestrcpy(o->driver, drv, sizeof o->driver);
}
int
usb_info_nth(int idx, struct usb_info *o)
{
if(!present_init)
return -1;
acquire(&present_lock);
int seen = 0;
for(int i = 0; i < NPRESENT; i++){
struct usb_dev *d = present[i];
if(d == 0 || seen++ != idx)
continue;
fill_info(d, o);
release(&present_lock);
return 0;
}
release(&present_lock);
return -1;
}
int
usb_nbuses(void)
{
int n = 0;
if(!present_init)
return 0;
acquire(&present_lock);
for(int b = 0; b < NBUSES; b++)
if(buses[b])
n++;
release(&present_lock);
return n;
}
static void announce_ifaces(struct usb_dev *dev, const char *action);
// Announce `dev` to udev. Attach says "add" once the device is in the table,
// so a rule that opens its /sys directory finds it; detach says "remove"
// first, while the directory still exists.
static void
announce(struct usb_dev *dev, const char *action)
{
struct usb_info u;
if(!present_init)
return;
acquire(&present_lock);
fill_info(dev, &u);
release(&present_lock);
sysfs_usb_uevent(&u, action);
}
void
usb_attach(struct usb_dev *dev)
{
struct usb_config_desc *cd;
void *buf = get_scratch();
int total;
if(buf == 0)
return;
present_add(dev);
announce(dev, "add");
// Nine bytes first, because wTotalLength is in them and there is no other
// way to know how much to ask for.
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, 9) < 0){
cprintf("usb: slot %d would not give up its configuration\n", dev->slot);
return;
}
cd = buf;
total = cd->wTotalLength;
// A configuration that does not contain its own descriptor is not one we
// can do anything with, and reading it as a length would walk off the end.
if(cd->bDescriptorType != USB_DT_CONFIG || total < (int)sizeof(*cd)){
cprintf("usb: slot %d returned a malformed configuration descriptor\n",
dev->slot);
return;
}
if(total > USB_MAXCONF)
total = USB_MAXCONF;
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, total) < 0)
return;
memmove(dev->conf, buf, total);
dev->conflen = total;
// 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){
cprintf("usb: slot %d refused SET_CONFIGURATION\n", dev->slot);
return;
}
// 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;
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;
}
kinfo("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){
kinfo("usb: no driver for this interface\n");
if(n > 0)
d->in_use = 0; // nothing will ever use it; give it back
}
}
// Every interface, once all of them have been offered to the drivers, so the
// event says which one was taken.
announce_ifaces(dev, "add");
}
static void
announce_ifaces(struct usb_dev *dev, const char *action)
{
struct usb_info u;
struct usb_if_info ii;
if(!present_init)
return;
acquire(&present_lock);
fill_info(dev, &u);
release(&present_lock);
for(int n = 0; if_desc(dev, n, &ii) == 0; n++)
sysfs_usb_if_uevent(&u, &ii, action);
}
// 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)
{
announce_ifaces(dev, "remove");
announce(dev, "remove");
present_del(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;
}