SchOS/kernel/drivers/usb/core/usb.c
Nexoniarz 4805dbd892 limits: 256 PCI functions, 128 sockets and devices, 64 USB interfaces
The fixed tables that a bigger machine than the one they were written
on reaches first, each raised to where it costs little:

  NPCI       64 -> 256   a desktop with cards and Thunderbolt passes 64,
                         and the devices past it -- often the disk or
                         NIC last in bus order -- were ignored; 44 KB
  NSOCK      32 -> 128   Unix sockets, which a desktop session uses by
                         the dozen; 4.9 KB each, 470 KB in all
  NDEVICE    32 -> 128   120 bytes each
  NIFDEV     16 -> 64    one dock is a hub, Ethernet, audio and a card
                         reader at two or three interfaces apiece
  NETIF_MAX   4 -> 8

Device names took one digit and clamped there, so the tenth device of
a kind and every one after it was named "...9". Two digits now.

Checked under QEMU with two bridges of test devices: 68 functions,
all in lspci, /proc/pci and /sys/bus/pci.

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

381 lines
12 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 "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "spinlock.h"
#include "dma.h"
#include "usb.h"
#include "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/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 void
present_add(struct usb_dev *dev)
{
if(!present_init){
initlock(&present_lock, "usbpresent");
present_init = 1;
}
acquire(&present_lock);
for(int i = 0; i < NPRESENT; i++)
if(present[i] == 0){ present[i] = dev; break; }
for(int b = 0; b < NBUSES; b++){
if(buses[b] == dev->hc) break;
if(buses[b] == 0){ buses[b] = dev->hc; break; }
}
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;
}
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;
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;
// "<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);
release(&present_lock);
return 0;
}
release(&present_lock);
return -1;
}
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);
// 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;
}
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)
{
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;
}