static char rcsid[] = "$Header: if_to.c,v 800.1 85/09/30 18:25:53 root Exp $";
static char sccsid[] = "%W% %Y% %Q% %G%";

/************************************************************************
*									*
*				Copyright 1985				*
*			VALID LOGIC SYSTEMS INCORPORATED		*
*									*
*	This listing contains confidential proprietary information	*
*	which is not to be disclosed to unauthorized persons without	*
*	written consent of an officer of Valid Logic Systems 		*
*	Incorporated.							*
*									*
*	The copyright notice appearing above is included to provide	*
*	statutory protection in the event of unauthorized or 		*
*	unintentional public disclosure.				*
*									*
************************************************************************/

/*
/*
 * to.c --
 *	Omninet driver for SCALDsystem IV.
 *
 * History:
 *
 *	8.2 (sas)	850605
 *
 *	Addded support for BULK protocol.
 *
 *	8.1 (sas)	850408
 *
 *	Took out support for BCAST. Added upper-bound on
 *	attempts to retry reset of the transporter.
 *
 *	8.0 (sas)	850405
 *
 *	Modified to support 4.2bsd.  Removed many of the
 *	statistics because they were really only useful in
 *	bringing the driver up.  Changed the semantics of resetting.
 *	Modified toioctl to support ioctl's through the
 *	socket interface.  Added support for SIOCSIFADDR.
 *	Got rid of support for summing of each packet.
 *	Got rid of references to XMITWAIT.  Changed the sizes of
 *	entries in the to_softc table.  Moved rdy, strobe, clear,
 *	stat outside of the softc structure.  Added support in
 *	tooutput and toinput for ARP packets.  Changed the
 *	input routine so that it no longer checks the type of return
 *	code for sends and receives.  Took the "spin" code out of
 *	the interrupt routine.  Added support for probe.  Fixed
 *	bug with IP broadcast packets.
 *
 *	7.3 (sas)	850329	
 *
 *	Added support for software controlled hardware reset.  The 
 *	following are the instructions for doing the reset:
 *	1)	To set Omninet reset on, write a 1 to data
 *		bit 2 at address 0x830F61.
 *		(Don't change any other bits inadvertantly)
 *	2)	Leave Omninet reset on for a mimimum of 4us.
 *	3)	To check th Omninet reset status, read data bit
 *		2 at address 0x830F7F.
 *	4)	Turn off the Omninet reset by writing a 0 to data bit
 *		2 at address 0x830F61.
 *	These instructions complements of Larry Melling of Corvus.
 *
 *	7.2 (sas)	850301	
 *
 *	Fixed bug in tooutput.  Drain output queue even when queue is full.
 *
 *	7.1 (sas)	850214	
 *
 *	Original version of this driver as shipped for SCALDsystem IV 
 *	beta period.
 */


/*
 * The version number of the driver
 */
short to_version[] = {
	8,	2 };

#define NTOM 1				/* Define only 1 unit */

#include "../h/errno.h"			/* Error numbers */
#include "../h/param.h"			/* Machine dependent parameters */
#include "../h/ioctl.h"			/* I/O control structures */
#include "../h/uio.h"			/* Iovec structure */
#include "../h/socket.h"		/* Socket address structure */
#include "../h/mbuf.h"			/* Mbuf header definitions */
#include "../net/if.h"			/* Network interface structs */
#include "../s32dev/mbvar.h"		/* Multibus definitions */
#include "../s32dev/omni.h"		/* Definition of generic Omninet */
#include "../white/if_toreg.h"		/* Transporter hardware */
#include "../net/netisr.h"		/* Network interrupt service */
#include "../net/route.h"		/* Internet routing tables */
#ifdef INET
#include "../netinet/in.h"		/* Internet definitions */
#include "../netinet/in_systm.h"
#include "../netinet/ip.h"		/* IP (Internet Protocol) definitions */
#include "../netinet/ip_var.h"		/* IP global variables */
#include "../netinet/if_ether.h"	/* Address resolution protocol */
#endif INET
#ifdef VALIDnet
#ifdef bsd42
#include "../vnet/vnet.h"	/* Valid Network definitions */
#include "../conn/conn.h"	/* Valid connection management */
#include "../rpc/rpc.h"		/* Remote procedure call definitions */
#else bsd42
#include "../vnet/vnet.h"	/* Valid Network definitions */
#include "../rpc/rpc.h"		/* Remote procedure call definitions */
#include "../rpc/rpc_vars.h"	/* RPC global variables */
#endif bsd42
#endif VALIDnet
#include "../net/netisr.h"		/* Net interrupt service */

/*
 * These macros are used called to lock out interrupts from the
 * SCALDsystem IV Omninet module.
 */
#define splTOMLOCK()	spl3()

extern struct ifnet loif;
int toprobe(), toattach(), tointr(), tooutput();
struct mbuf *togetm();
u_short tostd[] = { 0xF7F, 0 };	/* stupid, but non-zero */

/*
 * These are used to schedule timeouts for interrupts.
 */
short totimo = 1;		/* Call timeout */
int toxmittimo();

struct mb_device *toinfo[NTOM];
struct mb_driver todriver = 
	{ 0, toprobe, 0, toattach, tostd, "to", toinfo};


/*
 * Allocate static buffers in main memory for the send and receive
 * buffers, command blocks, and result records.
 */
struct omniccb torccb1, torccb2, tocccb1;
char tordata1[OMNIMTU], tordata2[OMNIMTU], tocdata1[OMNIMTU];
struct omnirslt torrslt1, torrslt2, tocrslt1;

struct omniccb *torccb[] =	/* receive command block */
	{ &torccb1, &torccb2 };
char *tordata[] =		/* receive data area */
	{ tordata1, tordata2 };
struct omnirslt *torrslt[] =	/* receive result record */
	{ &torrslt1, &torrslt2 };

struct omniccb *tocccb[] =	/* generic command block */
	{ &tocccb1 };
char *tocdata[] =		/* generic command data area */
	{ tocdata1 };
struct omnirslt *tocrslt[] =	/* generic command result record */
	{ &tocrslt1 };

struct omniccb toresetccb;
struct omnirslt toresetrslt;

/*
 * Per controller data structure.
 * Note that we should be using struct arpcom under 4.2
 */
