sdvirtio: experimental support for virtio-scsi
This commit is contained in:
parent
2739c6514a
commit
2e713acc2c
1 changed files with 269 additions and 46 deletions
|
@ -15,6 +15,14 @@ typedef struct Vused Vused;
|
|||
typedef struct Vqueue Vqueue;
|
||||
typedef struct Vdev Vdev;
|
||||
|
||||
typedef struct ScsiCfg ScsiCfg;
|
||||
|
||||
/* device types */
|
||||
enum {
|
||||
TypBlk = 2,
|
||||
TypSCSI = 8,
|
||||
};
|
||||
|
||||
/* status flags */
|
||||
enum {
|
||||
Acknowledge = 1,
|
||||
|
@ -72,6 +80,10 @@ struct Vused
|
|||
struct Vqueue
|
||||
{
|
||||
Lock;
|
||||
|
||||
Vdev *dev;
|
||||
int idx;
|
||||
|
||||
int size;
|
||||
|
||||
int free;
|
||||
|
@ -103,9 +115,30 @@ struct Vdev
|
|||
int nqueue;
|
||||
Vqueue *queue[16];
|
||||
|
||||
void *cfg; /* device specific config (for scsi) */
|
||||
|
||||
Vdev *next;
|
||||
};
|
||||
|
||||
enum {
|
||||
CDBSIZE = 32,
|
||||
SENSESIZE = 96,
|
||||
};
|
||||
|
||||
struct ScsiCfg
|
||||
{
|
||||
u32int num_queues;
|
||||
u32int seg_max;
|
||||
u32int max_sectors;
|
||||
u32int cmd_per_lun;
|
||||
u32int event_info_size;
|
||||
u32int sense_size;
|
||||
u32int cdb_size;
|
||||
u16int max_channel;
|
||||
u16int max_target;
|
||||
u32int max_lun;
|
||||
};
|
||||
|
||||
static Vqueue*
|
||||
mkvqueue(int size)
|
||||
{
|
||||
|
@ -160,14 +193,13 @@ static Vdev*
|
|||
viopnpdevs(int typ)
|
||||
{
|
||||
Vdev *vd, *h, *t;
|
||||
Vqueue *q;
|
||||
Pcidev *p;
|
||||
int n, i;
|
||||
|
||||
h = t = nil;
|
||||
for(p = nil; p = pcimatch(p, 0, 0);){
|
||||
if(p->vid != 0x1AF4)
|
||||
continue;
|
||||
if((p->did < 0x1000) || (p->did >= 0x1040))
|
||||
for(p = nil; p = pcimatch(p, 0x1AF4, 0);){
|
||||
if((p->did < 0x1000) || (p->did > 0x103F))
|
||||
continue;
|
||||
if(p->rid != 0)
|
||||
continue;
|
||||
|
@ -196,8 +228,11 @@ viopnpdevs(int typ)
|
|||
n = ins(vd->port+Qsize);
|
||||
if(n == 0 || (n & (n-1)) != 0)
|
||||
break;
|
||||
if((vd->queue[i] = mkvqueue(n)) == nil)
|
||||
if((q = mkvqueue(n)) == nil)
|
||||
break;
|
||||
q->dev = vd;
|
||||
q->idx = i;
|
||||
vd->queue[i] = q;
|
||||
coherence();
|
||||
outl(vd->port+Qaddr, PADDR(vd->queue[i]->desc)/BY2PG);
|
||||
}
|
||||
|
@ -219,39 +254,42 @@ struct Rock {
|
|||
};
|
||||
|
||||
static void
|
||||
viointerrupt(Ureg *, void *arg)
|
||||
vqinterrupt(Vqueue *q)
|
||||
{
|
||||
int id, free, m;
|
||||
struct Rock *r;
|
||||
Rendez *z;
|
||||
Vqueue *q;
|
||||
Vdev *vd;
|
||||
|
||||
vd = arg;
|
||||
if(inb(vd->port+Isr) & 1){
|
||||
q = vd->queue[0];
|
||||
m = q->size-1;
|
||||
m = q->size-1;
|
||||
|
||||
ilock(q);
|
||||
while((q->lastused ^ q->used->idx) & m){
|
||||
id = q->usedent[q->lastused++ & m].id;
|
||||
if(r = q->rock[id]){
|
||||
q->rock[id] = nil;
|
||||
z = r->sleep;
|
||||
r->done = 1; /* hands off */
|
||||
if(z != nil)
|
||||
wakeup(z);
|
||||
}
|
||||
do {
|
||||
free = id;
|
||||
id = q->desc[free].next;
|
||||
q->desc[free].next = q->free;
|
||||
q->free = free;
|
||||
q->nfree++;
|
||||
} while(q->desc[free].flags & Next);
|
||||
ilock(q);
|
||||
while((q->lastused ^ q->used->idx) & m){
|
||||
id = q->usedent[q->lastused++ & m].id;
|
||||
if(r = q->rock[id]){
|
||||
q->rock[id] = nil;
|
||||
z = r->sleep;
|
||||
r->done = 1; /* hands off */
|
||||
if(z != nil)
|
||||
wakeup(z);
|
||||
}
|
||||
iunlock(q);
|
||||
do {
|
||||
free = id;
|
||||
id = q->desc[free].next;
|
||||
q->desc[free].next = q->free;
|
||||
q->free = free;
|
||||
q->nfree++;
|
||||
} while(q->desc[free].flags & Next);
|
||||
}
|
||||
iunlock(q);
|
||||
}
|
||||
|
||||
static void
|
||||
viointerrupt(Ureg *, void *arg)
|
||||
{
|
||||
Vdev *vd = arg;
|
||||
|
||||
if(inb(vd->port+Isr) & 1)
|
||||
vqinterrupt(vd->queue[vd->typ == TypSCSI ? 2 : 0]);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -261,7 +299,7 @@ viodone(void *arg)
|
|||
}
|
||||
|
||||
static int
|
||||
vioreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
|
||||
vioblkreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
|
||||
{
|
||||
struct Rock rock;
|
||||
int free, head;
|
||||
|
@ -269,7 +307,7 @@ vioreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
|
|||
Vdesc *d;
|
||||
|
||||
u8int status;
|
||||
struct Vioreqhdr {
|
||||
struct Vioblkreqhdr {
|
||||
u32int typ;
|
||||
u32int prio;
|
||||
u64int lba;
|
||||
|
@ -320,7 +358,7 @@ vioreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
|
|||
coherence();
|
||||
q->availent[q->avail->idx++ & (q->size-1)] = head;
|
||||
coherence();
|
||||
outs(vd->port+Qnotify, 0);
|
||||
outs(vd->port+Qnotify, q->idx);
|
||||
iunlock(q);
|
||||
|
||||
while(!rock.done){
|
||||
|
@ -330,33 +368,151 @@ vioreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
|
|||
poperror();
|
||||
|
||||
if(!rock.done)
|
||||
viointerrupt(nil, vd);
|
||||
vqinterrupt(q);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
vioscsireq(SDreq *r)
|
||||
{
|
||||
u8int resp[4+4+2+2+SENSESIZE];
|
||||
u8int req[8+8+3+CDBSIZE];
|
||||
struct Rock rock;
|
||||
int free, head;
|
||||
u32int len;
|
||||
Vqueue *q;
|
||||
Vdesc *d;
|
||||
Vdev *vd;
|
||||
SDunit *u;
|
||||
ScsiCfg *cfg;
|
||||
|
||||
u = r->unit;
|
||||
vd = u->dev->ctlr;
|
||||
cfg = vd->cfg;
|
||||
|
||||
memset(resp, 0, sizeof(resp));
|
||||
memset(req, 0, sizeof(req));
|
||||
req[0] = 1;
|
||||
req[1] = u->subno;
|
||||
req[2] = r->lun>>8;
|
||||
req[3] = r->lun&0xFF;
|
||||
*(u64int*)(&req[8]) = (uintptr)r;
|
||||
|
||||
memmove(&req[8+8+3], r->cmd, r->clen);
|
||||
|
||||
rock.done = 0;
|
||||
rock.sleep = &up->sleep;
|
||||
|
||||
q = vd->queue[2];
|
||||
ilock(q);
|
||||
while(q->nfree < 3){
|
||||
iunlock(q);
|
||||
|
||||
if(!waserror())
|
||||
tsleep(&up->sleep, return0, 0, 500);
|
||||
poperror();
|
||||
|
||||
ilock(q);
|
||||
}
|
||||
|
||||
head = free = q->free;
|
||||
|
||||
d = &q->desc[free]; free = d->next;
|
||||
d->addr = PADDR(req);
|
||||
d->len = 8+8+3+cfg->cdb_size;
|
||||
d->flags = Next;
|
||||
|
||||
if(r->write && r->dlen > 0){
|
||||
d = &q->desc[free]; free = d->next;
|
||||
d->addr = PADDR(r->data);
|
||||
d->len = r->dlen;
|
||||
d->flags = Next;
|
||||
}
|
||||
|
||||
d = &q->desc[free]; free = d->next;
|
||||
d->addr = PADDR(resp);
|
||||
d->len = 4+4+2+2+cfg->sense_size;
|
||||
d->flags = Write;
|
||||
|
||||
if(!r->write && r->dlen > 0){
|
||||
d->flags |= Next;
|
||||
|
||||
d = &q->desc[free]; free = d->next;
|
||||
d->addr = PADDR(r->data);
|
||||
d->len = r->dlen;
|
||||
d->flags = Write;
|
||||
}
|
||||
|
||||
q->free = free;
|
||||
q->nfree -= 2 + (r->dlen > 0);
|
||||
|
||||
q->rock[head] = &rock;
|
||||
|
||||
coherence();
|
||||
q->availent[q->avail->idx++ & (q->size-1)] = head;
|
||||
coherence();
|
||||
outs(vd->port+Qnotify, q->idx);
|
||||
iunlock(q);
|
||||
|
||||
while(!rock.done){
|
||||
while(waserror())
|
||||
;
|
||||
tsleep(rock.sleep, viodone, &rock, 1000);
|
||||
poperror();
|
||||
|
||||
if(!rock.done)
|
||||
vqinterrupt(q);
|
||||
}
|
||||
|
||||
/* response+status */
|
||||
r->status = resp[10];
|
||||
if(resp[11] != 0)
|
||||
r->status = SDcheck;
|
||||
|
||||
/* sense_len */
|
||||
len = *((u32int*)&resp[0]);
|
||||
if(len > 0){
|
||||
if(len > sizeof(r->sense))
|
||||
len = sizeof(r->sense);
|
||||
memmove(r->sense, &resp[4+4+2+2], len);
|
||||
r->flags |= SDvalidsense;
|
||||
}
|
||||
|
||||
/* data residue */
|
||||
len = *((u32int*)&resp[4]);
|
||||
if(len > r->dlen)
|
||||
r->rlen = 0;
|
||||
else
|
||||
r->rlen = r->dlen - len;
|
||||
|
||||
return r->status;
|
||||
|
||||
}
|
||||
|
||||
static long
|
||||
viobio(SDunit *u, int, int write, void *a, long count, uvlong lba)
|
||||
viobio(SDunit *u, int lun, int write, void *a, long count, uvlong lba)
|
||||
{
|
||||
long ss, cc, max, ret;
|
||||
Vdev *vd;
|
||||
|
||||
vd = u->dev->ctlr;
|
||||
if(vd->typ == TypSCSI)
|
||||
return scsibio(u, lun, write, a, count, lba);
|
||||
|
||||
max = 32;
|
||||
ss = u->secsize;
|
||||
vd = u->dev->ctlr;
|
||||
|
||||
ret = 0;
|
||||
while(count > 0){
|
||||
if((cc = count) > max)
|
||||
cc = max;
|
||||
if(vioreq(vd, write != 0, (uchar*)a + ret, cc, ss, lba) != 0)
|
||||
if(vioblkreq(vd, write != 0, (uchar*)a + ret, cc, ss, lba) != 0)
|
||||
error(Eio);
|
||||
ret += cc*ss;
|
||||
count -= cc;
|
||||
lba += cc;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -366,10 +522,14 @@ viorio(SDreq *r)
|
|||
int i, count, rw;
|
||||
uvlong lba;
|
||||
SDunit *u;
|
||||
Vdev *vd;
|
||||
|
||||
u = r->unit;
|
||||
vd = u->dev->ctlr;
|
||||
if(vd->typ == TypSCSI)
|
||||
return vioscsireq(r);
|
||||
if(r->cmd[0] == 0x35 || r->cmd[0] == 0x91){
|
||||
if(vioreq(u->dev->ctlr, 4, nil, 0, 0, 0) != 0)
|
||||
if(vioblkreq(vd, 4, nil, 0, 0, 0) != 0)
|
||||
return sdsetsense(r, SDcheck, 3, 0xc, 2);
|
||||
return sdsetsense(r, SDok, 0, 0, 0);
|
||||
}
|
||||
|
@ -388,6 +548,9 @@ vioonline(SDunit *u)
|
|||
Vdev *vd;
|
||||
|
||||
vd = u->dev->ctlr;
|
||||
if(vd->typ == TypSCSI)
|
||||
return scsionline(u);
|
||||
|
||||
cap = inl(vd->port+Devspec+4);
|
||||
cap <<= 32;
|
||||
cap |= inl(vd->port+Devspec);
|
||||
|
@ -400,28 +563,41 @@ vioonline(SDunit *u)
|
|||
}
|
||||
|
||||
static int
|
||||
vioverify(SDunit *)
|
||||
vioverify(SDunit *u)
|
||||
{
|
||||
Vdev *vd;
|
||||
|
||||
vd = u->dev->ctlr;
|
||||
if(vd->typ == TypSCSI)
|
||||
return scsiverify(u);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDifc sdvirtioifc;
|
||||
|
||||
static void
|
||||
vdevenable(Vdev *vd)
|
||||
{
|
||||
intrenable(vd->pci->intl, viointerrupt, vd, vd->pci->tbdf, "virtio");
|
||||
outb(vd->port+Status, inb(vd->port+Status) | DriverOk);
|
||||
}
|
||||
|
||||
static SDev*
|
||||
viopnp(void)
|
||||
{
|
||||
SDev *s, *h, *t;
|
||||
Vdev *vd;
|
||||
int id;
|
||||
|
||||
id = 'F';
|
||||
|
||||
h = t = nil;
|
||||
for(vd = viopnpdevs(2); vd; vd = vd->next){
|
||||
|
||||
id = 'F';
|
||||
for(vd = viopnpdevs(TypBlk); vd; vd = vd->next){
|
||||
if(vd->nqueue != 1)
|
||||
continue;
|
||||
|
||||
intrenable(vd->pci->intl, viointerrupt, vd, vd->pci->tbdf, "virtio");
|
||||
outb(vd->port+Status, inb(vd->port+Status) | DriverOk);
|
||||
vdevenable(vd);
|
||||
|
||||
if((s = malloc(sizeof(*s))) == nil)
|
||||
break;
|
||||
|
@ -436,6 +612,53 @@ viopnp(void)
|
|||
t = s;
|
||||
}
|
||||
|
||||
id = '0';
|
||||
for(vd = viopnpdevs(TypSCSI); vd; vd = vd->next){
|
||||
ScsiCfg *cfg;
|
||||
|
||||
if(vd->nqueue < 3)
|
||||
continue;
|
||||
|
||||
if((cfg = malloc(sizeof(*cfg))) == nil)
|
||||
break;
|
||||
cfg->num_queues = inl(vd->port+Devspec+4*0);
|
||||
cfg->seg_max = inl(vd->port+Devspec+4*1);
|
||||
cfg->max_sectors = inl(vd->port+Devspec+4*2);
|
||||
cfg->cmd_per_lun = inl(vd->port+Devspec+4*3);
|
||||
cfg->event_info_size = inl(vd->port+Devspec+4*4);
|
||||
cfg->sense_size = inl(vd->port+Devspec+4*5);
|
||||
cfg->cdb_size = inl(vd->port+Devspec+4*6);
|
||||
cfg->max_channel = ins(vd->port+Devspec+4*7);
|
||||
cfg->max_target = ins(vd->port+Devspec+4*7+2);
|
||||
cfg->max_lun = inl(vd->port+Devspec+4*8);
|
||||
|
||||
if(cfg->max_target == 0){
|
||||
free(cfg);
|
||||
continue;
|
||||
}
|
||||
if((cfg->cdb_size > CDBSIZE) || (cfg->sense_size > SENSESIZE)){
|
||||
print("sdvirtio: cdb %ud or sense size %ud too big\n",
|
||||
cfg->cdb_size, cfg->sense_size);
|
||||
free(cfg);
|
||||
continue;
|
||||
}
|
||||
vd->cfg = cfg;
|
||||
|
||||
vdevenable(vd);
|
||||
|
||||
if((s = malloc(sizeof(*s))) == nil)
|
||||
break;
|
||||
s->ctlr = vd;
|
||||
s->idno = id++;
|
||||
s->ifc = &sdvirtioifc;
|
||||
s->nunit = cfg->max_target;
|
||||
if(h)
|
||||
t->next = s;
|
||||
else
|
||||
h = s;
|
||||
t = s;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue