]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/9/pc/sdvirtio.c
sdvirtio: accept multi-queue devices
[plan9front.git] / sys / src / 9 / pc / sdvirtio.c
index b13acb5a296078a05842e451a4d76e00197a09bb..4b422766717ab2ca3b638b691c1401956870d6db 100644 (file)
@@ -4,6 +4,7 @@
 #include "dat.h"
 #include "fns.h"
 #include "io.h"
+#include "../port/pci.h"
 #include "ureg.h"
 #include "../port/error.h"
 
@@ -15,13 +16,23 @@ 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,
-       Driver          = 2,
-       DriverOk        = 4,
-       Failed          = 128,
+       Acknowledge = 1,
+       Driver = 2,
+       DriverOk = 4,
+       Failed = 0x80,
 };
 
+/* virtio ports */
 enum {
        Devfeat = 0,
        Drvfeat = 4,
@@ -35,10 +46,16 @@ enum {
        Devspec = 20,
 };
 
+/* descriptor flags */
 enum {
        Next = 1,
        Write = 2,
        Indirect = 4,
+};
+
+/* struct sizes */
+enum {
+       VringSize = 4,
 };     
 
 struct Vring
@@ -64,6 +81,10 @@ struct Vused
 struct Vqueue
 {
        Lock;
+
+       Vdev    *dev;
+       int     idx;
+
        int     size;
 
        int     free;
@@ -95,9 +116,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)
 {
@@ -108,15 +150,15 @@ mkvqueue(int size)
        q = malloc(sizeof(*q) + sizeof(void*)*size);
        p = mallocalign(
                PGROUND(sizeof(Vdesc)*size + 
-                       sizeof(Vring) + 
+                       VringSize + 
                        sizeof(u16int)*size + 
                        sizeof(u16int)) +
-               PGROUND(sizeof(Vring) + 
+               PGROUND(VringSize + 
                        sizeof(Vused)*size + 
                        sizeof(u16int)), 
                BY2PG, 0, 0);
        if(p == nil || q == nil){
-               print("mkvqueue: no memory for Vqueue\n");
+               print("virtio: no memory for Vqueue\n");
                free(p);
                free(q);
                return nil;
@@ -125,15 +167,15 @@ mkvqueue(int size)
        q->desc = (void*)p;
        p += sizeof(Vdesc)*size;
        q->avail = (void*)p;
-       p += sizeof(Vring);
+       p += VringSize;
        q->availent = (void*)p;
        p += sizeof(u16int)*size;
        q->availevent = (void*)p;
        p += sizeof(u16int);
 
-       p = (uchar*)PGROUND((ulong)p);
+       p = (uchar*)PGROUND((uintptr)p);
        q->used = (void*)p;
-       p += sizeof(Vring);
+       p += VringSize;
        q->usedent = (void*)p;
        p += sizeof(Vused)*size;
        q->usedevent = (void*)p;
@@ -151,28 +193,34 @@ mkvqueue(int size)
 static Vdev*
 viopnpdevs(int typ)
 {
-       Vdev *vd, *head, *tail;
+       Vdev *vd, *h, *t;
+       Vqueue *q;
        Pcidev *p;
-       u32int a;
        int n, i;
 
-       head = tail = nil;
-       for(p = nil; p = pcimatch(p, 0, 0);){
-               if(p->vid != 0x1AF4)
-                       continue;
-               if((p->did < 0x1000) || (p->did >= 0x1040))
+       h = t = nil;
+       for(p = nil; p = pcimatch(p, 0x1AF4, 0);){
+               if((p->did < 0x1000) || (p->did > 0x103F))
                        continue;
                if(p->rid != 0)
                        continue;
+               if((p->mem[0].bar & 1) == 0)
+                       continue;
                if(pcicfgr16(p, 0x2E) != typ)
                        continue;
                if((vd = malloc(sizeof(*vd))) == nil){
-                       print("viopnpdevs: cannot allocate memory for Vdev\n");
+                       print("virtio: no memory for Vdev\n");
                        break;
                }
-               vd->port = p->mem[0].bar & ~0x1;
+               vd->port = p->mem[0].bar & ~3;
+               if(ioalloc(vd->port, p->mem[0].size, 0, "virtio") < 0){
+                       print("virtio: port %lux in use\n", vd->port);
+                       free(vd);
+                       continue;
+               }
                vd->typ = typ;
                vd->pci = p;
+               pcienable(p);
 
                /* reset */
                outb(vd->port+Status, 0);
@@ -181,23 +229,27 @@ viopnpdevs(int typ)
                outb(vd->port+Status, Acknowledge|Driver);
                for(i=0; i<nelem(vd->queue); i++){
                        outs(vd->port+Qselect, i);
-                       if((n = ins(vd->port+Qsize)) == 0)
+                       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();
-                       a = PADDR(vd->queue[i]->desc)/BY2PG;
-                       outl(vd->port+Qaddr, a);
+                       outl(vd->port+Qaddr, PADDR(vd->queue[i]->desc)/BY2PG);
                }
                vd->nqueue = i;
        
-               if(head == nil)
-                       head = vd;
+               if(h == nil)
+                       h = vd;
                else
-                       tail->next = vd;
-               tail = vd;
+                       t->next = vd;
+               t = vd;
        }
-       return head;
+
+       return h;
 }
 
 struct Rock {
@@ -206,36 +258,42 @@ struct Rock {
 };
 
 static void
-viointerrupt(Ureg *, void *arg)
+vqinterrupt(Vqueue *q)
 {
        int id, free, m;
        struct Rock *r;
-       Vqueue *q;
-       Vdev *vd;
+       Rendez *z;
 
-       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;
-                               r->done = 1;
-                               wakeup(r->sleep);
-                       }
-                       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
@@ -244,32 +302,57 @@ viodone(void *arg)
        return ((struct Rock*)arg)->done;
 }
 
-static int
-vioreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
+static void
+vqio(Vqueue *q, int head)
 {
        struct Rock rock;
-       int free, head;
+
+       rock.done = 0;
+       rock.sleep = &up->sleep;
+       q->rock[head] = &rock;
+       q->availent[q->avail->idx & (q->size-1)] = head;
+       coherence();
+       q->avail->idx++;
+       iunlock(q);
+       if((q->used->flags & 1) == 0)
+               outs(q->dev->port+Qnotify, q->idx);
+       while(!rock.done){
+               while(waserror())
+                       ;
+               tsleep(rock.sleep, viodone, &rock, 1000);
+               poperror();
+
+               if(!rock.done)
+                       vqinterrupt(q);
+       }
+}
+
+static int
+vioblkreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
+{
+       int need, free, head;
        Vqueue *q;
        Vdesc *d;
 
        u8int status;
-       struct Vioreqhdr {
+       struct Vioblkreqhdr {
                u32int  typ;
                u32int  prio;
                u64int  lba;
        } req;
 
-       status = 0;
+       need = 2;
+       if(a != nil)
+               need = 3;
+
+       status = -1;
        req.typ = typ;
        req.prio = 0;
        req.lba = lba;
 
-       rock.done = 0;
-       rock.sleep = &up->sleep;
-
        q = vd->queue[0];
        ilock(q);
-       while(q->nfree < 3){
+       while(q->nfree < need){
                iunlock(q);
 
                if(!waserror())
@@ -286,10 +369,12 @@ vioreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
        d->len = sizeof(req);
        d->flags = Next;
 
-       d = &q->desc[free]; free = d->next;
-       d->addr = PADDR(a);
-       d->len = secsize*count;
-       d->flags = typ ? Next : (Write|Next);
+       if(a != nil){
+               d = &q->desc[free]; free = d->next;
+               d->addr = PADDR(a);
+               d->len = secsize*count;
+               d->flags = typ ? Next : (Write|Next);
+       }
 
        d = &q->desc[free]; free = d->next;
        d->addr = PADDR(&status);
@@ -297,50 +382,134 @@ vioreq(Vdev *vd, int typ, void *a, long count, long secsize, uvlong lba)
        d->flags = Write;
 
        q->free = free;
-       q->nfree -= 3;
+       q->nfree -= need;
 
-       q->rock[head] = &rock;
+       /* queue io, unlock and wait for completion */
+       vqio(q, head);
 
-       coherence();
-       q->availent[q->avail->idx++ & (q->size-1)] = head;
-       coherence();
-       outs(vd->port+Qnotify, 0);
-       iunlock(q);
+       return status;
+}
 
-       while(!rock.done){
-               while(waserror())
-                       ;
-               tsleep(rock.sleep, viodone, &rock, 1000);
+static int
+vioscsireq(SDreq *r)
+{
+       u8int resp[4+4+2+2+SENSESIZE];
+       u8int req[8+8+3+CDBSIZE];
+       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);
+
+       q = vd->queue[2];
+       ilock(q);
+       while(q->nfree < 3){
+               iunlock(q);
+
+               if(!waserror())
+                       tsleep(&up->sleep, return0, 0, 500);
                poperror();
 
-               if(!rock.done)
-                       viointerrupt(nil, vd);
+               ilock(q);
        }
 
-       return status;
+       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);
+
+       /* queue io, unlock and wait for completion */
+       vqio(q, head);
+
+       /* 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;
 }
 
@@ -350,10 +519,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);
        }
@@ -372,6 +545,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);
@@ -384,31 +560,61 @@ 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 int
+vioenable(SDev *sd)
+{
+       char name[32];
+       Vdev *vd;
+
+       vd = sd->ctlr;
+       pcisetbme(vd->pci);
+       snprint(name, sizeof(name), "%s (%s)", sd->name, sd->ifc->name);
+       intrenable(vd->pci->intl, viointerrupt, vd, vd->pci->tbdf, name);
+       outb(vd->port+Status, inb(vd->port+Status) | DriverOk);
+       return 1;
+}
+
+static int
+viodisable(SDev *sd)
+{
+       char name[32];
+       Vdev *vd;
+
+       vd = sd->ctlr;
+       snprint(name, sizeof(name), "%s (%s)", sd->name, sd->ifc->name);
+       intrdisable(vd->pci->intl, viointerrupt, vd, vd->pci->tbdf, name);
+       pciclrbme(vd->pci);
+       return 1;
+}
+
 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){
-               if(vd->nqueue != 1)
-                       continue;
 
-               intrenable(vd->pci->intl, viointerrupt, vd, vd->pci->tbdf, "sdvirtio");
-               outb(vd->port+Status, inb(vd->port+Status) | DriverOk);
+       id = 'F';
+       for(vd =  viopnpdevs(TypBlk); vd; vd = vd->next){
+               if(vd->nqueue == 0)
+                       continue;
 
-               s = malloc(sizeof(*s));
-               if(s == nil)
+               if((s = malloc(sizeof(*s))) == nil)
                        break;
                s->ctlr = vd;
                s->idno = id++;
@@ -421,6 +627,51 @@ 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;
+
+               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;
 }
 
@@ -429,8 +680,8 @@ SDifc sdvirtioifc = {
 
        viopnp,                         /* pnp */
        nil,                            /* legacy */
-       nil,                            /* enable */
-       nil,                            /* disable */
+       vioenable,                      /* enable */
+       viodisable,                     /* disable */
 
        vioverify,                      /* verify */
        vioonline,                      /* online */