nusb: improved

This commit is contained in:
aiju 2011-07-27 20:07:30 +02:00
parent 5181f2e576
commit 05d09f086f
22 changed files with 5288 additions and 10 deletions

View File

@ -1,3 +1,7 @@
nusb/kb
csp=0x010103
csp=0x020103
nusb/disk
class=8

View File

@ -0,0 +1,982 @@
/*
* usb/disk - usb mass storage file server
*
* supports only the scsi command interface, not ata.
*/
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "scsireq.h"
#include "usb.h"
#include "ums.h"
enum
{
Qdir = 0,
Qctl,
Qraw,
Qdata,
Qpart,
Qmax = Maxparts,
};
typedef struct Dirtab Dirtab;
struct Dirtab
{
char *name;
int mode;
};
ulong ctlmode = 0664;
/*
* Partition management (adapted from disk/partfs)
*/
Part *
lookpart(Umsc *lun, char *name)
{
Part *part, *p;
part = lun->part;
for(p=part; p < &part[Qmax]; p++){
if(p->inuse && strcmp(p->name, name) == 0)
return p;
}
return nil;
}
Part *
freepart(Umsc *lun)
{
Part *part, *p;
part = lun->part;
for(p=part; p < &part[Qmax]; p++){
if(!p->inuse)
return p;
}
return nil;
}
int
addpart(Umsc *lun, char *name, vlong start, vlong end, ulong mode)
{
Part *p;
if(start < 0 || start > end || end > lun->blocks){
werrstr("bad partition boundaries");
return -1;
}
if(lookpart(lun, name) != nil) {
werrstr("partition name already in use");
return -1;
}
p = freepart(lun);
if(p == nil){
werrstr("no free partition slots");
return -1;
}
p->inuse = 1;
free(p->name);
p->id = p - lun->part;
p->name = estrdup(name);
p->offset = start;
p->length = end - start;
p->mode = mode;
return 0;
}
int
delpart(Umsc *lun, char *s)
{
Part *p;
p = lookpart(lun, s);
if(p == nil || p->id <= Qdata){
werrstr("partition not found");
return -1;
}
p->inuse = 0;
free(p->name);
p->name = nil;
p->vers++;
return 0;
}
void
fixlength(Umsc *lun, vlong blocks)
{
Part *part, *p;
part = lun->part;
part[Qdata].length = blocks;
for(p=&part[Qdata+1]; p < &part[Qmax]; p++){
if(p->inuse && p->offset + p->length > blocks){
if(p->offset > blocks){
p->offset =blocks;
p->length = 0;
}else
p->length = blocks - p->offset;
}
}
}
void
makeparts(Umsc *lun)
{
addpart(lun, "/", 0, 0, DMDIR | 0555);
addpart(lun, "ctl", 0, 0, 0664);
addpart(lun, "raw", 0, 0, 0640);
addpart(lun, "data", 0, lun->blocks, 0640);
}
/*
* ctl parsing & formatting (adapted from partfs)
*/
static char*
ctlstring(Usbfs *fs)
{
Part *p, *part;
Fmt fmt;
Umsc *lun;
Ums *ums;
ums = fs->dev->aux;
lun = fs->aux;
part = &lun->part[0];
fmtstrinit(&fmt);
fmtprint(&fmt, "dev %s\n", fs->dev->dir);
fmtprint(&fmt, "lun %ld\n", lun - &ums->lun[0]);
if(lun->flags & Finqok)
fmtprint(&fmt, "inquiry %s\n", lun->inq);
if(lun->blocks > 0)
fmtprint(&fmt, "geometry %llud %ld\n", lun->blocks, lun->lbsize);
for (p = &part[Qdata+1]; p < &part[Qmax]; p++)
if (p->inuse)
fmtprint(&fmt, "part %s %lld %lld\n",
p->name, p->offset, p->offset + p->length);
return fmtstrflush(&fmt);
}
static int
ctlparse(Usbfs *fs, char *msg)
{
vlong start, end;
char *argv[16];
int argc;
Umsc *lun;
lun = fs->aux;
argc = tokenize(msg, argv, nelem(argv));
if(argc < 1){
werrstr("empty control message");
return -1;
}
if(strcmp(argv[0], "part") == 0){
if(argc != 4){
werrstr("part takes 3 args");
return -1;
}
start = strtoll(argv[2], 0, 0);
end = strtoll(argv[3], 0, 0);
return addpart(lun, argv[1], start, end, 0640);
}else if(strcmp(argv[0], "delpart") == 0){
if(argc != 2){
werrstr("delpart takes 1 arg");
return -1;
}
return delpart(lun, argv[1]);
}
werrstr("unknown ctl");
return -1;
}
/*
* These are used by scuzz scsireq
*/
int exabyte, force6bytecmds;
int diskdebug;
static void
ding(void *, char *msg)
{
if(strstr(msg, "alarm") != nil)
noted(NCONT);
noted(NDFLT);
}
static int
getmaxlun(Dev *dev)
{
uchar max;
int r;
max = 0;
r = Rd2h|Rclass|Riface;
if(usbcmd(dev, r, Getmaxlun, 0, 0, &max, 1) < 0){
dprint(2, "disk: %s: getmaxlun failed: %r\n", dev->dir);
}else{
max &= 017; /* 15 is the max. allowed */
dprint(2, "disk: %s: maxlun %d\n", dev->dir, max);
}
return max;
}
static int
umsreset(Ums *ums)
{
int r;
r = Rh2d|Rclass|Riface;
if(usbcmd(ums->dev, r, Umsreset, 0, 0, nil, 0) < 0){
fprint(2, "disk: reset: %r\n");
return -1;
}
return 0;
}
static int
umsrecover(Ums *ums)
{
if(umsreset(ums) < 0)
return -1;
if(unstall(ums->dev, ums->epin, Ein) < 0)
dprint(2, "disk: unstall epin: %r\n");
/* do we need this when epin == epout? */
if(unstall(ums->dev, ums->epout, Eout) < 0)
dprint(2, "disk: unstall epout: %r\n");
return 0;
}
static void
umsfatal(Ums *ums)
{
int i;
devctl(ums->dev, "detach");
for(i = 0; i < ums->maxlun; i++)
usbfsdel(&ums->lun[i].fs);
}
static int
ispow2(uvlong ul)
{
return (ul & (ul - 1)) == 0;
}
/*
* return smallest power of 2 >= n
*/
static int
log2(int n)
{
int i;
for(i = 0; (1 << i) < n; i++)
;
return i;
}
static int
umscapacity(Umsc *lun)
{
uchar data[32];
lun->blocks = 0;
lun->capacity = 0;
lun->lbsize = 0;
memset(data, 0, sizeof data);
if(SRrcapacity(lun, data) < 0 && SRrcapacity(lun, data) < 0)
return -1;
lun->blocks = GETBELONG(data);
lun->lbsize = GETBELONG(data+4);
if(lun->blocks == 0xFFFFFFFF){
if(SRrcapacity16(lun, data) < 0){
lun->lbsize = 0;
lun->blocks = 0;
return -1;
}else{
lun->lbsize = GETBELONG(data + 8);
lun->blocks = (uvlong)GETBELONG(data)<<32 |
GETBELONG(data + 4);
}
}
lun->blocks++; /* SRcapacity returns LBA of last block */
lun->capacity = (vlong)lun->blocks * lun->lbsize;
fixlength(lun, lun->blocks);
if(diskdebug)
fprint(2, "disk: logical block size %lud, # blocks %llud\n",
lun->lbsize, lun->blocks);
return 0;
}
static int
umsinit(Ums *ums)
{
uchar i;
Umsc *lun;
int some;
umsreset(ums);
ums->maxlun = getmaxlun(ums->dev);
ums->lun = mallocz((ums->maxlun+1) * sizeof(*ums->lun), 1);
some = 0;
for(i = 0; i <= ums->maxlun; i++){
lun = &ums->lun[i];
lun->ums = ums;
lun->umsc = lun;
lun->lun = i;
lun->flags = Fopen | Fusb | Frw10;
if(SRinquiry(lun) < 0 && SRinquiry(lun) < 0){
dprint(2, "disk: lun %d inquiry failed\n", i);
continue;
}
switch(lun->inquiry[0]){
case Devdir:
case Devworm: /* a little different than the others */
case Devcd:
case Devmo:
break;
default:
fprint(2, "disk: lun %d is not a disk (type %#02x)\n",
i, lun->inquiry[0]);
continue;
}
SRstart(lun, 1);
/*
* we ignore the device type reported by inquiry.
* Some devices return a wrong value but would still work.
*/
some++;
lun->inq = smprint("%.48s", (char *)lun->inquiry+8);
umscapacity(lun);
}
if(some == 0){
dprint(2, "disk: all luns failed\n");
devctl(ums->dev, "detach");
return -1;
}
return 0;
}
/*
* called by SR*() commands provided by scuzz's scsireq
*/
long
umsrequest(Umsc *umsc, ScsiPtr *cmd, ScsiPtr *data, int *status)
{
Cbw cbw;
Csw csw;
int n, nio, left;
Ums *ums;
ums = umsc->ums;
memcpy(cbw.signature, "USBC", 4);
cbw.tag = ++ums->seq;
cbw.datalen = data->count;
cbw.flags = data->write? CbwDataOut: CbwDataIn;
cbw.lun = umsc->lun;
if(cmd->count < 1 || cmd->count > 16)
print("disk: umsrequest: bad cmd count: %ld\n", cmd->count);
cbw.len = cmd->count;
assert(cmd->count <= sizeof(cbw.command));
memcpy(cbw.command, cmd->p, cmd->count);
memset(cbw.command + cmd->count, 0, sizeof(cbw.command) - cmd->count);
werrstr(""); /* we use %r later even for n == 0 */
if(diskdebug){
fprint(2, "disk: cmd: tag %#lx: ", cbw.tag);
for(n = 0; n < cbw.len; n++)
fprint(2, " %2.2x", cbw.command[n]&0xFF);
fprint(2, " datalen: %ld\n", cbw.datalen);
}
/* issue tunnelled scsi command */
if(write(ums->epout->dfd, &cbw, CbwLen) != CbwLen){
fprint(2, "disk: cmd: %r\n");
goto Fail;
}
/* transfer the data */
nio = data->count;
if(nio != 0){
if(data->write)
n = write(ums->epout->dfd, data->p, nio);
else{
n = read(ums->epin->dfd, data->p, nio);
left = nio - n;
if (n >= 0 && left > 0) /* didn't fill data->p? */
memset(data->p + n, 0, left);
}
nio = n;
if(diskdebug)
if(n < 0)
fprint(2, "disk: data: %r\n");
else
fprint(2, "disk: data: %d bytes\n", n);
if(n <= 0)
if(data->write == 0)
unstall(ums->dev, ums->epin, Ein);
}
/* read the transfer's status */
n = read(ums->epin->dfd, &csw, CswLen);
if(n <= 0){
/* n == 0 means "stalled" */
unstall(ums->dev, ums->epin, Ein);
n = read(ums->epin->dfd, &csw, CswLen);
}
if(n != CswLen || strncmp(csw.signature, "USBS", 4) != 0){
dprint(2, "disk: read n=%d: status: %r\n", n);
goto Fail;
}
if(csw.tag != cbw.tag){
dprint(2, "disk: status tag mismatch\n");
goto Fail;
}
if(csw.status >= CswPhaseErr){
dprint(2, "disk: phase error\n");
goto Fail;
}
if(csw.dataresidue == 0 || ums->wrongresidues)
csw.dataresidue = data->count - nio;
if(diskdebug){
fprint(2, "disk: status: %2.2ux residue: %ld\n",
csw.status, csw.dataresidue);
if(cbw.command[0] == ScmdRsense){
fprint(2, "sense data:");
for(n = 0; n < data->count - csw.dataresidue; n++)
fprint(2, " %2.2x", data->p[n]);
fprint(2, "\n");
}
}
switch(csw.status){
case CswOk:
*status = STok;
break;
case CswFailed:
*status = STcheck;
break;
default:
dprint(2, "disk: phase error\n");
goto Fail;
}
ums->nerrs = 0;
return data->count - csw.dataresidue;
Fail:
*status = STharderr;
if(ums->nerrs++ > 15){
fprint(2, "disk: %s: too many errors: device detached\n", ums->dev->dir);
umsfatal(ums);
}else
umsrecover(ums);
return -1;
}
static int
dwalk(Usbfs *fs, Fid *fid, char *name)
{
Umsc *lun;
Part *p;
lun = fs->aux;
if((fid->qid.type & QTDIR) == 0){
werrstr("walk in non-directory");
return -1;
}
if(strcmp(name, "..") == 0)
return 0;
p = lookpart(lun, name);
if(p == nil){
werrstr(Enotfound);
return -1;
}
fid->qid.path = p->id | fs->qid;
fid->qid.vers = p->vers;
fid->qid.type = p->mode >> 24;
return 0;
}
static int
dstat(Usbfs *fs, Qid qid, Dir *d);
static void
dostat(Usbfs *fs, int path, Dir *d)
{
Umsc *lun;
Part *p;
lun = fs->aux;
p = &lun->part[path];
d->qid.path = path;
d->qid.vers = p->vers;
d->qid.type =p->mode >> 24;
d->mode = p->mode;
d->length = (vlong) p->length * lun->lbsize;
strecpy(d->name, d->name + Namesz - 1, p->name);
}
static int
dirgen(Usbfs *fs, Qid, int n, Dir *d, void*)
{
Umsc *lun;
int i;
lun = fs->aux;
for(i = Qctl; i < Qmax; i++){
if(lun->part[i].inuse == 0)
continue;
if(n-- == 0)
break;
}
if(i == Qmax)
return -1;
dostat(fs, i, d);
d->qid.path |= fs->qid;
return 0;
}
static int
dstat(Usbfs *fs, Qid qid, Dir *d)
{
int path;
path = qid.path & ~fs->qid;
dostat(fs, path, d);
d->qid.path |= fs->qid;
return 0;
}
static int
dopen(Usbfs *fs, Fid *fid, int)
{
ulong path;
Umsc *lun;
path = fid->qid.path & ~fs->qid;
lun = fs->aux;
switch(path){
case Qraw:
lun->phase = Pcmd;
break;
}
return 0;
}
/*
* check i/o parameters and compute values needed later.
* we shift & mask manually to avoid run-time calls to _divv and _modv,
* since we don't need general division nor its cost.
*/
static int
setup(Umsc *lun, Part *p, char *data, int count, vlong offset)
{
long nb, lbsize, lbshift, lbmask;
uvlong bno;
if(count < 0 || lun->lbsize <= 0 && umscapacity(lun) < 0 ||
lun->lbsize == 0)
return -1;
lbsize = lun->lbsize;
assert(ispow2(lbsize));
lbshift = log2(lbsize);
lbmask = lbsize - 1;
bno = offset >> lbshift; /* offset / lbsize */
nb = ((offset + count + lbsize - 1) >> lbshift) - bno;
if(bno + nb > p->length) /* past end of partition? */
nb = p->length - bno;
if(nb * lbsize > Maxiosize)
nb = Maxiosize / lbsize;
lun->nb = nb;
if(bno >= p->length || nb == 0)
return 0;
bno += p->offset; /* start of partition */
lun->offset = bno;
lun->off = offset & lbmask; /* offset % lbsize */
if(lun->off == 0 && (count & lbmask) == 0)
lun->bufp = data;
else
/* not transferring full, aligned blocks; need intermediary */
lun->bufp = lun->buf;
return count;
}
/*
* Upon SRread/SRwrite errors we assume the medium may have changed,
* and ask again for the capacity of the media.
* BUG: How to proceed to avoid confussing dossrv??
*/
static long
dread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
{
long n;
ulong path;
char buf[64];
char *s;
Part *p;
Umsc *lun;
Ums *ums;
Qid q;
q = fid->qid;
path = fid->qid.path & ~fs->qid;
ums = fs->dev->aux;
lun = fs->aux;
qlock(ums);
switch(path){
case Qdir:
count = usbdirread(fs, q, data, count, offset, dirgen, nil);
break;
case Qctl:
s = ctlstring(fs);
count = usbreadbuf(data, count, offset, s, strlen(s));
free(s);
break;
case Qraw:
if(lun->lbsize <= 0 && umscapacity(lun) < 0){
count = -1;
break;
}
switch(lun->phase){
case Pcmd:
qunlock(ums);
werrstr("phase error");
return -1;
case Pdata:
lun->data.p = data;
lun->data.count = count;
lun->data.write = 0;
count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
lun->phase = Pstatus;
if(count < 0)
lun->lbsize = 0; /* medium may have changed */
break;
case Pstatus:
n = snprint(buf, sizeof buf, "%11.0ud ", lun->status);
count = usbreadbuf(data, count, 0LL, buf, n);
lun->phase = Pcmd;
break;
}
break;
case Qdata:
default:
p = &lun->part[path];
if(!p->inuse){
count = -1;
werrstr(Eperm);
break;
}
count = setup(lun, p, data, count, offset);
if (count <= 0)
break;
n = SRread(lun, lun->bufp, lun->nb * lun->lbsize);
if(n < 0){
lun->lbsize = 0; /* medium may have changed */
count = -1;
} else if (lun->bufp == data)
count = n;
else{
/*
* if n == lun->nb*lun->lbsize (as expected),
* just copy count bytes.
*/
if(lun->off + count > n)
count = n - lun->off; /* short read */
if(count > 0)
memmove(data, lun->bufp + lun->off, count);
}
break;
}
qunlock(ums);
return count;
}
static long
dwrite(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
{
long len, ocount;
ulong path;
uvlong bno;
Ums *ums;
Part *p;
Umsc *lun;
char *s;
ums = fs->dev->aux;
lun = fs->aux;
path = fid->qid.path & ~fs->qid;
qlock(ums);
switch(path){
case Qdir:
count = -1;
werrstr(Eperm);
break;
case Qctl:
s = emallocz(count+1, 1);
memmove(s, data, count);
if(s[count-1] == '\n')
s[count-1] = 0;
if(ctlparse(fs, s) == -1)
count = -1;
free(s);
break;
case Qraw:
if(lun->lbsize <= 0 && umscapacity(lun) < 0){
count = -1;
break;
}
switch(lun->phase){
case Pcmd:
if(count != 6 && count != 10){
qunlock(ums);
werrstr("bad command length");
return -1;
}
memmove(lun->rawcmd, data, count);
lun->cmd.p = lun->rawcmd;
lun->cmd.count = count;
lun->cmd.write = 1;
lun->phase = Pdata;
break;
case Pdata:
lun->data.p = data;
lun->data.count = count;
lun->data.write = 1;
count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
lun->phase = Pstatus;
if(count < 0)
lun->lbsize = 0; /* medium may have changed */
break;
case Pstatus:
lun->phase = Pcmd;
werrstr("phase error");
count = -1;
break;
}
break;
case Qdata:
default:
p = &lun->part[path];
if(!p->inuse){
count = -1;
werrstr(Eperm);
break;
}
len = ocount = count;
count = setup(lun, p, data, count, offset);
if (count <= 0)
break;
bno = lun->offset;
if (lun->bufp == lun->buf) {
count = SRread(lun, lun->bufp, lun->nb * lun->lbsize);
if(count < 0) {
lun->lbsize = 0; /* medium may have changed */
break;
}
/*
* if count == lun->nb*lun->lbsize, as expected, just
* copy len (the original count) bytes of user data.
*/
if(lun->off + len > count)
len = count - lun->off; /* short read */
if(len > 0)
memmove(lun->bufp + lun->off, data, len);
}
lun->offset = bno;
count = SRwrite(lun, lun->bufp, lun->nb * lun->lbsize);
if(count < 0)
lun->lbsize = 0; /* medium may have changed */
else{
if(lun->off + len > count)
count -= lun->off; /* short write */
/* never report more bytes written than requested */
if(count < 0)
count = 0;
else if(count > ocount)
count = ocount;
}
break;
}
qunlock(ums);
return count;
}
int
findendpoints(Ums *ums)
{
Ep *ep;
Usbdev *ud;
ulong csp, sc;
int i, epin, epout;
epin = epout = -1;
ud = ums->dev->usb;
for(i = 0; i < nelem(ud->ep); i++){
if((ep = ud->ep[i]) == nil)
continue;
csp = ep->iface->csp;
sc = Subclass(csp);
if(!(Class(csp) == Clstorage && (Proto(csp) == Protobulk)))
continue;
if(sc != Subatapi && sc != Sub8070 && sc != Subscsi)
fprint(2, "disk: subclass %#ulx not supported. trying anyway\n", sc);
if(ep->type == Ebulk){
if(ep->dir == Eboth || ep->dir == Ein)
if(epin == -1)
epin = ep->id;
if(ep->dir == Eboth || ep->dir == Eout)
if(epout == -1)
epout = ep->id;
}
}
dprint(2, "disk: ep ids: in %d out %d\n", epin, epout);
if(epin == -1 || epout == -1)
return -1;
ums->epin = openep(ums->dev, epin);
if(ums->epin == nil){
fprint(2, "disk: openep %d: %r\n", epin);
return -1;
}
if(epout == epin){
incref(ums->epin);
ums->epout = ums->epin;
}else
ums->epout = openep(ums->dev, epout);
if(ums->epout == nil){
fprint(2, "disk: openep %d: %r\n", epout);
closedev(ums->epin);
return -1;
}
if(ums->epin == ums->epout)
opendevdata(ums->epin, ORDWR);
else{
opendevdata(ums->epin, OREAD);
opendevdata(ums->epout, OWRITE);
}
if(ums->epin->dfd < 0 || ums->epout->dfd < 0){
fprint(2, "disk: open i/o ep data: %r\n");
closedev(ums->epin);
closedev(ums->epout);
return -1;
}
dprint(2, "disk: ep in %s out %s\n", ums->epin->dir, ums->epout->dir);
devctl(ums->epin, "timeout 2000");
devctl(ums->epout, "timeout 2000");
if(usbdebug > 1 || diskdebug > 2){
devctl(ums->epin, "debug 1");
devctl(ums->epout, "debug 1");
devctl(ums->dev, "debug 1");
}
return 0;
}
static int
usage(void)
{
werrstr("usage: usb/disk [-d] [-N nb]");
return -1;
}
static void
umsdevfree(void *a)
{
Ums *ums = a;
if(ums == nil)
return;
closedev(ums->epin);
closedev(ums->epout);
ums->epin = ums->epout = nil;
free(ums->lun);
free(ums);
}
static Srv diskfs = {
.walk = dwalk,
.open = dopen,
.read = dread,
.write = dwrite,
.stat = dstat,
};
int
diskmain(Dev *dev, int argc, char **argv)
{
Ums *ums;
Umsc *lun;
int i, devid;
devid = dev->id;
ARGBEGIN{
case 'd':
scsidebug(diskdebug);
diskdebug++;
break;
case 'N':
devid = atoi(EARGF(usage()));
break;
default:
return usage();
}ARGEND
if(argc != 0) {
return usage();
}
// notify(ding);
ums = dev->aux = emallocz(sizeof(Ums), 1);
ums->maxlun = -1;
ums->dev = dev;
dev->free = umsdevfree;
if(findendpoints(ums) < 0){
werrstr("disk: endpoints not found");
return -1;
}
/*
* SanDISK 512M gets residues wrong.
*/
if(dev->usb->vid == 0x0781 && dev->usb->did == 0x5150)
ums->wrongresidues = 1;
if(umsinit(ums) < 0){
dprint(2, "disk: umsinit: %r\n");
return -1;
}
for(i = 0; i <= ums->maxlun; i++){
lun = &ums->lun[i];
lun->fs = diskfs;
snprint(lun->fs.name, sizeof(lun->fs.name), "sdU%d.%d", devid, i);
lun->fs.dev = dev;
incref(dev);
lun->fs.aux = lun;
makeparts(lun);
usbfsadd(&lun->fs);
}
return 0;
}

View File

@ -0,0 +1,24 @@
</$objtype/mkfile
TARG=disk
OFILES=\
disk.$O\
scsireq.$O\
scsierrs.$O\
HFILES =\
scsireq.h\
../lib/usb.h\
ums.h\
LIB=../lib/usb.a$O
BIN=/$objtype/bin/usb
</sys/src/cmd/mkone
CFLAGS=-I../lib $CFLAGS
CLEANFILES=scsierrs.c
scsierrs.c: /sys/lib/scsicodes mkscsierrs
mkscsierrs >scsierrs.c

View File

@ -0,0 +1,32 @@
#!/bin/rc
cat <<EOF
#include <u.h>
#include <libc.h>
typedef struct Err Err;
struct Err
{
int n;
char *s;
};
static Err scsierrs[] = {
EOF
grep '^[0-9a-c][0-9a-c][0-9a-c][0-9a-c][ ]' /sys/lib/scsicodes |
sed -e 's/^(....) (.*)/ {0x\1, "\2"},\n/'
cat <<EOF
};
char*
scsierrmsg(int n)
{
int i;
for(i = 0; i < nelem(scsierrs); i++)
if(scsierrs[i].n == n)
return scsierrs[i].s;
return "scsi error";
}
EOF

View File

@ -0,0 +1,986 @@
/*
* This is /sys/src/cmd/scuzz/scsireq.c
* changed to add more debug support, to keep
* disk compiling without a scuzz that includes these changes.
* Also, this includes minor tweaks for usb:
* we set req.lun/unit to rp->lun/unit in SRreqsense
* we set the rp->sense[0] bit Sd0valid in SRreqsense
* This does not use libdisk to retrieve the scsi error to make
* user see the diagnostics if we boot with debug enabled.
*
* BUGS:
* no luns
* and incomplete in many other ways
*/
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include "scsireq.h"
enum {
Debug = 0,
};
/*
* exabyte tape drives, at least old ones like the 8200 and 8505,
* are dumb: you have to read the exact block size on the tape,
* they don't take 10-byte SCSI commands, and various other fine points.
*/
extern int exabyte, force6bytecmds;
static int debug = Debug;
static char *scmdnames[256] = {
[ScmdTur] "Tur",
[ScmdRewind] "Rewind",
[ScmdRsense] "Rsense",
[ScmdFormat] "Format",
[ScmdRblimits] "Rblimits",
[ScmdRead] "Read",
[ScmdWrite] "Write",
[ScmdSeek] "Seek",
[ScmdFmark] "Fmark",
[ScmdSpace] "Space",
[ScmdInq] "Inq",
[ScmdMselect6] "Mselect6",
[ScmdMselect10] "Mselect10",
[ScmdMsense6] "Msense6",
[ScmdMsense10] "Msense10",
[ScmdStart] "Start",
[ScmdRcapacity] "Rcapacity",
[ScmdRcapacity16] "Rcap16",
[ScmdExtread] "Extread",
[ScmdExtwrite] "Extwrite",
[ScmdExtseek] "Extseek",
[ScmdSynccache] "Synccache",
[ScmdRTOC] "RTOC",
[ScmdRdiscinfo] "Rdiscinfo",
[ScmdRtrackinfo] "Rtrackinfo",
[ScmdReserve] "Reserve",
[ScmdBlank] "Blank",
[ScmdCDpause] "CDpause",
[ScmdCDstop] "CDstop",
[ScmdCDplay] "CDplay",
[ScmdCDload] "CDload",
[ScmdCDscan] "CDscan",
[ScmdCDstatus] "CDstatus",
[Scmdgetconf] "getconf",
};
long
SRready(ScsiReq *rp)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = cmd;
rp->data.count = 0;
rp->data.write = 1;
return SRrequest(rp);
}
long
SRrewind(ScsiReq *rp)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdRewind;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = cmd;
rp->data.count = 0;
rp->data.write = 1;
if(SRrequest(rp) >= 0){
rp->offset = 0;
return 0;
}
return -1;
}
long
SRreqsense(ScsiReq *rp)
{
uchar cmd[6];
ScsiReq req;
long status;
if(rp->status == Status_SD){
rp->status = STok;
return 0;
}
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdRsense;
cmd[4] = sizeof(req.sense);
memset(&req, 0, sizeof(req));
if(rp->flags&Fusb)
req.flags |= Fusb;
req.lun = rp->lun;
req.unit = rp->unit;
req.fd = rp->fd;
req.umsc = rp->umsc;
req.cmd.p = cmd;
req.cmd.count = sizeof cmd;
req.data.p = rp->sense;
req.data.count = sizeof(rp->sense);
req.data.write = 0;
status = SRrequest(&req);
rp->status = req.status;
if(status != -1)
rp->sense[0] |= Sd0valid;
return status;
}
long
SRformat(ScsiReq *rp)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdFormat;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = cmd;
rp->data.count = 6;
rp->data.write = 0;
return SRrequest(rp);
}
long
SRrblimits(ScsiReq *rp, uchar *list)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdRblimits;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = list;
rp->data.count = 6;
rp->data.write = 0;
return SRrequest(rp);
}
static int
dirdevrw(ScsiReq *rp, uchar *cmd, long nbytes)
{
long n;
n = nbytes / rp->lbsize;
if(rp->offset <= Max24off && n <= 256 && (rp->flags & Frw10) == 0){
PUTBE24(cmd+1, rp->offset);
cmd[4] = n;
cmd[5] = 0;
return 6;
}
cmd[0] |= ScmdExtread;
cmd[1] = 0;
PUTBELONG(cmd+2, rp->offset);
cmd[6] = 0;
cmd[7] = n>>8;
cmd[8] = n;
cmd[9] = 0;
return 10;
}
static int
seqdevrw(ScsiReq *rp, uchar *cmd, long nbytes)
{
long n;
/* don't set Cmd1sili; we want the ILI bit instead of a fatal error */
cmd[1] = rp->flags&Fbfixed? Cmd1fixed: 0;
n = nbytes / rp->lbsize;
PUTBE24(cmd+2, n);
cmd[5] = 0;
return 6;
}
extern int diskdebug;
long
SRread(ScsiReq *rp, void *buf, long nbytes)
{
uchar cmd[10];
long n;
if(rp->lbsize == 0 || (nbytes % rp->lbsize) || nbytes > Maxiosize){
if(diskdebug)
if (nbytes % rp->lbsize)
fprint(2, "disk: i/o size %ld %% %ld != 0\n",
nbytes, rp->lbsize);
else
fprint(2, "disk: i/o size %ld > %d\n",
nbytes, Maxiosize);
rp->status = Status_BADARG;
return -1;
}
/* set up scsi read cmd */
cmd[0] = ScmdRead;
if(rp->flags & Fseqdev)
rp->cmd.count = seqdevrw(rp, cmd, nbytes);
else
rp->cmd.count = dirdevrw(rp, cmd, nbytes);
rp->cmd.p = cmd;
rp->data.p = buf;
rp->data.count = nbytes;
rp->data.write = 0;
/* issue it */
n = SRrequest(rp);
if(n != -1){ /* it worked? */
rp->offset += n / rp->lbsize;
return n;
}
/* request failed; maybe we just read a short record? */
if (exabyte) {
fprint(2, "read error\n");
rp->status = STcheck;
return n;
}
if(rp->status != Status_SD || !(rp->sense[0] & Sd0valid))
return -1;
/* compute # of bytes not read */
n = GETBELONG(rp->sense+3) * rp->lbsize;
if(!(rp->flags & Fseqdev))
return -1;
/* device is a tape or something similar */
if (rp->sense[2] == Sd2filemark || rp->sense[2] == 0x08 ||
rp->sense[2] & Sd2ili && n > 0)
rp->data.count = nbytes - n;
else
return -1;
n = rp->data.count;
if (!rp->readblock++ || debug)
fprint(2, "SRread: tape data count %ld%s\n", n,
(rp->sense[2] & Sd2ili? " with ILI": ""));
rp->status = STok;
rp->offset += n / rp->lbsize;
return n;
}
long
SRwrite(ScsiReq *rp, void *buf, long nbytes)
{
uchar cmd[10];
long n;
if(rp->lbsize == 0 || (nbytes % rp->lbsize) || nbytes > Maxiosize){
if(diskdebug)
if (nbytes % rp->lbsize)
fprint(2, "disk: i/o size %ld %% %ld != 0\n",
nbytes, rp->lbsize);
else
fprint(2, "disk: i/o size %ld > %d\n",
nbytes, Maxiosize);
rp->status = Status_BADARG;
return -1;
}
/* set up scsi write cmd */
cmd[0] = ScmdWrite;
if(rp->flags & Fseqdev)
rp->cmd.count = seqdevrw(rp, cmd, nbytes);
else
rp->cmd.count = dirdevrw(rp, cmd, nbytes);
rp->cmd.p = cmd;
rp->data.p = buf;
rp->data.count = nbytes;
rp->data.write = 1;
/* issue it */
if((n = SRrequest(rp)) == -1){
if (exabyte) {
fprint(2, "write error\n");
rp->status = STcheck;
return n;
}
if(rp->status != Status_SD || rp->sense[2] != Sd2eom)
return -1;
if(rp->sense[0] & Sd0valid){
n -= GETBELONG(rp->sense+3) * rp->lbsize;
rp->data.count = nbytes - n;
}
else
rp->data.count = nbytes;
n = rp->data.count;
}
rp->offset += n / rp->lbsize;
return n;
}
long
SRseek(ScsiReq *rp, long offset, int type)
{
uchar cmd[10];
switch(type){
case 0:
break;
case 1:
offset += rp->offset;
if(offset >= 0)
break;
/*FALLTHROUGH*/
default:
if(diskdebug)
fprint(2, "disk: seek failed\n");
rp->status = Status_BADARG;
return -1;
}
memset(cmd, 0, sizeof cmd);
if(offset <= Max24off && (rp->flags & Frw10) == 0){
cmd[0] = ScmdSeek;
PUTBE24(cmd+1, offset & Max24off);
rp->cmd.count = 6;
}else{
cmd[0] = ScmdExtseek;
PUTBELONG(cmd+2, offset);
rp->cmd.count = 10;
}
rp->cmd.p = cmd;
rp->data.p = cmd;
rp->data.count = 0;
rp->data.write = 1;
SRrequest(rp);
if(rp->status == STok) {
rp->offset = offset;
return offset;
}
return -1;
}
long
SRfilemark(ScsiReq *rp, ulong howmany)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdFmark;
PUTBE24(cmd+2, howmany);
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = cmd;
rp->data.count = 0;
rp->data.write = 1;
return SRrequest(rp);
}
long
SRspace(ScsiReq *rp, uchar code, long howmany)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdSpace;
cmd[1] = code;
PUTBE24(cmd+2, howmany);
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = cmd;
rp->data.count = 0;
rp->data.write = 1;
/*
* what about rp->offset?
*/
return SRrequest(rp);
}
long
SRinquiry(ScsiReq *rp)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdInq;
cmd[4] = sizeof rp->inquiry;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
memset(rp->inquiry, 0, sizeof rp->inquiry);
rp->data.p = rp->inquiry;
rp->data.count = sizeof rp->inquiry;
rp->data.write = 0;
if(SRrequest(rp) >= 0){
rp->flags |= Finqok;
return 0;
}
rp->flags &= ~Finqok;
return -1;
}
long
SRmodeselect6(ScsiReq *rp, uchar *list, long nbytes)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdMselect6;
if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
cmd[1] = 0x10;
cmd[4] = nbytes;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = list;
rp->data.count = nbytes;
rp->data.write = 1;
return SRrequest(rp);
}
long
SRmodeselect10(ScsiReq *rp, uchar *list, long nbytes)
{
uchar cmd[10];
memset(cmd, 0, sizeof cmd);
if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
cmd[1] = 0x10;
cmd[0] = ScmdMselect10;
cmd[7] = nbytes>>8;
cmd[8] = nbytes;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = list;
rp->data.count = nbytes;
rp->data.write = 1;
return SRrequest(rp);
}
long
SRmodesense6(ScsiReq *rp, uchar page, uchar *list, long nbytes)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdMsense6;
cmd[2] = page;
cmd[4] = nbytes;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = list;
rp->data.count = nbytes;
rp->data.write = 0;
return SRrequest(rp);
}
long
SRmodesense10(ScsiReq *rp, uchar page, uchar *list, long nbytes)
{
uchar cmd[10];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdMsense10;
cmd[2] = page;
cmd[7] = nbytes>>8;
cmd[8] = nbytes;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = list;
rp->data.count = nbytes;
rp->data.write = 0;
return SRrequest(rp);
}
long
SRstart(ScsiReq *rp, uchar code)
{
uchar cmd[6];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdStart;
cmd[4] = code;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = cmd;
rp->data.count = 0;
rp->data.write = 1;
return SRrequest(rp);
}
long
SRrcapacity(ScsiReq *rp, uchar *data)
{
uchar cmd[10];
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdRcapacity;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = data;
rp->data.count = 8;
rp->data.write = 0;
return SRrequest(rp);
}
long
SRrcapacity16(ScsiReq *rp, uchar *data)
{
uchar cmd[16];
uint i;
i = 32;
memset(cmd, 0, sizeof cmd);
cmd[0] = ScmdRcapacity16;
cmd[1] = 0x10;
cmd[10] = i>>24;
cmd[11] = i>>16;
cmd[12] = i>>8;
cmd[13] = i;
rp->cmd.p = cmd;
rp->cmd.count = sizeof cmd;
rp->data.p = data;
rp->data.count = i;
rp->data.write = 0;
return SRrequest(rp);
}
void
scsidebug(int d)
{
debug = d;
if(debug)
fprint(2, "scsidebug on\n");
}
static long
request(int fd, ScsiPtr *cmd, ScsiPtr *data, int *status)
{
long n, r;
char buf[16];
/* this was an experiment but it seems to be a good idea */
*status = STok;
/* send SCSI command */
if(write(fd, cmd->p, cmd->count) != cmd->count){
fprint(2, "scsireq: write cmd: %r\n");
*status = Status_SW;
return -1;
}
/* read or write actual data */
werrstr("");
// alarm(5*1000);
if(data->write)
n = write(fd, data->p, data->count);
else {
n = read(fd, data->p, data->count);
if (n < 0)
memset(data->p, 0, data->count);
else if (n < data->count)
memset(data->p + n, 0, data->count - n);
}
// alarm(0);
if (n != data->count && n <= 0) {
if (debug)
fprint(2,
"request: tried to %s %ld bytes of data for cmd 0x%x but got %r\n",
(data->write? "write": "read"),
data->count, cmd->p[0]);
} else if (n != data->count && (data->write || debug))
fprint(2, "request: %s %ld of %ld bytes of actual data\n",
(data->write? "wrote": "read"), n, data->count);
/* read status */
buf[0] = '\0';
r = read(fd, buf, sizeof buf-1);
if(exabyte && r <= 0 || !exabyte && r < 0){
fprint(2, "scsireq: read status: %r\n");
*status = Status_SW;
return -1;
}
if (r >= 0)
buf[r] = '\0';
*status = atoi(buf);
if(n < 0 && (exabyte || *status != STcheck))
fprint(2, "scsireq: status 0x%2.2uX: data transfer: %r\n",
*status);
return n;
}
static char*
seprintcmd(char *s, char* e, char *cmd, int count, int args)
{
uint c;
if(count < 6)
return seprint(s, e, "<short cmd>");
c = cmd[0];
if(scmdnames[c] != nil)
s = seprint(s, e, "%s", scmdnames[c]);
else
s = seprint(s, e, "cmd:%#02uX", c);
if(args != 0)
switch(c){
case ScmdRsense:
case ScmdInq:
case ScmdMselect6:
case ScmdMsense6:
s = seprint(s, e, " sz %d", cmd[4]);
break;
case ScmdSpace:
s = seprint(s, e, " code %d", cmd[1]);
break;
case ScmdStart:
s = seprint(s, e, " code %d", cmd[4]);
break;
}
return s;
}
static char*
seprintdata(char *s, char *se, uchar *p, int count)
{
int i;
if(count == 0)
return s;
for(i = 0; i < 20 && i < count; i++)
s = seprint(s, se, " %02x", p[i]);
return s;
}
static void
SRdumpReq(ScsiReq *rp)
{
char buf[128];
char *s;
char *se;
se = buf+sizeof(buf);
s = seprint(buf, se, "lun %d ", rp->lun);
s = seprintcmd(s, se, (char*)rp->cmd.p, rp->cmd.count, 1);
s = seprint(s, se, " [%ld]", rp->data.count);
if(rp->cmd.write)
seprintdata(s, se, rp->data.p, rp->data.count);
fprint(2, "scsi⇒ %s\n", buf);
}
static void
SRdumpRep(ScsiReq *rp)
{
char buf[128];
char *s;
char *se;
se = buf+sizeof(buf);
s = seprint(buf, se, "lun %d ", rp->lun);
s = seprintcmd(s, se, (char*)rp->cmd.p, rp->cmd.count, 0);
switch(rp->status){
case STok:
s = seprint(s, se, " good [%ld] ", rp->data.count);
if(rp->cmd.write == 0)
s = seprintdata(s, se, rp->data.p, rp->data.count);
break;
case STnomem:
s = seprint(s, se, " buffer allocation failed");
break;
case STharderr:
s = seprint(s, se, " controller error");
break;
case STtimeout:
s = seprint(s, se, " bus timeout");
break;
case STcheck:
s = seprint(s, se, " check condition");
break;
case STcondmet:
s = seprint(s, se, " condition met/good");
break;
case STbusy:
s = seprint(s, se, " busy");
break;
case STintok:
s = seprint(s, se, " intermediate/good");
break;
case STintcondmet:
s = seprint(s, se, " intermediate/condition met/good");
break;
case STresconf:
s = seprint(s, se, " reservation conflict");
break;
case STterminated:
s = seprint(s, se, " command terminated");
break;
case STqfull:
s = seprint(s, se, " queue full");
break;
default:
s = seprint(s, se, " sts=%#x", rp->status);
}
USED(s);
fprint(2, "scsi← %s\n", buf);
}
static char*
scsierr(ScsiReq *rp)
{
int ec;
switch(rp->status){
case 0:
return "";
case Status_SD:
ec = (rp->sense[12] << 8) | rp->sense[13];
return scsierrmsg(ec);
case Status_SW:
return "software error";
case Status_BADARG:
return "bad argument";
case Status_RO:
return "device is read only";
default:
return "unknown";
}
}
static void
SRdumpErr(ScsiReq *rp)
{
char buf[128];
char *se;
se = buf+sizeof(buf);
seprintcmd(buf, se, (char*)rp->cmd.p, rp->cmd.count, 0);
print("\t%s status: %s\n", buf, scsierr(rp));
}
long
SRrequest(ScsiReq *rp)
{
long n;
int status;
retry:
if(debug)
SRdumpReq(rp);
if(rp->flags&Fusb)
n = umsrequest(rp->umsc, &rp->cmd, &rp->data, &status);
else
n = request(rp->fd, &rp->cmd, &rp->data, &status);
rp->status = status;
if(status == STok)
rp->data.count = n;
if(debug)
SRdumpRep(rp);
switch(status){
case STok:
break;
case STcheck:
if(rp->cmd.p[0] != ScmdRsense && SRreqsense(rp) != -1)
rp->status = Status_SD;
if(debug || exabyte)
SRdumpErr(rp);
werrstr("%s", scsierr(rp));
return -1;
case STbusy:
sleep(1000); /* TODO: try a shorter sleep? */
goto retry;
default:
if(debug || exabyte)
SRdumpErr(rp);
werrstr("%s", scsierr(rp));
return -1;
}
return n;
}
int
SRclose(ScsiReq *rp)
{
if((rp->flags & Fopen) == 0){
if(diskdebug)
fprint(2, "disk: closing closed file\n");
rp->status = Status_BADARG;
return -1;
}
close(rp->fd);
rp->flags = 0;
return 0;
}
static int
dirdevopen(ScsiReq *rp)
{
uvlong blocks;
uchar data[8+4+20]; /* 16-byte result: lba, blksize, reserved */
memset(data, 0, sizeof data);
if(SRstart(rp, 1) == -1 || SRrcapacity(rp, data) == -1)
return -1;
rp->lbsize = GETBELONG(data+4);
blocks = GETBELONG(data);
if(debug)
fprint(2, "disk: dirdevopen: 10-byte logical block size %lud, "
"# blocks %llud\n", rp->lbsize, blocks);
if(blocks == 0xffffffff){
if(SRrcapacity16(rp, data) == -1)
return -1;
rp->lbsize = GETBELONG(data + 8);
blocks = (vlong)GETBELONG(data)<<32 | GETBELONG(data + 4);
if(debug)
fprint(2, "disk: dirdevopen: 16-byte logical block size"
" %lud, # blocks %llud\n", rp->lbsize, blocks);
}
/* some newer dev's don't support 6-byte commands */
if(blocks > Max24off && !force6bytecmds)
rp->flags |= Frw10;
return 0;
}
static int
seqdevopen(ScsiReq *rp)
{
uchar mode[16], limits[6];
if(SRrblimits(rp, limits) == -1)
return -1;
if(limits[1] == 0 && limits[2] == limits[4] && limits[3] == limits[5]){
rp->flags |= Fbfixed;
rp->lbsize = limits[4]<<8 | limits[5];
if(debug)
fprint(2, "disk: seqdevopen: 10-byte logical block size %lud\n",
rp->lbsize);
return 0;
}
/*
* On some older hardware the optional 10-byte
* modeselect command isn't implemented.
*/
if (force6bytecmds)
rp->flags |= Fmode6;
if(!(rp->flags & Fmode6)){
/* try 10-byte command first */
memset(mode, 0, sizeof mode);
mode[3] = 0x10; /* device-specific param. */
mode[7] = 8; /* block descriptor length */
/*
* exabytes can't handle this, and
* modeselect(10) is optional.
*/
if(SRmodeselect10(rp, mode, sizeof mode) != -1){
rp->lbsize = 1;
return 0; /* success */
}
/* can't do 10-byte commands, back off to 6-byte ones */
rp->flags |= Fmode6;
}
/* 6-byte command */
memset(mode, 0, sizeof mode);
mode[2] = 0x10; /* device-specific param. */
mode[3] = 8; /* block descriptor length */
/*
* bsd sez exabytes need this bit (NBE: no busy enable) in
* vendor-specific page (0), but so far we haven't needed it.
mode[12] |= 8;
*/
if(SRmodeselect6(rp, mode, 4+8) == -1)
return -1;
rp->lbsize = 1;
return 0;
}
static int
wormdevopen(ScsiReq *rp)
{
long status;
uchar list[MaxDirData];
if (SRstart(rp, 1) == -1 ||
(status = SRmodesense10(rp, Allmodepages, list, sizeof list)) == -1)
return -1;
/* nbytes = list[0]<<8 | list[1]; */
/* # of bytes of block descriptors of 8 bytes each; not even 1? */
if((list[6]<<8 | list[7]) < 8)
rp->lbsize = 2048;
else
/* last 3 bytes of block 0 descriptor */
rp->lbsize = GETBE24(list+13);
if(debug)
fprint(2, "disk: wormdevopen: 10-byte logical block size %lud\n",
rp->lbsize);
return status;
}
int
SRopenraw(ScsiReq *rp, char *unit)
{
char name[128];
if(rp->flags & Fopen){
if(diskdebug)
fprint(2, "disk: opening open file\n");
rp->status = Status_BADARG;
return -1;
}
memset(rp, 0, sizeof *rp);
rp->unit = unit;
snprint(name, sizeof name, "%s/raw", unit);
if((rp->fd = open(name, ORDWR)) == -1){
rp->status = STtimeout;
return -1;
}
rp->flags = Fopen;
return 0;
}
int
SRopen(ScsiReq *rp, char *unit)
{
if(SRopenraw(rp, unit) == -1)
return -1;
SRready(rp);
if(SRinquiry(rp) >= 0){
switch(rp->inquiry[0]){
default:
fprint(2, "unknown device type 0x%.2x\n", rp->inquiry[0]);
rp->status = Status_SW;
break;
case Devdir:
case Devcd:
case Devmo:
if(dirdevopen(rp) == -1)
break;
return 0;
case Devseq:
rp->flags |= Fseqdev;
if(seqdevopen(rp) == -1)
break;
return 0;
case Devprint:
rp->flags |= Fprintdev;
return 0;
case Devworm:
rp->flags |= Fwormdev;
if(wormdevopen(rp) == -1)
break;
return 0;
case Devjuke:
rp->flags |= Fchanger;
return 0;
}
}
SRclose(rp);
return -1;
}

