ref: 2e713acc2c24b59003c4abeb099d87c88064842d
parent: 2739c6514a914972ed7ae2a2907b19f6496bcdce
author: cinap_lenrek <[email protected]>
date: Fri Jun 6 22:13:57 EDT 2014
sdvirtio: experimental support for virtio-scsi
--- a/sys/src/9/pc/sdvirtio.c
+++ b/sys/src/9/pc/sdvirtio.c
@@ -15,6 +15,14 @@
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 Vqueue
{
Lock;
+
+ Vdev *dev;
+ int idx;
+
int size;
int free;
@@ -103,9 +115,30 @@
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,15 +193,14 @@
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)
+ for(p = nil; p = pcimatch(p, 0x1AF4, 0);){
+ if((p->did < 0x1000) || (p->did > 0x103F))
continue;
- if((p->did < 0x1000) || (p->did >= 0x1040))
- continue;
if(p->rid != 0)
continue;
if(pcicfgr16(p, 0x2E) != typ)
@@ -196,8 +228,11 @@
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,41 +254,44 @@
};
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
viodone(void *arg)
{
@@ -261,7 +299,7 @@
}
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 @@
Vdesc *d;
u8int status;
- struct Vioreqhdr {
+ struct Vioblkreqhdr {
u32int typ;
u32int prio;
u64int lba;
@@ -320,7 +358,7 @@
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 @@
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;
- max = 32;
- ss = u->secsize;
vd = u->dev->ctlr;
+ if(vd->typ == TypSCSI)
+ return scsibio(u, lun, write, a, count, lba);
+ max = 32;
+ ss = u->secsize;
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 @@
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 @@
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,13 +563,26 @@
}
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)
{
@@ -413,15 +589,15 @@
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;
@@ -429,6 +605,53 @@
s->idno = id++;
s->ifc = &sdvirtioifc;
s->nunit = 1;
+ if(h)
+ t->next = s;
+ else
+ h = s;
+ 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