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:
Nexoniarz 2026-08-09 13:47:01 +02:00
commit d0f7acf9eb
275 changed files with 51417 additions and 0 deletions

View 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");
}

View file

@ -0,0 +1,305 @@
// USB HID, boot protocol: keyboards and mice.
//
// Boot protocol is the reason a USB keyboard works in a BIOS setup screen
// before any operating system exists. A HID device normally describes its
// reports with a report descriptor -- a small stack language that has to be
// parsed to know which bit means which key -- but a device claiming the boot
// subclass also promises to support one fixed layout on request. Eight bytes
// for a keyboard, three for a mouse, defined in the HID specification and
// identical on every device.
//
// That is worth taking. A full report-descriptor parser is a real piece of
// work and buys nothing for a keyboard; it is what a game controller or a
// touchscreen will need, and it can arrive with them.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "dma.h"
#include "usb.h"
#include "input.h"
#define NHID 4
struct hid {
int in_use;
int proto; // USB_HID_PROTO_KEYBOARD or _MOUSE
uchar prev[8]; // the previous report, for edge detection
};
static struct hid hids[NHID];
// ---------------------------------------------------------------------------
// Keyboard
//
// The report is: modifiers, a reserved byte, then up to six usage IDs of keys
// currently held. It is a *state*, not an event -- a key that stays down
// appears in every report until it is released, so a character is emitted
// only for usages that were not in the previous report. Sending one per
// report instead would repeat every held key a few hundred times a second.
// ---------------------------------------------------------------------------
#define HID_MOD_CTRL 0x11 // left | right
#define HID_MOD_SHIFT 0x22
#define HID_MOD_ALT 0x44
// Usage IDs 0x00-0x38 of the Keyboard/Keypad page. 0 means "nothing to type".
static const uchar hid_unshifted[0x39] = {
0, 0, 0, 0,
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z',
'1','2','3','4','5','6','7','8','9','0',
'\n', 0x1B, '\b', '\t', ' ',
'-','=','[',']','\\','#',';','\'','`',',','.','/',
};
static const uchar hid_shifted[0x39] = {
0, 0, 0, 0,
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'!','@','#','$','%','^','&','*','(',')',
'\n', 0x1B, '\b', '\t', ' ',
'_','+','{','}','|','~',':','"','~','<','>','?',
};
// Characters decoded from a report, waiting to be handed to the console.
//
// The console pulls input through a getc function rather than being pushed
// to (consoleintr takes one), which is exactly the shape the PS/2 driver
// needs and exactly the wrong shape here -- a USB report arrives complete,
// with up to six keys in it. So the report is decoded into this queue and
// the console then drains it through the same path as any other keyboard,
// which means the entire line discipline is reused rather than reimplemented.
#define KQSIZE 64
static struct {
uchar buf[KQSIZE];
uint r, w;
} kq;
static void
kq_put(int c)
{
if(kq.w - kq.r < KQSIZE)
kq.buf[kq.w++ % KQSIZE] = c;
}
static int
usb_kbd_getc(void)
{
if(kq.r == kq.w)
return -1;
return kq.buf[kq.r++ % KQSIZE];
}
// Was this usage in that report?
static int
was_down(const uchar *prev, uchar usage)
{
int i;
for(i = 2; i < 8; i++)
if(prev[i] == usage)
return 1;
return 0;
}
// Turn the difference between two reports into press and release events for
// /dev/input/event0.
//
// The console path below cannot do this job: it emits a character per new key
// and nothing at all for a release, because a character stream has no way to
// say "released". A program that wants to know a key is being *held* -- which
// is every game and every compositor -- needs exactly the events this makes.
static void
keyboard_events(const uchar *prev, const uchar *rep)
{
int i, bit, n = 0;
for(bit = 0; bit < 8; bit++){
int now = (rep[0] >> bit) & 1;
int then = (prev[0] >> bit) & 1;
if(now != then){
input_report(INPUT_KBD, EV_KEY, input_hid_keycode(0xE0 + bit), now);
n++;
}
}
for(i = 2; i < 8; i++){
uchar u = rep[i];
if(u && !was_down(prev, u)){
int k = input_hid_keycode(u);
if(k){ input_report(INPUT_KBD, EV_KEY, k, 1); n++; }
}
}
for(i = 2; i < 8; i++){
uchar u = prev[i];
if(u && !was_down(rep, u)){
int k = input_hid_keycode(u);
if(k){ input_report(INPUT_KBD, EV_KEY, k, 0); n++; }
}
}
if(n)
input_sync(INPUT_KBD);
}
static void
keyboard_report(struct hid *h, const uchar *rep, int len)
{
uchar mods = rep[0];
int i, c;
if(len < 8)
return;
keyboard_events(h->prev, rep);
// Grabbed: the events above still went to /dev/input/event0, but nothing
// reaches the console. Note prev[] is updated before returning, or the next
// report would look like every held key had just been pressed again.
if(input_is_grabbed(INPUT_KBD)){
memmove(h->prev, rep, 8);
return;
}
for(i = 2; i < 8; i++){
uchar usage = rep[i];
if(usage == 0 || usage >= 0x39)
continue;
if(was_down(h->prev, usage))
continue; // still held from last time, not a new press
c = (mods & HID_MOD_SHIFT) ? hid_shifted[usage] : hid_unshifted[usage];
if(c == 0)
continue;
// Ctrl folds a letter down into its control character, which is how ^C
// and ^D reach the line discipline at all.
if(mods & HID_MOD_CTRL){
if(c >= 'a' && c <= 'z')
c = c - 'a' + 1;
else if(c >= 'A' && c <= 'Z')
c = c - 'A' + 1;
else
continue;
}
kq_put(c);
}
memmove(h->prev, rep, 8);
consoleintr(usb_kbd_getc);
}
// ---------------------------------------------------------------------------
// Mouse
// ---------------------------------------------------------------------------
static void
mouse_report(struct hid *h, const uchar *rep, int len)
{
int dx, dy;
(void)h;
if(len < 3)
return;
// The deltas are signed bytes. Reading them as unsigned is the classic way
// to get a pointer that only travels right and down.
dx = (signed char)rep[1];
dy = (signed char)rep[2];
// HID's Y axis points down, and so does the screen's. The PS/2 protocol's
// points up, which is why mouseintr subtracts where this adds.
mouse_inject(dx, dy, rep[0] & 0x07);
}
// ---------------------------------------------------------------------------
static void
hid_complete(struct usb_dev *dev, void *buf, int len)
{
struct hid *h = dev->drvdata;
if(h == 0)
return;
if(h->proto == USB_HID_PROTO_KEYBOARD)
keyboard_report(h, buf, len);
else
mouse_report(h, buf, len);
}
static int
hid_request(struct usb_dev *dev, int request, int value)
{
struct usb_setup s;
s.bmRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
s.bRequest = request;
s.wValue = value;
s.wIndex = dev->iface.bInterfaceNumber;
s.wLength = 0;
return dev->ops->control(dev, &s, 0, 0);
}
static int
hid_probe(struct usb_dev *dev)
{
struct hid *h = 0;
int i, proto = dev->iface.bInterfaceProtocol;
if(!dev->has_intr_in){
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
return -1;
}
for(i = 0; i < NHID; i++)
if(!hids[i].in_use){ h = &hids[i]; break; }
if(h == 0)
return -1;
// Ask for the fixed layout explicitly. A device that supports the boot
// subclass still starts in report protocol, so without this the reports
// arrive in whatever shape its report descriptor declares -- which we have
// not read and cannot interpret.
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
return -1;
}
// Idle rate 0 = "report only when something changes". The default is to
// resend the current state every few milliseconds forever, which for a
// keyboard means a constant stream of interrupts saying nothing.
hid_request(dev, USB_HID_SET_IDLE, 0);
if(dev->ops->open_ep(dev, &dev->intr_in) < 0)
return -1;
h->in_use = 1;
h->proto = proto;
dev->drvdata = h;
dev->complete = hid_complete;
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
// has nowhere to put a report, so nothing would ever arrive.
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
return -1;
cprintf("usb-hid: %s on slot %d, endpoint 0x%x every %d ms\n",
proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
proto == USB_HID_PROTO_MOUSE ? "mouse" : "device",
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
return 0;
}
// Boot subclass only. A HID device that does not claim it needs its report
// descriptor parsed, and we would have nothing useful to do with it.
const struct usb_driver usb_hid_driver = {
.name = "usb-hid",
.class = USB_CLASS_HID,
.subclass = USB_HID_SUBCLASS_BOOT,
.protocol = 0xFF,
.devclass = DEVCLASS_INPUT,
.probe = hid_probe,
};

