#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/netif.h"
#include "../port/etherif.h"

typedef struct Hio Hio;
typedef struct Desc Desc;
typedef struct Ring Ring;
typedef struct Ctlr Ctlr;

/*
 * SEEQ 8003 interfaced to HPC3 (very different from IP20)
 */
struct Hio
{
	ulong	unused0[20480];
	ulong	crbp;		/* current receive buf desc ptr */
	ulong	nrbdp;		/* next receive buf desc ptr */
	ulong	unused1[1022];
	ulong	rbc;		/* receive byte count */
	ulong	rstat;		/* receiver status */
	ulong	rgio;		/* receive gio fifo ptr */
	ulong	rdev;		/* receive device fifo ptr */
	ulong	unused2;
	ulong	ctl;		/* interrupt, channel reset, buf oflow */
	ulong	dmacfg;		/* dma configuration */
	ulong	piocfg;		/* pio configuration */
	ulong 	unused3[1016];
	ulong	cxbdp;		/* current xmit buf desc ptr */
	ulong	nxbdp;		/* next xmit buffer desc. pointer */
	ulong	unused4[1022];
	ulong	xbc;		/* xmit byte count */
	ulong	xstat;
	ulong	xgio;		/* xmit gio fifo ptr */
	ulong	xdev;		/* xmit device fifo ptr */
	ulong	unused5[1020];
	ulong	crbdp;		/* current receive descriptor ptr */
	ulong	unused6[2047];
	ulong	cpfxbdp;	/* current/previous packet 1st xmit */
	ulong	ppfxbdp;	/* desc ptr */
	ulong	unused7[59390];
	ulong	eaddr[6];	/* seeq station address wo */
	ulong	csr;		/* seeq receiver cmd/status reg */
	ulong	csx;		/* seeq transmitter cmd/status reg */
};

enum
{			/* ctl */
	Cover=	0x08,		/* receive buffer overflow */
	Cnormal=0x00,		/* 1=normal, 0=loopback */
	Cint=	0x02,		/* interrupt (write 1 to clear) */
	Creset=	0x01,		/* ethernet channel reset */

			/* xstat */
	Xdma=	0x200,		/* dma active */
	Xold=	0x080,		/* register has been read */
	Xok=	0x008,		/* transmission was successful */
	Xmaxtry=0x004,		/* transmission failed after 16 attempts */
	Xcoll=	0x002,		/* transmission collided */
	Xunder=	0x001,		/* transmitter underflowed */

			/* csx */
	Xreg0=	0x00,		/* access reg bank 0 incl station addr */
	XIok=	0x08,
	XImaxtry=0x04,
	XIcoll=	0x02,
	XIunder=0x01,

			/* rstat */
	Rlshort=0x800,		/* [small len in received frame] */
	Rdma=	0x200,		/* dma active */
	Rold=	0x80,		/* register has been read */
	Rok=	0x20,		/* received good frame */
	Rend=	0x10,		/* received end of frame */
	Rshort=	0x08,		/* received short frame */
	Rdrbl=	0x04,		/* dribble error */
	Rcrc=	0x02,		/* CRC error */
	Rover=	0x01,		/* overflow error */

			/* csr */
	Rsmb=	0xc0,		/* receive station/broadcast/multicast frames */
	Rsb=	0x80,		/* receive station/broadcast frames */
	Rprom=	0x40,		/* receive all frames */
	RIok=	0x20,	 	/* interrupt on good frame */
	RIend=	0x10,		/* interrupt on end of frame */
	RIshort=0x08,		/* interrupt on short frame */
	RIdrbl=	0x04,		/* interrupt on dribble error */
	RIcrc=	0x02,		/* interrupt on CRC error */
	RIover=	0x01,		/* interrupt on overflow error */

	HPC_MODNORM=	0x0,	/* mode: 0=normal, 1=loopback */
	HPC_FIX_INTR=	0x8000,	/* start timeout counter after */
	HPC_FIX_EOP=	0x4000,	/* rcv_eop_intr/eop_in_chip is set */ 
	HPC_FIX_RXDC=	0x2000,	/* clear eop status upon rxdc */
};

struct Desc
{
	ulong	addr;		/* addr */
	ulong	count;		/* eox / eop / busy / xie / count:13 */
	ulong	next;
	uchar*	base;
};

struct Ring
{
	Rendez;
	int	size;
	int	free;
	uchar*	base;
	Desc*	head;
	Desc*	tail;
};