View File

@ -0,0 +1,238 @@
/*
* This is /sys/src/cmd/scuzz/scsireq.h
* changed to add more debug support, and to keep
* disk compiling without a scuzz that includes these changes.
*
* scsireq.h is also included by usb/disk and cdfs.
*/
typedef struct Umsc Umsc;
#pragma incomplete Umsc
enum { /* fundamental constants/defaults */
MaxDirData = 255, /* max. direct data returned */
/*
* Because we are accessed via devmnt, we can never get i/o counts
* larger than 8216 (Msgsize and devmnt's offered iounit) - 24
* (IOHDRSZ) = 8K.
*/
Maxiosize = 8216 - IOHDRSZ, /* max. I/O transfer size */
};
typedef struct {
uchar *p;
long count;
uchar write;
} ScsiPtr;
typedef struct {
int flags;
char *unit; /* unit directory */
int lun;
ulong lbsize;
uvlong offset; /* in blocks of lbsize bytes */
int fd;
Umsc *umsc; /* lun */
ScsiPtr cmd;
ScsiPtr data;
int status; /* returned status */
uchar sense[MaxDirData]; /* returned sense data */
uchar inquiry[MaxDirData]; /* returned inquiry data */
int readblock; /* flag: read a block since open */
} ScsiReq;
enum { /* software flags */
Fopen = 0x0001, /* open */
Fseqdev = 0x0002, /* sequential-access device */
Fwritten = 0x0004, /* device written */
Fronly = 0x0008, /* device is read-only */
Fwormdev = 0x0010, /* write-once read-multiple device */
Fprintdev = 0x0020, /* printer */
Fbfixed = 0x0040, /* fixed block size */
Fchanger = 0x0080, /* medium-changer device */
Finqok = 0x0100, /* inquiry data is OK */
Fmode6 = 0x0200, /* use 6-byte modeselect */
Frw10 = 0x0400, /* use 10-byte read/write */
Fusb = 0x0800, /* USB transparent scsi */
};
enum {
STnomem =-4, /* buffer allocation failed */
STharderr =-3, /* controller error of some kind */
STtimeout =-2, /* bus timeout */
STok = 0, /* good */
STcheck = 0x02, /* check condition */
STcondmet = 0x04, /* condition met/good */
STbusy = 0x08, /* busy */
STintok = 0x10, /* intermediate/good */
STintcondmet = 0x14, /* intermediate/condition met/good */
STresconf = 0x18, /* reservation conflict */
STterminated = 0x22, /* command terminated */
STqfull = 0x28, /* queue full */
};
enum { /* status */
Status_SD = 0x80, /* sense-data available */
Status_SW = 0x83, /* internal software error */
Status_BADARG = 0x84, /* bad argument to request */
Status_RO = 0x85, /* device is read-only */
};
enum { /* SCSI command codes */
ScmdTur = 0x00, /* test unit ready */
ScmdRewind = 0x01, /* rezero/rewind */
ScmdRsense = 0x03, /* request sense */
ScmdFormat = 0x04, /* format unit */
ScmdRblimits = 0x05, /* read block limits */
ScmdRead = 0x08, /* read */
ScmdWrite = 0x0A, /* write */
ScmdSeek = 0x0B, /* seek */
ScmdFmark = 0x10, /* write filemarks */
ScmdSpace = 0x11, /* space forward/backward */
ScmdInq = 0x12, /* inquiry */
ScmdMselect6 = 0x15, /* mode select */
ScmdMselect10 = 0x55, /* mode select */
ScmdMsense6 = 0x1A, /* mode sense */
ScmdMsense10 = 0x5A, /* mode sense */
ScmdStart = 0x1B, /* start/stop unit */
ScmdRcapacity = 0x25, /* read capacity */
ScmdRcapacity16 = 0x9e, /* long read capacity */
ScmdExtread = 0x28, /* extended read */
ScmdExtwrite = 0x2A, /* extended write */
ScmdExtseek = 0x2B, /* extended seek */
ScmdSynccache = 0x35, /* flush cache */
ScmdRTOC = 0x43, /* read TOC data */
ScmdRdiscinfo = 0x51, /* read disc information */
ScmdRtrackinfo = 0x52, /* read track information */
ScmdReserve = 0x53, /* reserve track */
ScmdBlank = 0xA1, /* blank *-RW media */
ScmdCDpause = 0x4B, /* pause/resume */
ScmdCDstop = 0x4E, /* stop play/scan */
ScmdCDplay = 0xA5, /* play audio */
ScmdCDload = 0xA6, /* load/unload */
ScmdCDscan = 0xBA, /* fast forward/reverse */
ScmdCDstatus = 0xBD, /* mechanism status */
Scmdgetconf = 0x46, /* get configuration */
ScmdEInitialise = 0x07, /* initialise element status */
ScmdMMove = 0xA5, /* move medium */
ScmdEStatus = 0xB8, /* read element status */
ScmdMExchange = 0xA6, /* exchange medium */
ScmdEposition = 0x2B, /* position to element */
ScmdReadDVD = 0xAD, /* read dvd structure */
ScmdReportKey = 0xA4, /* read dvd key */
ScmdSendKey = 0xA3, /* write dvd key */
ScmdClosetracksess= 0x5B,
ScmdRead12 = 0xA8,
ScmdSetcdspeed = 0xBB,
ScmdReadcd = 0xBE,
/* vendor-specific */
ScmdFwaddr = 0xE2, /* first writeable address */
ScmdTreserve = 0xE4, /* reserve track */
ScmdTinfo = 0xE5, /* read track info */
ScmdTwrite = 0xE6, /* write track */
ScmdMload = 0xE7, /* medium load/unload */
ScmdFixation = 0xE9, /* fixation */
};
enum {
/* sense data byte 0 */
Sd0valid = 0x80, /* valid sense data present */
/* sense data byte 2 */
/* incorrect-length indicator, difference in bytes 3—6 */
Sd2ili = 0x20,
Sd2eom = 0x40, /* end of medium (tape) */
Sd2filemark = 0x80, /* at a filemark (tape) */
/* command byte 1 */
Cmd1fixed = 1, /* use fixed-length blocks */
Cmd1sili = 2, /* don't set Sd2ili */
/* limit of block #s in 24-bit ccbs */
Max24off = (1<<21) - 1, /* 2⁲ⁱ - 1 */
/* mode pages */
Allmodepages = 0x3F,
};
/* scsi device types, from the scsi standards */
enum {
Devdir, /* usually disk */
Devseq, /* usually tape */
Devprint,
Dev3,
Devworm, /* also direct, but special */
Devcd, /* also direct */
Dev6,
Devmo, /* also direct */
Devjuke,
};
/* p arguments should be of type uchar* */
#define GETBELONG(p) ((ulong)(p)[0]<<24 | (ulong)(p)[1]<<16 | (p)[2]<<8 | (p)[3])
#define PUTBELONG(p, ul) ((p)[0] = (ul)>>24, (p)[1] = (ul)>>16, \
(p)[2] = (ul)>>8, (p)[3] = (ul))
#define GETBE24(p) ((ulong)(p)[0]<<16 | (p)[1]<<8 | (p)[2])
#define PUTBE24(p, ul) ((p)[0] = (ul)>>16, (p)[1] = (ul)>>8, (p)[2] = (ul))
long SRready(ScsiReq*);
long SRrewind(ScsiReq*);
long SRreqsense(ScsiReq*);
long SRformat(ScsiReq*);
long SRrblimits(ScsiReq*, uchar*);
long SRread(ScsiReq*, void*, long);
long SRwrite(ScsiReq*, void*, long);
long SRseek(ScsiReq*, long, int);
long SRfilemark(ScsiReq*, ulong);
long SRspace(ScsiReq*, uchar, long);
long SRinquiry(ScsiReq*);
long SRmodeselect6(ScsiReq*, uchar*, long);
long SRmodeselect10(ScsiReq*, uchar*, long);
long SRmodesense6(ScsiReq*, uchar, uchar*, long);
long SRmodesense10(ScsiReq*, uchar, uchar*, long);
long SRstart(ScsiReq*, uchar);
long SRrcapacity(ScsiReq*, uchar*);
long SRrcapacity16(ScsiReq*, uchar*);
long SRblank(ScsiReq*, uchar, uchar); /* MMC CD-R/CD-RW commands */
long SRsynccache(ScsiReq*);
long SRTOC(ScsiReq*, void*, int, uchar, uchar);
long SRrdiscinfo(ScsiReq*, void*, int);
long SRrtrackinfo(ScsiReq*, void*, int, int);
long SRcdpause(ScsiReq*, int); /* MMC CD audio commands */
long SRcdstop(ScsiReq*);
long SRcdload(ScsiReq*, int, int);
long SRcdplay(ScsiReq*, int, long, long);
long SRcdstatus(ScsiReq*, uchar*, int);
long SRgetconf(ScsiReq*, uchar*, int);
/* old CD-R/CD-RW commands */
long SRfwaddr(ScsiReq*, uchar, uchar, uchar, uchar*);
long SRtreserve(ScsiReq*, long);
long SRtinfo(ScsiReq*, uchar, uchar*);
long SRwtrack(ScsiReq*, void*, long, uchar, uchar);
long SRmload(ScsiReq*, uchar);
long SRfixation(ScsiReq*, uchar);
long SReinitialise(ScsiReq*); /* CHANGER commands */
long SRestatus(ScsiReq*, uchar, uchar*, int);
long SRmmove(ScsiReq*, int, int, int, int);
long SRrequest(ScsiReq*);
int SRclose(ScsiReq*);
int SRopenraw(ScsiReq*, char*);
int SRopen(ScsiReq*, char*);
void makesense(ScsiReq*);
long umsrequest(struct Umsc*, ScsiPtr*, ScsiPtr*, int*);
void scsidebug(int);
char* scsierrmsg(int n);