View file

@ -0,0 +1,185 @@
// USB hub class driver.
//
// A hub is what makes USB a tree rather than a handful of sockets, and
// nothing about driving one is special: every operation is an ordinary
// control request sent to the hub as a device. "Reset port 3" is a
// SET_FEATURE(PORT_RESET, wIndex=3). The hub has an interrupt endpoint too,
// which reports which ports have changed, but that is only needed for
// hotplug -- at boot it is enough to walk every port and look.
//
// What *is* special is what the host controller has to be told. xHCI does not
// discover topology; it is informed of it. A device three hubs deep is
// addressed by a route string naming the port taken at each tier, and a
// slow device behind a fast hub is scheduled against that hub's transaction
// translator rather than against the device. So this driver's real job is
// less "operate the hub" than "tell the controller what shape the tree is".
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "dma.h"
#include "usb.h"
#define MAX_PORTS 15 // per hub; more than any real bus hub has
static int
hub_request(struct usb_dev *dev, int type, int req, int value, int index,
void *buf, int len)
{
struct usb_setup s;
s.bmRequestType = type;
s.bRequest = req;
s.wValue = value;
s.wIndex = index;
s.wLength = len;
return dev->ops->control(dev, &s, buf, len);
}
static int
port_set_feature(struct usb_dev *dev, int port, int feature)
{
return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_SET_FEATURE, feature, port, 0, 0);
}
static int
port_clear_feature(struct usb_dev *dev, int port, int feature)
{
return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_CLEAR_FEATURE, feature, port, 0, 0);
}
// Returns wPortStatus, or -1. The change bits are in the upper half and are
// deliberately dropped here -- they are acknowledged separately.
static int
port_status(struct usb_dev *dev, int port, void *scratch)
{
uint16 *st = scratch;
if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_GET_STATUS, 0, port, scratch, 4) < 0)
return -1;
return st[0];
}
// Bring up whatever is plugged into one downstream port.
static void
hub_port(struct usb_dev *hub, int port, void *scratch)
{
int st, speed, i;
st = port_status(hub, port, scratch);
if(st < 0 || !(st & PORTSTAT_CONNECTION))
return;
// Acknowledge the connect-change bit before resetting. Leaving it set is
// harmless here but confuses any later attempt to use these bits for
// hotplug, which is what they are actually for.
port_clear_feature(hub, port, PORT_FEAT_C_CONNECTION);
if(port_set_feature(hub, port, PORT_FEAT_RESET) < 0){
cprintf("usb-hub: port %d would not reset\n", port);
return;
}
// A hub does not report reset completion by clearing the request; it sets
// C_PORT_RESET and enables the port. Poll for the enable rather than
// guessing at a delay, because the time depends on the device.
for(i = 0; i < 200; i++){
microdelay(1000);
st = port_status(hub, port, scratch);
if(st >= 0 && !(st & PORTSTAT_RESET) && (st & PORTSTAT_ENABLE))
break;
}
port_clear_feature(hub, port, PORT_FEAT_C_RESET);
st = port_status(hub, port, scratch);
if(st < 0 || !(st & PORTSTAT_ENABLE)){
cprintf("usb-hub: port %d did not enable after reset\n", port);
return;
}
// Speed comes out of two status bits rather than a field, and the absence
// of both means full speed.
if(st & PORTSTAT_LOWSPEED)
speed = USB_SPEED_LOW;
else if(st & PORTSTAT_HIGHSPEED)
speed = USB_SPEED_HIGH;
else
speed = USB_SPEED_FULL;
// A device needs time to settle after reset before it will answer on
// endpoint 0. The specification calls this TRSTRCY and allows 10 ms.
microdelay(20000);
hub->ops->attach(hub, port, speed);
}
static int
hub_probe(struct usb_dev *dev)
{
struct usb_hub_desc *hd;
void *scratch;
int nports, mtt, i;
scratch = dma_alloc(64, 64);
if(scratch == 0)
return -1;
// The hub descriptor is class-specific, so it comes from a class GET
// request rather than the standard one -- same request number, different
// recipient and type.
if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE,
USB_REQ_GET_DESCRIPTOR, USB_DT_HUB << 8, 0,
scratch, sizeof(struct usb_hub_desc)) < 0){
cprintf("usb-hub: no hub descriptor\n");
dma_free(scratch, 64);
return -1;
}
hd = scratch;
nports = hd->bNbrPorts;
if(nports < 1 || nports > MAX_PORTS){
cprintf("usb-hub: %d ports is not a number of ports\n", nports);
dma_free(scratch, 64);
return -1;
}
mtt = (hd->wHubCharacteristics & HUB_CHAR_TT_MASK) != 0;
cprintf("usb-hub: %d ports on slot %d\n", nports, dev->slot);
// Tell the controller *before* powering anything on. Addressing a device
// behind a slot the controller does not know is a hub fails, and it fails
// at Address Device with a completion code rather than anywhere useful.
if(dev->ops->set_hub(dev, nports, mtt) < 0){
dma_free(scratch, 64);
return -1;
}
// Ports come up unpowered. bPwrOn2PwrGood is how long the hub says it needs
// after switching power on before a device behind it is worth talking to,
// in 2 ms units -- a number the hub knows and we do not.
for(i = 1; i <= nports; i++)
port_set_feature(dev, i, PORT_FEAT_POWER);
microdelay((hd->bPwrOn2PwrGood * 2 + 10) * 1000);
for(i = 1; i <= nports; i++)
hub_port(dev, i, scratch);
dma_free(scratch, 64);
return 0;
}
// Any hub, whatever its protocol: 0 is a full-speed hub, 1 single-TT, 2
// multi-TT, and they differ only in how transactions are translated, which
// is the controller's problem rather than ours.
const struct usb_driver usb_hub_driver = {
.name = "usb-hub",
.class = USB_CLASS_HUB,
.subclass = 0xFF,
.protocol = 0xFF,
.devclass = DEVCLASS_BUS,
.probe = hub_probe,
};