enum
{
	Eor=	1<<31,		/* end of ring */
	Eop=	1<<30,
	Ioc=	1<<29,		/* interrupt on completion */
	Busy=	1<<24,
	Empty=	1<<14,		/* no data here */
	Done=	1<<15,		/* transmit done */
};

enum {
	Rbsize = ETHERMAXTU+3,
};

struct Ctlr
{
	int	attach;
	char*	txerr;
	ulong	txwdog;

	Hio*	io;

	Ring	rx;
	Ring	tx;
};

static void startup(Ether*);
static void shutdown(Ether*);

static void
txintr(Ctlr *ctlr)
{
	Hio *io;
	ulong s;
	Desc *p;

	io = ctlr->io;
	s = io->xstat;
	if((s & Xdma) != 0)
		return;
	if((s & Xmaxtry) != 0)
		ctlr->txerr = "transmission failed";
	if((s & Xunder) != 0)
		ctlr->txerr = "transmitter underflowed";
	for(p = IO(Desc, ctlr->tx.head->next); (p->count & Busy) != 0; p = IO(Desc, p->next)){
		if((p->count & Done) == 0){
			io->nxbdp = PADDR(p);
			io->xstat = Xdma;
			ctlr->txwdog = MACHP(0)->ticks;
			break;
		}
		p->count = Eor|Eop;
		ctlr->tx.head = p;
		ctlr->tx.free++;
		ctlr->txwdog = 0;
	}
	wakeup(&ctlr->tx);
}

static void
interrupt(Ureg *, void *arg)
{
	Ether *edev;
	Ctlr *ctlr;
	Hio *io;
	uint s;

	edev = arg;
	ctlr = edev->ctlr;
	io = ctlr->io;
	s = io->ctl;
	if(s & Cover){
		io->ctl = Cnormal | Cover;
		edev->overflows++;
	}
	if(s & Cint) {
		io->ctl = Cnormal | Cint;
		txintr(ctlr);
		wakeup(&ctlr->rx);
	}
}

static int
notempty(void *arg)
{
	Ctlr *ctlr = arg;

	return (IO(Desc, ctlr->rx.head->next)->count & Empty) == 0;
}

static char*
checkerr(Ctlr *ctlr)
{
	ulong t;

	if(ctlr->txerr != nil)
		return ctlr->txerr;
	t = ctlr->txwdog;
	if(t != 0 && TK2MS(MACHP(0)->ticks - t) > 1000)
		return "transmitter dma timeout";
	if((ctlr->io->rstat & Rdma) == 0)
		return "recevier dma stopped";
	return nil;
}

static void
rxproc(void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr;
	char *err;
	Block *b;
	Desc *p;
	int n;

	while(waserror())
		;

	ctlr = edev->ctlr;
	for(p = IO(Desc, ctlr->rx.head->next);; p = IO(Desc, p->next)){
		while((p->count & Empty) != 0){
			err = checkerr(ctlr);
			if(err != nil){
				print("%s: %s; reseting\n", up->text, err);
				splhi();
				shutdown(edev);
				startup(edev);
				spllo();
			}
			tsleep(&ctlr->rx, notempty, ctlr, 500);
		}
		n = Rbsize - (p->count & 0x3fff)-3;
		if(n >= ETHERMINTU && (p->base[n+2] & Rok) != 0){
			b = allocb(n);
			b->wp += n;
			memmove(b->rp, p->base+2, n);
			etheriq(edev, b);
		}
		p->addr = PADDR(p->base);
		p->count = Ioc|Empty|Rbsize;
		ctlr->rx.head = p;
	}
}

static int
notbusy(void *arg)
{
	Ctlr *ctlr = arg;
	return ctlr->tx.free > 0;
}

static void
txproc(void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr;
	Block *b;
	Desc *p;
	int n;

	while(waserror())
		;

	ctlr = edev->ctlr;
	for(p = IO(Desc, ctlr->tx.tail->next); (b = qbread(edev->oq, 1000000)) != nil; p = IO(Desc, p->next)){
		while(ctlr->tx.free == 0)
			sleep(&ctlr->tx, notbusy, ctlr);

		n = BLEN(b);
		if(n > ETHERMAXTU)
			n = ETHERMAXTU;
		memmove(p->base, b->rp, n);

		p->addr = PADDR(p->base);
		p->count = Ioc|Eor|Eop|Busy|n;

		ctlr->tx.tail->count &= ~(Ioc|Eor);
		ctlr->tx.tail = p;

		splhi();
		ctlr->tx.free--;
		txintr(ctlr);
		spllo();

		freeb(b);
	}
}

