/* Copyright 1984 by the Massachusetts Institute of Technology */

/* Device driver for the Interlan Ethernet unibus network interface.
 * The driver is written for the 68000 Q-bus processor.
 */

#include	<types.h>
#include	"../../sys/include/sys.h"

#define	NULL		0
#define	ETHMINSIZ	60

/* recieved packet internal header */
struct eth_rhd {
		unsb	ehd_unus;	/* unused */
		unsb	ehd_stat;	/* status */
		unss	ehd_len;	/* length */
};

#define	RCV_ERR	03			/* error bits in ehd_stat */

/* device registers */
struct eth_reg {
		unss	eth_csr;	/* control/status register */
		unss	eth_bar;	/* byte address register */
		unss	eth_bcr;	/* byte count register */
};

/* bit definitions for the csr */
#define	CSCODE	0xf		/* command status code */
#define	RCVIE	0x10		/* rcv interrupt enable */
#define	RCVDN	0x20		/* rcv done */
#define	CMDIE	0x40		/* cmd interrupt enable */
#define	CMDDN	0x80		/* cmd done */
#define	CFCODE	0x3f00		/* cmd func. code */
#define	HADD	0xc000		/* high two address bits */

/* command status codes */
#define	CSWIN	0		/* success */
#define	CSWINR	01		/* success with retries */
#define	CSILL	02		/* illegal command */
#define	CSINAPP	03		/* inappropriate command */
#define	CSFAIL	04		/* failure */
#define	CSBFR	05		/* buffer size exceeded */
#define	CSFTS	06		/* frame too small */
#define	CSRSV1	07		/* reserved */
#define	CSCOL	010		/* excessive collisions */
#define	CSRSV2	011		/* reserved */
#define	CSALN	012		/* buffer alignment error */
#define	CSRSV3	013		/* reserved */
#define	CSRSV4	014		/* reserved */
#define	CSRSV5	015		/* reserved */
#define	CSRSV6	016		/* reserved */
#define	CSNXM	017		/* non-existant memory */

/* Command function codes */
#define	CFONL	(011 << 8)	/* go online */
#define	CFCISA	(016 << 8)	/* clear insert source address */
#define	CFSTAT	(030 << 8)	/* report statistics (get address) */
#define	CFRCV	(040 << 8)	/* supply receive buffer */
#define	CFXMT	(051 << 8)	/* load xmit data and send */
#define	CFRST	(077 << 8)	/* reset */

/* Error codes */
#define	ERSUC	0		/* success */
#define	ERNXM	017		/* non-existant memory */

/* Special DCT usage  -  Also uses d_buf on the output dct as
 * a storage place for input buffers before the input command
 * can be sent to the board.
 */
#define	D_CIP	D_DV1		/* Command in progress */
#define	D_IIP	D_DV2		/* Input command pending */


/* Device initialization routine.  This routine resets the
 * interface and finds out what its ethernet address is.
 * A pointer to the local net address is left in dev->d_dev1.
 */
ieu_up(devp)
reg	dct	*devp;
{
	reg	struct eth_reg	*dreg;
	reg	iorb		*iob;
	static	byte		buf[70];

	if (devp->d_lnk->d_flg & D_INI) {
		devp->d_dev1 = devp->d_lnk->d_dev1;
		devp->d_flg |= D_INI;
		return;
	}
	dreg = (struct eth_reg *)devp->d_csr;

	/* reset the interface */
	dreg->eth_csr = CFRST;
	while (!(dreg->eth_csr & CMDDN))
		;

	/* set interface not to insert its own address */
	dreg->eth_csr = CFCISA;
	while (!(dreg->eth_csr & CMDDN))
		;

	/* get local ethernet address */
	dreg->eth_bar = (uns)buf;
	dreg->eth_bcr = 66;
	dreg->eth_csr = (((unsl)buf >> 2) & HADD) | CFSTAT;
	while (!(dreg->eth_csr & CMDDN))
		;
	devp->d_dev1 = mem_alloc(6);
	copy(&buf[4], devp->d_dev1, 6);
	bswap(devp->d_dev1, 6);

	/* go online */
	dreg->eth_csr = CFONL;
	while (!(dreg->eth_csr & CMDDN))
		;
	devp->d_flg = D_INI;
}

/* Input initialization transfer routine */
ieuin(devp)
reg	dct	*devp;
{
	reg	struct eth_reg	*dreg;
	reg	dct		*odevp;
	
	odevp = devp->d_lnk;
	odevp->d_buf = (word)devp->d_qhd;
	ieuout(odevp);
}

/* Command initialization transfer routine */
ieuout(devp)
reg	dct	*devp;
{
	reg	struct eth_reg	*dreg;
	reg	iorb		*iob;
	xreg	unss		func;

	if (devp->d_flg & D_CIP) return;
	dreg = (struct eth_reg *)devp->d_csr;
	if ((iob = mkiorb(devp->d_buf)) != NULL) {
		devp->d_flg |= D_IIP;
		func = CFRCV;
	}
	else {
		if ((iob = devp->d_qhd) == NULL) return;
		func = CFXMT;
	}
	devp->d_flg |= D_CIP;
	dreg->eth_bar = (unss)iob->i_addr;
	if (iob->i_breq < ETHMINSIZ)
		dreg->eth_bcr = ETHMINSIZ;
	else
		dreg->eth_bcr = iob->i_breq;
	func = (((unsl)iob->i_addr >> 2) & HADD) | func | CMDIE | RCVIE;
	dreg->eth_csr = func;
}


/* Input done interrupt routine */
ieuii(devp)
reg	dct	*devp;
{
	reg	struct eth_reg	*dreg;
	reg	iorb		*iob;
	xreg	struct eth_rhd	*pkt;

	dreg = (struct eth_reg *)devp->d_csr;
	iob = devp->d_qhd;
	pkt = (struct eth_rhd *)iob->i_addr;
	if (pkt->ehd_stat & RCV_ERR) iob->i_stat |= I_ERR;
	iob->i_stat |= I_DONE;
	iob->i_bxfr = pkt->ehd_len - 4;
	iob->i_addr += sizeof(struct eth_rhd);
	iocmr(devp);
}

/* Command complete interrupt routine */
ieuoi(devp)
reg	dct	*devp;
{
	reg	struct eth_reg	*dreg;
	reg	iorb		*iob;
	xreg	unss		csr;

	devp->d_flg &= ~D_CIP;
	if (devp->d_flg & D_IIP) {
		devp->d_flg &= ~D_IIP;
		devp->d_buf = NULL;
	}
	else {
		iob = devp->d_qhd;
		dreg = (struct eth_reg *)devp->d_csr;
		csr = dreg->eth_csr;
		if ((csr & CSCODE) == ERNXM) iob->i_stat |= I_ERR;
		iob->i_stat |= I_DONE;
		iob->i_bxfr = iob->i_breq;
		iocmr(devp);
	}
	ieuout(devp);
}

/* this routine exists in assembly language but this is for testing */
copy(from, to, len)
byte	*from, *to;
int	len;
{
	while(len--)
		*to++ = *from++;
}
