usb: plug and play -- hot-plug, unplug, every interface, real mice
Devices can now arrive and leave after boot. A "usbhotplug" kernel thread handles root-port changes, which it is told about by Port Status Change events and also checks for every quarter second. It also handles changes behind hubs, which each hub reports on its status-change endpoint. An unplugged device has its endpoints closed and any transfer in flight failed. Then its drivers' disconnect hooks run, the slot is disabled and its memory is freed. Everything behind a hub that goes goes with it. This needed the interrupt handler to file command and endpoint-0 completions for their waiters, so that enumeration can run while keyboards interrupt. On real hardware: the controller is taken from the firmware through USB Legacy Support before reset, with SMIs off. On Intel 7/8/9 series parts the ports are switched over from EHCI, which is where firmware leaves them. Every interface of a device is offered to the class drivers, not just the first, so a receiver's mouse on interface 1 gets a driver. Configure Endpoint now copies the slot context from the controller. Before, it rebuilt it and lost the route string and TT fields of devices behind hubs. HID: pointers are read in report protocol from their report descriptor. That covers buttons, X/Y and wheel, relative or absolute, and report IDs. Boot protocol is kept as the fallback for mice and for keyboards. Non-pointer HID interfaces are declined rather than treated as tablets. USB keyboards get software key repeat. Mass storage handles up to four sticks, each unregistered from the block layer when it is pulled out, along with its cached blocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
This commit is contained in:
parent
c1e52e172f
commit
fa7669254e
13 changed files with 1367 additions and 240 deletions
|
|
@ -76,11 +76,15 @@ set_configuration(struct usb_dev *dev, int value)
|
|||
// 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)
|
||||
//
|
||||
// `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;
|
||||
|
||||
|
|
@ -92,10 +96,15 @@ parse_config(struct usb_dev *dev)
|
|||
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; // first interface only, for now
|
||||
memmove(&dev->iface, &dev->conf[off], sizeof(dev->iface));
|
||||
seen_iface = 1;
|
||||
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;
|
||||
|
|
@ -116,8 +125,15 @@ parse_config(struct usb_dev *dev)
|
|||
}
|
||||
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: sixteen is more
|
||||
// interfaces than a desk of keyboards, mice and receivers adds up to.
|
||||
#define NIFDEV 16
|
||||
static struct usb_dev ifdevs[NIFDEV];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class drivers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -146,6 +162,7 @@ usb_offer(const struct usb_driver *drv, struct usb_dev *dev)
|
|||
if(drv->probe(dev) != 0)
|
||||
return 0;
|
||||
dev->driver = drv->name;
|
||||
dev->drv = drv;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -185,8 +202,6 @@ usb_attach(struct usb_dev *dev)
|
|||
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){
|
||||
|
|
@ -194,16 +209,67 @@ usb_attach(struct usb_dev *dev)
|
|||
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" : "");
|
||||
// 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;
|
||||
|
||||
usb_bind_all(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;
|
||||
}
|
||||
|
||||
if(dev->driver == 0)
|
||||
cprintf("usb: no driver for this device\n");
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
// USB HID, boot protocol: keyboards and mice.
|
||||
// USB HID: keyboards, mice and absolute pointers.
|
||||
//
|
||||
// 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.
|
||||
// Keyboards are driven in boot protocol, which 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 one fixed layout on
|
||||
// request: eight bytes, defined in the HID specification and identical on
|
||||
// every keyboard. Nothing a keyboard sends beyond that is worth the parser.
|
||||
//
|
||||
// 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.
|
||||
// Pointers are not. The boot mouse report is three bytes -- buttons and an
|
||||
// 8-bit delta on each axis -- and has no wheel, which every mouse sold this
|
||||
// century has. So a pointer is driven in report protocol, from its report
|
||||
// descriptor: where its buttons, X, Y and wheel are, how wide, and whether
|
||||
// they are relative (a mouse) or absolute (a tablet, a touchscreen, a VM's
|
||||
// pointer). Only the fields a pointer needs are looked for; everything else
|
||||
// in the descriptor is stepped over. A boot mouse whose descriptor makes no
|
||||
// sense is still driven, in boot protocol, as before.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
|
|
@ -21,12 +26,28 @@
|
|||
#include "usb.h"
|
||||
#include "input.h"
|
||||
|
||||
#define NHID 4
|
||||
#define NHID 8
|
||||
|
||||
// One field of a report, as the report descriptor placed it.
|
||||
struct hfield {
|
||||
int ok;
|
||||
int bit, size; // offset and width in bits, after any ID byte
|
||||
int lmin, lmax; // logical range; lmin < 0 means signed
|
||||
int rel; // relative (a delta) rather than absolute
|
||||
};
|
||||
|
||||
#define HID_NBTN 5
|
||||
|
||||
struct hid {
|
||||
int in_use;
|
||||
int proto; // USB_HID_PROTO_KEYBOARD or _MOUSE
|
||||
int proto; // USB_HID_PROTO_KEYBOARD, _MOUSE or _TABLET
|
||||
uchar prev[8]; // the previous report, for edge detection
|
||||
|
||||
// A pointer in report protocol. `boot` instead means the three-byte boot
|
||||
// report, for a mouse whose descriptor could not be used.
|
||||
int boot;
|
||||
int report_id; // 0 when reports carry no ID byte
|
||||
struct hfield x, y, wheel, btn[HID_NBTN];
|
||||
};
|
||||
|
||||
static struct hid hids[NHID];
|
||||
|
|
@ -78,19 +99,42 @@ static struct {
|
|||
uint r, w;
|
||||
} kq;
|
||||
|
||||
// The queue is filled from the interrupt handler and, for key repeat, from
|
||||
// the hot-plug thread; consoleintr() drains it from either.
|
||||
static struct spinlock kqlock;
|
||||
static int kqinit;
|
||||
|
||||
// Software key repeat. A USB keyboard does not repeat: a held key is simply
|
||||
// still in the report, and the next report comes only when something
|
||||
// changes. The PS/2 keyboard repeats in its own firmware, so without this a
|
||||
// held key auto-repeats on one keyboard and not the other.
|
||||
#define REPEAT_DELAY (HZ / 2) // 500 ms, the PC default
|
||||
#define REPEAT_EVERY 3 // ticks: about 33 a second
|
||||
static struct {
|
||||
int c; // the character to repeat, 0 for none
|
||||
uchar usage; // the key producing it
|
||||
uint at; // tick of the next repeat
|
||||
} krep;
|
||||
|
||||
static void
|
||||
kq_put(int c)
|
||||
{
|
||||
acquire(&kqlock);
|
||||
if(kq.w - kq.r < KQSIZE)
|
||||
kq.buf[kq.w++ % KQSIZE] = c;
|
||||
release(&kqlock);
|
||||
}
|
||||
|
||||
static int
|
||||
usb_kbd_getc(void)
|
||||
{
|
||||
if(kq.r == kq.w)
|
||||
return -1;
|
||||
return kq.buf[kq.r++ % KQSIZE];
|
||||
int c = -1;
|
||||
|
||||
acquire(&kqlock);
|
||||
if(kq.r != kq.w)
|
||||
c = kq.buf[kq.r++ % KQSIZE];
|
||||
release(&kqlock);
|
||||
return c;
|
||||
}
|
||||
|
||||
// Was this usage in that report?
|
||||
|
|
@ -187,68 +231,250 @@ keyboard_report(struct hid *h, const uchar *rep, int len)
|
|||
continue;
|
||||
}
|
||||
kq_put(c);
|
||||
|
||||
// The newest key is the one that repeats, as on any keyboard.
|
||||
acquire(&kqlock);
|
||||
krep.c = c;
|
||||
krep.usage = usage;
|
||||
krep.at = ticks + REPEAT_DELAY;
|
||||
release(&kqlock);
|
||||
}
|
||||
|
||||
// Released, or another key's report without it: no more repeats.
|
||||
acquire(&kqlock);
|
||||
if(krep.c && !was_down(rep, krep.usage))
|
||||
krep.c = 0;
|
||||
release(&kqlock);
|
||||
|
||||
memmove(h->prev, rep, 8);
|
||||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
void
|
||||
usb_hid_tick(void)
|
||||
{
|
||||
int c = 0;
|
||||
|
||||
if(!kqinit)
|
||||
return;
|
||||
acquire(&kqlock);
|
||||
if(krep.c && (int)(ticks - krep.at) >= 0){
|
||||
c = krep.c;
|
||||
krep.at = ticks + REPEAT_EVERY;
|
||||
if(kq.w - kq.r < KQSIZE)
|
||||
kq.buf[kq.w++ % KQSIZE] = c;
|
||||
}
|
||||
release(&kqlock);
|
||||
|
||||
if(c && !input_is_grabbed(INPUT_KBD))
|
||||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mouse
|
||||
// Report descriptors
|
||||
//
|
||||
// A report descriptor is a sequence of items, each a prefix byte -- size in
|
||||
// bits 1:0 (3 meaning four bytes), type in 3:2, tag in 7:4 -- and that many
|
||||
// bytes of data. Global items (usage page, logical range, report size and
|
||||
// count, report ID) persist until changed; local items (usages) apply to the
|
||||
// next main item only. A main Input item lays down `count` fields of `size`
|
||||
// bits each, taking usages in order, and advances the position in that
|
||||
// report. Push, pop, delimiters and designators are not used by anything
|
||||
// that matters here and are passed over.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// QEMU's usb-tablet, and absolute pointers generally.
|
||||
//
|
||||
// Report: one byte of buttons, then X and Y as 16-bit little-endian values
|
||||
// over the device's logical range, then a wheel byte. The range is 0..0x7fff
|
||||
// and is *not* read from the report descriptor -- parsing one is a real piece
|
||||
// of work, and this decodes exactly the fixed layout the tablet declares.
|
||||
// A device whose report is a different length is left alone rather than
|
||||
// guessed at.
|
||||
//
|
||||
// Scaling happens here because this is the only place that knows the logical
|
||||
// range; everything above deals in pixels.
|
||||
#define TABLET_LOGICAL_MAX 0x7fff
|
||||
#define PAGE_DESKTOP 0x01
|
||||
#define PAGE_BUTTON 0x09
|
||||
#define U_X 0x30
|
||||
#define U_Y 0x31
|
||||
#define U_WHEEL 0x38
|
||||
|
||||
static void
|
||||
tablet_report(struct hid *h, const uchar *rep, int len)
|
||||
take_field(struct hid *h, uint32 usage, int bit, int size, int lmin, int lmax,
|
||||
int rel, int id, int *target)
|
||||
{
|
||||
(void)h;
|
||||
if(len < 5)
|
||||
struct hfield *f = 0;
|
||||
int page = usage >> 16, u = usage & 0xFFFF;
|
||||
|
||||
if(page == PAGE_DESKTOP && u == U_X) f = &h->x;
|
||||
else if(page == PAGE_DESKTOP && u == U_Y) f = &h->y;
|
||||
else if(page == PAGE_DESKTOP && u == U_WHEEL) f = &h->wheel;
|
||||
else if(page == PAGE_BUTTON && u >= 1 && u <= HID_NBTN)
|
||||
f = &h->btn[u - 1];
|
||||
if(f == 0 || f->ok || size < 1 || size > 32)
|
||||
return;
|
||||
|
||||
int rawx = rep[1] | (rep[2] << 8);
|
||||
int rawy = rep[3] | (rep[4] << 8);
|
||||
// Everything comes from one report: the first one with any of this in it.
|
||||
// A keyboard-and-mouse receiver describes both in one descriptor, and a
|
||||
// button bit from the keyboard's report would be nonsense in the mouse's.
|
||||
if(*target < 0)
|
||||
*target = id;
|
||||
if(id != *target)
|
||||
return;
|
||||
|
||||
int w = (int)gfx_width();
|
||||
int h_px = (int)gfx_height();
|
||||
if(w <= 0) w = 800;
|
||||
if(h_px <= 0) h_px = 600;
|
||||
f->ok = 1;
|
||||
f->bit = bit;
|
||||
f->size = size;
|
||||
f->lmin = lmin;
|
||||
f->lmax = lmax;
|
||||
f->rel = rel;
|
||||
}
|
||||
|
||||
// Scale in 64-bit-free arithmetic: 0x7fff * 4096 still fits in an int.
|
||||
int x = (int)(((long long)rawx * (w - 1)) / TABLET_LOGICAL_MAX);
|
||||
int y = (int)(((long long)rawy * (h_px - 1)) / TABLET_LOGICAL_MAX);
|
||||
// Returns 0 if a pointer was found: an X and a Y in the same report.
|
||||
static int
|
||||
parse_report(struct hid *h, const uchar *d, int n)
|
||||
{
|
||||
uint32 page = 0, rsize = 0, rcount = 0, umin = 0, umax = 0;
|
||||
uint32 usage[16];
|
||||
int lmin = 0, lmax = 0, id = 0, nusage = 0, range = 0, target = -1;
|
||||
int off[16];
|
||||
int i = 0, k;
|
||||
|
||||
mouse_set_abs(x, y, rep[0] & 0x07);
|
||||
memset(off, 0, sizeof(off));
|
||||
while(i < n){
|
||||
uchar b = d[i++];
|
||||
int sz = b & 3, type = (b >> 2) & 3, tag = b >> 4;
|
||||
uint32 v = 0;
|
||||
int sv;
|
||||
|
||||
if(b == 0xFE){ // long item: size, tag, data
|
||||
if(i >= n)
|
||||
break;
|
||||
i += 2 + d[i];
|
||||
continue;
|
||||
}
|
||||
if(sz == 3)
|
||||
sz = 4;
|
||||
if(i + sz > n)
|
||||
break;
|
||||
for(k = 0; k < sz; k++)
|
||||
v |= (uint32)d[i + k] << (8 * k);
|
||||
i += sz;
|
||||
sv = sz == 1 ? (signed char)v : sz == 2 ? (short)v : (int)v;
|
||||
|
||||
if(type == 1){ // global
|
||||
switch(tag){
|
||||
case 0: page = v; break;
|
||||
case 1: lmin = sv; break;
|
||||
// Signed or not depends on the minimum: 0..255 in one byte is 255,
|
||||
// not -1.
|
||||
case 2: lmax = sv < lmin ? (int)v : sv; break;
|
||||
case 7: rsize = v; break;
|
||||
case 8: id = v; break;
|
||||
case 9: rcount = v; break;
|
||||
}
|
||||
} else if(type == 2){ // local
|
||||
uint32 full = sz == 4 ? v : (page << 16) | v;
|
||||
switch(tag){
|
||||
case 0: if(nusage < 16) usage[nusage++] = full; break;
|
||||
case 1: umin = full; range = 1; break;
|
||||
case 2: umax = full; break;
|
||||
}
|
||||
} else if(type == 0){ // main
|
||||
if(tag == 8 && id < 16 && rsize <= 32 && rcount <= 256){
|
||||
for(k = 0; k < (int)rcount; k++){
|
||||
uint32 u = 0;
|
||||
if(k < nusage)
|
||||
u = usage[k];
|
||||
else if(range && umin + (k - nusage) <= umax)
|
||||
u = umin + (k - nusage);
|
||||
else if(nusage > 0)
|
||||
u = usage[nusage - 1];
|
||||
// Bit 0 set is a constant field: padding, there to be stepped over.
|
||||
if(!(v & 1))
|
||||
take_field(h, u, off[id], rsize, lmin, lmax, (v & 4) != 0, id,
|
||||
&target);
|
||||
off[id] += rsize;
|
||||
}
|
||||
}
|
||||
nusage = 0;
|
||||
range = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(!h->x.ok || !h->y.ok)
|
||||
return -1;
|
||||
h->report_id = target;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
field(const uchar *p, int len, const struct hfield *f)
|
||||
{
|
||||
uint32 v = 0;
|
||||
int i;
|
||||
|
||||
if(!f->ok || (f->bit + f->size + 7) / 8 > len)
|
||||
return 0;
|
||||
for(i = 0; i < f->size; i++){
|
||||
int b = f->bit + i;
|
||||
if((p[b >> 3] >> (b & 7)) & 1)
|
||||
v |= 1u << i;
|
||||
}
|
||||
if(f->lmin < 0 && f->size < 32 && ((v >> (f->size - 1)) & 1))
|
||||
v |= ~0u << f->size; // sign-extend
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pointers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
scale(int v, const struct hfield *f, int pixels)
|
||||
{
|
||||
long long range = (long long)f->lmax - f->lmin;
|
||||
if(range <= 0)
|
||||
return 0;
|
||||
return (int)(((long long)(v - f->lmin) * (pixels - 1)) / range);
|
||||
}
|
||||
|
||||
static void
|
||||
mouse_report(struct hid *h, const uchar *rep, int len)
|
||||
pointer_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
int dx, dy;
|
||||
int buttons = 0, i, wheel;
|
||||
|
||||
(void)h;
|
||||
if(len < 3)
|
||||
if(h->boot){
|
||||
// Boot protocol: buttons, then signed 8-bit X and Y. The deltas are
|
||||
// signed. Reading them as unsigned is the classic way to get a pointer
|
||||
// that only travels right and down.
|
||||
if(len < 3)
|
||||
return;
|
||||
mouse_inject((signed char)rep[1], (signed char)rep[2], rep[0] & 0x07);
|
||||
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];
|
||||
if(h->report_id){
|
||||
if(len < 1 || rep[0] != h->report_id)
|
||||
return; // some other report of the device's
|
||||
rep++;
|
||||
len--;
|
||||
}
|
||||
|
||||
for(i = 0; i < HID_NBTN; i++)
|
||||
if(field(rep, len, &h->btn[i]))
|
||||
buttons |= 1 << i;
|
||||
|
||||
// A wheel step is reported before the motion it came with, so that both
|
||||
// reach a reader in one batch: mouse_inject() is what ends the batch.
|
||||
wheel = field(rep, len, &h->wheel);
|
||||
if(wheel && h->wheel.rel)
|
||||
input_report(INPUT_MOUSE, EV_REL, REL_WHEEL, wheel);
|
||||
|
||||
// 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);
|
||||
if(h->x.rel){
|
||||
mouse_inject(field(rep, len, &h->x), field(rep, len, &h->y),
|
||||
buttons & 0x07);
|
||||
} else {
|
||||
// Scaled here because this is the only place that knows the logical
|
||||
// range; everything above deals in pixels.
|
||||
int w = (int)gfx_width(), hp = (int)gfx_height();
|
||||
if(w <= 0) w = 800;
|
||||
if(hp <= 0) hp = 600;
|
||||
mouse_set_abs(scale(field(rep, len, &h->x), &h->x, w),
|
||||
scale(field(rep, len, &h->y), &h->y, hp), buttons & 0x07);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -260,12 +486,10 @@ hid_complete(struct usb_dev *dev, void *buf, int len)
|
|||
|
||||
if(h == 0)
|
||||
return;
|
||||
if(h->proto == USB_HID_PROTO_TABLET)
|
||||
tablet_report(h, buf, len);
|
||||
else if(h->proto == USB_HID_PROTO_KEYBOARD)
|
||||
if(h->proto == USB_HID_PROTO_KEYBOARD)
|
||||
keyboard_report(h, buf, len);
|
||||
else
|
||||
mouse_report(h, buf, len);
|
||||
pointer_report(h, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
@ -281,37 +505,110 @@ hid_request(struct usb_dev *dev, int request, int value)
|
|||
return dev->ops->control(dev, &s, 0, 0);
|
||||
}
|
||||
|
||||
// The report descriptor's length is in the HID descriptor, which sits in the
|
||||
// configuration right after this interface's own descriptor.
|
||||
static int
|
||||
report_desc_len(struct usb_dev *dev)
|
||||
{
|
||||
int off = 0, mine = 0;
|
||||
|
||||
while(off + 2 <= dev->conflen){
|
||||
int len = dev->conf[off], type = dev->conf[off + 1];
|
||||
if(len < 2 || off + len > dev->conflen)
|
||||
break;
|
||||
if(type == USB_DT_INTERFACE && len >= 4)
|
||||
mine = dev->conf[off + 2] == dev->iface.bInterfaceNumber &&
|
||||
dev->conf[off + 3] == 0;
|
||||
else if(mine && type == USB_DT_HID && len >= 9 &&
|
||||
dev->conf[off + 6] == USB_DT_REPORT)
|
||||
return dev->conf[off + 7] | (dev->conf[off + 8] << 8);
|
||||
off += len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
read_report_desc(struct usb_dev *dev, struct hid *h)
|
||||
{
|
||||
struct usb_setup s;
|
||||
int len = report_desc_len(dev), rc;
|
||||
uchar *buf;
|
||||
|
||||
if(len <= 0)
|
||||
return -1;
|
||||
if(len > 1024)
|
||||
len = 1024;
|
||||
buf = dma_alloc(len, 64);
|
||||
if(buf == 0)
|
||||
return -1;
|
||||
|
||||
s.bmRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE;
|
||||
s.bRequest = USB_REQ_GET_DESCRIPTOR;
|
||||
s.wValue = USB_DT_REPORT << 8;
|
||||
s.wIndex = dev->iface.bInterfaceNumber;
|
||||
s.wLength = len;
|
||||
memset(buf, 0, len);
|
||||
rc = dev->ops->control(dev, &s, buf, len);
|
||||
if(rc >= 0)
|
||||
rc = parse_report(h, buf, len);
|
||||
dma_free(buf, len);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
hid_probe(struct usb_dev *dev)
|
||||
{
|
||||
struct hid *h = 0;
|
||||
int i, proto = dev->iface.bInterfaceProtocol;
|
||||
int i;
|
||||
int boot = dev->iface.bInterfaceSubClass == USB_HID_SUBCLASS_BOOT;
|
||||
int proto = dev->iface.bInterfaceProtocol;
|
||||
|
||||
if(!dev->has_intr_in){
|
||||
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
|
||||
return -1;
|
||||
}
|
||||
if(!kqinit){
|
||||
initlock(&kqlock, "usbkbd");
|
||||
kqinit = 1;
|
||||
}
|
||||
|
||||
for(i = 0; i < NHID; i++)
|
||||
if(!hids[i].in_use){ h = &hids[i]; break; }
|
||||
if(h == 0)
|
||||
if(h == 0){
|
||||
cprintf("usb-hid: %d devices already; this one is not used\n", NHID);
|
||||
return -1;
|
||||
}
|
||||
memset(h, 0, sizeof(*h));
|
||||
|
||||
// A HID device that claims no boot protocol is taken to be an absolute
|
||||
// pointer -- in practice a tablet, which is what a VM offers so that its
|
||||
// cursor and the host's can agree. It must not be asked for boot protocol:
|
||||
// it has none, the request fails, and refusing on that basis is what kept
|
||||
// usb-tablet from binding at all.
|
||||
if(proto != USB_HID_PROTO_KEYBOARD && proto != USB_HID_PROTO_MOUSE)
|
||||
proto = USB_HID_PROTO_TABLET;
|
||||
|
||||
// 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(proto != USB_HID_PROTO_TABLET &&
|
||||
hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
if(boot && proto == USB_HID_PROTO_KEYBOARD){
|
||||
// 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.
|
||||
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
return -1;
|
||||
}
|
||||
h->proto = USB_HID_PROTO_KEYBOARD;
|
||||
} else if(read_report_desc(dev, h) == 0){
|
||||
// Report protocol is the default after a reset, but a firmware that used
|
||||
// the mouse may have left it in boot protocol, and a port reset is not
|
||||
// always a device reset. Only a boot device knows the request.
|
||||
if(boot)
|
||||
hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_REPORT);
|
||||
h->proto = h->x.rel ? USB_HID_PROTO_MOUSE : USB_HID_PROTO_TABLET;
|
||||
} else if(boot && proto == USB_HID_PROTO_MOUSE){
|
||||
memset(h, 0, sizeof(*h));
|
||||
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
return -1;
|
||||
}
|
||||
h->proto = USB_HID_PROTO_MOUSE;
|
||||
h->boot = 1;
|
||||
} else {
|
||||
// A consumer-control interface (volume keys), a game pad, a UPS: HID,
|
||||
// and not something this driver has a use for.
|
||||
cprintf("usb-hid: interface %d is neither a keyboard nor a pointer\n",
|
||||
dev->iface.bInterfaceNumber);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -324,32 +621,52 @@ hid_probe(struct usb_dev *dev)
|
|||
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)
|
||||
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0){
|
||||
h->in_use = 0;
|
||||
dev->drvdata = 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" :
|
||||
proto == USB_HID_PROTO_TABLET ? "tablet (absolute)" : "device",
|
||||
cprintf("usb-hid: %s%s on slot %d, endpoint 0x%x every %d ms\n",
|
||||
h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
|
||||
h->proto == USB_HID_PROTO_MOUSE ? "mouse" : "absolute pointer",
|
||||
h->proto == USB_HID_PROTO_KEYBOARD || h->boot ? "" :
|
||||
h->wheel.ok ? " with wheel" : "",
|
||||
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Any HID interface, not just the boot subclass.
|
||||
//
|
||||
// It was boot-only, on the reasoning that a device without boot protocol needs
|
||||
// its report descriptor parsed and there would be nothing useful to do with it.
|
||||
// That is right in general and wrong for the one device that matters here:
|
||||
// usb-tablet declares subclass 0, so it was refused at the door -- and its
|
||||
// report layout is fixed and known, so there is something useful to do with it
|
||||
// after all. hid_probe decides what a given interface actually is; anything it
|
||||
// cannot make sense of is still declined there.
|
||||
// Unplugged. A key held at that moment is released, so that a reader of
|
||||
// /dev/input does not see it held for ever, and the slot is given back.
|
||||
static void
|
||||
hid_disconnect(struct usb_dev *dev)
|
||||
{
|
||||
struct hid *h = dev->drvdata;
|
||||
static const uchar none[8];
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
if(h->proto == USB_HID_PROTO_KEYBOARD){
|
||||
keyboard_events(h->prev, none);
|
||||
acquire(&kqlock);
|
||||
krep.c = 0;
|
||||
release(&kqlock);
|
||||
}
|
||||
cprintf("usb-hid: %s on slot %d unplugged\n",
|
||||
h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" : "pointer",
|
||||
dev->slot);
|
||||
h->in_use = 0;
|
||||
dev->drvdata = 0;
|
||||
dev->complete = 0;
|
||||
}
|
||||
|
||||
// Any HID interface, not just the boot subclass: hid_probe decides what a
|
||||
// given interface actually is, and declines anything it cannot make sense of.
|
||||
const struct usb_driver usb_hid_driver = {
|
||||
.name = "usb-hid",
|
||||
.class = USB_CLASS_HID,
|
||||
|
|
@ -357,4 +674,5 @@ const struct usb_driver usb_hid_driver = {
|
|||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_INPUT,
|
||||
.probe = hid_probe,
|
||||
.disconnect = hid_disconnect,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
// 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.
|
||||
// which reports which ports have changed. At boot it is enough to walk every
|
||||
// port and look; after that, the endpoint is how a device plugged into the
|
||||
// hub, or pulled out of it, is noticed at all. See usb_hub_poll().
|
||||
//
|
||||
// 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
|
||||
|
|
@ -22,6 +23,21 @@
|
|||
#include "usb.h"
|
||||
|
||||
#define MAX_PORTS 15 // per hub; more than any real bus hub has
|
||||
#define NHUB 8
|
||||
|
||||
// Port change bits, the second half of GET_STATUS(port).
|
||||
#define PORTCHG_CONNECTION (1 << 0)
|
||||
#define PORTCHG_OTHERS 0x1E // enable, suspend, over-current, reset
|
||||
|
||||
struct hub {
|
||||
int in_use;
|
||||
struct usb_dev *dev;
|
||||
int nports;
|
||||
void *scratch; // DMA, for GET_STATUS
|
||||
volatile uint32 change; // bit n: port n has changed; 0 is hub
|
||||
};
|
||||
|
||||
static struct hub hubs[NHUB];
|
||||
|
||||
static int
|
||||
hub_request(struct usb_dev *dev, int type, int req, int value, int index,
|
||||
|
|
@ -64,6 +80,19 @@ port_status(struct usb_dev *dev, int port, void *scratch)
|
|||
return st[0];
|
||||
}
|
||||
|
||||
// The other half: what has changed since the bits were last cleared.
|
||||
static int
|
||||
port_change(struct usb_dev *dev, int port, void *scratch, int *status)
|
||||
{
|
||||
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;
|
||||
*status = st[0];
|
||||
return st[1];
|
||||
}
|
||||
|
||||
// Bring up whatever is plugged into one downstream port.
|
||||
static void
|
||||
hub_port(struct usb_dev *hub, int port, void *scratch)
|
||||
|
|
@ -117,6 +146,23 @@ hub_port(struct usb_dev *hub, int port, void *scratch)
|
|||
hub->ops->attach(hub, port, speed);
|
||||
}
|
||||
|
||||
// The status-change endpoint has spoken: a bitmap, one bit per port. From
|
||||
// interrupt context, so only note it.
|
||||
static void
|
||||
hub_complete(struct usb_dev *dev, void *buf, int len)
|
||||
{
|
||||
struct hub *h = dev->drvdata;
|
||||
uchar *b = buf;
|
||||
uint32 bits = 0;
|
||||
int i;
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
for(i = 0; i < len && i < 4; i++)
|
||||
bits |= (uint32)b[i] << (8 * i);
|
||||
__sync_fetch_and_or(&h->change, bits);
|
||||
}
|
||||
|
||||
static int
|
||||
hub_probe(struct usb_dev *dev)
|
||||
{
|
||||
|
|
@ -168,10 +214,93 @@ hub_probe(struct usb_dev *dev)
|
|||
for(i = 1; i <= nports; i++)
|
||||
hub_port(dev, i, scratch);
|
||||
|
||||
dma_free(scratch, 64);
|
||||
// From here the hub is listened to: its status-change endpoint completes
|
||||
// whenever a port changes, and usb_hub_poll() acts on it. A hub without
|
||||
// one is unheard of and could still be driven by polling, but is not.
|
||||
for(i = 0; i < NHUB; i++)
|
||||
if(!hubs[i].in_use)
|
||||
break;
|
||||
if(i == NHUB || !dev->has_intr_in){
|
||||
if(i == NHUB)
|
||||
cprintf("usb-hub: too many hubs; this one will not see hot-plug\n");
|
||||
dma_free(scratch, 64);
|
||||
return 0;
|
||||
}
|
||||
hubs[i].dev = dev;
|
||||
hubs[i].nports = nports;
|
||||
hubs[i].scratch = scratch;
|
||||
hubs[i].change = 0;
|
||||
hubs[i].in_use = 1;
|
||||
dev->drvdata = &hubs[i];
|
||||
dev->complete = hub_complete;
|
||||
if(dev->ops->open_ep(dev, &dev->intr_in) < 0 ||
|
||||
dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
|
||||
cprintf("usb-hub: status-change endpoint would not open; no hot-plug\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The hub is gone. Whatever was behind it went with it -- the controller
|
||||
// tears those down first -- so all that is left is to forget it.
|
||||
static void
|
||||
hub_disconnect(struct usb_dev *dev)
|
||||
{
|
||||
struct hub *h = dev->drvdata;
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
h->in_use = 0;
|
||||
dma_free(h->scratch, 64);
|
||||
dev->drvdata = 0;
|
||||
}
|
||||
|
||||
// Act on every port change the hubs have reported. From the hot-plug thread,
|
||||
// which is the only thing that enumerates once the machine is up.
|
||||
void
|
||||
usb_hub_poll(void)
|
||||
{
|
||||
int i, port;
|
||||
|
||||
for(i = 0; i < NHUB; i++){
|
||||
struct hub *h = &hubs[i];
|
||||
uint32 bits;
|
||||
|
||||
if(!h->in_use || h->change == 0)
|
||||
continue;
|
||||
bits = __sync_fetch_and_and(&h->change, 0);
|
||||
|
||||
for(port = 1; port <= h->nports; port++){
|
||||
int st, chg;
|
||||
|
||||
if(!(bits & (1u << port)))
|
||||
continue;
|
||||
chg = port_change(h->dev, port, h->scratch, &st);
|
||||
if(chg < 0)
|
||||
continue;
|
||||
if(chg & PORTCHG_OTHERS){
|
||||
if(chg & (1 << 1)) port_clear_feature(h->dev, port, PORT_FEAT_C_ENABLE);
|
||||
if(chg & (1 << 2)) port_clear_feature(h->dev, port, 18);
|
||||
if(chg & (1 << 3)) port_clear_feature(h->dev, port, 19);
|
||||
if(chg & (1 << 4)) port_clear_feature(h->dev, port, PORT_FEAT_C_RESET);
|
||||
}
|
||||
if(!(chg & PORTCHG_CONNECTION))
|
||||
continue;
|
||||
|
||||
// Whatever was there before is not there now, even if something is:
|
||||
// a connection change with the port still connected is a swap.
|
||||
port_clear_feature(h->dev, port, PORT_FEAT_C_CONNECTION);
|
||||
h->dev->ops->detach(h->dev, port);
|
||||
if(st & PORTSTAT_CONNECTION){
|
||||
microdelay(100000); // debounce, as on a root port
|
||||
cprintf("usb-hub: slot %d port %d: plugged in\n", h->dev->slot, port);
|
||||
hub_port(h->dev, port, h->scratch);
|
||||
}
|
||||
// The hub may have been unplugged by what was just done; stop if so.
|
||||
if(!h->in_use)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
@ -182,4 +311,5 @@ const struct usb_driver usb_hub_driver = {
|
|||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_BUS,
|
||||
.probe = hub_probe,
|
||||
.disconnect = hub_disconnect,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -61,9 +61,12 @@ struct csw {
|
|||
|
||||
#define SECTOR_SIZE 512
|
||||
|
||||
static struct {
|
||||
// One per stick. A desk with a stick in each port is a reasonable thing to
|
||||
// have, and the first one to arrive is the one the fixed USBDISK_DEV means.
|
||||
struct ums {
|
||||
struct usb_dev *dev;
|
||||
int present;
|
||||
int gone; // unplugged; fail everything
|
||||
uint32 nsectors;
|
||||
uint32 sector_size;
|
||||
uint32 tag;
|
||||
|
|
@ -71,7 +74,23 @@ static struct {
|
|||
struct cbw *cbw; // allocated once, not per command
|
||||
struct csw *csw;
|
||||
struct sleeplock lock; // one command at a time
|
||||
} ums;
|
||||
char name[8]; // usb0, usb1, ...
|
||||
int blkdev; // its number in the registry, or -1
|
||||
};
|
||||
|
||||
#define NUMS 4
|
||||
static struct ums disks[NUMS];
|
||||
|
||||
// What USBDISK_DEV (3) means: the first stick still present. It is the whole
|
||||
// medium, which is how a stick holding a bare SchFS volume is mounted.
|
||||
static struct ums *
|
||||
legacy(void)
|
||||
{
|
||||
for(int i = 0; i < NUMS; i++)
|
||||
if(disks[i].present && !disks[i].gone)
|
||||
return &disks[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// SCSI is big-endian, everywhere, for everything. x86 is not.
|
||||
static void
|
||||
|
|
@ -92,12 +111,13 @@ rd_be32(const uint8 *p)
|
|||
// `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)
|
||||
bot_command(struct ums *u, 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;
|
||||
struct usb_dev *dev = u->dev;
|
||||
struct cbw *cbw = u->cbw;
|
||||
struct csw *csw = u->csw;
|
||||
uint32 tag = ++u->tag;
|
||||
int i, data_ok = 1;
|
||||
|
||||
memset(cbw, 0, sizeof(*cbw));
|
||||
|
|
@ -160,13 +180,13 @@ bot_command(const uint8 *cdb, int cdb_len, void *buf, int len, int in)
|
|||
}
|
||||
|
||||
static int
|
||||
scsi_inquiry(char *vendor, char *product)
|
||||
scsi_inquiry(struct ums *u, char *vendor, char *product)
|
||||
{
|
||||
uint8 cdb[6] = { SCSI_INQUIRY, 0, 0, 0, 36, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
uint8 *r = u->xfer;
|
||||
int i;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 36, 1) < 0)
|
||||
if(bot_command(u, cdb, sizeof(cdb), r, 36, 1) < 0)
|
||||
return -1;
|
||||
|
||||
// Bytes 8-15 are the vendor, 16-31 the product, space padded and not
|
||||
|
|
@ -181,22 +201,22 @@ scsi_inquiry(char *vendor, char *product)
|
|||
}
|
||||
|
||||
static int
|
||||
scsi_read_capacity(void)
|
||||
scsi_read_capacity(struct ums *u)
|
||||
{
|
||||
uint8 cdb[10] = { SCSI_READ_CAPACITY10, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
uint8 *r = u->xfer;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 8, 1) < 0)
|
||||
if(bot_command(u, 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);
|
||||
u->nsectors = rd_be32(r) + 1;
|
||||
u->sector_size = rd_be32(r + 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_rw(uint32 lba, int count, void *buf, int write)
|
||||
scsi_rw(struct ums *u, uint32 lba, int count, void *buf, int write)
|
||||
{
|
||||
uint8 cdb[10];
|
||||
|
||||
|
|
@ -206,7 +226,7 @@ scsi_rw(uint32 lba, int count, void *buf, int write)
|
|||
cdb[7] = (count >> 8) & 0xFF;
|
||||
cdb[8] = count & 0xFF;
|
||||
|
||||
return bot_command(cdb, sizeof(cdb), buf, count * SECTOR_SIZE, !write);
|
||||
return bot_command(u, cdb, sizeof(cdb), buf, count * SECTOR_SIZE, !write);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -216,7 +236,7 @@ scsi_rw(uint32 lba, int count, void *buf, int write)
|
|||
int
|
||||
usb_storage_present(void)
|
||||
{
|
||||
return ums.present;
|
||||
return legacy() != 0;
|
||||
}
|
||||
|
||||
// Sync one buffer-cache block with the stick. Same contract as iderw() and
|
||||
|
|
@ -229,32 +249,33 @@ usb_storage_rw(struct buf *b)
|
|||
uint32 lba = b->blockno * per_block;
|
||||
int write = (b->flags & B_DIRTY) != 0;
|
||||
int i, n = -1;
|
||||
struct ums *u = legacy();
|
||||
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("usb_storage_rw: buf not locked");
|
||||
if(!ums.present)
|
||||
if(u == 0)
|
||||
panic("usb_storage_rw: no USB disk");
|
||||
if(lba + per_block > ums.nsectors)
|
||||
if(lba + per_block > u->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);
|
||||
acquiresleep(&u->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);
|
||||
memmove(u->xfer, b->data, BSIZE);
|
||||
n = scsi_rw(u, lba, per_block, u->xfer, write);
|
||||
if(n == BSIZE)
|
||||
break;
|
||||
cprintf("usb-storage: %s of block %d failed, retrying\n",
|
||||
write ? "write" : "read", b->blockno);
|
||||
}
|
||||
|
||||
releasesleep(&ums.lock);
|
||||
releasesleep(&u->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 --
|
||||
|
|
@ -265,7 +286,7 @@ usb_storage_rw(struct buf *b)
|
|||
panic("usb_storage_rw: transfer failed");
|
||||
|
||||
if(!write)
|
||||
memmove(b->data, ums.xfer, BSIZE);
|
||||
memmove(b->data, u->xfer, BSIZE);
|
||||
b->flags |= B_VALID;
|
||||
b->flags &= ~B_DIRTY;
|
||||
}
|
||||
|
|
@ -280,21 +301,18 @@ usb_storage_rw(struct buf *b)
|
|||
// than ignored, because the caller is about to write an uberblock on the
|
||||
// strength of it -- and a barrier that silently does nothing is worse than no
|
||||
// barrier at all, since it makes an unsafe volume look safe.
|
||||
int
|
||||
usb_storage_flush(void)
|
||||
static int
|
||||
ums_flush(struct ums *u)
|
||||
{
|
||||
uint8 cdb[10];
|
||||
int r;
|
||||
|
||||
if(!ums.present)
|
||||
return 0;
|
||||
|
||||
memset(cdb, 0, sizeof(cdb));
|
||||
cdb[0] = SCSI_SYNC_CACHE10; // LBA 0, count 0: the entire medium
|
||||
|
||||
acquiresleep(&ums.lock);
|
||||
r = bot_command(cdb, sizeof(cdb), 0, 0, 0);
|
||||
releasesleep(&ums.lock);
|
||||
acquiresleep(&u->lock);
|
||||
r = u->gone ? -1 : bot_command(u, cdb, sizeof(cdb), 0, 0, 0);
|
||||
releasesleep(&u->lock);
|
||||
|
||||
if(r < 0){
|
||||
cprintf("usb-storage: SYNCHRONIZE CACHE failed; writes are not durable\n");
|
||||
|
|
@ -303,6 +321,13 @@ usb_storage_flush(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
usb_storage_flush(void)
|
||||
{
|
||||
struct ums *u = legacy();
|
||||
return u ? ums_flush(u) : 0;
|
||||
}
|
||||
|
||||
// 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:
|
||||
|
|
@ -317,14 +342,15 @@ usb_storage_dump(void)
|
|||
{
|
||||
struct buf *b;
|
||||
struct superblock *sb;
|
||||
struct ums *u = legacy();
|
||||
|
||||
if(!ums.present){
|
||||
if(u == 0){
|
||||
cprintf("usb-storage: no disk\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cprintf("usb-storage: %d sectors of %d bytes\n", ums.nsectors,
|
||||
ums.sector_size);
|
||||
cprintf("usb-storage: %d sectors of %d bytes\n", u->nsectors,
|
||||
u->sector_size);
|
||||
|
||||
b = bread(USBDISK_DEV, 1);
|
||||
sb = (struct superblock*)b->data;
|
||||
|
|
@ -341,25 +367,28 @@ usb_storage_dump(void)
|
|||
static int
|
||||
ums_blk_rw(void *priv, uint64 lba, uint32 count, void *buf, int write)
|
||||
{
|
||||
struct ums *u = priv;
|
||||
int n;
|
||||
(void)priv;
|
||||
if(count * SECTOR_SIZE > BSIZE || lba + count > ums.nsectors)
|
||||
if(count * SECTOR_SIZE > BSIZE || lba + count > u->nsectors)
|
||||
return -1;
|
||||
acquiresleep(&ums.lock);
|
||||
acquiresleep(&u->lock);
|
||||
if(u->gone){
|
||||
releasesleep(&u->lock);
|
||||
return -1;
|
||||
}
|
||||
if(write)
|
||||
memmove(ums.xfer, buf, count * SECTOR_SIZE);
|
||||
n = scsi_rw((uint32)lba, count, ums.xfer, write);
|
||||
memmove(u->xfer, buf, count * SECTOR_SIZE);
|
||||
n = scsi_rw(u, (uint32)lba, count, u->xfer, write);
|
||||
if(n == (int)(count * SECTOR_SIZE) && !write)
|
||||
memmove(buf, ums.xfer, count * SECTOR_SIZE);
|
||||
releasesleep(&ums.lock);
|
||||
memmove(buf, u->xfer, count * SECTOR_SIZE);
|
||||
releasesleep(&u->lock);
|
||||
return n == (int)(count * SECTOR_SIZE) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
ums_blk_flush(void *priv)
|
||||
{
|
||||
(void)priv;
|
||||
return usb_storage_flush();
|
||||
return ums_flush(priv);
|
||||
}
|
||||
|
||||
static const struct blkdev_ops ums_blk_ops = {
|
||||
|
|
@ -373,28 +402,40 @@ static int
|
|||
storage_probe(struct usb_dev *dev)
|
||||
{
|
||||
char vendor[9], product[17];
|
||||
struct ums *u = 0;
|
||||
int i;
|
||||
|
||||
if(ums.present)
|
||||
return -1; // one disk is enough for now
|
||||
for(i = 0; i < NUMS; i++)
|
||||
if(!disks[i].present && disks[i].dev == 0){ u = &disks[i]; break; }
|
||||
if(u == 0){
|
||||
cprintf("usb-storage: %d sticks already; this one is not used\n", NUMS);
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
initsleeplock(&u->lock, "usbms");
|
||||
u->dev = dev;
|
||||
u->gone = 0;
|
||||
u->blkdev = -1;
|
||||
// The name is the slot's, not a count: pull usb0 out and put it back and
|
||||
// it comes back as usb0.
|
||||
safestrcpy(u->name, "usb0", sizeof(u->name));
|
||||
u->name[3] = '0' + (u - disks);
|
||||
|
||||
// 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)
|
||||
u->xfer = dma_alloc(BSIZE, 4096);
|
||||
u->cbw = dma_alloc(sizeof(struct cbw), 64);
|
||||
u->csw = dma_alloc(sizeof(struct csw), 64);
|
||||
if(u->xfer == 0 || u->cbw == 0 || u->csw == 0)
|
||||
goto fail;
|
||||
dev->drvdata = u;
|
||||
|
||||
// Deliberately OUT first, which is the order that used to break.
|
||||
//
|
||||
|
|
@ -418,46 +459,82 @@ storage_probe(struct usb_dev *dev)
|
|||
// 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)
|
||||
if(bot_command(u, cdb, sizeof(cdb), 0, 0, 0) >= 0)
|
||||
break;
|
||||
microdelay(100000);
|
||||
}
|
||||
|
||||
if(scsi_inquiry(vendor, product) < 0)
|
||||
if(scsi_inquiry(u, vendor, product) < 0)
|
||||
goto fail;
|
||||
if(scsi_read_capacity() < 0)
|
||||
if(scsi_read_capacity(u) < 0)
|
||||
goto fail;
|
||||
|
||||
if(ums.sector_size != SECTOR_SIZE){
|
||||
if(u->sector_size != SECTOR_SIZE){
|
||||
cprintf("usb-storage: %d-byte sectors are not supported\n",
|
||||
ums.sector_size);
|
||||
u->sector_size);
|
||||
goto fail;
|
||||
}
|
||||
if(ums.nsectors == 0){
|
||||
if(u->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)));
|
||||
blkdev_register("usb0", &ums_blk_ops, 0, ums.nsectors, SECTOR_SIZE);
|
||||
u->present = 1;
|
||||
cprintf("usb-storage: %s: %s %s, %d sectors (%d MB)\n", u->name,
|
||||
vendor, product, u->nsectors,
|
||||
(int)((uint64)u->nsectors * SECTOR_SIZE / (1024 * 1024)));
|
||||
u->blkdev = blkdev_register(u->name, &ums_blk_ops, u, u->nsectors,
|
||||
SECTOR_SIZE);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
// Give the memory back and forget the device. Leaving ums.dev set would let
|
||||
// Give the memory back and forget the device. Leaving u->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;
|
||||
if(u->xfer) dma_free(u->xfer, BSIZE);
|
||||
if(u->cbw) dma_free(u->cbw, sizeof(struct cbw));
|
||||
if(u->csw) dma_free(u->csw, sizeof(struct csw));
|
||||
u->xfer = 0;
|
||||
u->cbw = 0;
|
||||
u->csw = 0;
|
||||
u->dev = 0;
|
||||
dev->drvdata = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Pulled out. The controller has already failed whatever transfer was in
|
||||
// flight, so the lock comes free; from then on every request fails at once,
|
||||
// and the registry forgets the disk and its partitions. Mounted from, it
|
||||
// fails the filesystem's next read -- which panics, by the rule in
|
||||
// blkdev_rw(), because a filesystem that carried on would be guessing.
|
||||
static void
|
||||
storage_disconnect(struct usb_dev *dev)
|
||||
{
|
||||
struct ums *u = dev->drvdata;
|
||||
|
||||
if(u == 0)
|
||||
return;
|
||||
acquiresleep(&u->lock);
|
||||
u->gone = 1;
|
||||
releasesleep(&u->lock);
|
||||
|
||||
cprintf("usb-storage: %s unplugged\n", u->name);
|
||||
if(u->blkdev >= 0)
|
||||
blkdev_unregister(u->blkdev);
|
||||
// USBDISK_DEV may have been this stick; nothing it cached is true now.
|
||||
binval(USBDISK_DEV);
|
||||
|
||||
dma_free(u->xfer, BSIZE);
|
||||
dma_free(u->cbw, sizeof(struct cbw));
|
||||
dma_free(u->csw, sizeof(struct csw));
|
||||
u->xfer = 0;
|
||||
u->cbw = 0;
|
||||
u->csw = 0;
|
||||
u->present = 0;
|
||||
u->blkdev = -1;
|
||||
u->dev = 0;
|
||||
dev->drvdata = 0;
|
||||
}
|
||||
|
||||
const struct usb_driver usb_storage_driver = {
|
||||
.name = "usb-storage",
|
||||
.class = USB_CLASS_MASS_STORAGE,
|
||||
|
|
@ -465,4 +542,5 @@ const struct usb_driver usb_storage_driver = {
|
|||
.protocol = USB_MSC_PROTO_BOT,
|
||||
.devclass = DEVCLASS_STORAGE,
|
||||
.probe = storage_probe,
|
||||
.disconnect = storage_disconnect,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue