ethervirtio: implement promisc and multicast mode, cleanup

add vctlcmd() function to setup and comlete control commands.
handle Vctlq and implement promiscuous and multicast mode commands.
remove Vqueue.block[] and Vqueue.header. these are not properties
of the queue (Vctlq as no block array).

the block[] array only needs to be half the queue size as we use
two descriptors per packet.

fix broken shutdown() and remove useless ctl() function.
This commit is contained in:
cinap_lenrek 2014-12-07 17:58:51 +01:00
parent aff0dc5e67
commit 6b5d69391c

View file

@ -11,10 +11,6 @@
/*
* virtio ethernet driver
* http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html
*
* TODO
*
* implement control queue
*/
typedef struct Vring Vring;
@ -78,6 +74,16 @@ enum {
Vrxq = 0,
Vtxq = 1,
Vctlq = 2,
/* class/cmd for Vctlq */
CtrlRx = 0x00,
CmdPromisc = 0x00,
CmdAllmulti = 0x01,
CtrlMac = 0x01,
CmdMacTableSet = 0x00,
CtrlVlan= 0x02,
CmdVlanAdd = 0x00,
CmdVlanDel = 0x01,
};
struct Vring
@ -128,24 +134,23 @@ struct Vqueue
Vused *usedent;
u16int *usedevent;
u16int lastused;
Vheader *header;
Block **block;
};
struct Ctlr {
Lock;
int attached;
QLock ctllock;
int port;
Pcidev* pcidev;
Ctlr* next;
int active;
int id;
int typ;
int attached;
int port;
Pcidev *pcidev;
Ctlr *next;
int active;
int id;
int typ;
ulong feat;
int nqueue;
int nqueue;
/* virtioether has 3 queues: rx, tx and ctl */
Vqueue *queue[3];
@ -166,6 +171,8 @@ vhasroom(void *v)
static void
txproc(void *v)
{
Vheader *header;
Block **blocks;
Ether *edev;
Ctlr *ctlr;
Vqueue *q;
@ -177,12 +184,12 @@ txproc(void *v)
ctlr = edev->ctlr;
q = ctlr->queue[Vtxq];
while(waserror())
;
header = smalloc(VheaderSize);
blocks = smalloc(sizeof(Block*) * (q->qsize/2));
for(i = 0; i < q->qsize/2; i++){
j = i << 1;
q->desc[j].addr = PADDR(q->header);
q->desc[j].addr = PADDR(header);
q->desc[j].len = VheaderSize;
q->desc[j].next = j | 1;
q->desc[j].flags = Dnext;
@ -196,22 +203,25 @@ txproc(void *v)
q->used->flags &= ~Rnointerrupt;
while(waserror())
;
while((b = qbread(edev->oq, 1000000)) != nil){
for(;;){
/* retire completed packets */
while((i = q->lastused) != q->used->idx){
u = &q->usedent[i & q->qmask];
i = (u->id & q->qmask) >> 1;
if(q->block[i] == nil)
if(blocks[i] == nil)
break;
freeb(q->block[i]);
q->block[i] = nil;
freeb(blocks[i]);
blocks[i] = nil;
q->lastused++;
}
/* have free slot? */
i = q->avail->idx & (q->qmask >> 1);
if(q->block[i] == nil)
if(blocks[i] == nil)
break;
/* ring full, wait and retry */
@ -220,7 +230,7 @@ txproc(void *v)
}
/* slot is free, fill in descriptor */
q->block[i] = b;
blocks[i] = b;
j = (i << 1) | 1;
q->desc[j].addr = PADDR(b->rp);
q->desc[j].len = BLEN(b);
@ -235,6 +245,8 @@ txproc(void *v)
static void
rxproc(void *v)
{
Vheader *header;
Block **blocks;
Ether *edev;
Ctlr *ctlr;
Vqueue *q;
@ -246,12 +258,12 @@ rxproc(void *v)
ctlr = edev->ctlr;
q = ctlr->queue[Vrxq];
while(waserror())
;
header = smalloc(VheaderSize);
blocks = smalloc(sizeof(Block*) * (q->qsize/2));
for(i = 0; i < q->qsize/2; i++){
j = i << 1;
q->desc[j].addr = PADDR(q->header);
q->desc[j].addr = PADDR(header);
q->desc[j].len = VheaderSize;
q->desc[j].next = j | 1;
q->desc[j].flags = Dwrite|Dnext;
@ -265,15 +277,18 @@ rxproc(void *v)
q->used->flags &= ~Rnointerrupt;
while(waserror())
;
for(;;){
/* replenish receive ring */
do {
i = q->avail->idx & (q->qmask >> 1);
if(q->block[i] != nil)
if(blocks[i] != nil)
break;
if((b = iallocb(ETHERMAXTU)) == nil)
break;
q->block[i] = b;
blocks[i] = b;
j = (i << 1) | 1;
q->desc[j].addr = PADDR(b->rp);
q->desc[j].len = BALLOC(b);
@ -290,10 +305,10 @@ rxproc(void *v)
while((i = q->lastused) != q->used->idx) {
u = &q->usedent[i & q->qmask];
i = (u->id & q->qmask) >> 1;
if((b = q->block[i]) == nil)
if((b = blocks[i]) == nil)
break;
q->block[i] = nil;
blocks[i] = nil;
b->wp = b->rp + u->len;
etheriq(edev, b, 1);
@ -302,21 +317,81 @@ rxproc(void *v)
}
}
static int
vctlcmd(Ether *edev, uchar class, uchar cmd, uchar *data, int ndata)
{
uchar hdr[2], ack[1];
Ctlr *ctlr;
Vqueue *q;
Vdesc *d;
int i;
ctlr = edev->ctlr;
q = ctlr->queue[Vctlq];
if(q == nil || q->qsize < 3)
return -1;
qlock(&ctlr->ctllock);
while(waserror())
;
ack[0] = 0x55;
hdr[0] = class;
hdr[1] = cmd;
d = &q->desc[0];
d->addr = PADDR(hdr);
d->len = sizeof(hdr);
d->next = 1;
d->flags = Dnext;
d++;
d->addr = PADDR(data);
d->len = ndata;
d->next = 2;
d->flags = Dnext;
d++;
d->addr = PADDR(ack);
d->len = sizeof(ack);
d->next = 0;
d->flags = Dwrite;
i = q->avail->idx & q->qmask;
q->availent[i] = 0;
coherence();
q->used->flags &= ~Rnointerrupt;
q->avail->idx++;
outs(ctlr->port+Qnotify, Vctlq);
while(!vhasroom(q))
sleep(q, vhasroom, q);
q->lastused = q->used->idx;
q->used->flags |= Rnointerrupt;
qunlock(&ctlr->ctllock);
poperror();
if(ack[0] != 0)
print("#l%d: vctlcmd: %ux.%ux -> %ux\n", edev->ctlrno, class, cmd, ack[0]);
return ack[0];
}
static void
interrupt(Ureg*, void* arg)
{
Ether *edev;
Ctlr* ctlr;
Ctlr *ctlr;
Vqueue *q;
int i;
edev = arg;
ctlr = edev->ctlr;
if(inb(ctlr->port+Qisr) & 1){
if(vhasroom(q = ctlr->queue[Vtxq]))
wakeup(q);
if(vhasroom(q = ctlr->queue[Vrxq]))
wakeup(q);
for(i = 0; i < ctlr->nqueue; i++){
q = ctlr->queue[i];
if(vhasroom(q))
wakeup(q);
}
}
}
@ -327,21 +402,19 @@ attach(Ether* edev)
Ctlr* ctlr;
ctlr = edev->ctlr;
lock(ctlr);
if(!ctlr->attached){
ctlr->attached = 1;
/* ready to go */
outb(ctlr->port+Qstatus, inb(ctlr->port+Qstatus) | Sdriverok);
/* start kprocs */
snprint(name, sizeof name, "#l%drx", edev->ctlrno);
kproc(name, rxproc, edev);
snprint(name, sizeof name, "#l%dtx", edev->ctlrno);
kproc(name, txproc, edev);
/* ready to go */
outb(ctlr->port+Qstatus, inb(ctlr->port+Qstatus) | Sdriverok);
}
unlock(ctlr);
}
@ -375,48 +448,31 @@ ifstat(Ether *edev, void *a, long n, ulong offset)
return n;
}
/* XXX: not done */
static long
ctl(Ether *, void *, long)
{
return 0;
}
/* XXX: not done */
static void
promiscuous(void *v, int on)
shutdown(Ether* edev)
{
Ether *edev;
Ctlr *ctlr;
edev = v;
ctlr = edev->ctlr;
USED(ctlr, on);
}
/* XXX: not done */
static void
shutdown(Ether* ether)
{
Ctlr *ctlr;
ctlr = (Ctlr*) ether;
Ctlr *ctlr = edev->ctlr;
outb(ctlr->port+Qstatus, 0);
}
/* XXX: not done */
static void
promiscuous(void *arg, int on)
{
Ether *edev = arg;
uchar b[1];
b[0] = on != 0;
vctlcmd(edev, CtrlRx, CmdPromisc, b, sizeof(b));
}
static void
multicast(void *arg, uchar*, int)
{
Ether *edev;
Ctlr *ctlr;
Ether *edev = arg;
uchar b[1];
edev = arg;
ctlr = edev->ctlr;
USED(ctlr);
b[0] = edev->nmaddr > 0;
vctlcmd(edev, CtrlRx, CmdAllmulti, b, sizeof(b));
}
/* §2.4.2 Legacy Interfaces: A Note on Virtqueue Layout */
@ -466,9 +522,6 @@ mkqueue(int size)
q->lastused = q->avail->idx = q->used->idx = 0;
q->block = mallocz(sizeof(Block*) * size, 1);
q->header = mallocz(VheaderSize, 1);
/* disable interrupts
* virtio spec says we still get interrupts if
* VnotifyEmpty is set in Drvfeat */
@ -614,9 +667,8 @@ reset(Ether* edev)
edev->interrupt = interrupt;
edev->ifstat = ifstat;
edev->ctl = ctl;
edev->promiscuous = promiscuous;
edev->multicast = multicast;
edev->promiscuous = promiscuous;
return 0;
}