124
sys/src/cmd/nusb/disk/ums.h Normal file
View File

@ -0,0 +1,124 @@
/*
* mass storage transport protocols and subclasses,
* from usb mass storage class specification overview rev 1.2
*/
typedef struct Umsc Umsc;
typedef struct Ums Ums;
typedef struct Cbw Cbw; /* command block wrapper */
typedef struct Csw Csw; /* command status wrapper */
typedef struct Part Part;
enum
{
Protocbi = 0, /* control/bulk/interrupt; mainly floppies */
Protocb = 1, /* " with no interrupt; mainly floppies */
Protobulk = 0x50, /* bulk only */
Subrbc = 1, /* reduced blk cmds */
Subatapi = 2, /* cd/dvd using sff-8020i or mmc-2 cmd blks */
Subqic = 3, /* QIC-157 tapes */
Subufi = 4, /* floppy */
Sub8070 = 5, /* removable media, atapi-like */
Subscsi = 6, /* scsi transparent cmd set */
Subisd200 = 7, /* ISD200 ATA */
Subdev = 0xff, /* use device's value */
Umsreset = 0xFF,
Getmaxlun = 0xFE,
// Maxlun = 256,
Maxlun = 32,
CMreset = 1,
Pcmd = 0,
Pdata,
Pstatus,
CbwLen = 31,
CbwDataIn = 0x80,
CbwDataOut = 0x00,
CswLen = 13,
CswOk = 0,
CswFailed = 1,
CswPhaseErr = 2,
Maxparts = 16,
};
/*
* corresponds to a lun.
* these are ~600+Maxiosize bytes each; ScsiReq is not tiny.
*/
struct Part
{
int id;
int inuse;
int vers;
ulong mode;
char *name;
vlong offset; /* in lbsize units */
vlong length; /* in lbsize units */
};
struct Umsc
{
ScsiReq;
uvlong blocks;
vlong capacity;
/* from setup */
char *bufp;
long off; /* offset within a block */
long nb; /* byte count */
/* partitions */
Part part[Maxparts];
uchar rawcmd[10];
uchar phase;
char *inq;
Ums *ums;
char buf[Maxiosize];
};
struct Ums
{
QLock;
Dev *dev;
Dev *epin;
Dev *epout;
Umsc *lun;
uchar maxlun;
int seq;
int nerrs;
int wrongresidues;
};
/*
* USB transparent SCSI devices
*/
struct Cbw
{
char signature[4]; /* "USBC" */
long tag;
long datalen;
uchar flags;
uchar lun;
uchar len;
char command[16];
};
struct Csw
{
char signature[4]; /* "USBS" */
long tag;
long dataresidue;
uchar status;
};
int diskmain(Dev*, int, char**);

65
sys/src/cmd/nusb/kb/hid.h Normal file
View File

@ -0,0 +1,65 @@
/*
* USB keyboard/mouse constants
*/
enum {
Stack = 32 * 1024,
/* HID class subclass protocol ids */
PtrCSP = 0x020103, /* mouse.boot.hid */
KbdCSP = 0x010103, /* keyboard.boot.hid */
/* Requests */
Getreport = 0x01,
Setreport = 0x09,
Getproto = 0x03,
Setproto = 0x0b,
/* protocols for SET_PROTO request */
Bootproto = 0,
Reportproto = 1,
/* protocols for SET_REPORT request */
Reportout = 0x0200,
};
enum {
/* keyboard modifier bits */
Mlctrl = 0,
Mlshift = 1,
Mlalt = 2,
Mlgui = 3,
Mrctrl = 4,
Mrshift = 5,
Mralt = 6,
Mrgui = 7,
/* masks for byte[0] */
Mctrl = 1<<Mlctrl | 1<<Mrctrl,
Mshift = 1<<Mlshift | 1<<Mrshift,
Malt = 1<<Mlalt | 1<<Mralt,
Mcompose = 1<<Mlalt,
Maltgr = 1<<Mralt,
Mgui = 1<<Mlgui | 1<<Mrgui,
MaxAcc = 3, /* max. ptr acceleration */
PtrMask= 0xf, /* 4 buttons: should allow for more. */
};
/*
* Plan 9 keyboard driver constants.
*/
enum {
/* Scan codes (see kbd.c) */
SCesc1 = 0xe0, /* first of a 2-character sequence */
SCesc2 = 0xe1,
SClshift = 0x2a,
SCrshift = 0x36,
SCctrl = 0x1d,
SCcompose = 0x38,
Keyup = 0x80, /* flag bit */
Keymask = 0x7f, /* regular scan code bits */
};
int kbmain(Dev *d, int argc, char*argv[]);

595
sys/src/cmd/nusb/kb/kb.c Normal file
View File