struct	to_softc {
	unsigned char	ts_omniaddr;	/* Omninet address of local host */
	char		ts_init;	/* Interface initialized */
	short		ts_unitActive;	/* Unit is active */
	short		ts_count;	/* Count */
	short		ts_state;	/* Controller state */
	short		ts_unit;	/* Unit number of this controller */
	struct ifqueue	ts_output;	/* Output queue */
	short		ts_rcvindex;	/* Active receive index */
	short		ts_cmdindex;	/* Active generic command index */
#ifdef INET
	struct arpcom	ts_ac;		/* Internet interface */
#define if_ip		ts_ac.ac_if
#endif INET
#ifdef VALIDnet
	struct ifnet	if_bulk;	/* VALID BULK interface */
	struct ifnet	if_conn;	/* VALID CONN interface */
	struct ifnet	if_rpc;		/* VALID RPC interface */
#endif VALIDnet
	struct omnistats	ts_stats;	/* Driver statistics */
};

char *toready[NTOM] = { TO_RDYADDR };
char *tostrobeaddr[NTOM] = { TO_STROBEADDR };
char *toclear[NTOM] = { TO_CLEARADDR };
char *tostat[NTOM] = { TO_STATADDR };
short toresetretry[NTOM];

/*
 * Maximum number of retries of a reset before we give up
 */
#define TS_MAXRETRY_RESET	6

/*
 * Maximum length of the output queue (see comment below)
 */
#define TS_MAXIFQLEN	16

/*
 * Maximum number of consecutive reset retries
 */
#define TS_MAXRESET_RETRY	6

/*
 * Possible values for ts_state in ts_softc structure
 */
#define	TS_RUNNING	0x1		/* Controller is running */
#define TS_RCVACTV	0x2		/* Controller has a receive active */
#define TS_SETUP	0x4		/* Controller has a setup active */
#define TS_OUTACTV	0x8		/* Controller has output active */
#define TS_REARMWAITING 0x10		/* Socket re-arm is waiting */
#define TS_RESETACTV	0x20		/* Reset in progress */


struct to_softc to_softc[NTOM];	/* soft controller structs */
struct omnistats *tstats = &to_softc[0].ts_stats;


short	todebug = 0;			/* debug flags */
int	toipl = 3;			/* interrupt level -- kluge */
int	toinitcount = 0x3000;		/* kluge -- count of polls to init */
int	tocount = 1;			/* Count of xmits to receives */
u_char	tobaddr = 0xFF;		/* broadcast address  -- kluge */
extern int hz;

/* return 1 if the transporter is ready to be strobed */
#define tordy(unit)	(*toready[unit]&0x1)
/* clear interrupt */
#define toclearintr(unit)	{ register char dummy = *toclear[unit]; }
/* increment driver statistic */
#define toIncStat(ts,stat)	((ts)->ts_stats.stat++)


/*
 * toprobe --
 *	Probe for the SCALDsystem IV Ommninet.  If we find the
 *	I/O registers, we return with a non-zero value.  We don't
 *	try to send an interrupt.
 */
toprobe(reg, intr, unit)
	caddr_t	reg;
	int (*intr)();
	int unit;
{
	register int i;
	register struct to_softc *ts = &to_softc[unit];
	int tointr();
	extern int cvec;

	if (intr != tointr)
		return 0;

	ts->ts_unit = unit;
	ts->ts_omniaddr = -1;		/* initialize to bad value */

	for (i = 0; i < NTOM; i++) {
		if (toinfo[i] && toinfo[i]->md_alive &&
			toinfo[i]->md_addr == reg)
				return 0;
	}

	tohardinit(unit);
	 
	/*
	 * Because we know that there ought to be an Omninet
	 * inetrface out there, we will always configure for
	 * it.  This way if for some bizarre reason (e.g.,
	 * the user doesn't connect his tap cable to the 
	 * electronics tray) the Omninet doesn't work right but
	 * does interrupt, someone will be there to catch us.
	 */
	cvec = toipl - 1;
	return(1);
}


/*
 * toattach --
 *	Attach appropriate protocols
 */
toattach(md)
	struct mb_device *md;
{
	register struct to_softc *ts = &to_softc[md->md_unit];
	register struct ifnet *ifp;
	extern int toinit(), toioctl(), toreset();

	tomessage("", md->md_unit);

	/*
	 * We foolishly told people they could have address 0.
	 * Now it's time to take it back...
	 */
	if (ts->ts_omniaddr == 0) {
		printf("Omninet address 0 can no longer be used.\n");
		ts->ts_init = 0;
		return 0;
	}

	if (ts->ts_omniaddr <= 0x3F) {
		printf("Omninet host address %02X, transporter at 0x%x\n",
			ts->ts_omniaddr, tostrobeaddr[md->md_unit]);
	} else {
		printf("bad Omninet address %02X\n",
			ts->ts_omniaddr);
		md->md_alive = 0;
	}

	/*
	 * Set the arpcom address field
	 */
	bzero(ts->ts_ac.ac_enaddr, 6);
	ts->ts_ac.ac_enaddr[5] = ts->ts_omniaddr;

	if (!md->md_alive)
		return 0;

	/*
	 * Set the addresses in the IP/BCAST/CONN/RPC interface
	 * structures
	 */
	toinitaddress(ts);

	/*
	 * Kluge -- set the maximum queue length for output here.
	 */
	ts->ts_output.ifq_maxlen = TS_MAXIFQLEN;

	/*
	 * Fill and attach the network interface data bases
	 */
	
#ifdef INET
	/* DARPA Internet (IP) */

	ifp = &ts->if_ip;
	ifp->if_unit = md->md_unit;
	ifp->if_name = "to";
	ifp->if_mtu = OMNIMTU;
	ifp->if_init = toinit;
	ifp->if_ioctl = toioctl;
	ifp->if_output = tooutput;
	ifp->if_reset = toreset;
	if_attach(ifp);
#endif INET


#ifdef VALIDnet
	/* VALID BULK */

	ifp = &ts->if_bulk;
	ifp->if_unit = md->md_unit;
	ifp->if_name = "to-bulk";
	ifp->if_mtu = OMNIMTU;
	ifp->if_init = toinit;
	ifp->if_ioctl = toioctl;
	ifp->if_output = tooutput;
	ifp->if_reset = toreset;
	if_attach(ifp);

	/* VALID CONN */

	ifp = &ts->if_conn;
	ifp->if_unit = md->md_unit;
	ifp->if_name = "to-conn";
	ifp->if_mtu = OMNIMTU;
	ifp->if_init = toinit;
	ifp->if_ioctl = toioctl;
	ifp->if_output = tooutput;
	ifp->if_reset = toreset;
	if_attach(ifp);

	/* VALID RPC */

	ifp = &ts->if_rpc;
	ifp->if_unit = md->md_unit;
	ifp->if_name = "to-rpc";
	ifp->if_mtu = OMNIMTU;
	ifp->if_init = toinit;
	ifp->if_ioctl = toioctl;
	ifp->if_output = tooutput;
	ifp->if_reset = toreset;
	if_attach(ifp);

	/*
	 * Initialize the receive and command indices
	 */
	ts->ts_rcvindex = ts->ts_cmdindex = 0;

}


/*
 * toinitaddress --
 *	Initialize the addresses of the various interfaces.  This routine is
 *	based on (copied from) setUnitAddress() in ../s32dev/ec.c
 */
toinitaddress(ts)
	register struct to_softc *ts;
{
	register struct ifnet *ifp;

#ifdef INET
	{
	register struct sockaddr_in *sin;

	sin = (struct sockaddr_in *)&ts->if_ip.if_addr;
	sin->sin_family = AF_INET;
	sin->sin_addr = arpmyaddr((struct arpcom *)0);
	sin = (struct sockaddr_in *)&ts->if_ip.if_broadaddr;
	sin->sin_family = AF_INET;
	}
#endif INET

#ifdef VALIDnet
	{
	register struct sockaddr_rpc *srpc;

	/*
	 * Initialize the myNode global variable (side effect!!!)
	 */
	myNode.net = 0;
	myNode.host.high = 0;
	myNode.host.low = ts->ts_omniaddr & 0xFF;

	ifp = &ts->if_bulk;
	ifp->if_host[0] = ts->ts_omniaddr & 0xFF;
	ifp->if_net = 0;
	srpc = (struct sockaddr_rpc *)&ifp->if_addr;
	srpc->addr_family = AF_BULK;
	srpc = (struct sockaddr_rpc *)&ifp->if_broadaddr;
	srpc->addr_family = AF_BULK;

	ifp = &ts->if_conn;
	ifp->if_host[0] = ts->ts_omniaddr & 0xFF;
	ifp->if_net = 0;
	srpc = (struct sockaddr_rpc *)&ifp->if_addr;
	srpc->addr_family = AF_CONN;
	srpc = (struct sockaddr_rpc *)&ifp->if_broadaddr;
	srpc->addr_family = AF_CONN;

	ifp = &ts->if_rpc;
	ifp->if_host[0] = ts->ts_omniaddr & 0xFF;
	ifp->if_net = 0;
	srpc = (struct sockaddr_rpc *)&ifp->if_addr;
	srpc->addr_family = AF_RPC;
	srpc->node = myNode;
	}
#endif VALIDnet
}

/*
 * toinit --
 *	Initialize the IP interface.
 */
toinit(unit)
	int unit;
{
	register struct to_softc *ts = &to_softc[unit];
	register struct ifnet *ifp = &ts->if_ip;
	register struct sockaddr_in *sin;
	int s;

	sin = (struct sockaddr_in *)&ifp->if_addr;
	if (sin->sin_addr.s_addr == 0)
		return 0;
	if ((ifp->if_flags & IFF_RUNNING) == 0) {
		s = splimp();
		ifp->if_flags |= IFF_UP|IFF_RUNNING;
		splx(s);
		s = spl7();
		if (!ts->ts_unitActive) {
			ts->ts_unitActive = 1;
			splTOMLOCK();
			tostart(ts);
		}
		splx(s);
	}
	if_rtinit(ifp, RTF_UP);
	arpattach(&ts->ts_ac);
	arpwhohas(&ts->ts_ac, &sin->sin_addr);
}

short toprint = 0;

/*
 * toreset --
 *	Handles resets to the driver.
 */
toreset(unit)
	int unit;
{
	register struct to_softc *ts = &to_softc[unit];
	int s;

	if (unit > NTOM)
		return 0;

	s = splTOMLOCK();
	if (!(ts->ts_state & TS_RESETACTV))
		ts->ts_state |= TS_RESETACTV;
	else {
		tomessage("reset in progress\n", ts->ts_unit);
		splx(s);
		return;
	}

	if (toprint)
		tomessage("**RESET**\n", unit);
	
	toIncStat(ts,reset);

	if (tohardinit(unit) && torearmsock(unit)) {
		if (toprint)
			tomessage("**RESET COMPLETE**\n", unit);
		splx(s);
		toresetretry[unit] = 0;
		ts->ts_state |= TS_RUNNING;
		ts->ts_state &= ~TS_RESETACTV;
		toinit(unit);
		return 1;
	} else {
		if (toresetretry[unit]++ >= TS_MAXRESET_RETRY) {
			tomessage("unable to reset interface.\n", unit);
			tomessage("shutting down Omninet.\n", unit);
			ts->ts_init = 0;
		} else {
			tomessage("**RESET FAILED**\n", unit);
			tomessage("retry in 8 seconds.\n", unit);
			timeout(toreset, unit, hz<<3);
		}
		ts->ts_state = 0;
		splx(s);
		return 0;
	}
}


/*
 * tohardinit --
 *	Do a hardwaare initialization of the Omninet transporter.
 * 	Send an initialize command to the board.
 */
#define OMNI_W_RESET_ADDR	0x830F61
#define OMNI_R_RESET_ADDR	0x830F7F
tohardinit(unit)
	int unit;
{
	register struct to_softc *ts = &to_softc[unit];
	register i, j;

	for (j = 0; j < 5; j++) {
		char value;

		bzero(&toresetccb, sizeof(struct omniccb));
		bzero(&toresetrslt, sizeof(struct omnirslt));

		*(u_char *)OMNI_W_RESET_ADDR |= (1 << 2);
		for (i = 0; i < 60; i++); /* > 7us of idle */
		value = *(u_char *)OMNI_R_RESET_ADDR & (1 << 2);
		*(u_char *)OMNI_W_RESET_ADDR &= ~(1 << 2);

		toresetccb.cmd = OMNIINIT;
		toset24(&toresetccb.rslt, &toresetrslt);
		toresetrslt.rcode = -1;
		if (tostrobe(&toresetccb, unit)) {
			for (i = 0; i < toinitcount && 
				toresetrslt.rcode == -1; i++);
			if (i >= toinitcount)
				continue;
			/*
			toclearintr(unit);
			 */
			if (ts->ts_omniaddr == -1)
				ts->ts_omniaddr = toresetrslt.rcode;
			return 1;
		} else
			tomessage("couldn't strobe transporter.\n", unit);
	}
	return 0;
}

/*
 * torearmsock --
 *	Re-arm the receive socket after reset.
 */
torearmsock(unit)
	int unit;
{
	register struct to_softc *ts = &to_softc[unit];
	register i, j;
	
	for (j = 0; j < 5; j++) {
		if (ts->ts_unitActive)
			panic("torearmsock");
		else
			ts->ts_unitActive = 1;
		if (!tosetrcv(ts)) {
			ts->ts_unitActive = 0;
			continue;
		}
		for (i = 0; i < toinitcount && 
		  torrslt[ts->ts_rcvindex]->rcode == -1; i++);
		if (i < toinitcount)
			return 1;
		ts->ts_unitActive = 0;
	}
	return 0;
}

/*
 * tooutput --
 *	Called from higher level protocols.  Queue a packet to go out
 *	out over the Omninet.  Takes as arguments the ifnet structure,
 *	an mbuf chain, and a destination socket address.
 */
tooutput(ifp, m0, dst)
	register struct ifnet *ifp;
	register struct mbuf *m0;
	struct sockaddr *dst;
{
	register struct to_softc *ts = &to_softc[ifp->if_unit];
	register struct mbuf *m;
	struct mbuf *mcopy;		/* copy of mbuf chain m0 */
	int error = 0;
	u_char omnidst;
	struct omniheader omni_h;
	u_char type;
	int s;

	m = (struct mbuf *)0;
	mcopy = (struct mbuf *)0;

	if (!(ts->ts_state & TS_RUNNING)) {
		error = 0;
		goto bad;
	}


	switch (dst->sa_family) {


#ifdef INET
	case AF_INET:
		{
		struct in_addr idst;
		u_char edst[6];

		idst = ((struct sockaddr_in *)dst)->sin_addr;
		if (!arpresolve(&ts->ts_ac, m0, &idst, edst))
			return 0;
		if (in_lnaof(idst) == INADDR_ANY) {
			mcopy = m_copy(m0, 0, (int)M_COPYALL);
			omnidst = tobaddr;
		} else {
			omnidst = edst[5];
		}
		if (omnidst == ts->ts_omniaddr) {
			mcopy = m0;
			m0 = (struct mbuf *)0;
			goto gotlocal;
		}
		type = OMNITYPE_IP;
		}
		break;
	case AF_UNSPEC:
		{
		/*
		 * We expect an Ethernet header in the data
		 * portion of the socket address structure.
		 * If the Ethernet address is broadcast
		 * (ff.ff.ff.ff.ff.ff), we broadcast it
		 * on Omninet (no loop-back).  Otherwise,
		 * we send it to the address located in
		 * byte 6.
		 */
		register struct ether_header *ec;

		ec = (struct ether_header *)dst->sa_data;
		if (!bcmp(etherbroadcastaddr, ec->destAddr, 6))
			omnidst = tobaddr;
		else
			omnidst = ec->destAddr[5];
		switch (ec->etherType) {
		case ETHERTYPE_IP:
			type = OMNITYPE_IP;
			break;
		case ETHERTYPE_ARP:
			type = OMNITYPE_ARP;
			break;
		default:
			goto bad;
		}
		break;
		}
		

#endif INET
#ifdef VALIDnet
	case AF_CONN:
		/*
		 * CONN addresses are mapped through.  In other
		 * words, we look at the low 8 bits of the conn address
		 * and take it as the destination address.
		 */
		omnidst = tobaddr;
		mcopy = m_copy(m0, 0, (int)M_COPYALL);
		type = OMNITYPE_CONN;
		break;
	case AF_RPC:
		/*
		 * RPC addresses are mapped through. 
		 */
		omnidst = ((struct sockaddr_rpc *)dst)->node.host.low & 0xFF;
		if (omnidst == ts->ts_omniaddr) {
			mcopy = m0;
			m0 = (struct mbuf *)0;
			goto gotlocal;
		}
		type = OMNITYPE_RPC;
		break;
	case AF_BULK:
		/*
		 * BULK addresses are mapped through.
		 */
		omnidst = ((struct sockaddr_rpc *)dst)->node.host.low & 0xFF;
		if (omnidst == ts->ts_omniaddr) {
			mcopy = m0;
			m0 = (struct mbuf *)0;
			goto gotlocal;
		}
		type = OMNITYPE_BULK;
		break;
#endif VALIDnet
	default:
		/*
		 * We don't know any other address families
		 */
		tomessage("unknown address family ", ifp->if_unit);
		printf("af%d\n", dst->sa_family);
		error = EAFNOSUPPORT;
		goto bad;
	
	}

	if (omnidst == ts->ts_omniaddr)
		goto gotlocal;

	ifp->if_opackets++;
	
	/*
	 * In this version of the driver, we have a dummy "omniheader"
	 * structure which serves as a control for the receiving host.
	 */
	MGET(m, M_DONTWAIT, MT_HEADER);
	if (m == 0) {
		toIncStat(ts, outNoBufs);
		error = ENOBUFS;
		goto bad;
	}
	m->m_next = m0;
	m->m_off = MMINOFF;
	m->m_len = sizeof(struct omniheader);
	
	/*
	 * Fill in the Omniheader structure
	 */
	omni_h.omnimagic = OMNIMAGIC;
	omni_h.omnitype = type;
	omni_h.omnidst = omnidst;
	omni_h.omnisrc = ts->ts_omniaddr;
	omni_h.omnilen = mbuflen(m);
	omni_h.omnisum = 0x10000; /* back compat */

	/*
	 * Copy the Omniheader into the mbuf data segment
	 */
	bcopy(&omni_h, mtod(m, char *), sizeof(struct omniheader));

	/*
	 * Do some statistics
	 */
	if (omnidst == OMNIBROADADDR)
		toIncStat(ts,xmitbcast);
	else
		toIncStat(ts,xmitsingle);

	/*
	 * Queue up the mbuf chain onto the interface output queue.  If
	 * the queue is empty, start the xmit by calling tostart().
	 */
	s = splimp();
	if (IF_QFULL(&ts->ts_output)) {
		IF_DROP(&ts->ts_output);
		m0 = m;
		splx(s);
		toIncStat(ts,xmitqfull);
		error = ENOBUFS;
	} else {
		IF_ENQUEUE(&ts->ts_output, m);
		splx(s);
		toIncStat(ts,xmitqueued);
	}
	s = spl7();
	if (!ts->ts_unitActive) {
		ts->ts_unitActive = 1;
		/*
		 * Lock out interrupts while sending.
		 */
		splTOMLOCK();		/* Lock out interrupts */
		tostart(ts);
	}
	splx(s);

	/*
	 * If the output queue was full, we started a transmit (to
	 * try and clear the queue) but set error.  If it was full,
	 * we don't want to loopback but instead just free up the mbufs.
	 */
	if (error)
		goto bad;
gotlocal:
	if (mcopy) {
		toIncStat(ts,xmitloop);
		return(looutput(&loif, mcopy, dst));
	} else
		return 0;

	/*
	 * Release mbuf chains
	 */
bad:
	m_freem(m0);
	m_freem(mcopy);
	return error;
}


/*
 * tostart --
 *	This routine can be called either from the upper half (tooutput)
 *	when the send queue is empty, or from the lower half (tointr)
 *	after an xmitr interrupt to send the next packet out.
 */
tostart(ts)
	register struct to_softc *ts;
{
	register struct mbuf *m;
	register struct omniheader *h;
	register int index;
	struct omniccb *ccb;
	int len;
	u_char dst;
	int s;

	if (todebug)
		printf("tostart\n");

	if (ts->ts_state & TS_OUTACTV) {
		toerror("tostart: called while output active.\n",
			ts->ts_unit);
		ts->ts_unitActive = 0;
		return 0;
	}

	/*
	 * Dequeue the mbuf chain from the output queue
	 */
	s = splimp();
	IF_DEQUEUE(&ts->ts_output, m);
	if (m == 0) {
		ts->ts_unitActive = 0;
		toIncStat(ts,xmitEmpty);
		splx(s);
		return 0;
	}
	splx(s);

	/*
	 * Gather information from the omniheader (first mbuf data
	 * segment)
	 */
	h = mtod(m, struct omniheader *);
	dst = h->omnidst;
	index = ts->ts_cmdindex;
	/*
	 * toputm() loads the data portion of the mbuf chain onto the
	 * on-board memory and the frees up the mbuf chain.
	 */
	len = toputm(ts, tocdata[index], m);

	/*
	 * Fill the command block record and pack it off to the buffer
	 */
	ccb = tocccb[index];
	ccb->cmd = OMNISND;
	toset24(&ccb->rslt, tocrslt[index]);
	ccb->c_un.snd.skt = TOSKT;
	toset24(&ccb->c_un.snd.data, tocdata[index]);
	ccb->c_un.snd.len = len;
	ccb->c_un.snd.ctl = 0;
	ccb->c_un.snd.host = dst;

	tocrslt[index]->rcode = -1;

	/*
	 * Strobe in the address and set the output flag if successful
	 */
	s = splTOMLOCK();
	/*
	 * Call timeout if lost interrupt
	 */
	if (totimo)
		timeout(toxmittimo, ts, 5*hz);

	if (tostrobe(tocccb[index], ts->ts_unit)) {
		ts->ts_state |= TS_OUTACTV;
		splx(s);
		toIncStat(ts,xmitOut);
	} else {
		if (totimo)
			untimeout(toxmittimo, ts);
		splx(s);
		toerror("tostart: send error\n", ts->ts_unit);
		toIncStat(ts,xmitFail);
	}

}	


/*
 * toxmittimo --
 *	Yet another horrible kluge to make the driver work.  This routine
 * is called via timeout when we haven't heard from an xmit strobe in 5 seconds.
 */
toxmittimo(ts)
	register struct to_softc *ts;
{
	int s;

	s = splTOMLOCK();
	if (tocrslt[ts->ts_cmdindex]->rcode == 0xFF) {
		toIncStat(ts,intrlost);
		ts->ts_state = 0;	/* Clear state */
		ts->ts_unitActive = 0;
		tomessage("lost xmit interrupt.\n", ts->ts_unit);
		toreset(ts->ts_unit);
		splx(s);
	} else {
		splx(s);
		tomessage("xmit timeout called.\n", ts->ts_unit);
		toIncStat(ts,nointrlost);
		/*
		 * What if no interrupt ?
		 */
	}
	splx(s);
}


short to_intrActive = 0;		/* Set this when interrupt active */

/*
 * tointr --
 * 	Interrupt routine called from mbdint.  We assume that the
 *	TS_CMDACTV flag is set.
 *	This routine assumes that there is only one Omninet unit
 *	and that any interrupt on this level is from the Omninet.
 * Algorithm:
 *	As in most network devices, we have two operations taking place at
 *	the same time:  transmits and receives.  Transmits can be initiated
 *	from the upper half by calling tostart() from tooutput() (the
 *	case in which the unit is not active), or from tointr() (the lower
 *	half).  Receives are completely driven from interrupt service.
 *	When the board is initialized, a receive socket is set up.  When
 *	a receive interrupt comes in, we service the interrupt, re-arm a
 *	socket (if possible...see below), and hand off the receive packet
 *	to its appropriate higher-level protocol.
 *	Because this device has only one interrupt line for both receives and
 *	transmits and because only one operation (xmit, re-arm) is allowed
 *	at one time, we are forced to handle special cases in a clumsy
 *	manner.
 *	Two state machines are operating at one time:  the send machine
 *	and the receive machine.  The send machine takes packets off the
 *	queue, sends them, and at interrupt service repeats.  The receive
 *	machine receives a packet and re-arms the socket.  Unfortunately,
 *	there are a couple of sepcial cases to handle.  First, there
 *	is the case in which we get a send interrupt while an xmit is
 *	active.  Because we can't re-arm the socket (only one active
 *	command at a time), we are forced to set a flag (REARMWAITING)
 *	that tells the send state machine that we are blocked on its
 *	interrupt/command completion.  When the xmit interrupt arrives,
 *	the send state machine looks at the flag.  If it is set, a re-arm
 *	command is sent without trying another xmit.  We also have the
 *	case in which we get both interrupts at the same time.  Because
 *	we can have only one active command, we have to make a choice as to
 *	which state machine to enter.  What this version of the driver does
 *	is to bias toward sending but set a counter which, when set to
 *	zero, initiates a re-arm.  This is the ms_count field in the
 *	softc structure.  When the count goes to zero and a receive packet
 *	is pending, the XMITWAIT flag is set and the receive machine is entered.
 *	After the re-arm completes, the XMITWAIT flag is looked at.  If it
 *	is set, the send state machine is re-entered.
 *	
 */
tointr()
{
	register int i = 0;	/* Used as unit # */
	register struct to_softc *ts = &to_softc[0];
	int rindex;
	int cindex;
	u_char rcode;
	int s;
	int returnval = 0;
	char dummy;

	if (to_intrActive)
		panic("tointr: two interrupts active.");
	else
		to_intrActive = 1;
	/*
	 * Immediately clear the interrupt
	 */
	toclearintr(i);

	if (!ts->ts_init)
		goto leaveintr;
	if (!(ts->ts_state & TS_RUNNING))
		goto leaveintr;

	if (todebug)
		printf("tointr: to%d\n", i);

	rindex = ts->ts_rcvindex;	/* Set the rcv index */
	cindex = ts->ts_cmdindex;	/* Set the cmd index */

	if (!(ts->ts_state & TS_RCVACTV)) {
		if (!ts->ts_unitActive) {
			toerror("tointr: active flag not set.\n",
				ts->ts_unit);
			goto leaveintr;
		}
		if (!(ts->ts_state & 
		      (TS_OUTACTV|TS_SETUP)))  {
			toerror("tointr: no command active.\n",
				ts->ts_unit);
			goto leaveintr;
		}
	}

	/*
	 * If the unit is really active, we'll find out below.
	 */
	if (ts->ts_state & (TS_OUTACTV|TS_SETUP))
		ts->ts_unitActive = 0;

	if (ts->ts_state & TS_SETUP) {
		/*
		 * If TS_SETUP is active, we return from this
		 * module without check the other conditions.
		 * Since only one command can be active at a time,
		 * we "know" that no other command flag could be set
		 */

		ts->ts_state &= ~TS_SETUP;

	startsw:
		rcode = torrslt[rindex]->rcode;
		switch(rcode) {
		case RSETUP:	/* Socket armed */
			ts->ts_state |= TS_RCVACTV;
			break;
		case SUCCESS:
			goto gotrcv;
		case 0xFF:
			/*
			 * This is a truly grotesque kluge
			 */
			if (tospinhack(ts, &torrslt[rindex]->rcode))
				goto startsw;
		default:
			printf("tointr: rcode = 0x%x\n", rcode);
			toIncStat(ts,badsetrcv);
			toerror("tointr: setrcv bad rcode\n",
				ts->ts_unit);
			goto leaveintr;
			}
	done:
		if (!ts->ts_unitActive)
			ts->ts_unitActive = 1;
		else {
			panic("tointr 0");
			goto leaveintr;
		}
		tostart(ts);
		goto leaveintr;
	}

	if (ts->ts_state & TS_OUTACTV) {

		/*
		 * What happens if it reads while DMA is updating it?
		 */
		rcode = tocrslt[cindex]->rcode;
		/*
		 * If the rcode is still -1, there are 2 
		 * possibilities:
		 *  1)  Receive interrupt.
		 *  2)  Bad return code
		 * If it is a receive interrupt (i.e., the receive
		 * rcode is SUCCESS), we jump the the receive state
		 * machine.  Otherwise, we assume the return code was
		 * 0xFF.
		 */
		if (rcode == 0xFF) {
			if (ts->ts_state & TS_RCVACTV) {
				if (torrslt[rindex]->rcode != RSETUP) {
					toIncStat(ts,sndrcvintr);
					/*
				 	 * The unit is still active
				 	 */
					if (ts->ts_unitActive)
						panic("tointr 1");
					else 
						ts->ts_unitActive = 1;
					goto startrcv;
				}
			} else
				tomessage("xmit FF\n", i);
		}

		if (totimo)
			untimeout(toxmittimo, ts);
		ts->ts_state &= ~TS_OUTACTV;
		if (rcode < 0x80) {
			/* Successful xmit */
			toIncStat(ts,xmitGood);
		} else
			toIncStat(ts,xmitBad);

		if (ts->ts_state & TS_REARMWAITING) {
			ts->ts_state &= ~TS_REARMWAITING;
			if (ts->ts_unitActive)
				panic("tointr 2");
			else
				ts->ts_unitActive = 1;
			if (!(tosetrcv(ts))) {
				ts->ts_unitActive = 0;
			}
			goto leaveintr;
		}

		/*
		 * It's critical that we only jump below
		 * if we know that we'll get an interrupt.
		 * If we don't get the interrupt, we never
		 * ever initiate another xmit (since the
		 * upper half looks at the unitActive).
		 */
		ts->ts_count -= 1;
		rcode = torrslt[rindex]->rcode;
		if (!(ts->ts_state & TS_RCVACTV) || 
		   (rcode != SUCCESS) || (ts->ts_count > 0)) {
			if (ts->ts_unitActive)
				panic("tointr 3");
			else
				ts->ts_unitActive = 1;
			tostart(ts);
			/*
			 * If active, exit.
			 */
			if (ts->ts_unitActive)
				goto leaveintr;
		}
	}

	startrcv:
	/*
	 * Need to write a watchdog routine that gets called
	 * every so often.  If receive dies on us, we need to know
	 */
	ts->ts_count = tocount;

	if (ts->ts_state & TS_RCVACTV) {
		/*
		 * We have to make sure that this is a receive
		 * interrupt before we alter flags
		 */
		rcode = torrslt[rindex]->rcode;
		switch(rcode) {
		case SUCCESS:
	gotrcv:
			ts->ts_state &= ~TS_RCVACTV;
			/*
			 * toinput() re-arms the socket
			 */
			toinput(ts);
			break;
		case RSETUP:
			break;
		default:
			printf("tointr: rcode=0x%x\n", rcode);
			toerror("tointr: rcv bad rcode\n",
				ts->ts_unit);
			goto leaveintr;
			break;
		}
	}
leaveintr:
	to_intrActive = 0;
	return 1;
}


