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.
209 lines
6.8 KiB
C
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");
|
|
}
|