static void
allocring(Ring *r, int n)
{
	uchar *b;
	Desc *p;
	int m;

	r->size = n;
	r->free = n;

	m = n*BY2PG/2;
	b = xspanalloc(m, BY2PG, 0);
	dcflush(b, m);
	b = IO(uchar, b);
	memset(b, 0, m);
	r->base = b;

	m = n*sizeof(Desc);
	p = xspanalloc(m, BY2PG, 0);
	dcflush(p, m);
	p = IO(Desc, p);
	memset(p, 0, m);
	r->head = r->tail = p;

	for(m=0; m<n; m++, p++, b += (BY2PG/2)){
		p->base = b;
		p->next = PADDR(p+1);
	}
	p[-1].next = PADDR(r->head);
}

static void
startup(Ether *edev)
{
	Ctlr *ctlr;
	Desc *p;
	Hio *io;
	int i;

	ctlr = edev->ctlr;
	io = ctlr->io;

	io->dmacfg |= HPC_FIX_INTR | HPC_FIX_EOP | HPC_FIX_RXDC;

	p = ctlr->rx.head;
	do {
		p->addr = PADDR(p->base);
		p->count = Ioc|Empty|Rbsize;
		p = IO(Desc, p->next);
	} while(p != ctlr->rx.head);
	io->crbdp = PADDR(p);
	io->nrbdp = p->next;
	ctlr->rx.tail = p;
	ctlr->rx.free = ctlr->rx.size;

	p = ctlr->tx.tail;
	do {
		p->addr = PADDR(p->base);
		p->count = Eor|Eop;
		p = IO(Desc, p->next);
	} while(p != ctlr->tx.tail);
	io->cxbdp = PADDR(p);
	io->nxbdp = p->next;
	ctlr->tx.head = p;
	ctlr->tx.free = ctlr->tx.size;

	for(i=0; i<6; i++)
		io->eaddr[i] = edev->ea[i];

	io->csx = XIok | XImaxtry | XIcoll | XIunder;
	io->csr = Rprom | RIok|RIend|RIshort|RIdrbl|RIcrc;

	io->rstat = Rdma;

	wakeup(&ctlr->rx);
	wakeup(&ctlr->tx);
}

static void
shutdown(Ether *edev)
{
	Ctlr *ctlr = edev->ctlr;
	Hio *io = ctlr->io;

	ctlr->txerr = nil;
	ctlr->txwdog = 0;

	io->csx = Xreg0;
	io->rstat = 0;
	io->xstat = 0;
	io->ctl = Cnormal | Creset | Cint;
	delay(10);
	io->ctl = Cnormal;
	io->csx = 0;
	io->csr = 0;
}

/*
 * do nothing for promiscuous() and multicast() as we
 * are always in promisc mode.
 */
static void
promiscuous(void*, int)
{
}
static void
multicast(void*, uchar*, int)
{
}

static void
attach(Ether *edev)
{
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	if(ctlr->attach)
		return;
	ctlr->attach = 1;
	splhi();
	startup(edev);
	spllo();
	kproc("#l0rx", rxproc, edev);
	kproc("#l0tx", txproc, edev);
}

static int
pnp(Ether *edev)
{
	static Ctlr ctlr0;
	Ctlr *ctlr;
	char *s;

	/* only one controller */
	if(edev->ctlrno != 0)
		return -1;

	/* get mac address from nvram */
	if((s = getconf("eaddr")) != nil)
		parseether(edev->ea, s);

	ctlr = &ctlr0;
	edev->ctlr = ctlr;
	edev->port = HPC3_ETHER;
	edev->irq = IRQENET;
	edev->ctlr = ctlr;
	edev->promiscuous = promiscuous;
	edev->multicast = multicast;
	edev->attach = attach;
	edev->shutdown = shutdown;
	edev->arg = edev;
	edev->mbps = 10;
	edev->link = 1;

	ctlr->io = IO(Hio, edev->port);
	allocring(&ctlr->rx, 256);
	allocring(&ctlr->tx, 64);
	shutdown(edev);
	intrenable(hpc3irqlevel(edev->irq), interrupt, edev);

	return 0;
}

void
etherseeqlink(void)
{
	addethercard("seeq", pnp);
}
