f0a314605f
we allow devether to create ethernet cards on attach. this is useull for virtual cards like the sink driver, so we can create a sink by simply: bind -a '#l2:sink ea=112233445566' /net the detach routine was never called, so remove it from the few drivers that attempted to implement it.
496 lines
10 KiB
C
496 lines
10 KiB
C
/*
|
|
* Xen virtual network interface frontend
|
|
*/
|
|
|
|
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "io.h"
|
|
#include "../port/error.h"
|
|
#include "../port/netif.h"
|
|
#include "../port/etherif.h"
|
|
|
|
#define LOG(a)
|
|
|
|
enum {
|
|
Nvif = 4,
|
|
Ntb = 16,
|
|
Nrb = 32,
|
|
};
|
|
|
|
typedef struct Ctlr Ctlr;
|
|
typedef union Txframe Txframe;
|
|
typedef union Rxframe Rxframe;
|
|
|
|
struct Ctlr {
|
|
int attached;
|
|
int backend;
|
|
int vifno;
|
|
int evtchn;
|
|
int rxcopy;
|
|
Txframe *txframes;
|
|
Txframe *freetxframe;
|
|
Rxframe *rxframes;
|
|
netif_tx_front_ring_t txring;
|
|
netif_rx_front_ring_t rxring;
|
|
int *txrefs;
|
|
int *rxrefs;
|
|
int txringref;
|
|
int rxringref;
|
|
Lock txlock;
|
|
QLock attachlock;
|
|
Rendez wtxframe;
|
|
Rendez wtxblock;
|
|
|
|
ulong interrupts;
|
|
ulong transmits;
|
|
ulong receives;
|
|
ulong txerrors;
|
|
ulong rxerrors;
|
|
ulong rxoverflows;
|
|
};
|
|
|
|
union Txframe {
|
|
struct {
|
|
Txframe *next;
|
|
char data[2];
|
|
} tf;
|
|
uchar page[BY2PG];
|
|
};
|
|
|
|
union Rxframe {
|
|
uchar page[BY2PG];
|
|
};
|
|
|
|
static int nvif;
|
|
|
|
/*
|
|
* conversions to machine page numbers, pages and addresses
|
|
*/
|
|
#define MFN(pa) (patomfn[(pa)>>PGSHIFT])
|
|
#define MFNPG(pa) (MFN(pa)<<PGSHIFT)
|
|
#define PA2MA(pa) (MFNPG(pa) | PGOFF(pa))
|
|
#define VA2MA(va) PA2MA(PADDR(va))
|
|
|
|
static int
|
|
puttxrequest(Ctlr *ctlr, netif_tx_request_t *tr)
|
|
{
|
|
netif_tx_request_t *req;
|
|
int i, notify;
|
|
|
|
LOG(dprint("puttxrequest id %d ref %d size %d\n", tr->id, tr->gref, tr->size);)
|
|
i = ctlr->txring.req_prod_pvt;
|
|
req = RING_GET_REQUEST(&ctlr->txring, i);
|
|
memmove(req, tr, sizeof(*req));
|
|
ctlr->txring.req_prod_pvt = i+1;
|
|
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&ctlr->txring, notify);
|
|
return notify;
|
|
}
|
|
|
|
static int
|
|
putrxrequest(Ctlr *ctlr, netif_rx_request_t *rr)
|
|
{
|
|
netif_rx_request_t *req;
|
|
int i;
|
|
int notify;
|
|
|
|
LOG(dprint("putrxrequest %d %d\n", rr->id, rr->gref);)
|
|
i = ctlr->rxring.req_prod_pvt;
|
|
req = RING_GET_REQUEST(&ctlr->rxring, i);
|
|
memmove(req, rr, sizeof(*req));
|
|
ctlr->rxring.req_prod_pvt = i+1;
|
|
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&ctlr->rxring, notify);
|
|
return notify;
|
|
}
|
|
|
|
static int
|
|
gettxresponse(Ctlr *ctlr, netif_tx_response_t *tr)
|
|
{
|
|
int i, avail;
|
|
netif_tx_response_t *rx;
|
|
|
|
RING_FINAL_CHECK_FOR_RESPONSES(&ctlr->txring, avail);
|
|
if (!avail)
|
|
return 0;
|
|
i = ctlr->txring.rsp_cons;
|
|
rx = RING_GET_RESPONSE(&ctlr->txring, i);
|
|
LOG(dprint("gettxresponse id %d status %d\n", rx->id, rx->status);)
|
|
if(rx->status)
|
|
ctlr->txerrors++;
|
|
*tr = *rx;
|
|
ctlr->txring.rsp_cons = ++i;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
getrxresponse(Ctlr *ctlr, netif_rx_response_t* rr)
|
|
{
|
|
int i, avail;
|
|
netif_rx_response_t *rx;
|
|
|
|
RING_FINAL_CHECK_FOR_RESPONSES(&ctlr->rxring, avail);
|
|
if (!avail)
|
|
return 0;
|
|
i = ctlr->rxring.rsp_cons;
|
|
rx = RING_GET_RESPONSE(&ctlr->rxring, i);
|
|
LOG(dprint("getrxresponse id %d offset %d flags %ux status %d\n", rx->id, rx->offset, rx->flags, rx->status);)
|
|
*rr = *rx;
|
|
ctlr->rxring.rsp_cons = ++i;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
ringinit(Ctlr *ctlr, char *a)
|
|
{
|
|
netif_tx_sring_t *txr;
|
|
netif_rx_sring_t *rxr;
|
|
|
|
txr = (netif_tx_sring_t*)a;
|
|
memset(txr, 0, BY2PG);
|
|
SHARED_RING_INIT(txr);
|
|
FRONT_RING_INIT(&ctlr->txring, txr, BY2PG);
|
|
ctlr->txringref = shareframe(ctlr->backend, txr, 1);
|
|
|
|
rxr = (netif_rx_sring_t*)(a+BY2PG);
|
|
SHARED_RING_INIT(rxr);
|
|
FRONT_RING_INIT(&ctlr->rxring, rxr, BY2PG);
|
|
ctlr->rxringref = shareframe(ctlr->backend, rxr, 1);
|
|
|
|
return 2*BY2PG;
|
|
}
|
|
|
|
static int
|
|
vifsend(Ctlr *ctlr, Block *bp)
|
|
{
|
|
netif_tx_request_t tr;
|
|
Txframe *tx;
|
|
int id;
|
|
|
|
ilock(&ctlr->txlock);
|
|
tx = ctlr->freetxframe;
|
|
ctlr->freetxframe = tx->tf.next;
|
|
iunlock(&ctlr->txlock);
|
|
id = tx - ctlr->txframes;
|
|
tr.gref = ctlr->txrefs[id];
|
|
tr.offset = tx->tf.data - (char*)tx;
|
|
tr.flags = 0; // XXX checksum?
|
|
tr.id = id;
|
|
tr.size = BLEN(bp);
|
|
memmove(tx->tf.data, bp->rp, tr.size);
|
|
return puttxrequest(ctlr, &tr);
|
|
}
|
|
|
|
static int
|
|
vifsenddone(Ctlr *ctlr, netif_tx_response_t *tr)
|
|
{
|
|
Txframe *tx;
|
|
|
|
tx = &ctlr->txframes[tr->id]; // XXX check validity of id
|
|
ilock(&ctlr->txlock);
|
|
tx->tf.next = ctlr->freetxframe;
|
|
ctlr->freetxframe = tx;
|
|
iunlock(&ctlr->txlock);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
vifrecv(Ctlr *ctlr, Rxframe *rx)
|
|
{
|
|
netif_rx_request_t rr;
|
|
int id;
|
|
int ref;
|
|
|
|
id = rx - ctlr->rxframes;
|
|
if (ctlr->rxcopy)
|
|
ref = ctlr->rxrefs[id];
|
|
else {
|
|
ref = donateframe(ctlr->backend, rx);
|
|
ctlr->rxrefs[id] = ref;
|
|
}
|
|
rr.id = id;
|
|
rr.gref = ref;
|
|
return putrxrequest(ctlr, &rr);
|
|
}
|
|
|
|
static int
|
|
vifrecvdone(Ether *ether, netif_rx_response_t *rr)
|
|
{
|
|
Ctlr *ctlr;
|
|
Rxframe *rx;
|
|
Block *bp;
|
|
int len;
|
|
|
|
ctlr = ether->ctlr;
|
|
rx = &ctlr->rxframes[rr->id]; // XXX check validity of id
|
|
if (!ctlr->rxcopy)
|
|
acceptframe(ctlr->rxrefs[rr->id], rx);
|
|
if ((len = rr->status) <= 0) {
|
|
ctlr->rxerrors++;
|
|
vifrecv(ctlr, rx);
|
|
return 1;
|
|
}
|
|
if(len > sizeof(Etherpkt) || (bp = iallocb(sizeof(Etherpkt))) == nil) {
|
|
ctlr->rxoverflows++;
|
|
vifrecv(ctlr, rx);
|
|
return 1;
|
|
}
|
|
|
|
ctlr->receives++;
|
|
memmove(bp->base, rx->page + rr->offset, len);
|
|
vifrecv(ctlr, rx);
|
|
|
|
bp->rp = bp->base;
|
|
bp->wp = bp->rp + len;
|
|
bp->free = 0;
|
|
bp->next = 0;
|
|
bp->list = 0;
|
|
if (rr->flags & NETRXF_data_validated)
|
|
bp->flag |= Btcpck|Budpck;
|
|
etheriq(ether, bp);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
wtxframe(void *a)
|
|
{
|
|
return ((struct Ctlr*)a)->freetxframe != 0;
|
|
}
|
|
|
|
static int
|
|
wtxblock(void *a)
|
|
{
|
|
return qcanread(((struct Ether*)a)->oq);
|
|
}
|
|
|
|
static void
|
|
etherxenproc(void *a)
|
|
{
|
|
Ether *ether = a;
|
|
Ctlr *ctlr = ether->ctlr;
|
|
Block *bp;
|
|
int notify;
|
|
|
|
for (;;) {
|
|
while (ctlr->freetxframe == 0)
|
|
sleep(&ctlr->wtxframe, wtxframe, ctlr);
|
|
while ((bp = qget(ether->oq)) == 0)
|
|
sleep(&ctlr->wtxblock, wtxblock, ether);
|
|
notify = vifsend(ctlr, bp);
|
|
freeb(bp);
|
|
if (notify)
|
|
xenchannotify(ctlr->evtchn);
|
|
}
|
|
}
|
|
|
|
static void
|
|
etherxentransmit(Ether *ether)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = ether->ctlr;
|
|
ctlr->transmits++;
|
|
wakeup(&ctlr->wtxblock);
|
|
}
|
|
|
|
static void
|
|
etherxenintr(Ureg*, void *a)
|
|
{
|
|
Ether *ether = a;
|
|
Ctlr *ctlr = ether->ctlr;
|
|
int txnotify;
|
|
netif_tx_response_t tr;
|
|
netif_rx_response_t rr;
|
|
|
|
ctlr->interrupts++;
|
|
txnotify = 0;
|
|
while (getrxresponse(ctlr, &rr))
|
|
vifrecvdone(ether, &rr);
|
|
while (gettxresponse(ctlr, &tr)) {
|
|
if (vifsenddone(ctlr, &tr))
|
|
txnotify = 1;
|
|
}
|
|
if (txnotify)
|
|
wakeup(&ctlr->wtxframe);
|
|
}
|
|
|
|
static long
|
|
etherxenctl(Ether *ether, void *buf, long n)
|
|
{
|
|
uchar ea[Eaddrlen];
|
|
Cmdbuf *cb;
|
|
|
|
cb = parsecmd(buf, n);
|
|
if(cb->nf >= 2
|
|
&& strcmp(cb->f[0], "ea")==0
|
|
&& parseether(ea, cb->f[1]) == 0){
|
|
free(cb);
|
|
memmove(ether->ea, ea, Eaddrlen);
|
|
memmove(ether->addr, ether->ea, Eaddrlen);
|
|
return 0;
|
|
}
|
|
free(cb);
|
|
error(Ebadctl);
|
|
return -1; /* not reached */
|
|
}
|
|
|
|
static void
|
|
backendconnect(Ctlr *ctlr)
|
|
{
|
|
char dir[64];
|
|
char buf[64];
|
|
|
|
sprint(dir, "device/vif/%d/", ctlr->vifno);
|
|
xenstore_setd(dir, "state", XenbusStateInitialising);
|
|
xenstore_setd(dir, "tx-ring-ref", ctlr->txringref);
|
|
xenstore_setd(dir, "rx-ring-ref", ctlr->rxringref);
|
|
xenstore_setd(dir, "event-channel", ctlr->evtchn);
|
|
print("etherxen: request-rx-copy=%d\n", ctlr->rxcopy);
|
|
if (ctlr->rxcopy)
|
|
xenstore_setd(dir, "request-rx-copy", 1);
|
|
xenstore_setd(dir, "state", XenbusStateConnected);
|
|
xenstore_gets(dir, "backend", buf, sizeof buf);
|
|
sprint(dir, "%s/", buf);
|
|
HYPERVISOR_yield();
|
|
xenstore_gets(dir, "state", buf, sizeof buf);
|
|
while (strtol(buf, 0, 0) != XenbusStateConnected) {
|
|
print("etherxen: waiting for vif %d to connect\n", ctlr->vifno);
|
|
tsleep(&up->sleep, return0, 0, 50);
|
|
xenstore_gets(dir, "state", buf, sizeof buf);
|
|
}
|
|
}
|
|
|
|
static void
|
|
etherxenattach(Ether *ether)
|
|
{
|
|
Ctlr *ctlr;
|
|
char *p;
|
|
Txframe *tx;
|
|
int npage, i;
|
|
|
|
LOG(dprint("etherxenattach\n");)
|
|
ctlr = ether->ctlr;
|
|
qlock(&ctlr->attachlock);
|
|
if (ctlr->attached) {
|
|
qunlock(&ctlr->attachlock);
|
|
return;
|
|
}
|
|
|
|
npage = 2 + Ntb + Nrb;
|
|
p = (char*)xspanalloc(npage<<PGSHIFT, BY2PG, 0);
|
|
p += ringinit(ctlr, p);
|
|
ctlr->txrefs = malloc(Ntb*sizeof(int));
|
|
ctlr->rxrefs = malloc(Nrb*sizeof(int));
|
|
ctlr->txframes = (Txframe*)p;
|
|
for (i = 0; i < Ntb; i++, p += BY2PG) {
|
|
tx = (Txframe*)p;
|
|
if (i != Ntb-1)
|
|
tx->tf.next = tx + 1;
|
|
else
|
|
tx->tf.next = 0;
|
|
ctlr->txrefs[i] = shareframe(ctlr->backend, tx, 0);
|
|
}
|
|
ctlr->freetxframe = ctlr->txframes;
|
|
ctlr->rxframes = (Rxframe*)p;
|
|
for (i = 0; i < Nrb; i++, p += BY2PG) {
|
|
if (ctlr->rxcopy)
|
|
ctlr->rxrefs[i] = shareframe(ctlr->backend, (Rxframe*)p, 1);
|
|
vifrecv(ctlr, (Rxframe*)p);
|
|
}
|
|
|
|
ctlr->evtchn = xenchanalloc(ctlr->backend);
|
|
intrenable(ctlr->evtchn, etherxenintr, ether, BUSUNKNOWN, "vif");
|
|
|
|
kproc("vif", etherxenproc, ether);
|
|
backendconnect(ctlr);
|
|
ctlr->attached = 1;
|
|
qunlock(&ctlr->attachlock);
|
|
}
|
|
|
|
static void
|
|
etherxenmulticast(void* arg, uchar* addr, int on)
|
|
{
|
|
USED(arg, addr, on);
|
|
}
|
|
|
|
static long
|
|
ifstat(Ether* ether, void* a, long n, ulong offset)
|
|
{
|
|
Ctlr *ctlr;
|
|
char *buf, *p;
|
|
int l, len;
|
|
|
|
ctlr = ether->ctlr;
|
|
if(n == 0)
|
|
return 0;
|
|
if((p = malloc(READSTR)) == nil)
|
|
error(Enomem);
|
|
l = snprint(p, READSTR, "intr: %lud\n", ctlr->interrupts);
|
|
l += snprint(p+l, READSTR-l, "transmits: %lud\n", ctlr->transmits);
|
|
l += snprint(p+l, READSTR-l, "receives: %lud\n", ctlr->receives);
|
|
l += snprint(p+l, READSTR-l, "txerrors: %lud\n", ctlr->txerrors);
|
|
l += snprint(p+l, READSTR-l, "rxerrors: %lud\n", ctlr->rxerrors);
|
|
snprint(p+l, READSTR-l, "rxoverflows: %lud\n", ctlr->rxoverflows);
|
|
|
|
buf = a;
|
|
len = readstr(offset, buf, n, p);
|
|
free(p);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
pnp(Ether* ether)
|
|
{
|
|
uchar ea[Eaddrlen];
|
|
char dir[64];
|
|
char buf[64];
|
|
Ctlr *ctlr;
|
|
int domid, rxcopy;
|
|
|
|
if (nvif > Nvif)
|
|
return -1;
|
|
sprint(dir, "device/vif/%d/", nvif);
|
|
if (xenstore_gets(dir, "backend-id", buf, sizeof buf) <= 0)
|
|
return -1;
|
|
domid = strtol(buf, 0, 0);
|
|
if (xenstore_gets(dir, "mac", buf, sizeof buf) <= 0)
|
|
return -1;
|
|
if (parseether(ea, buf) < 0)
|
|
return -1;
|
|
if (xenstore_gets(dir, "backend", buf, sizeof buf) <= 0)
|
|
return 1;
|
|
sprint(dir, "%s/", buf);
|
|
rxcopy = 0;
|
|
if (xenstore_gets(dir, "feature-rx-copy", buf, sizeof buf) >= 0)
|
|
rxcopy = strtol(buf, 0, 0);
|
|
ether->ctlr = ctlr = malloc(sizeof(Ctlr));
|
|
memset(ctlr, 0, sizeof(Ctlr));
|
|
ctlr->backend = domid;
|
|
ctlr->vifno = nvif++;
|
|
ctlr->rxcopy = rxcopy;
|
|
|
|
memmove(ether->ea, ea, sizeof ether->ea);
|
|
ether->mbps = 100; // XXX what speed?
|
|
ether->attach = etherxenattach;
|
|
ether->transmit = etherxentransmit;
|
|
ether->irq = -1;
|
|
ether->tbdf = BUSUNKNOWN;
|
|
ether->ifstat = ifstat;
|
|
ether->ctl = etherxenctl;
|
|
ether->promiscuous = nil;
|
|
ether->multicast = etherxenmulticast;
|
|
ether->arg = ether;
|
|
|
|
intrenable(ether->irq, etherxenintr, ether, ether->tbdf, ether->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
etherxenlink(void)
|
|
{
|
|
addethercard("xen", pnp);
|
|
}
|