/*
 * 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++;
	if (rr->flags & NETRXF_data_validated)
		bp->flag |= Btcpck|Budpck;
	bp->rp = bp->base;
	bp->wp = bp->rp + len;
	memmove(bp->rp, rx->page + rr->offset, len);
	vifrecv(ctlr, rx);
	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);
}

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 char*
ifstat(void *arg, char *p, char *e)
{
	Ether *ether = arg;
	Ctlr *ctlr = ether->ctlr;

	if(p >= e)
		return p;

	p = seprint(p, e, "intr: %lud\n", ctlr->interrupts);
	p = seprint(p, e, "transmits: %lud\n", ctlr->transmits);
	p = seprint(p, e, "receives: %lud\n", ctlr->receives);
	p = seprint(p, e, "txerrors: %lud\n", ctlr->txerrors);
	p = seprint(p, e, "rxerrors: %lud\n", ctlr->rxerrors);
	p = seprint(p, e, "rxoverflows: %lud\n", ctlr->rxoverflows);

	return p;
}

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->ctl = etherxenctl;
	ether->promiscuous = nil;
	ether->multicast = etherxenmulticast;
	ether->ifstat = ifstat;
	ether->arg = ether;

	intrenable(ether->irq, etherxenintr, ether, ether->tbdf, ether->name);

	return 0;
}

void
etherxenlink(void)
{
	addethercard("xen", pnp);
}