/*
 * tospinhack --
 *	This kluges around a late result code change.
 */
tospinhack(ts, c)
	register struct to_softc *ts;
	register char *c;
{
	register int spincount = 0;

	while (spincount++ < 400)
		if (*c != -1)
			return 1;
	toIncStat(ts, spinout);
	return 0;
}


/*
 * toputm --
 *	Copy an mbuf chain from host memory into the 68000 memory buffer.
 *	Make sure that the size of data does not exceed the xmit
 *	buffer size (XMITMAXSIZ).  This routine is only called from
 *	tostart().
 */
toputm(ts, buf, m)
	register struct to_softc *ts;
	caddr_t buf;
	struct mbuf *m;
{
	register struct mbuf *mp;
	register int totlen = 0;		/* total length of data */
	register int len;
	
	for (mp = m; mp; mp = mp->m_next) {
		len = mp->m_len;
		totlen += len;
		if (totlen > XMITMAXSIZ) {
			m_freem(m);
			return -1;
		}
		bcopy(mtod(mp, char *), buf, len);
		buf = (caddr_t)((int)buf + len);
	}
	m_freem(m);
	return totlen;
}

/*
 * mbuflen --
 *	Returns the sum of lengths in an mbuf chain.  
 */
mbuflen(m)
	struct mbuf *m;
{
	register struct mbuf *mp;
	register int len = 0;

	for (mp = m; mp; mp= mp->m_next)
		len += mp->m_len;
	return len;
}


/*
 * togetm --
 *	Get data from the on-board buffer and return an mbuf chain.
 *	Note that we do not use the mbuf cluster pages.  The reason
 *	is that we never get enough data (4 Kby) to fill a cluster page --
 *	our max receive buffer size is 2 Kby.  This routine is only
 *	called from toinput().
 */
struct mbuf *
togetm(ts, buf, len)
	struct to_softc *ts;
	register caddr_t buf;		/* address of receive buffer */
	register int len;
{
	register int copylen;		/* length of copy */
	register struct mbuf *mp;
	register struct mbuf *top;	/* top of mbuf chain */
	struct omniheader *h;
	struct mbuf *m;

	/*
	 * Get the omniheader first
	 */

	MGET(top, M_DONTWAIT, MT_DATA);
	if (top == 0) {
		m_freem(top);
		return 0;
	}
	top->m_len = sizeof(struct omniheader);
	top->m_off = MMINOFF;
	bcopy(buf, mtod(top, char *), top->m_len);
	buf = (caddr_t)((int)buf + top->m_len);
	len -= top->m_len;
	m = top;


	/*
	 * Now, get the rest of the packet
	 */
	while (len > 0) {
		MGET(mp, M_DONTWAIT, MT_DATA);
		if (mp == 0) {
			m_freem(top);
			return 0;
		}
		mp->m_len = copylen = MIN(MLEN, len);
		mp->m_off = MMINOFF;
		bcopy(buf, mtod(mp, char *), copylen);
		len -= copylen;
		m->m_next = mp;
		m = mp;
		buf = (caddr_t)((int)buf + copylen);
	}
	return (top);
}
	


/*
 * toinput --
 *	Called from interrupt service.  Get data from the buffer and into
 *	an mbuf chain.  Determine what input routine to call and then
 *	call it.
 */
toinput(ts)
	register struct to_softc *ts;
{
	register struct mbuf *m;
	register struct mbuf *m0;
	struct omnirslt *rslt;
	struct omniheader h;
	int index;
	int len;
	int s;
	int sum;

	index = ts->ts_rcvindex;
	/*
	 * Get the new index for using a buffer
	 */
	ts->ts_rcvindex = (index == 0) ? 1 : 0;

	/*
	 * Get the receive record
	 */
	rslt = torrslt[index];
	len = rslt->len;

	m = togetm(ts, tordata[index], len);

	/*
	 * Re-arm the receive socket
	 */
	s = spl7();
	if (!ts->ts_unitActive) {
		ts->ts_unitActive = 1;
		splx(s);
		if (!(tosetrcv(ts))) {
			ts->ts_unitActive = 0;
			m_freem(m);
			return;
		}
	} else {
		splx(s);
		if (!(ts->ts_state & TS_OUTACTV)) {
			toerror("toinput: unable to rearm socket.\n",
				ts->ts_unit);
			m_freem(m);
			return;
		}
		ts->ts_state |= TS_REARMWAITING;
	}
	
	if (!m)
		return 0;
	m0 = m->m_next;
	if (!m0) {
		m_freem(m);
		return 0;
	}
	
	/*
	 * Check the omniheader
	 */
	bcopy(mtod(m, char *), &h, sizeof(struct omniheader));

	/*
	 * Free up the omniheader mbuf
	 */
	m->m_next = (struct mbuf *)0;
	m_freem(m);

	if (h.omnimagic != OMNIMAGIC) {
		toIncStat(ts,rcvBadMagic);
		m_freem(m0);
		return 0;
	}

	if (len != h.omnilen) {
		printf("toinput: src=0x%x m=0x%x len=0x%x  h.omnilen=0x%x\n",
			h.omnisrc, m, len, h.omnilen);
		tomessage("toinput: lengths not equal\n", ts->ts_unit);
		m_freem(m0);
		return;
	}

	switch (h.omnitype) {
#ifdef INET
	case OMNITYPE_IP:
		s = splimp();
		if (IF_QFULL(&ipintrq)) {
			IF_DROP(&ipintrq);
			splx(s);
			m_freem(m0);
			toIncStat(ts,rcvQFull);
			return;
		} else {
			IF_ENQUEUE(&ipintrq, m0);
			splx(s);
			toIncStat(ts,rcvOK);
		}
		ts->if_ip.if_ipackets++;
		schednetisr(NETISR_IP);
		break;
	case OMNITYPE_ARP:
		arpinput(&ts->ts_ac, m0);
		break;
#endif INET

#ifdef VALIDnet
	case OMNITYPE_BULK:
		s = splimp();
		if (IF_QFULL(&bulk_intrq)) {
			IF_DROP(&bulk_intrq);
			splx(s);
			m_freem(m0);
			toIncStat(ts,rcvQFull);
			return;
		} else {
			IF_ENQUEUE(&bulk_intrq, m0);
			splx(s);
			toIncStat(ts,rcvOK);
		}
		ts->if_bulk.if_ipackets++;
		schednetisr(NETISR_BULK);
		break;
	case OMNITYPE_CONN:
		s = splimp();
		if (IF_QFULL(&conn_intrq)) {
			IF_DROP(&conn_intrq);
			splx(s);
			m_freem(m0);
			toIncStat(ts,rcvQFull);
			return;
		} else {
			IF_ENQUEUE(&conn_intrq, m0);
			splx(s);
			toIncStat(ts,rcvOK);
		}
		ts->if_conn.if_ipackets++;
		schednetisr(NETISR_CONN);
		break;
	case OMNITYPE_RPC:
		ts->if_rpc.if_ipackets++;
		rpc_input(m0);
		toIncStat(ts,rcvOK);
		break;
#endif VALIDnet
	default:
		toIncStat(ts,rcvBadValid);
		m_freem(m0);
		break;
	}

	/*
	 * Do stats on packet origin, etc.
	 */

}