View file

@ -0,0 +1,395 @@
// USB mass storage: bulk-only transport, SCSI transparent command set.
//
// Almost every USB stick, external drive and card reader is this, and the
// name is a fair description of the whole protocol: there are exactly two
// bulk endpoints and everything happens by sending three things over them.
//
// command a 31-byte wrapper (CBW) carrying a SCSI command block, OUT
// data the payload, IN or OUT, if the command has one
// status a 13-byte wrapper (CSW) saying whether it worked, IN
//
// That is the entire transport. The interesting content is SCSI, which is
// what the wrapper carries and what the device actually speaks -- so a USB
// stick is a SCSI disk that happens to be reached over USB, and the commands
// below (INQUIRY, READ CAPACITY, READ(10), WRITE(10)) are the same ones a
// SCSI drive answered in 1990.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "buf.h"
#include "dma.h"
#include "usb.h"
#define CBW_SIGNATURE 0x43425355 // "USBC"
#define CSW_SIGNATURE 0x53425355 // "USBS"
struct cbw {
uint32 signature;
uint32 tag;
uint32 data_len;
uint8 flags; // 0x80 = device-to-host
uint8 lun;
uint8 cb_len; // 1..16
uint8 cb[16];
} __attribute__((packed));
struct csw {
uint32 signature;
uint32 tag;
uint32 residue;
uint8 status; // 0 = passed, 1 = failed, 2 = phase error
} __attribute__((packed));
// SCSI opcodes
#define SCSI_TEST_UNIT_READY 0x00
#define SCSI_REQUEST_SENSE 0x03
#define SCSI_INQUIRY 0x12
#define SCSI_READ_CAPACITY10 0x25
#define SCSI_READ10 0x28
#define SCSI_WRITE10 0x2A
#define SECTOR_SIZE 512
static struct {
struct usb_dev *dev;
int present;
uint32 nsectors;
uint32 sector_size;
uint32 tag;
void *xfer; // DMA bounce buffer, one block
struct cbw *cbw; // allocated once, not per command
struct csw *csw;
struct sleeplock lock; // one command at a time
} ums;
// SCSI is big-endian, everywhere, for everything. x86 is not.
static void
be32(uint8 *p, uint32 v)
{
p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v;
}
static uint32
rd_be32(const uint8 *p)
{
return ((uint32)p[0] << 24) | ((uint32)p[1] << 16) |
((uint32)p[2] << 8) | p[3];
}
// One complete command-data-status exchange.
//
// `buf`, when there is data, must be DMA memory -- it is handed to the
// controller as a physical address.
static int
bot_command(const uint8 *cdb, int cdb_len, void *buf, int len, int in)
{
struct usb_dev *dev = ums.dev;
struct cbw *cbw = ums.cbw;
struct csw *csw = ums.csw;
uint32 tag = ++ums.tag;
int i, data_ok = 1;
memset(cbw, 0, sizeof(*cbw));
cbw->signature = CBW_SIGNATURE;
cbw->tag = tag;
cbw->data_len = len;
cbw->flags = in ? 0x80 : 0x00;
cbw->lun = 0;
cbw->cb_len = cdb_len;
for(i = 0; i < cdb_len && i < 16; i++)
cbw->cb[i] = cdb[i];
if(dev->ops->bulk(dev, dev->bulk_out.bEndpointAddress, cbw,
sizeof(*cbw)) != (int)sizeof(*cbw)){
cprintf("usb-storage: the device would not take the command\n");
return -1;
}
if(len > 0){
int addr = in ? dev->bulk_in.bEndpointAddress
: dev->bulk_out.bEndpointAddress;
if(dev->ops->bulk(dev, addr, buf, len) < 0)
data_ok = 0;
}
// The status wrapper is not optional and not advisory: a device that has
// sent its data is still waiting to be asked how it went.
//
// It is read even when the data phase failed, which is the whole point --
// returning early there would leave the device still holding a status it
// has not been asked for, so the *next* command's data phase would receive
// this command's status instead. One failed read would turn into every
// subsequent read failing, which is a far worse fault than the one that
// started it, and one that looks nothing like its cause.
memset(csw, 0, sizeof(*csw));
if(dev->ops->bulk(dev, dev->bulk_in.bEndpointAddress, csw,
sizeof(*csw)) != (int)sizeof(*csw)){
cprintf("usb-storage: no status returned for command 0x%x\n", cdb[0]);
return -1;
}
if(csw->signature != CSW_SIGNATURE || csw->tag != tag){
cprintf("usb-storage: status wrapper does not match the command\n");
return -1;
}
if(csw->status != 0){
cprintf("usb-storage: command 0x%x failed (status %d)\n",
cdb[0], csw->status);
return -1;
}
if(!data_ok)
return -1;
// Residue is what was *not* transferred, and a device is allowed to report
// more than was asked for only if it is broken -- clamp rather than return
// a negative length that a caller would read as an error code.
if(csw->residue > (uint32)len)
return -1;
return len - (int)csw->residue;
}
static int
scsi_inquiry(char *vendor, char *product)
{
uint8 cdb[6] = { SCSI_INQUIRY, 0, 0, 0, 36, 0 };
uint8 *r = ums.xfer;
int i;
if(bot_command(cdb, sizeof(cdb), r, 36, 1) < 0)
return -1;
// Bytes 8-15 are the vendor, 16-31 the product, space padded and not
// NUL-terminated -- the only fixed-width strings left in computing.
for(i = 0; i < 8; i++)
vendor[i] = r[8 + i];
vendor[8] = 0;
for(i = 0; i < 16; i++)
product[i] = r[16 + i];
product[16] = 0;
return 0;
}
static int
scsi_read_capacity(void)
{
uint8 cdb[10] = { SCSI_READ_CAPACITY10, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8 *r = ums.xfer;
if(bot_command(cdb, sizeof(cdb), r, 8, 1) < 0)
return -1;
// The first word is the address of the *last* sector, not the count.
ums.nsectors = rd_be32(r) + 1;
ums.sector_size = rd_be32(r + 4);
return 0;
}
static int
scsi_rw(uint32 lba, int count, void *buf, int write)
{
uint8 cdb[10];
memset(cdb, 0, sizeof(cdb));
cdb[0] = write ? SCSI_WRITE10 : SCSI_READ10;
be32(&cdb[2], lba);
cdb[7] = (count >> 8) & 0xFF;
cdb[8] = count & 0xFF;
return bot_command(cdb, sizeof(cdb), buf, count * SECTOR_SIZE, !write);
}
// ---------------------------------------------------------------------------
// Block device
// ---------------------------------------------------------------------------
int
usb_storage_present(void)
{
return ums.present;
}
// Sync one buffer-cache block with the stick. Same contract as iderw() and
// ramdiskrw(): on a read fill b->data and set B_VALID, on a write push it out
// and clear B_DIRTY.
void
usb_storage_rw(struct buf *b)
{
int per_block = BSIZE / SECTOR_SIZE;
uint32 lba = b->blockno * per_block;
int write = (b->flags & B_DIRTY) != 0;
int i, n = -1;
if(!holdingsleep(&b->lock))
panic("usb_storage_rw: buf not locked");
if(!ums.present)
panic("usb_storage_rw: no USB disk");
if(lba + per_block > ums.nsectors)
panic("usb_storage_rw: block out of range");
// One command at a time. Bulk-only transport has no tagging beyond the CBW
// tag and no queueing: a second command sent while the first is mid-phase
// desynchronises the endpoint and every command after it fails.
acquiresleep(&ums.lock);
// Removable media fails transiently in ways a fixed disk does not, so a
// couple of retries are worth having before giving up.
for(i = 0; i < 3; i++){
if(write)
memmove(ums.xfer, b->data, BSIZE);
n = scsi_rw(lba, per_block, ums.xfer, write);
if(n == BSIZE)
break;
cprintf("usb-storage: %s of block %d failed, retrying\n",
write ? "write" : "read", b->blockno);
}
releasesleep(&ums.lock);
// A read that failed must not return quietly. bread() hands the buffer
// straight back to its caller, which has no way to ask whether it worked --
// so leaving B_VALID clear would deliver whatever the buffer happened to
// contain as though it were the block on disk. Corrupting a filesystem
// silently is worse than stopping.
if(n != BSIZE)
panic("usb_storage_rw: transfer failed");
if(!write)
memmove(b->data, ums.xfer, BSIZE);
b->flags |= B_VALID;
b->flags &= ~B_DIRTY;
}
// Read block 1 through the ordinary buffer cache and report what is there.
//
// This is the only test that proves the whole path rather than a piece of it:
// bread() -> bdevrw() -> usb_storage_rw() -> bulk-only transport -> SCSI
// READ(10) -> an interrupt -> a sleeping process woken with data. Everything
// before it -- INQUIRY answering, a plausible capacity -- can be true of a
// device that cannot actually read a sector.
//
// Reached with `echo s > /proc/sysrq`.
void
usb_storage_dump(void)
{
struct buf *b;
struct superblock *sb;
if(!ums.present){
cprintf("usb-storage: no disk\n");
return;
}
cprintf("usb-storage: %d sectors of %d bytes\n", ums.nsectors,
ums.sector_size);
b = bread(USBDISK_DEV, 1);
sb = (struct superblock*)b->data;
cprintf("usb-storage: block 1 magic %x size %d nblocks %d ninodes %d\n",
sb->magic, sb->size, sb->nblocks, sb->ninodes);
brelse(b);
}
// ---------------------------------------------------------------------------
static int
storage_probe(struct usb_dev *dev)
{
char vendor[9], product[17];
int i;
if(ums.present)
return -1; // one disk is enough for now
if(!dev->has_bulk_in || !dev->has_bulk_out){
cprintf("usb-storage: needs a bulk endpoint in each direction\n");
return -1;
}
initsleeplock(&ums.lock, "usbms");
ums.dev = dev;
// One block, used for every transfer. bread() hands us b->data, which lives
// in the buffer cache rather than in DMA memory, so everything bounces
// through here. The wrappers are allocated once too: they are used on every
// single I/O, and churning the DMA extent list twice per block read would
// fragment an arena that has no business fragmenting.
ums.xfer = dma_alloc(BSIZE, 4096);
ums.cbw = dma_alloc(sizeof(struct cbw), 64);
ums.csw = dma_alloc(sizeof(struct csw), 64);
if(ums.xfer == 0 || ums.cbw == 0 || ums.csw == 0)
goto fail;
// Deliberately OUT first, which is the order that used to break.
//
// The slot context's Context Entries field must cover the highest DCI in
// use, and xhci_open_ep originally wrote whichever endpoint it was adding.
// Bulk OUT here has the higher DCI, so opening it first and then IN would
// *lower* the field and silently invalidate the endpoint that was already
// configured. Opening IN first hid that. Now that xhci_open_ep tracks the
// maximum itself the order genuinely does not matter -- and doing it in the
// formerly-broken order is what keeps that true.
if(dev->ops->open_ep(dev, &dev->bulk_out) < 0 ||
dev->ops->open_ep(dev, &dev->bulk_in) < 0)
goto fail;
// Deliberately no GET MAX LUN. It is answered with a STALL by
// single-LUN devices, and recovering from a STALL on endpoint 0 needs a
// Reset Endpoint command this driver does not implement yet. LUN 0 is the
// only one used below, so asking would gain nothing and could wedge us.
// A device that has just been configured is usually "not ready" for the
// first command or two while it spins up or reads its own media.
for(i = 0; i < 5; i++){
uint8 cdb[6] = { SCSI_TEST_UNIT_READY, 0, 0, 0, 0, 0 };
if(bot_command(cdb, sizeof(cdb), 0, 0, 0) >= 0)
break;
microdelay(100000);
}
if(scsi_inquiry(vendor, product) < 0)
goto fail;
if(scsi_read_capacity() < 0)
goto fail;
if(ums.sector_size != SECTOR_SIZE){
cprintf("usb-storage: %d-byte sectors are not supported\n",
ums.sector_size);
goto fail;
}
if(ums.nsectors == 0){
cprintf("usb-storage: device reports no capacity\n");
goto fail;
}
ums.present = 1;
cprintf("usb-storage: %s %s, %d sectors (%d MB)\n",
vendor, product, ums.nsectors,
(int)((uint64)ums.nsectors * SECTOR_SIZE / (1024 * 1024)));
return 0;
fail:
// Give the memory back and forget the device. Leaving ums.dev set would let
// a later block read reach a device we decided we could not drive.
if(ums.xfer) dma_free(ums.xfer, BSIZE);
if(ums.cbw) dma_free(ums.cbw, sizeof(struct cbw));
if(ums.csw) dma_free(ums.csw, sizeof(struct csw));
ums.xfer = 0;
ums.cbw = 0;
ums.csw = 0;
ums.dev = 0;
return -1;
}
const struct usb_driver usb_storage_driver = {
.name = "usb-storage",
.class = USB_CLASS_MASS_STORAGE,
.subclass = USB_MSC_SUBCLASS_SCSI,
.protocol = USB_MSC_PROTO_BOT,
.devclass = DEVCLASS_STORAGE,
.probe = storage_probe,
};