@ -0,0 +1,595 @@
/*
* USB Human Interaction Device: keyboard and mouse.
*
* If there's no usb keyboard, it tries to setup the mouse, if any.
* It should be started at boot time.
*
* Mouse events are converted to the format of mouse(3)'s
* mousein file.
* Keyboard keycodes are translated to scan codes and sent to kbin(3).
*
*/
#include <u.h>
#include <libc.h>
#include <thread.h>
#include "usb.h"
#include "hid.h"
enum
{
Awakemsg=0xdeaddead,
Diemsg = 0xbeefbeef,
};
typedef struct KDev KDev;
typedef struct Kin Kin;
struct KDev
{
Dev* dev; /* usb device*/
Dev* ep; /* endpoint to get events */
Kin* in; /* used to send events to kernel */
Channel*repeatc; /* only for keyboard */
int accel; /* only for mouse */
};
/*
* Kbdin and mousein files must be shared among all instances.
*/
struct Kin
{
int ref;
int fd;
char* name;
};
/*
* Map for the logitech bluetooth mouse with 8 buttons and wheels.
* { ptr ->mouse}
* { 0x01, 0x01 }, // left
* { 0x04, 0x02 }, // middle
* { 0x02, 0x04 }, // right
* { 0x40, 0x08 }, // up
* { 0x80, 0x10 }, // down
* { 0x10, 0x08 }, // side up
* { 0x08, 0x10 }, // side down
* { 0x20, 0x02 }, // page
* besides wheel and regular up/down report the 4th byte as 1/-1
*/
/*
* key code to scan code; for the page table used by
* the logitech bluetooth keyboard.
*/
static char sctab[256] =
{
[0x00] 0x0, 0x0, 0x0, 0x0, 0x1e, 0x30, 0x2e, 0x20,
[0x08] 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26,
[0x10] 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1f, 0x14,
[0x18] 0x16, 0x2f, 0x11, 0x2d, 0x15, 0x2c, 0x2, 0x3,
[0x20] 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
[0x28] 0x1c, 0x1, 0xe, 0xf, 0x39, 0xc, 0xd, 0x1a,
[0x30] 0x1b, 0x2b, 0x2b, 0x27, 0x28, 0x29, 0x33, 0x34,
[0x38] 0x35, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
[0x40] 0x41, 0x42, 0x43, 0x44, 0x57, 0x58, 0x63, 0x46,
[0x48] 0x77, 0x52, 0x47, 0x49, 0x53, 0x4f, 0x51, 0x4d,
[0x50] 0x4b, 0x50, 0x48, 0x45, 0x35, 0x37, 0x4a, 0x4e,
[0x58] 0x1c, 0x4f, 0x50, 0x51, 0x4b, 0x4c, 0x4d, 0x47,
[0x60] 0x48, 0x49, 0x52, 0x53, 0x56, 0x7f, 0x74, 0x75,
[0x68] 0x55, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
[0x70] 0x78, 0x79, 0x7a, 0x7b, 0x0, 0x0, 0x0, 0x0,
[0x78] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71,
[0x80] 0x73, 0x72, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0,
[0x88] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0x90] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0x98] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xa0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xa8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xb0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xb8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xc0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xc8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xd0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xd8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xe0] 0x1d, 0x2a, 0x38, 0x7d, 0x61, 0x36, 0x64, 0x7e,
[0xe8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, 0x72, 0x71,
[0xf0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
[0xf8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
};
static QLock inlck;
static Kin kbdin =
{
.ref = 0,
.name = "/dev/kbin",
.fd = -1,
};
static Kin ptrin =
{
.ref = 0,
.name = "#m/mousein",
.fd = -1,
};
static int kbdebug;
static int
setbootproto(KDev* f, int eid)
{
int r, id;
r = Rh2d|Rclass|Riface;
id = f->dev->usb->ep[eid]->iface->id;
return usbcmd(f->dev, r, Setproto, Bootproto, id, nil, 0);
}
static int
setleds(KDev* f, int, uchar leds)
{
return usbcmd(f->dev, Rh2d|Rclass|Riface, Setreport, Reportout, 0, &leds, 1);
}
/*
* Try to recover from a babble error. A port reset is the only way out.
* BUG: we should be careful not to reset a bundle with several devices.
*/
static void
recoverkb(KDev *f)
{
int i;
close(f->dev->dfd); /* it's for usbd now */
devctl(f->dev, "reset");
for(i = 0; i < 10; i++){
sleep(500);
if(opendevdata(f->dev, ORDWR) >= 0){
setbootproto(f, f->ep->id);
break;
}
/* else usbd still working... */
}
}
static void
kbfatal(KDev *kd, char *sts)
{
Dev *dev;
if(sts != nil)
fprint(2, "kb: fatal: %s\n", sts);
else
fprint(2, "kb: exiting\n");
if(kd->repeatc != nil)
nbsendul(kd->repeatc, Diemsg);
dev = kd->dev;
kd->dev = nil;
if(kd->ep != nil)
closedev(kd->ep);
kd->ep = nil;
devctl(dev, "detach");
closedev(dev);
/*
* free(kd); done by closedev.
*/
threadexits(sts);
}
static int
scale(KDev *f, int x)
{
int sign = 1;
if(x < 0){
sign = -1;
x = -x;
}
switch(x){
case 0:
case 1:
case 2:
case 3:
break;
case 4:
x = 6 + (f->accel>>2);
break;
case 5:
x = 9 + (f->accel>>1);
break;
default:
x *= MaxAcc;
break;
}
return sign*x;
}
/*
* ps2 mouse is processed mostly at interrupt time.
* for usb we do what we can.
*/
static void
sethipri(void)
{
char fn[30];
int fd;
snprint(fn, sizeof(fn), "/proc/%d/ctl", getpid());
fd = open(fn, OWRITE);
if(fd < 0)
return;
fprint(fd, "pri 13");
close(fd);
}
static void
ptrwork(void* a)
{
static char maptab[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7};
int x, y, b, c, ptrfd;
int mfd, nerrs;
char buf[32];
char mbuf[80];
KDev* f = a;
int hipri;
hipri = nerrs = 0;
ptrfd = f->ep->dfd;
mfd = f->in->fd;
if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
kbfatal(f, "weird mouse maxpkt");
for(;;){
memset(buf, 0, sizeof buf);
if(f->ep == nil)
kbfatal(f, nil);
c = read(ptrfd, buf, f->ep->maxpkt);
assert(f->dev != nil);
assert(f->ep != nil);
if(c < 0){
dprint(2, "kb: mouse: %s: read: %r\n", f->ep->dir);
if(++nerrs < 3){
recoverkb(f);
continue;
}
}
if(c <= 0)
kbfatal(f, nil);
if(c < 3)
continue;
if(f->accel){
x = scale(f, buf[1]);
y = scale(f, buf[2]);
}else{
x = buf[1];
y = buf[2];
}
b = maptab[buf[0] & 0x7];
if(c > 3 && buf[3] == 1) /* up */
b |= 0x08;
if(c > 3 && buf[3] == -1) /* down */
b |= 0x10;
if(kbdebug > 1)
fprint(2, "kb: m%11d %11d %11d\n", x, y, b);
seprint(mbuf, mbuf+sizeof(mbuf), "m%11d %11d %11d", x, y,b);
if(write(mfd, mbuf, strlen(mbuf)) < 0)
kbfatal(f, "mousein i/o");
if(hipri == 0){
sethipri();
hipri = 1;
}
}
}
static void
stoprepeat(KDev *f)
{
sendul(f->repeatc, Awakemsg);
}
static void
startrepeat(KDev *f, uchar esc1, uchar sc)
{
ulong c;
if(esc1)
c = SCesc1 << 8 | (sc & 0xff);
else
c = sc;
sendul(f->repeatc, c);
}
static void
putscan(int kbinfd, uchar esc, uchar sc)
{
uchar s[2] = {SCesc1, 0};
if(sc == 0x41){
kbdebug += 2;
return;
}
if(sc == 0x42){
kbdebug = 0;
return;
}
if(kbdebug)
fprint(2, "sc: %x %x\n", (esc? SCesc1: 0), sc);
s[1] = sc;
if(esc && sc != 0)
write(kbinfd, s, 2);
else if(sc != 0)
write(kbinfd, s+1, 1);
}
static void
repeatproc(void* a)
{
KDev *f;
Channel *repeatc;
int kbdinfd;
ulong l, t, i;
uchar esc1, sc;
/*
* too many jumps here.
* Rewrite instead of debug, if needed.
*/
f = a;
repeatc = f->repeatc;
kbdinfd = f->in->fd;
l = Awakemsg;
Repeat:
if(l == Diemsg)
goto Abort;
while(l == Awakemsg)
l = recvul(repeatc);
if(l == Diemsg)
goto Abort;
esc1 = l >> 8;
sc = l;
t = 160;
for(;;){
for(i = 0; i < t; i += 5){
if(l = nbrecvul(repeatc))
goto Repeat;
sleep(5);
}
putscan(kbdinfd, esc1, sc);
t = 30;
}
Abort:
chanfree(repeatc);
threadexits("aborted");
}
#define hasesc1(sc) (((sc) > 0x47) || ((sc) == 0x38))
static void
putmod(int fd, uchar mods, uchar omods, uchar mask, uchar esc, uchar sc)
{
/* BUG: Should be a single write */
if((mods&mask) && !(omods&mask))
putscan(fd, esc, sc);
if(!(mods&mask) && (omods&mask))
putscan(fd, esc, Keyup|sc);
}
/*
* This routine diffs the state with the last known state
* and invents the scan codes that would have been sent
* by a non-usb keyboard in that case. This also requires supplying
* the extra esc1 byte as well as keyup flags.
* The aim is to allow future addition of other keycode pages
* for other keyboards.
*/
static uchar
putkeys(KDev *f, uchar buf[], uchar obuf[], int n, uchar dk)
{
int i, j;
uchar uk;
int fd;
fd = f->in->fd;
putmod(fd, buf[0], obuf[0], Mctrl, 0, SCctrl);
putmod(fd, buf[0], obuf[0], (1<<Mlshift), 0, SClshift);
putmod(fd, buf[0], obuf[0], (1<<Mrshift), 0, SCrshift);
putmod(fd, buf[0], obuf[0], Mcompose, 0, SCcompose);
putmod(fd, buf[0], obuf[0], Maltgr, 1, SCcompose);
/* Report key downs */
for(i = 2; i < n; i++){
for(j = 2; j < n; j++)
if(buf[i] == obuf[j])
break;
if(j == n && buf[i] != 0){
dk = sctab[buf[i]];
putscan(fd, hasesc1(dk), dk);
startrepeat(f, hasesc1(dk), dk);
}
}
/* Report key ups */
uk = 0;
for(i = 2; i < n; i++){
for(j = 2; j < n; j++)
if(obuf[i] == buf[j])
break;
if(j == n && obuf[i] != 0){
uk = sctab[obuf[i]];
putscan(fd, hasesc1(uk), uk|Keyup);
}
}
if(uk && (dk == 0 || dk == uk)){
stoprepeat(f);
dk = 0;
}
return dk;
}
static int
kbdbusy(uchar* buf, int n)
{
int i;
for(i = 1; i < n; i++)
if(buf[i] == 0 || buf[i] != buf[0])
return 0;
return 1;
}
static void
kbdwork(void *a)
{
int c, i, kbdfd, nerrs;
uchar dk, buf[64], lbuf[64];
char err[128];
KDev *f = a;
kbdfd = f->ep->dfd;
if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
kbfatal(f, "weird maxpkt");
f->repeatc = chancreate(sizeof(ulong), 0);
if(f->repeatc == nil)
kbfatal(f, "chancreate failed");
proccreate(repeatproc, f, Stack);
memset(lbuf, 0, sizeof lbuf);
dk = nerrs = 0;
for(;;){
memset(buf, 0, sizeof buf);
c = read(kbdfd, buf, f->ep->maxpkt);
assert(f->dev != nil);
assert(f->ep != nil);
if(c < 0){
rerrstr(err, sizeof(err));
fprint(2, "kb: %s: read: %s\n", f->ep->dir, err);
if(strstr(err, "babble") != 0 && ++nerrs < 3){
recoverkb(f);
continue;
}
}
if(c <= 0)
kbfatal(f, nil);
if(c < 3)
continue;
if(kbdbusy(buf + 2, c - 2))
continue;
if(usbdebug > 2 || kbdebug > 1){
fprint(2, "kbd mod %x: ", buf[0]);
for(i = 2; i < c; i++)
fprint(2, "kc %x ", buf[i]);
fprint(2, "\n");
}
dk = putkeys(f, buf, lbuf, f->ep->maxpkt, dk);
memmove(lbuf, buf, c);
nerrs = 0;
}
}
static void
freekdev(void *a)
{
KDev *kd;
kd = a;
if(kd->in != nil){
qlock(&inlck);
if(--kd->in->ref == 0){
close(kd->in->fd);
kd->in->fd = -1;
}
qunlock(&inlck);
}
dprint(2, "freekdev\n");
free(kd);
}
static void
kbstart(Dev *d, Ep *ep, Kin *in, void (*f)(void*), int accel)
{
KDev *kd;
qlock(&inlck);
if(in->fd < 0){
in->fd = open(in->name, OWRITE);
if(in->fd < 0){
fprint(2, "kb: %s: %r\n", in->name);
qunlock(&inlck);
return;
}
}
in->ref++; /* for kd->in = in */
qunlock(&inlck);
kd = d->aux = emallocz(sizeof(KDev), 1);
d->free = freekdev;
kd->in = in;
kd->dev = d;
if(setbootproto(kd, ep->id) < 0){
fprint(2, "kb: %s: bootproto: %r\n", d->dir);
return;
}
kd->accel = accel;
kd->ep = openep(d, ep->id);
if(kd->ep == nil){
fprint(2, "kb: %s: openep %d: %r\n", d->dir, ep->id);
return;
}
if(opendevdata(kd->ep, OREAD) < 0){
fprint(2, "kb: %s: opendevdata: %r\n", kd->ep->dir);
closedev(kd->ep);
kd->ep = nil;
return;
}
if(setleds(kd, ep->id, 0) < 0){
fprint(2, "kb: %s: setleds: %r\n", d->dir);
return;
}
incref(d);
proccreate(f, kd, Stack);
}
static void
usage(void)
{
werrstr("usage: usb/kb [-dkm] [-a n] [-N nb]");
threadexits("usage");
}
void
threadmain(int argc, char* argv[])
{
int accel, i;
Dev *d;
Ep *ep;
Usbdev *ud;
accel = 0;
ARGBEGIN{
case 'a':
accel = strtol(EARGF(usage()), nil, 0);
break;
case 'd':
kbdebug++;
break;
default:
usage();
}ARGEND;
if(argc != 1)
usage();
d = getdev(atoi(*argv));
if(d == nil)
sysfatal("getdev: %r");
ud = d->usb;
for(i = 0; i < nelem(ud->ep); i++){
if((ep = ud->ep[i]) == nil)
break;
if(ep->type == Eintr && ep->dir == Ein && ep->iface->csp == KbdCSP)
kbstart(d, ep, &kbdin, kbdwork, accel);
if(ep->type == Eintr && ep->dir == Ein && ep->iface->csp == PtrCSP)
kbstart(d, ep, &ptrin, ptrwork, accel);
}
threadexits(nil);
}

View File

@ -0,0 +1,20 @@
</$objtype/mkfile
TARG=kb
OFILES=kb.$O
HFILES=\
../lib/usb.h\
hid.h\
LIB=../lib/usb.a$O
BIN=/$objtype/bin/nusb
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
</sys/src/cmd/mkone
CFLAGS=-I../lib $CFLAGS

512
sys/src/cmd/nusb/lib/dev.c Normal file
View File

@ -0,0 +1,512 @@
#include <u.h>
#include <libc.h>
#include <thread.h>
#include "usb.h"
/*
* epN.M -> N
*/
static int
nameid(char *s)
{
char *r;
char nm[20];
r = strrchr(s, 'p');
if(r == nil)
return -1;
strecpy(nm, nm+sizeof(nm), r+1);
r = strchr(nm, '.');
if(r == nil)
return -1;
*r = 0;
return atoi(nm);
}
Dev*
openep(Dev *d, int id)
{
char *mode; /* How many modes? */
Ep *ep;
Altc *ac;
Dev *epd;
Usbdev *ud;
char name[40];
if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
return nil;
if(d->cfd < 0 || d->usb == nil){
werrstr("device not configured");
return nil;
}
ud = d->usb;
if(id < 0 || id >= nelem(ud->ep) || ud->ep[id] == nil){
werrstr("bad enpoint number");
return nil;
}
ep = ud->ep[id];
mode = "rw";
if(ep->dir == Ein)
mode = "r";
if(ep->dir == Eout)
mode = "w";
snprint(name, sizeof(name), "/dev/usb/ep%d.%d", d->id, id);
if(access(name, AEXIST) == 0){
dprint(2, "%s: %s already exists; trying to open\n", argv0, name);
epd = opendev(name);
if(epd != nil)
epd->maxpkt = ep->maxpkt; /* guess */
return epd;
}
if(devctl(d, "new %d %d %s", id, ep->type, mode) < 0){
dprint(2, "%s: %s: new: %r\n", argv0, d->dir);
return nil;
}
epd = opendev(name);
if(epd == nil)
return nil;
epd->id = id;
if(devctl(epd, "maxpkt %d", ep->maxpkt) < 0)
fprint(2, "%s: %s: openep: maxpkt: %r\n", argv0, epd->dir);
else
dprint(2, "%s: %s: maxpkt %d\n", argv0, epd->dir, ep->maxpkt);
epd->maxpkt = ep->maxpkt;
ac = ep->iface->altc[0];
if(ep->ntds > 1 && devctl(epd, "ntds %d", ep->ntds) < 0)
fprint(2, "%s: %s: openep: ntds: %r\n", argv0, epd->dir);
else
dprint(2, "%s: %s: ntds %d\n", argv0, epd->dir, ep->ntds);
/*
* For iso endpoints and high speed interrupt endpoints the pollival is
* actually 2 and not n.
* The kernel usb driver must take that into account.
* It's simpler this way.
*/
if(ac != nil && (ep->type == Eintr || ep->type == Eiso) && ac->interval != 0)
if(devctl(epd, "pollival %d", ac->interval) < 0)
fprint(2, "%s: %s: openep: pollival: %r\n", argv0, epd->dir);
return epd;
}
Dev*
opendev(char *fn)
{
Dev *d;
int l;
if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
return nil;
d = emallocz(sizeof(Dev), 1);
incref(d);
l = strlen(fn);
d->dfd = -1;
/*
* +30 to allocate extra size to concat "/<epfilename>"
* we should probably remove that feature from the manual
* and from the code after checking out that nobody relies on
* that.
*/
d->dir = emallocz(l + 30, 0);
strcpy(d->dir, fn);
strcpy(d->dir+l, "/ctl");
d->cfd = open(d->dir, ORDWR|OCEXEC);
d->dir[l] = 0;
d->id = nameid(fn);
if(d->cfd < 0){
werrstr("can't open endpoint %s: %r", d->dir);
free(d->dir);
free(d);
return nil;
}
dprint(2, "%s: opendev %#p %s\n", argv0, d, fn);
return d;
}
int
opendevdata(Dev *d, int mode)
{
char buf[80]; /* more than enough for a usb path */
seprint(buf, buf+sizeof(buf), "%s/data", d->dir);
d->dfd = open(buf, mode|OCEXEC);
return d->dfd;
}
enum
{
/*
* Max device conf is also limited by max control request size as
* limited by Maxctllen in the kernel usb.h (both limits are arbitrary).
*/
Maxdevconf = 4 * 1024, /* asking for 16K kills Newsham's disk */
};
int
loaddevconf(Dev *d, int n)
{
uchar *buf;
int nr;
int type;
if(n >= nelem(d->usb->conf)){
werrstr("loaddevconf: bug: out of configurations in device");
fprint(2, "%s: %r\n", argv0);
return -1;
}
buf = emallocz(Maxdevconf, 0);
type = Rd2h|Rstd|Rdev;
nr = usbcmd(d, type, Rgetdesc, Dconf<<8|n, 0, buf, Maxdevconf);
if(nr < Dconflen){
free(buf);
return -1;
}
if(d->usb->conf[n] == nil)
d->usb->conf[n] = emallocz(sizeof(Conf), 1);
nr = parseconf(d->usb, d->usb->conf[n], buf, nr);
free(buf);
return nr;
}
Ep*
mkep(Usbdev *d, int id)
{
Ep *ep;
d->ep[id] = ep = emallocz(sizeof(Ep), 1);
ep->id = id;
return ep;
}
static char*
mkstr(uchar *b, int n)
{
Rune r;
char *us;
char *s;
char *e;
if(n <= 2 || (n & 1) != 0)
return strdup("none");
n = (n - 2)/2;
b += 2;
us = s = emallocz(n*UTFmax+1, 0);
e = s + n*UTFmax+1;
for(; --n >= 0; b += 2){
r = GET2(b);
s = seprint(s, e, "%C", r);
}
return us;
}
char*
loaddevstr(Dev *d, int sid)
{
uchar buf[128];
int type;
int nr;
if(sid == 0)
return estrdup("none");
type = Rd2h|Rstd|Rdev;
nr=usbcmd(d, type, Rgetdesc, Dstr<<8|sid, 0, buf, sizeof(buf));
return mkstr(buf, nr);
}
int
loaddevdesc(Dev *d)
{
uchar buf[Ddevlen+255];
int nr;
int type;
Ep *ep0;
type = Rd2h|Rstd|Rdev;
nr = sizeof(buf);
memset(buf, 0, Ddevlen);
if((nr=usbcmd(d, type, Rgetdesc, Ddev<<8|0, 0, buf, nr)) < 0)
return -1;
/*
* Several hubs are returning descriptors of 17 bytes, not 18.
* We accept them and leave number of configurations as zero.
* (a get configuration descriptor also fails for them!)
*/
if(nr < Ddevlen){
print("%s: %s: warning: device with short descriptor\n",
argv0, d->dir);
if(nr < Ddevlen-1){
werrstr("short device descriptor (%d bytes)", nr);
return -1;
}
}
d->usb = emallocz(sizeof(Usbdev), 1);
ep0 = mkep(d->usb, 0);
ep0->dir = Eboth;
ep0->type = Econtrol;
ep0->maxpkt = d->maxpkt = 8; /* a default */
nr = parsedev(d, buf, nr);
if(nr >= 0){
d->usb->vendor = loaddevstr(d, d->usb->vsid);
if(strcmp(d->usb->vendor, "none") != 0){
d->usb->product = loaddevstr(d, d->usb->psid);
d->usb->serial = loaddevstr(d, d->usb->ssid);
}
}
return nr;
}
int
configdev(Dev *d)
{
int i;
if(d->dfd < 0)
opendevdata(d, ORDWR);
if(d->dfd < 0)
return -1;
if(loaddevdesc(d) < 0)
return -1;
for(i = 0; i < d->usb->nconf; i++)
if(loaddevconf(d, i) < 0)
return -1;
return 0;
}
static void
closeconf(Conf *c)
{
int i;
int a;
if(c == nil)
return;
for(i = 0; i < nelem(c->iface); i++)
if(c->iface[i] != nil){
for(a = 0; a < nelem(c->iface[i]->altc); a++)
free(c->iface[i]->altc[a]);
free(c->iface[i]);
}
free(c);
}
void
closedev(Dev *d)
{
int i;
Usbdev *ud;
if(d==nil || decref(d) != 0)
return;
dprint(2, "%s: closedev %#p %s\n", argv0, d, d->dir);
if(d->free != nil)
d->free(d->aux);
if(d->cfd >= 0)
close(d->cfd);
if(d->dfd >= 0)
close(d->dfd);
d->cfd = d->dfd = -1;
free(d->dir);
d->dir = nil;
ud = d->usb;
d->usb = nil;
if(ud != nil){
free(ud->vendor);
free(ud->product);
free(ud->serial);
for(i = 0; i < nelem(ud->ep); i++)
free(ud->ep[i]);
for(i = 0; i < nelem(ud->ddesc); i++)
free(ud->ddesc[i]);
for(i = 0; i < nelem(ud->conf); i++)
closeconf(ud->conf[i]);
free(ud);
}
free(d);
}
static char*
reqstr(int type, int req)
{
char *s;
static char* ds[] = { "dev", "if", "ep", "oth" };
static char buf[40];
if(type&Rd2h)
s = seprint(buf, buf+sizeof(buf), "d2h");
else
s = seprint(buf, buf+sizeof(buf), "h2d");
if(type&Rclass)
s = seprint(s, buf+sizeof(buf), "|cls");
else if(type&Rvendor)
s = seprint(s, buf+sizeof(buf), "|vnd");
else
s = seprint(s, buf+sizeof(buf), "|std");
s = seprint(s, buf+sizeof(buf), "|%s", ds[type&3]);
switch(req){
case Rgetstatus: s = seprint(s, buf+sizeof(buf), " getsts"); break;
case Rclearfeature: s = seprint(s, buf+sizeof(buf), " clrfeat"); break;
case Rsetfeature: s = seprint(s, buf+sizeof(buf), " setfeat"); break;
case Rsetaddress: s = seprint(s, buf+sizeof(buf), " setaddr"); break;
case Rgetdesc: s = seprint(s, buf+sizeof(buf), " getdesc"); break;
case Rsetdesc: s = seprint(s, buf+sizeof(buf), " setdesc"); break;
case Rgetconf: s = seprint(s, buf+sizeof(buf), " getcnf"); break;
case Rsetconf: s = seprint(s, buf+sizeof(buf), " setcnf"); break;
case Rgetiface: s = seprint(s, buf+sizeof(buf), " getif"); break;
case Rsetiface: s = seprint(s, buf+sizeof(buf), " setif"); break;
}
USED(s);
return buf;
}
static int
cmdreq(Dev *d, int type, int req, int value, int index, uchar *data, int count)
{
int ndata, n;
uchar *wp;
uchar buf[8];
char *hd, *rs;
assert(d != nil);
if(data == nil){
wp = buf;
ndata = 0;
}else{
ndata = count;
wp = emallocz(8+ndata, 0);
}
wp[0] = type;
wp[1] = req;
PUT2(wp+2, value);
PUT2(wp+4, index);
PUT2(wp+6, count);
if(data != nil)
memmove(wp+8, data, ndata);
if(usbdebug>2){
hd = hexstr(wp, ndata+8);
rs = reqstr(type, req);
fprint(2, "%s: %s val %d|%d idx %d cnt %d out[%d] %s\n",
d->dir, rs, value>>8, value&0xFF,
index, count, ndata+8, hd);
free(hd);
}
n = write(d->dfd, wp, 8+ndata);
if(wp != buf)
free(wp);
if(n < 0)
return -1;
if(n != 8+ndata){
dprint(2, "%s: cmd: short write: %d\n", argv0, n);
return -1;
}
return n;
}
static int
cmdrep(Dev *d, void *buf, int nb)
{
char *hd;
nb = read(d->dfd, buf, nb);
if(nb >0 && usbdebug > 2){
hd = hexstr(buf, nb);
fprint(2, "%s: in[%d] %s\n", d->dir, nb, hd);
free(hd);
}
return nb;
}
int
usbcmd(Dev *d, int type, int req, int value, int index, uchar *data, int count)
{
int i, r, nerr;
char err[64];
/*
* Some devices do not respond to commands some times.
* Others even report errors but later work just fine. Retry.
*/
r = -1;
*err = 0;
for(i = nerr = 0; i < Uctries; i++){
if(type & Rd2h)
r = cmdreq(d, type, req, value, index, nil, count);
else
r = cmdreq(d, type, req, value, index, data, count);
if(r > 0){
if((type & Rd2h) == 0)
break;
r = cmdrep(d, data, count);
if(r > 0)
break;
if(r == 0)
werrstr("no data from device");
}
nerr++;
if(*err == 0)
rerrstr(err, sizeof(err));
sleep(Ucdelay);
}
if(r > 0 && i >= 2)
/* let the user know the device is not in good shape */
fprint(2, "%s: usbcmd: %s: required %d attempts (%s)\n",
argv0, d->dir, i, err);
return r;
}
int
unstall(Dev *dev, Dev *ep, int dir)
{
int r;
if(dir == Ein)
dir = 0x80;
else
dir = 0;
r = Rh2d|Rstd|Rep;
if(usbcmd(dev, r, Rclearfeature, Fhalt, ep->id|dir, nil, 0)<0){
werrstr("unstall: %s: %r", ep->dir);
return -1;
}
if(devctl(ep, "clrhalt") < 0){
werrstr("clrhalt: %s: %r", ep->dir);
return -1;
}
return 0;
}
/*
* To be sure it uses a single write.
*/
int
devctl(Dev *dev, char *fmt, ...)
{
char buf[128];
va_list arg;
char *e;
va_start(arg, fmt);
e = vseprint(buf, buf+sizeof(buf), fmt, arg);
va_end(arg);
return write(dev->cfd, buf, e-buf);
}
Dev *
getdev(int id)
{
Dev *d;
char buf[40];
snprint(buf, sizeof buf, "/dev/usb/ep%d.0", id);
d = opendev(buf);
if(d == nil)
return nil;
if(configdev(d) < 0){
closedev(d);
return nil;
}
return d;
}

176
sys/src/cmd/nusb/lib/dump.c Normal file
View File

@ -0,0 +1,176 @@
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <bio.h>
#include "usb.h"
int usbdebug;
static char *edir[] = {"in", "out", "inout"};
static char *etype[] = {"ctl", "iso", "bulk", "intr"};
static char* cnames[] =
{
"none", "audio", "comms", "hid", "",
"", "", "printer", "storage", "hub", "data"
};
static char* devstates[] =
{
"detached", "attached", "enabled", "assigned", "configured"
};
char*
classname(int c)
{
static char buf[30];
if(c >= 0 && c < nelem(cnames))
return cnames[c];
else{
seprint(buf, buf+30, "%d", c);
return buf;
}
}
char *
hexstr(void *a, int n)
{
int i;
char *dbuff, *s, *e;
uchar *b;
b = a;
dbuff = s = emallocz(1024, 0);
*s = 0;
e = s + 1024;
for(i = 0; i < n; i++)
s = seprint(s, e, " %.2ux", b[i]);
if(s == e)
fprint(2, "%s: usb/lib: hexdump: bug: small buffer\n", argv0);
return dbuff;
}
static char *
seprintiface(char *s, char *e, Iface *i)
{
int j;
Altc *a;
Ep *ep;
char *eds, *ets;
s = seprint(s, e, "\t\tiface csp %s.%uld.%uld\n",
classname(Class(i->csp)), Subclass(i->csp), Proto(i->csp));
for(j = 0; j < Naltc; j++){
a=i->altc[j];
if(a == nil)
break;
s = seprint(s, e, "\t\t alt %d attr %d ival %d",
j, a->attrib, a->interval);
if(a->aux != nil)
s = seprint(s, e, " devspec %p\n", a->aux);
else
s = seprint(s, e, "\n");
}
for(j = 0; j < Nep; j++){
ep = i->ep[j];
if(ep == nil)
break;
eds = ets = "";
if(ep->dir <= nelem(edir))
eds = edir[ep->dir];
if(ep->type <= nelem(etype))
ets = etype[ep->type];
s = seprint(s, e, "\t\t ep id %d addr %d dir %s type %s"
" itype %d maxpkt %d ntds %d\n",
ep->id, ep->addr, eds, ets, ep->isotype,
ep->maxpkt, ep->ntds);
}
return s;
}
static char*
seprintconf(char *s, char *e, Usbdev *d, int ci)
{
int i;
Conf *c;
char *hd;
c = d->conf[ci];
s = seprint(s, e, "\tconf: cval %d attrib %x %d mA\n",
c->cval, c->attrib, c->milliamps);
for(i = 0; i < Niface; i++)
if(c->iface[i] == nil)
break;
else
s = seprintiface(s, e, c->iface[i]);
for(i = 0; i < Nddesc; i++)
if(d->ddesc[i] == nil)
break;
else if(d->ddesc[i]->conf == c){
hd = hexstr((uchar*)&d->ddesc[i]->data,
d->ddesc[i]->data.bLength);
s = seprint(s, e, "\t\tdev desc %x[%d]: %s\n",
d->ddesc[i]->data.bDescriptorType,
d->ddesc[i]->data.bLength, hd);
free(hd);
}
return s;
}
int
Ufmt(Fmt *f)
{
int i;
Dev *d;
Usbdev *ud;
char buf[1024];
char *s, *e;
s = buf;
e = buf+sizeof(buf);
d = va_arg(f->args, Dev*);
if(d == nil)
return fmtprint(f, "<nildev>\n");
s = seprint(s, e, "%s", d->dir);
ud = d->usb;
if(ud == nil)
return fmtprint(f, "%s %ld refs\n", buf, d->ref);
s = seprint(s, e, " csp %s.%uld.%uld",
classname(Class(ud->csp)), Subclass(ud->csp), Proto(ud->csp));
s = seprint(s, e, " vid %#ux did %#ux", ud->vid, ud->did);
s = seprint(s, e, " refs %ld\n", d->ref);
s = seprint(s, e, "\t%s %s %s\n", ud->vendor, ud->product, ud->serial);
for(i = 0; i < Nconf; i++){
if(ud->conf[i] == nil)
break;
else
s = seprintconf(s, e, ud, i);
}
return fmtprint(f, "%s", buf);
}
char*
estrdup(char *s)
{
char *d;
d = strdup(s);
if(d == nil)
sysfatal("strdup: %r");
setmalloctag(d, getcallerpc(&s));
return d;
}
void*
emallocz(ulong size, int zero)
{
void *x;
x = malloc(size);
if(x == nil)
sysfatal("malloc: %r");
if(zero)
memset(x, 0, size);
setmalloctag(x, getcallerpc(&size));
return x;
}

View File

@ -0,0 +1,24 @@
</$objtype/mkfile
LIB=usb.a$O
OFILES=\
dev.$O\
dump.$O\
parse.$O\
HFILES=\
usb.h\
UPDATE=\
$HFILES\
${OFILES:%.$O=%.c}\
mkfile\
</sys/src/cmd/mklib
install:V: $LIB
date
safeinstall:V: install
safeinstallall:V: installall
nuke:V:
rm -f *.[$OS] y.tab.? y.output y.error *.a[$OS]

View File

@ -0,0 +1,270 @@
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <bio.h>
#include "usb.h"
int
parsedev(Dev *xd, uchar *b, int n)
{
Usbdev *d;
DDev *dd;
char *hd;
d = xd->usb;
assert(d != nil);
dd = (DDev*)b;
if(usbdebug>1){
hd = hexstr(b, Ddevlen);
fprint(2, "%s: parsedev %s: %s\n", argv0, xd->dir, hd);
free(hd);
}
if(dd->bLength < Ddevlen){
werrstr("short dev descr. (%d < %d)", dd->bLength, Ddevlen);
return -1;
}
if(dd->bDescriptorType != Ddev){
werrstr("%d is not a dev descriptor", dd->bDescriptorType);
return -1;
}
d->csp = CSP(dd->bDevClass, dd->bDevSubClass, dd->bDevProtocol);
d->ep[0]->maxpkt = xd->maxpkt = dd->bMaxPacketSize0;
d->class = dd->bDevClass;
d->nconf = dd->bNumConfigurations;
if(d->nconf == 0)
dprint(2, "%s: %s: no configurations\n", argv0, xd->dir);
d->vid = GET2(dd->idVendor);
d->did = GET2(dd->idProduct);
d->dno = GET2(dd->bcdDev);
d->vsid = dd->iManufacturer;
d->psid = dd->iProduct;
d->ssid = dd->iSerialNumber;
if(n > Ddevlen && usbdebug>1)
fprint(2, "%s: %s: parsedev: %d bytes left",
argv0, xd->dir, n - Ddevlen);
return Ddevlen;
}
static int
parseiface(Usbdev *d, Conf *c, uchar *b, int n, Iface **ipp, Altc **app)
{
int class, subclass, proto;
int ifid, altid;
DIface *dip;
Iface *ip;
assert(d != nil && c != nil);
if(n < Difacelen){
werrstr("short interface descriptor");
return -1;
}
dip = (DIface *)b;
ifid = dip->bInterfaceNumber;
if(ifid < 0 || ifid >= nelem(c->iface)){
werrstr("bad interface number %d", ifid);
return -1;
}
if(c->iface[ifid] == nil)
c->iface[ifid] = emallocz(sizeof(Iface), 1);
ip = c->iface[ifid];
class = dip->bInterfaceClass;
subclass = dip->bInterfaceSubClass;
proto = dip->bInterfaceProtocol;
ip->csp = CSP(class, subclass, proto);
if(d->csp == 0) /* use csp from 1st iface */
d->csp = ip->csp; /* if device has none */
if(d->class == 0)
d->class = class;
ip->id = ifid;
if(c == d->conf[0] && ifid == 0) /* ep0 was already there */
d->ep[0]->iface = ip;
altid = dip->bAlternateSetting;
if(altid < 0 || altid >= nelem(ip->altc)){
werrstr("bad alternate conf. number %d", altid);
return -1;
}
if(ip->altc[altid] == nil)
ip->altc[altid] = emallocz(sizeof(Altc), 1);
*ipp = ip;
*app = ip->altc[altid];
return Difacelen;
}
extern Ep* mkep(Usbdev *, int);
static int
parseendpt(Usbdev *d, Conf *c, Iface *ip, Altc *altc, uchar *b, int n, Ep **epp)
{
int i, dir, epid;
Ep *ep;
DEp *dep;
assert(d != nil && c != nil && ip != nil && altc != nil);
if(n < Deplen){
werrstr("short endpoint descriptor");
return -1;
}
dep = (DEp *)b;
altc->attrib = dep->bmAttributes; /* here? */
altc->interval = dep->bInterval;
epid = dep->bEndpointAddress & 0xF;
assert(epid < nelem(d->ep));
if(dep->bEndpointAddress & 0x80)
dir = Ein;
else
dir = Eout;
ep = d->ep[epid];
if(ep == nil){
ep = mkep(d, epid);
ep->dir = dir;
}else if((ep->addr & 0x80) != (dep->bEndpointAddress & 0x80))
ep->dir = Eboth;
ep->maxpkt = GET2(dep->wMaxPacketSize);
ep->ntds = 1 + ((ep->maxpkt >> 11) & 3);
ep->maxpkt &= 0x7FF;
ep->addr = dep->bEndpointAddress;
ep->type = dep->bmAttributes & 0x03;
ep->isotype = (dep->bmAttributes>>2) & 0x03;
ep->conf = c;
ep->iface = ip;
for(i = 0; i < nelem(ip->ep); i++)
if(ip->ep[i] == nil)
break;
if(i == nelem(ip->ep)){
werrstr("parseendpt: bug: too many end points on interface "
"with csp %#lux", ip->csp);
fprint(2, "%s: %r\n", argv0);
return -1;
}
*epp = ip->ep[i] = ep;
return Dep;
}
static char*
dname(int dtype)
{
switch(dtype){
case Ddev: return "device";
case Dconf: return "config";
case Dstr: return "string";
case Diface: return "interface";
case Dep: return "endpoint";
case Dreport: return "report";
case Dphysical: return "phys";
default: return "desc";
}
}
int
parsedesc(Usbdev *d, Conf *c, uchar *b, int n)
{
int len, nd, tot;
Iface *ip;
Ep *ep;
Altc *altc;
char *hd;
assert(d != nil && c != nil);
tot = 0;
ip = nil;
ep = nil;
altc = nil;
for(nd = 0; nd < nelem(d->ddesc); nd++)
if(d->ddesc[nd] == nil)
break;
while(n > 2 && b[0] != 0 && b[0] <= n){
len = b[0];
if(usbdebug>1){
hd = hexstr(b, len);
fprint(2, "%s:\t\tparsedesc %s %x[%d] %s\n",
argv0, dname(b[1]), b[1], b[0], hd);
free(hd);
}
switch(b[1]){
case Ddev:
case Dconf:
werrstr("unexpected descriptor %d", b[1]);
ddprint(2, "%s\tparsedesc: %r", argv0);
break;
case Diface:
if(parseiface(d, c, b, n, &ip, &altc) < 0){
ddprint(2, "%s\tparsedesc: %r\n", argv0);
return -1;
}
break;
case Dep:
if(ip == nil || altc == nil){
werrstr("unexpected endpoint descriptor");
break;
}
if(parseendpt(d, c, ip, altc, b, n, &ep) < 0){
ddprint(2, "%s\tparsedesc: %r\n", argv0);
return -1;
}
break;
default:
if(nd == nelem(d->ddesc)){
fprint(2, "%s: parsedesc: too many "
"device-specific descriptors for device"
" %s %s\n",
argv0, d->vendor, d->product);
break;
}
d->ddesc[nd] = emallocz(sizeof(Desc)+b[0], 0);
d->ddesc[nd]->iface = ip;
d->ddesc[nd]->ep = ep;
d->ddesc[nd]->altc = altc;
d->ddesc[nd]->conf = c;
memmove(&d->ddesc[nd]->data, b, len);
++nd;
}
n -= len;
b += len;
tot += len;
}
return tot;
}
int
parseconf(Usbdev *d, Conf *c, uchar *b, int n)
{
DConf* dc;
int l;
int nr;
char *hd;
assert(d != nil && c != nil);
dc = (DConf*)b;
if(usbdebug>1){
hd = hexstr(b, Dconflen);
fprint(2, "%s:\tparseconf %s\n", argv0, hd);
free(hd);
}
if(dc->bLength < Dconflen){
werrstr("short configuration descriptor");
return -1;
}
if(dc->bDescriptorType != Dconf){
werrstr("not a configuration descriptor");
return -1;
}
c->cval = dc->bConfigurationValue;
c->attrib = dc->bmAttributes;
c->milliamps = dc->MaxPower*2;
l = GET2(dc->wTotalLength);
if(n < l){
werrstr("truncated configuration info");
return -1;
}
n -= Dconflen;
b += Dconflen;
nr = 0;
if(n > 0 && (nr=parsedesc(d, c, b, n)) < 0)
return -1;
n -= nr;
if(n > 0 && usbdebug>1)
fprint(2, "%s:\tparseconf: %d bytes left\n", argv0, n);
return l;
}

361
sys/src/cmd/nusb/lib/usb.h Normal file
View File

@ -0,0 +1,361 @@
typedef struct Altc Altc;
typedef struct Conf Conf;
typedef struct DConf DConf;
typedef struct DDesc DDesc;
typedef struct DDev DDev;
typedef struct DEp DEp;
typedef struct DIface DIface;
typedef struct Desc Desc;
typedef struct Dev Dev;
typedef struct Ep Ep;
typedef struct Iface Iface;
typedef struct Usbdev Usbdev;
enum {
/* fundamental constants */
Nep = 256, /* max. endpoints per usb device & per interface */
/* tunable parameters */
Nconf = 16, /* max. configurations per usb device */
Nddesc = 8*Nep, /* max. device-specific descriptors per usb device */
Niface = 16, /* max. interfaces per configuration */
Naltc = 256, /* max. alt configurations per interface */
Uctries = 4, /* no. of tries for usbcmd */
Ucdelay = 50, /* delay before retrying */
/* request type */
Rh2d = 0<<7, /* host to device */
Rd2h = 1<<7, /* device to host */
Rstd = 0<<5, /* types */
Rclass = 1<<5,
Rvendor = 2<<5,
Rdev = 0, /* recipients */
Riface = 1,
Rep = 2, /* endpoint */
Rother = 3,
/* standard requests */
Rgetstatus = 0,
Rclearfeature = 1,
Rsetfeature = 3,
Rsetaddress = 5,
Rgetdesc = 6,
Rsetdesc = 7,
Rgetconf = 8,
Rsetconf = 9,
Rgetiface = 10,
Rsetiface = 11,
Rsynchframe = 12,
Rgetcur = 0x81,
Rgetmin = 0x82,
Rgetmax = 0x83,
Rgetres = 0x84,
Rsetcur = 0x01,
Rsetmin = 0x02,
Rsetmax = 0x03,
Rsetres = 0x04,
/* dev classes */
Clnone = 0, /* not in usb */
Claudio = 1,
Clcomms = 2,
Clhid = 3,
Clprinter = 7,
Clstorage = 8,
Clhub = 9,
Cldata = 10,
/* standard descriptor sizes */
Ddevlen = 18,
Dconflen = 9,
Difacelen = 9,
Deplen = 7,
/* descriptor types */
Ddev = 1,
Dconf = 2,
Dstr = 3,
Diface = 4,
Dep = 5,
Dreport = 0x22,
Dfunction = 0x24,
Dphysical = 0x23,
/* feature selectors */
Fdevremotewakeup = 1,
Fhalt = 0,
/* device state */
Detached = 0,
Attached,
Enabled,
Assigned,
Configured,
/* endpoint direction */
Ein = 0,
Eout,
Eboth,
/* endpoint type */
Econtrol = 0,
Eiso = 1,
Ebulk = 2,
Eintr = 3,
/* endpoint isotype */
Eunknown = 0,
Easync = 1,
Eadapt = 2,
Esync = 3,
/* config attrib */
Cbuspowered = 1<<7,
Cselfpowered = 1<<6,
Cremotewakeup = 1<<5,
/* report types */
Tmtype = 3<<2,
Tmitem = 0xF0,
Tmain = 0<<2,
Tinput = 0x80,
Toutput = 0x90,
Tfeature = 0xB0,
Tcoll = 0xA0,
Tecoll = 0xC0,
Tglobal = 1<<2,
Tusagepage = 0x00,
Tlmin = 0x10,
Tlmax = 0x20,
Tpmin = 0x30,
Tpmax = 0x40,
Tunitexp = 0x50,
Tunit = 0x60,
Trepsize = 0x70,
TrepID = 0x80,
Trepcount = 0x90,
Tpush = 0xA0,
Tpop = 0xB0,
Tlocal = 2<<2,
Tusage = 0x00,
Tumin = 0x10,
Tumax = 0x20,
Tdindex = 0x30,
Tdmin = 0x40,
Tdmax = 0x50,
Tsindex = 0x70,
Tsmin = 0x80,
Tsmax = 0x90,
Tsetdelim = 0xA0,
Treserved = 3<<2,
Tlong = 0xFE,
};
/*
* Usb device (when used for ep0s) or endpoint.
* RC: One ref because of existing, another one per ogoing I/O.
* per-driver resources (including FS if any) are released by aux
* once the last ref is gone. This may include other Devs using
* to access endpoints for actual I/O.
*/
struct Dev
{
Ref;
char* dir; /* path for the endpoint dir */
int id; /* usb id for device or ep. number */
int dfd; /* descriptor for the data file */
int cfd; /* descriptor for the control file */
int maxpkt; /* cached from usb description */
Ref nerrs; /* number of errors in requests */
Usbdev* usb; /* USB description */
void* aux; /* for the device driver */
void (*free)(void*); /* idem. to release aux */
};
/*
* device description as reported by USB (unpacked).
*/
struct Usbdev
{
ulong csp; /* USB class/subclass/proto */
int vid; /* vendor id */
int did; /* product (device) id */
int dno; /* device release number */
char* vendor;
char* product;
char* serial;
int vsid;
int psid;
int ssid;
int class; /* from descriptor */
int nconf; /* from descriptor */
Conf* conf[Nconf]; /* configurations */
Ep* ep[Nep]; /* all endpoints in device */
Desc* ddesc[Nddesc]; /* (raw) device specific descriptors */
};
struct Ep
{
uchar addr; /* endpt address, 0-15 (|0x80 if Ein) */
uchar dir; /* direction, Ein/Eout */
uchar type; /* Econtrol, Eiso, Ebulk, Eintr */
uchar isotype; /* Eunknown, Easync, Eadapt, Esync */
int id;
int maxpkt; /* max. packet size */
int ntds; /* nb. of Tds per µframe */
Conf* conf; /* the endpoint belongs to */
Iface* iface; /* the endpoint belongs to */
};
struct Altc
{
int attrib;
int interval;
void* aux; /* for the driver program */
};
struct Iface
{
int id; /* interface number */
ulong csp; /* USB class/subclass/proto */
Altc* altc[Naltc];
Ep* ep[Nep];
void* aux; /* for the driver program */
};
struct Conf
{
int cval; /* value for set configuration */
int attrib;
int milliamps; /* maximum power in this config. */
Iface* iface[Niface];
};
/*
* Device-specific descriptors.
* They show up mixed with other descriptors
* within a configuration.
* These are unknown to the library but handed to the driver.
*/
struct DDesc
{
uchar bLength;
uchar bDescriptorType;
uchar bbytes[1];
/* extra bytes allocated here to keep the rest of it */
};
struct Desc
{
Conf* conf; /* where this descriptor was read */
Iface* iface; /* last iface before desc in conf. */
Ep* ep; /* last endpt before desc in conf. */
Altc* altc; /* last alt.c. before desc in conf. */
DDesc data; /* unparsed standard USB descriptor */
};
/*
* layout of standard descriptor types
*/
struct DDev
{
uchar bLength;
uchar bDescriptorType;
uchar bcdUSB[2];
uchar bDevClass;
uchar bDevSubClass;
uchar bDevProtocol;
uchar bMaxPacketSize0;
uchar idVendor[2];
uchar idProduct[2];
uchar bcdDev[2];
uchar iManufacturer;
uchar iProduct;
uchar iSerialNumber;
uchar bNumConfigurations;
};
struct DConf
{
uchar bLength;
uchar bDescriptorType;
uchar wTotalLength[2];
uchar bNumInterfaces;
uchar bConfigurationValue;
uchar iConfiguration;
uchar bmAttributes;
uchar MaxPower;
};
struct DIface
{
uchar bLength;
uchar bDescriptorType;
uchar bInterfaceNumber;
uchar bAlternateSetting;
uchar bNumEndpoints;
uchar bInterfaceClass;
uchar bInterfaceSubClass;
uchar bInterfaceProtocol;
uchar iInterface;
};
struct DEp
{
uchar bLength;
uchar bDescriptorType;
uchar bEndpointAddress;
uchar bmAttributes;
uchar wMaxPacketSize[2];
uchar bInterval;
};
#define Class(csp) ((csp) & 0xff)
#define Subclass(csp) (((csp)>>8) & 0xff)
#define Proto(csp) (((csp)>>16) & 0xff)
#define CSP(c, s, p) ((c) | (s)<<8 | (p)<<16)
#define GET2(p) (((p)[1] & 0xFF)<<8 | ((p)[0] & 0xFF))
#define PUT2(p,v) {(p)[0] = (v); (p)[1] = (v)>>8;}
#define GET4(p) (((p)[3]&0xFF)<<24 | ((p)[2]&0xFF)<<16 | \
((p)[1]&0xFF)<<8 | ((p)[0]&0xFF))
#define PUT4(p,v) {(p)[0] = (v); (p)[1] = (v)>>8; \
(p)[2] = (v)>>16; (p)[3] = (v)>>24;}
#define dprint if(usbdebug)fprint
#define ddprint if(usbdebug > 1)fprint
#pragma varargck type "U" Dev*
#pragma varargck argpos devctl 2
int Ufmt(Fmt *f);
char* classname(int c);
void closedev(Dev *d);
int configdev(Dev *d);
int devctl(Dev *dev, char *fmt, ...);
void* emallocz(ulong size, int zero);
char* estrdup(char *s);
int matchdevcsp(char *info, void *a);
int finddevs(int (*matchf)(char*,void*), void *farg, char** dirs, int ndirs);
char* hexstr(void *a, int n);
int loaddevconf(Dev *d, int n);
int loaddevdesc(Dev *d);
char* loaddevstr(Dev *d, int sid);
Dev* opendev(char *fn);
int opendevdata(Dev *d, int mode);
Dev* openep(Dev *d, int id);
int parseconf(Usbdev *d, Conf *c, uchar *b, int n);
int parsedesc(Usbdev *d, Conf *c, uchar *b, int n);
int parsedev(Dev *xd, uchar *b, int n);
void startdevs(char *args, char *argv[], int argc, int (*mf)(char*,void*), void*ma, int (*df)(Dev*,int,char**));
int unstall(Dev *dev, Dev *ep, int dir);
int usbcmd(Dev *d, int type, int req, int value, int index, uchar *data, int count);
Dev* getdev(int id);
extern int usbdebug; /* more messages for bigger values */