/*
 * tosetrcv --
 *	Send a set receive command to the transporter.  This routine
 *	should only be called if the transporter does not have a command
 *	or set up active.  We set the TS_CMDACTV and TS_SETUP flags in
 *	the softc structure, set up a command block, copy it into the buffer
 *	and then strobe the transporter.  This routine can be called either
 *	from the upper of the lower half.
 */
tosetrcv(ts)
	register struct to_softc *ts;
{
	register int index = ts->ts_rcvindex;	/* receive buffer index */
	int s;
	register struct omniccb *ccb;

	/*
	 * Fill in the command block record
	 */
	ccb = torccb[index];
	ccb->cmd = OMNIRCV;
	toset24(&ccb->rslt, torrslt[index]);
	ccb->c_un.rcv.skt = TOSKT;
	toset24(&ccb->c_un.rcv.data, tordata[index]);
	ccb->c_un.rcv.len = RCVMAXSIZ;
	ccb->c_un.rcv.ctl = 0;

	/*
	 * Initialize the return code
	 */
	torrslt[index]->rcode = -1;

	/*
	 * Strobe the transporter
	 */
	s = splTOMLOCK();
	if (tostrobe(torccb[index], ts->ts_unit)) {
		ts->ts_state |= TS_SETUP;
		splx(s);
		toIncStat(ts,rcvstrobed);
		return 1;
	} else {
		splx(s);
		toerror("tosetrcv: unable to strobe transporter.\n",
			ts->ts_unit);
		toIncStat(ts,rcvnostrobe);
		return 0;
	}
}



/*
 * tostrobe --
 *	Strobe the transporter.  Return 1 if successful, 0 if not.
 */
tostrobe(addr, unit)
	caddr_t addr;
	int unit;
{
	register unsigned char *cp = (unsigned char *)&addr;
	register i, j;

	if (todebug)
		printf("tostrobe\n");
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 0x8000 && !tordy(unit); j++)
		if (j == 0x8000)
			return(0);
		*tostrobeaddr[unit] = *++cp;
	}
	return(1);
}


/*
 * toset24 --
 *	Create a 24-bit address
 */
toset24(a, b)
	register u_char *a;
	u_char *b;
{
	register unsigned char *cp;

	if (todebug)
		printf("toset24\n");
	cp = (unsigned char *)(&b);
	*a++ = *++cp;
	*a++ = *++cp;
	*a++ = *++cp;
}


/*
 * tomessage --
 *	Put a message from the driver onto the console
 */
tomessage(string, unit)
	int unit;
	char *string;
{
	printf("%s%d: %s", todriver.mdr_dname, unit, string);
}

/*
 * toerror --
 *	Called to print out an error message onto the console
 */
toerror(string, unit)
	int unit;
	char *string;
{
	register struct to_softc *ts = &to_softc[unit];
	printf("%s%d: ***ERROR*** %s", todriver.mdr_dname, unit, string);
	ts->ts_state = 0;
	ts->ts_unitActive = 0;
	timeout(toreset, unit, hz<<2);
	return;
}

/*
 * tostartup --
 *	Called from the cdevsw table.
 */
tostartup()
{
	register struct to_softc *ts = &to_softc[0];

	ts->ts_init = 0;
	if (toreset(0))
		ts->ts_init = 1;
	else
		tomessage("couldn't init unit.\n", 0);

	toprint = 1;
}


/*
 * toioctl --
 *	I/O controls sent to the controller
 */
toioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	int cmd;
	caddr_t data;
{
	register struct to_softc *ts = &to_softc[ifp->if_unit];
	register struct ifreq *ifr = (struct ifreq *) data;
	int s;

	switch (cmd) {

	/* Generic ioctl's */
	case SIOCSIFADDR:
		if (ifp != &ts->if_ip)
			return EINVAL;
		if (ifp->if_flags & IFF_RUNNING)
			if_rtinit(ifp, -1);
		tosetaddr(ifp, (struct sockaddr_in *)&ifr->ifr_addr);
		toinit(ifp->if_unit);
		break;
	/* End Generic ioctl's */

	case TOIOCVERSION:
		copyout(to_version, ifr->ifr_data, sizeof(to_version));
		break;
	case TOIOCINIT:
		if (!suser())
			return EPERM;
		s = splTOMLOCK();
		tomessage("operator reset.\n", ts->ts_unit);
		toIncStat(ts,userreset);
		ts->ts_state = 0;	/* Clear state */
		ts->ts_unitActive = 0;
		toreset(ts->ts_unit);
		splx(s);
		break;
	case TOIOCGETADDR:
		copyout(&ts->ts_omniaddr, ifr->ifr_data, 1);
		break;
	case TOIOCSTATS:
		copyout(&ts->ts_stats, ifr->ifr_data, sizeof(struct omnistats));
		break;
	default:
		return EINVAL;
	}
	return 0;
}


/*
 * tosetaddress --
 *	Reset the address for the IP interface.
 */
tosetaddr(ifp, sin)
	register struct ifnet *ifp;
	register struct sockaddr_in *sin;
{
	ifp->if_addr = *(struct sockaddr *)sin;
	ifp->if_net = in_netof(sin->sin_addr);
	ifp->if_host[0] = in_lnaof(sin->sin_addr);
	sin = (struct sockaddr_in *)&ifp->if_broadaddr;
	sin->sin_addr = if_makeaddr(ifp->if_net, INADDR_ANY);
	ifp->if_flags |= IFF_BROADCAST;
}
