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.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
209
kernel/drivers/usb/core/usb.c
Normal file
209
kernel/drivers/usb/core/usb.c
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
// 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");
|
||||
}
|
||||
Loading…
Reference in a new issue