View File

@ -1,7 +1,8 @@
</$objtype/mkfile
# order matters here. build lib first and usbd last.
DIRS=\
lib\
kb\
usbd\
UPDATE=\
@ -21,7 +22,6 @@ install installall safeinstall safeinstallall:V:
for (i in $DIRS) @{
cd $i && mk $target
}
cp probe /$objtype/bin/usb/probe
update:V:
update $UPDATEFLAGS $UPDATE

View File

@ -1,6 +1,8 @@
typedef struct Rule Rule;
typedef struct Cond Cond;
typedef struct Dev Dev;
typedef struct Hub Hub;
typedef struct DHub DHub;
typedef struct Port Port;
struct Rule {
char **argv;
@ -17,6 +19,110 @@ struct Cond {
Cond *and, *or;
};
struct Dev {
u32int class, vid, did;
enum
{
Stack = 32*1024,
Dhub = 0x29, /* hub descriptor type */
Dhublen = 9, /* hub descriptor length */
/* hub class feature selectors */
Fhublocalpower = 0,
Fhubovercurrent = 1,
Fportconnection = 0,
Fportenable = 1,
Fportsuspend = 2,
Fportovercurrent = 3,
Fportreset = 4,
Fportpower = 8,
Fportlowspeed = 9,
Fcportconnection = 16,
Fcportenable = 17,
Fcportsuspend = 18,
Fcportovercurrent= 19,
Fcportreset = 20,
Fportindicator = 22,
/* Port status and status change bits
* Constants at /sys/src/9/pc/usb.h starting with HP-
* must have the same values or root hubs won't work.
*/
PSpresent = 0x0001,
PSenable = 0x0002,
PSsuspend = 0x0004,
PSovercurrent = 0x0008,
PSreset = 0x0010,
PSpower = 0x0100,
PSslow = 0x0200,
PShigh = 0x0400,
PSstatuschg = 0x10000, /* PSpresent changed */
PSchange = 0x20000, /* PSenable changed */
/* port/device state */
Pdisabled = 0, /* must be 0 */
Pattached,
Pconfiged,
/* Delays, timeouts (ms) */
// Spawndelay = 1000, /* how often may we re-spawn a driver */
Spawndelay = 250, /* how often may we re-spawn a driver */
// Connectdelay = 1000, /* how much to wait after a connect */
Connectdelay = 500, /* how much to wait after a connect */
Resetdelay = 20, /* how much to wait after a reset */
Enabledelay = 20, /* how much to wait after an enable */
Powerdelay = 100, /* after powering up ports */
Pollms = 250, /* port poll interval */
Chgdelay = 100, /* waiting for port become stable */
Chgtmout = 1000, /* ...but at most this much */
/*
* device tab for embedded usb drivers.
*/
DCL = 0x01000000, /* csp identifies just class */
DSC = 0x02000000, /* csp identifies just subclass */
DPT = 0x04000000, /* csp identifies just proto */
};
struct Hub
{
uchar pwrmode;
uchar compound;
uchar pwrms; /* time to wait in ms */
uchar maxcurrent; /* after powering port*/
int leds; /* has port indicators? */
int maxpkt;
uchar nport;
Port *port;
int failed; /* I/O error while enumerating */
int isroot; /* set if root hub */
Dev *dev; /* for this hub */
Hub *next; /* in list of hubs */
};
struct Port
{
int state; /* state of the device */
int sts; /* old port status */
uchar removable;
uchar pwrctl;
Dev *dev; /* attached device (if non-nil) */
Hub *hub; /* non-nil if hub attached */
int devnb; /* device number */
uvlong *devmaskp; /* ptr to dev mask */
};
/* USB HUB descriptor */
struct DHub
{
uchar bLength;
uchar bDescriptorType;
uchar bNbrPorts;
uchar wHubCharacteristics[2];
uchar bPwrOn2PwrGood;
uchar bHubContrCurrent;
uchar DeviceRemovable[1]; /* variable length */
};

View File

@ -1,2 +1,4 @@
void parserules(char*);
Rule* rulesmatch(Dev*);
Rule* rulesmatch(Usbdev*);
int startdev(Port*);
void work(void);

690
sys/src/cmd/nusb/usbd/hub.c Normal file
View File

@ -0,0 +1,690 @@
#include <u.h>
#include <libc.h>
#include <thread.h>
#include "usb.h"
#include "dat.h"
#include "fns.h"
static Hub *hubs;
static int nhubs;
static int mustdump;
static int pollms = Pollms;
static Lock masklck;
static char *dsname[] = { "disabled", "attached", "configed" };
int
getdevnb(uvlong *maskp)
{
int i;
lock(&masklck);
for(i = 0; i < 8 * sizeof *maskp; i++)
if((*maskp & (1ULL<<i)) == 0){
*maskp |= 1ULL<<i;
unlock(&masklck);
return i;
}
unlock(&masklck);
return -1;
}
void
putdevnb(uvlong *maskp, int id)
{
lock(&masklck);
if(id >= 0)
*maskp &= ~(1ULL<<id);
unlock(&masklck);
}
static int
hubfeature(Hub *h, int port, int f, int on)
{
int cmd;
if(on)
cmd = Rsetfeature;
else
cmd = Rclearfeature;
return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0);
}
/*
* This may be used to detect overcurrent on the hub
*/
static void
checkhubstatus(Hub *h)
{
uchar buf[4];
int sts;
if(h->isroot) /* not for root hubs */
return;
if(usbcmd(h->dev, Rd2h|Rclass|Rdev, Rgetstatus, 0, 0, buf, 4) < 0){
dprint(2, "%s: get hub status: %r\n", h->dev->dir);
return;
}
sts = GET2(buf);
dprint(2, "hub %s: status %#ux\n", h->dev->dir, sts);
}
static int
confighub(Hub *h)
{
int type;
uchar buf[128]; /* room for extra descriptors */
int i;
Usbdev *d;
DHub *dd;
Port *pp;
int nr;
int nmap;
uchar *PortPwrCtrlMask;
int offset;
int mask;
d = h->dev->usb;
for(i = 0; i < nelem(d->ddesc); i++)
if(d->ddesc[i] == nil)
break;
else if(d->ddesc[i]->data.bDescriptorType == Dhub){
dd = (DHub*)&d->ddesc[i]->data;
nr = Dhublen;
goto Config;
}
type = Rd2h|Rclass|Rdev;
nr = usbcmd(h->dev, type, Rgetdesc, Dhub<<8|0, 0, buf, sizeof buf);
if(nr < Dhublen){
dprint(2, "%s: %s: getdesc hub: %r\n", argv0, h->dev->dir);
return -1;
}
dd = (DHub*)buf;
Config:
h->nport = dd->bNbrPorts;
nmap = 1 + h->nport/8;
if(nr < 7 + 2*nmap){
fprint(2, "%s: %s: descr. too small\n", argv0, h->dev->dir);
return -1;
}
h->port = emallocz((h->nport+1)*sizeof(Port), 1);
h->pwrms = dd->bPwrOn2PwrGood*2;
if(h->pwrms < Powerdelay)
h->pwrms = Powerdelay;
h->maxcurrent = dd->bHubContrCurrent;
h->pwrmode = dd->wHubCharacteristics[0] & 3;
h->compound = (dd->wHubCharacteristics[0] & (1<<2))!=0;
h->leds = (dd->wHubCharacteristics[0] & (1<<7)) != 0;
PortPwrCtrlMask = dd->DeviceRemovable + nmap;
for(i = 1; i <= h->nport; i++){
pp = &h->port[i];
offset = i/8;
mask = 1<<(i%8);
pp->removable = (dd->DeviceRemovable[offset] & mask) != 0;
pp->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0;
}
return 0;
}
static void
configroothub(Hub *h)
{
Dev *d;
char buf[128];
char *p;
int nr;
d = h->dev;
h->nport = 2;
h->maxpkt = 8;
seek(d->cfd, 0, 0);
nr = read(d->cfd, buf, sizeof(buf)-1);
if(nr < 0)
goto Done;
buf[nr] = 0;
p = strstr(buf, "ports ");
if(p == nil)
fprint(2, "%s: %s: no port information\n", argv0, d->dir);
else
h->nport = atoi(p+6);
p = strstr(buf, "maxpkt ");
if(p == nil)
fprint(2, "%s: %s: no maxpkt information\n", argv0, d->dir);
else
h->maxpkt = atoi(p+7);
Done:
h->port = emallocz((h->nport+1)*sizeof(Port), 1);
dprint(2, "%s: %s: ports %d maxpkt %d\n", argv0, d->dir, h->nport, h->maxpkt);
}
Hub*
newhub(char *fn, Dev *d)
{
Hub *h;
int i;
Usbdev *ud;
h = emallocz(sizeof(Hub), 1);
h->isroot = (d == nil);
if(h->isroot){
h->dev = opendev(fn);
if(h->dev == nil){
fprint(2, "%s: opendev: %s: %r", argv0, fn);
goto Fail;
}
if(opendevdata(h->dev, ORDWR) < 0){
fprint(2, "%s: opendevdata: %s: %r\n", argv0, fn);
goto Fail;
}
configroothub(h); /* never fails */
}else{
h->dev = d;
if(confighub(h) < 0){
fprint(2, "%s: %s: config: %r\n", argv0, fn);
goto Fail;
}
}
if(h->dev == nil){
fprint(2, "%s: opendev: %s: %r\n", argv0, fn);
goto Fail;
}
devctl(h->dev, "hub");
ud = h->dev->usb;
if(h->isroot)
devctl(h->dev, "info roothub csp %#08ux ports %d",
0x000009, h->nport);
else{
devctl(h->dev, "info hub csp %#08ulx ports %d %q %q",
ud->csp, h->nport, ud->vendor, ud->product);
for(i = 1; i <= h->nport; i++)
if(hubfeature(h, i, Fportpower, 1) < 0)
fprint(2, "%s: %s: power: %r\n", argv0, fn);
sleep(h->pwrms);
for(i = 1; i <= h->nport; i++)
if(h->leds != 0)
hubfeature(h, i, Fportindicator, 1);
}
h->next = hubs;
hubs = h;
nhubs++;
dprint(2, "%s: hub %#p allocated:", argv0, h);
dprint(2, " ports %d pwrms %d max curr %d pwrm %d cmp %d leds %d\n",
h->nport, h->pwrms, h->maxcurrent,
h->pwrmode, h->compound, h->leds);
incref(h->dev);
return h;
Fail:
if(d != nil)
devctl(d, "detach");
free(h->port);
free(h);
dprint(2, "%s: hub %#p failed to start:", argv0, h);
return nil;
}
static void portdetach(Hub *h, int p);
/*
* If during enumeration we get an I/O error the hub is gone or
* in pretty bad shape. Because of retries of failed usb commands
* (and the sleeps they include) it can take a while to detach all
* ports for the hub. This detaches all ports and makes the hub void.
* The parent hub will detect a detach (probably right now) and
* close it later.
*/
static void
hubfail(Hub *h)
{
int i;
for(i = 1; i <= h->nport; i++)
portdetach(h, i);
h->failed = 1;
}
static void
closehub(Hub *h)
{
Hub **hl;
dprint(2, "%s: closing hub %#p\n", argv0, h);
for(hl = &hubs; *hl != nil; hl = &(*hl)->next)
if(*hl == h)
break;
if(*hl == nil)
sysfatal("closehub: no hub");
*hl = h->next;
nhubs--;
hubfail(h); /* detach all ports */
free(h->port);
assert(h->dev != nil);
devctl(h->dev, "detach");
closedev(h->dev);
free(h);
}
static int
portstatus(Hub *h, int p)
{
Dev *d;
uchar buf[4];
int t;
int sts;
int dbg;
dbg = usbdebug;
if(dbg != 0 && dbg < 4)
usbdebug = 1; /* do not be too chatty */
d = h->dev;
t = Rd2h|Rclass|Rother;
if(usbcmd(d, t, Rgetstatus, 0, p, buf, sizeof(buf)) < 0)
sts = -1;
else
sts = GET2(buf);
usbdebug = dbg;
return sts;
}
static char*
stsstr(int sts)
{
static char s[80];
char *e;
e = s;
if(sts&PSsuspend)
*e++ = 'z';
if(sts&PSreset)
*e++ = 'r';
if(sts&PSslow)
*e++ = 'l';
if(sts&PShigh)
*e++ = 'h';
if(sts&PSchange)
*e++ = 'c';
if(sts&PSenable)
*e++ = 'e';
if(sts&PSstatuschg)
*e++ = 's';
if(sts&PSpresent)
*e++ = 'p';
if(e == s)
*e++ = '-';
*e = 0;
return s;
}
static int
getmaxpkt(Dev *d, int islow)
{
uchar buf[64]; /* More room to try to get device-specific descriptors */
DDev *dd;
dd = (DDev*)buf;
if(islow)
dd->bMaxPacketSize0 = 8;
else
dd->bMaxPacketSize0 = 64;
if(usbcmd(d, Rd2h|Rstd|Rdev, Rgetdesc, Ddev<<8|0, 0, buf, sizeof(buf)) < 0)
return -1;
return dd->bMaxPacketSize0;
}
/*
* BUG: does not consider max. power avail.
*/
static Dev*
portattach(Hub *h, int p, int sts)
{
Dev *d;
Port *pp;
Dev *nd;
char fname[80];
char buf[40];
char *sp;
int mp;
int nr;
d = h->dev;
pp = &h->port[p];
nd = nil;
pp->state = Pattached;
dprint(2, "%s: %s: port %d attach sts %#ux\n", argv0, d->dir, p, sts);
sleep(Connectdelay);
if(hubfeature(h, p, Fportenable, 1) < 0)
dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
sleep(Enabledelay);
if(hubfeature(h, p, Fportreset, 1) < 0){
dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);
goto Fail;
}
sleep(Resetdelay);
sts = portstatus(h, p);
if(sts < 0)
goto Fail;
if((sts & PSenable) == 0){
dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
hubfeature(h, p, Fportenable, 1);
sts = portstatus(h, p);
if((sts & PSenable) == 0)
goto Fail;
}
sp = "full";
if(sts & PSslow)
sp = "low";
if(sts & PShigh)
sp = "high";
dprint(2, "%s: %s: port %d: attached status %#ux\n", argv0, d->dir, p, sts);
if(devctl(d, "newdev %s %d", sp, p) < 0){
fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
goto Fail;
}
seek(d->cfd, 0, 0);
nr = read(d->cfd, buf, sizeof(buf)-1);
if(nr == 0){
fprint(2, "%s: %s: port %d: newdev: eof\n", argv0, d->dir, p);
goto Fail;
}
if(nr < 0){
fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
goto Fail;
}
buf[nr] = 0;
snprint(fname, sizeof(fname), "/dev/usb/%s", buf);
nd = opendev(fname);
if(nd == nil){
fprint(2, "%s: %s: port %d: opendev: %r\n", argv0, d->dir, p);
goto Fail;
}
if(usbdebug > 2)
devctl(nd, "debug 1");
if(opendevdata(nd, ORDWR) < 0){
fprint(2, "%s: %s: opendevdata: %r\n", argv0, nd->dir);
goto Fail;
}
if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){
dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);
goto Fail;
}
if(devctl(nd, "address") < 0){
dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
goto Fail;
}
mp=getmaxpkt(nd, strcmp(sp, "low") == 0);
if(mp < 0){
dprint(2, "%s: %s: port %d: getmaxpkt: %r\n", argv0, d->dir, p);
goto Fail;
}else{
dprint(2, "%s; %s: port %d: maxpkt %d\n", argv0, d->dir, p, mp);
devctl(nd, "maxpkt %d", mp);
}
if((sts & PSslow) != 0 && strcmp(sp, "full") == 0)
dprint(2, "%s: %s: port %d: %s is full speed when port is low\n",
argv0, d->dir, p, nd->dir);
if(configdev(nd) < 0){
dprint(2, "%s: %s: port %d: configdev: %r\n", argv0, d->dir, p);
goto Fail;
}
/*
* We always set conf #1. BUG.
*/
if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){
dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);
unstall(nd, nd, Eout);
if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)
goto Fail;
}
dprint(2, "%s: %U", argv0, nd);
pp->state = Pconfiged;
dprint(2, "%s: %s: port %d: configed: %s\n",
argv0, d->dir, p, nd->dir);
return pp->dev = nd;
Fail:
pp->state = Pdisabled;
pp->sts = 0;
if(pp->hub != nil)
pp->hub = nil; /* hub closed by enumhub */
hubfeature(h, p, Fportenable, 0);
if(nd != nil)
devctl(nd, "detach");
closedev(nd);
return nil;
}
static void
portdetach(Hub *h, int p)
{
Dev *d;
Port *pp;
d = h->dev;
pp = &h->port[p];
/*
* Clear present, so that we detect an attach on reconnects.
*/
pp->sts &= ~(PSpresent|PSenable);
if(pp->state == Pdisabled)
return;
pp->state = Pdisabled;
dprint(2, "%s: %s: port %d: detached\n", argv0, d->dir, p);
if(pp->hub != nil){
closehub(pp->hub);
pp->hub = nil;
}
if(pp->devmaskp != nil)
putdevnb(pp->devmaskp, pp->devnb);
pp->devmaskp = nil;
if(pp->dev != nil){
devctl(pp->dev, "detach");
closedev(pp->dev);
pp->dev = nil;
}
}
/*
* The next two functions are included to
* perform a port reset asked for by someone (usually a driver).
* This must be done while no other device is in using the
* configuration address and with care to keep the old address.
* To keep drivers decoupled from usbd they write the reset request
* to the #u/usb/epN.0/ctl file and then exit.
* This is unfortunate because usbd must now poll twice as much.
*
* An alternative to this reset process would be for the driver to detach
* the device. The next function could see that, issue a port reset, and
* then restart the driver once to see if it's a temporary error.
*
* The real fix would be to use interrupt endpoints for non-root hubs
* (would probably make some hubs fail) and add an events file to
* the kernel to report events to usbd. This is a severe change not
* yet implemented.
*/
static int
portresetwanted(Hub *h, int p)
{
char buf[5];
Port *pp;
Dev *nd;
pp = &h->port[p];
nd = pp->dev;
if(nd != nil && nd->cfd >= 0 && pread(nd->cfd, buf, 5, 0LL) == 5)
return strncmp(buf, "reset", 5) == 0;
else
return 0;
}
static void
portreset(Hub *h, int p)
{
int sts;
Dev *d, *nd;
Port *pp;
d = h->dev;
pp = &h->port[p];
nd = pp->dev;
dprint(2, "%s: %s: port %d: resetting\n", argv0, d->dir, p);
if(hubfeature(h, p, Fportreset, 1) < 0){
dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);
goto Fail;
}
sleep(Resetdelay);
sts = portstatus(h, p);
if(sts < 0)
goto Fail;
if((sts & PSenable) == 0){
dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
hubfeature(h, p, Fportenable, 1);
sts = portstatus(h, p);
if((sts & PSenable) == 0)
goto Fail;
}
nd = pp->dev;
opendevdata(nd, ORDWR);
if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){
dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);
goto Fail;
}
if(devctl(nd, "address") < 0){
dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
goto Fail;
}
if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){
dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);
unstall(nd, nd, Eout);
if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)
goto Fail;
}
if(nd->dfd >= 0)
close(nd->dfd);
return;
Fail:
pp->state = Pdisabled;
pp->sts = 0;
if(pp->hub != nil)
pp->hub = nil; /* hub closed by enumhub */
hubfeature(h, p, Fportenable, 0);
if(nd != nil)
devctl(nd, "detach");
closedev(nd);
}
static int
portgone(Port *pp, int sts)
{
if(sts < 0)
return 1;
/*
* If it was enabled and it's not now then it may be reconnect.
* We pretend it's gone and later we'll see it as attached.
*/
if((pp->sts & PSenable) != 0 && (sts & PSenable) == 0)
return 1;
return (pp->sts & PSpresent) != 0 && (sts & PSpresent) == 0;
}
static int
enumhub(Hub *h, int p)
{
int sts;
Dev *d;
Port *pp;
int onhubs;
if(h->failed)
return 0;
d = h->dev;
if(usbdebug > 3)
fprint(2, "%s: %s: port %d enumhub\n", argv0, d->dir, p);
sts = portstatus(h, p);
if(sts < 0){
hubfail(h); /* avoid delays on detachment */
return -1;
}
pp = &h->port[p];
onhubs = nhubs;
if((sts & PSsuspend) != 0){
if(hubfeature(h, p, Fportenable, 1) < 0)
dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
sleep(Enabledelay);
sts = portstatus(h, p);
fprint(2, "%s: %s: port %d: resumed (sts %#ux)\n", argv0, d->dir, p, sts);
}
if((pp->sts & PSpresent) == 0 && (sts & PSpresent) != 0){
if(portattach(h, p, sts) != nil)
if(startdev(pp) < 0)
portdetach(h, p);
}else if(portgone(pp, sts))
portdetach(h, p);
else if(portresetwanted(h, p))
portreset(h, p);
else if(pp->sts != sts){
dprint(2, "%s: %s port %d: sts %s %#x ->",
argv0, d->dir, p, stsstr(pp->sts), pp->sts);
dprint(2, " %s %#x\n",stsstr(sts), sts);
}
pp->sts = sts;
if(onhubs != nhubs)
return -1;
return 0;
}
static void
dump(void)
{
Hub *h;
int i;
mustdump = 0;
for(h = hubs; h != nil; h = h->next)
for(i = 1; i <= h->nport; i++)
fprint(2, "%s: hub %#p %s port %d: %U",
argv0, h, h->dev->dir, i, h->port[i].dev);
}
void
work(void)
{
char *fn;
Hub *h;
int i;
hubs = nil;
while((fn = rendezvous(work, nil)) != nil){
dprint(2, "%s: %s starting\n", argv0, fn);
h = newhub(fn, nil);
if(h == nil)
fprint(2, "%s: %s: newhub failed: %r\n", argv0, fn);
free(fn);
}
/*
* Enumerate (and acknowledge after first enumeration).
* Do NOT perform enumeration concurrently for the same
* controller. new devices attached respond to a default
* address (0) after reset, thus enumeration has to work
* one device at a time at least before addresses have been
* assigned.
* Do not use hub interrupt endpoint because we
* have to poll the root hub(s) in any case.
*/
for(;;){
Again:
for(h = hubs; h != nil; h = h->next)
for(i = 1; i <= h->nport; i++)
if(enumhub(h, i) < 0){
/* changes in hub list; repeat */
goto Again;
}
sleep(pollms);
if(mustdump)
dump();
}
}

