diff options
-rw-r--r-- | sys/src/9/pc/sdvirtio.c | 315 |
1 files changed, 269 insertions, 46 deletions
diff --git a/sys/src/9/pc/sdvirtio.c b/sys/src/9/pc/sdvirtio.c index 1c26cea58..fdcc929b2 100644 --- a/sys/src/9/pc/sdvirtio.c +++ b/sys/src/9/pc/sdvirtio.c @@ -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; - 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 @@ 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; } |