SchOS/kernel/drivers/usb/core/usb.c
Nexoniarz d0f7acf9eb 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.
2026-08-09 13:47:01 +02:00

209 lines
6.8 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"
// 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.
static void
parse_config(struct usb_dev *dev)
{
int off = 0;
int seen_iface = 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)){
if(seen_iface)
break; // first interface only, for now
memmove(&dev->iface, &dev->conf[off], 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;
}
}
// ---------------------------------------------------------------------------
// 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;
return 1;
}
// ---------------------------------------------------------------------------
void
usb_attach(struct usb_dev *dev)
{
struct usb_config_desc *cd;
void *buf = get_scratch();
int total;
if(buf == 0)
return;
// 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;
parse_config(dev);
// 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;
}
cprintf("usb: interface class %d.%d protocol %d, %d endpoint%s%s%s%s\n",
dev->iface.bInterfaceClass, dev->iface.bInterfaceSubClass,
dev->iface.bInterfaceProtocol, dev->iface.bNumEndpoints,
dev->iface.bNumEndpoints == 1 ? "" : "s",
dev->has_intr_in ? ", interrupt IN" : "",
dev->has_bulk_in ? ", bulk IN" : "",
dev->has_bulk_out ? ", bulk OUT" : "");
usb_bind_all(dev);
if(dev->driver == 0)
cprintf("usb: no driver for this device\n");
}