View File

@ -3,6 +3,7 @@
OFILES=\
usbd.$O\
rules.$O\
hub.$O\
HFILES=\
dat.h\
@ -10,4 +11,6 @@ HFILES=\
TARG=usbd
BIN=/$objtype/bin/nusb
LIB=../lib/usb.a$O
</sys/src/cmd/mkone
CFLAGS=-I../lib $CFLAGS

View File

@ -2,6 +2,7 @@
#include <libc.h>
#include <thread.h>
#include <ctype.h>
#include "usb.h"
#include "dat.h"
#include "fns.h"
@ -63,7 +64,7 @@ parsesh(int *argc, char ***argv)
}
}
static Dev dummy;
static Usbdev dummy;
struct field {
char *s;
@ -72,6 +73,7 @@ struct field {
"class", &dummy.class,
"vid", &dummy.vid,
"did", &dummy.did,
"csp", &dummy.csp,
nil, nil,
};
@ -218,7 +220,7 @@ parserules(char *s)
}
Rule *
rulesmatch(Dev *dev)
rulesmatch(Usbdev *dev)
{
Rule *r;
Cond *c;

View File

@ -3,6 +3,7 @@
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include "usb.h"
#include "dat.h"
#include "fns.h"
@ -56,12 +57,73 @@ readrules(void)
close(fd);
}
void
main()
int
startdev(Port *p)
{
Rule *r;
char buf[14];
if(p->dev == nil || p->dev->usb == nil){
fprint(2, "okay what?\n");
return -1;
}
rlock(&rulelock);
r = rulesmatch(p->dev->usb);
if(r == nil || r->argv == nil){
fprint(2, "no driver for device\n");
runlock(&rulelock);
return -1;
}
snprint(buf, sizeof buf, "%d", p->dev->id);
r->argv[r->argc] = buf;
closedev(p->dev);
switch(fork()){
case -1:
fprint(2, "fork: %r");
runlock(&rulelock);
return -1;
case 0:
chdir("/bin");
exec(r->argv[0], r->argv);
sysfatal("exec: %r");
}
runlock(&rulelock);
return 0;
}
void
main(int argc, char **argv)
{
int fd, i, nd;
Dir *d;
readrules();
parserules(rules);
luser = getuser();
argc--; argv++;
rfork(RFNOTEG);
switch(rfork(RFPROC|RFMEM)){
case -1: sysfatal("rfork: %r");
case 0: work(); exits(nil);
}
if(argc == 0){
fd = open("/dev/usb", OREAD);
if(fd < 0)
sysfatal("/dev/usb: %r");
nd = dirreadall(fd, &d);
close(fd);
if(nd < 2)
sysfatal("/dev/usb: no hubs");
for(i = 0; i < nd; i++)
if(strcmp(d[i].name, "ctl") != 0)
rendezvous(work, smprint("/dev/usb/%s", d[i].name));
free(d);
}else
for(i = 0; i < argc; i++)
rendezvous(work, strdup(argv[i]));
rendezvous(work, nil);
usbdsrv.tree = alloctree(luser, luser, 0555, nil);
usbdb = createfile(usbdsrv.tree->root, "usbdb", luser, 0775, nil);
postsharesrv(&usbdsrv, nil, "usb", "usbd", "b");