/* 
 * Driver Name : ne , NE
 * filename    : ne.c
 * purpose     : Character Special Device Driver
 *             : Supports Polling
 *             : Supports ioctl
 *             : Major device number 19
 * Device      : NE1000 or NE2000 ethernet card
 * Copyright   : ((C)) Randy Wright 1993
 *
 * $Log:	ne.c,v $
 * Revision 2.0  94/01/19  16:10:57  root
 * added buffer useage stats, mulituser control,
 * all known bugs removed, It now seems robust.
 * 
 * Revision 1.5  94/01/02  13:41:20  root
 * added Space.c functionality, cleaned up code.
 * works with neinstall.
 * 
 * Revision 1.4  93/12/26  20:59:31  root
 * fixed last byte bug, upgraded thru-put
 * to 550 kb /sec on write side. Changed
 * address print to hex, fixed number of
 * simultaneuos cards to be 1 - 4.
 * 
 * Revision 1.3  93/12/16  12:33:15  root
 * working version. gets 714 kb/sec recv
 * and 223 kb/sec send on raw speed tests.
 * 
 * Revision 1.2  93/12/15  16:19:36  root
 * moved code from neth.c into ne.c
 * 
 * Revision 1.1  93/12/15  14:09:03  root
 * Initial revision
 * 
 */
/* typedef unsigned char u_char; */
/* typedef unsigned short u_short; */

#define NE_NUMBUFFS 16	/* number of static 1536 byte buffers for read side
			 * and for write side of each board.
			 */

#define NNE 4		/* number of ethernet boards */

#define NNE_MAX	4	/* maximum number of ethernet boards */

#define	NE_LOWWAT 4	/* must be more than this many empty write
			 * packet slots before we will add more
			 */

	/* undefine this if you have moved a finalized copy
	 * of devices.h into /usr/include/sys
	 */
#define NETEST NETEST

#include <sys/types.h>
#include <sys/coherent.h>
#include <sys/con.h>
#ifndef NETEST
#include <sys/devices.h>
#else
#include "devices.h.new"
#endif /* NETEST */
#include <fcntl.h>
#include <sys/file.h>
#include <sys/signal.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/cmn_err.h>

#include <sys/sched.h>
#include <sys/seg.h>
#include <sys/stat.h>
#include <sys/uproc.h>
#include <sys/io.h>
#include <sys/proc.h>
#include <sys/kmem.h>
#include <sys/ksynch.h>
#include <sys/inline.h>


#include <errno.h>
#include "ds8390.h"
#include <sys/neioctl.h>

/*
 * External function declarations
 * H.C.P. replaced these with donone() in mz, I like it.
extern int	nulldev();
extern int	nonedev();
 */

/*
 * Driver function declarations
 */
static void	neload();
static void	neunload();
static void	neopen();
static void	neclose();
static void	neread();
static void	newrite();
static int	neioctl();
static void	newatch();
static void	neblock();
static int	nepoll();

/*
 * Forward references
 */ 
static void	neput();
static void	nefetch();
static void	ne_donone();
static int	nerestart();
static void	necycle();

	/* neintr needs to be externally visible */
void	neintr();

static void     ne0intr();
static void     ne1intr();
static void     ne2intr();
static void     ne3intr();

/*
 * Driver Configuration CON struct.
 */
CON
necon = {
        DFCHR|DFPOL,                    /* Flags        */
        NE_MAJOR,                       /* Major Index  */
        neopen,                         /* Open         */
        neclose,	                /* Close        */
        ne_donone,                      /* Not Blocking */
        neread,                         /* Read         */
        newrite,                        /* Write        */
        neioctl,                        /* Ioctl        */
        ne_donone,                      /* Power fail   */
        ne_donone,                      /* Timeout      */
        neload,                         /* Load         */
        neunload,                       /* Unload       */
        nepoll                          /* Poll         */
};


/*
 * Interrupt Entry Points. use them if you support mulitple devices
 */
void (*neintf[NNE_MAX])() = {
        ne0intr,
        ne1intr,
        ne2intr,
        ne3intr
};

/*
 * Patchable parameters
 */

	/* these are tuneable values for irq, defined in Space.c */
extern int ne0_irq;
extern int ne1_irq;
extern int ne2_irq;
extern int ne3_irq;

	/* these are tuneable values for base of io ports for each board,
	 * defined in Space.c
	 */
extern int ne0_iobase;
extern int ne1_iobase;
extern int ne2_iobase;
extern int ne3_iobase;

	/* these tunable parameters are not used by the NE1000/2000
         * devices. They are present for the day that wd/smc cards
	 * are supported by this driver.
	 * defined in Space.c
	 */
extern int ne0_mem;
extern int ne1_mem;
extern int ne2_mem;
extern int ne3_mem;


int    ne_irq[NNE_MAX];
int ne_iobase[NNE_MAX];
paddr_t ne_mem[NNE_MAX];

/* NE1000/NE2000 do not use dual ported ram, but these mem pararmeters are
 * here in order to port this driver to ds8390 boards that do need dual
 * port memory mapped io.
 */

/* there is an ioctl to change this dynamically */

int ne_lowwater = NE_LOWWAT;

/*
 * Register addresses. Locations that might need to be patched
 * Registers eg. Read, Write, Status, Control.
 */
ushort ne_eaddr[NNE_MAX][6];

/*
 * Control struct
 */

extern time_t lbolt;

/*
 * data_c struct.
 */ 
#define ETHER_MIN_LEN 64
#define ETHER_MAX_LEN 1536

typedef struct ebuf_t {
	int len;		/* get the length here */
	char data[PKTSZ];	/* get the data here   */
} ebuf_t;

/*
 * Ethernet interface local data. There is an NNE sized array
 * of these. 
 */
struct	nedata_c {
	struct	arpcom nd_ac;	/* Ethernet common part, neioctl.h */
#define	nd_if	nd_ac		/* network-visible interface */
#define	nd_addrp	nd_ac.ac_enaddr /* hardware Ethernet address */

	int	nd_flags;
#define	DSF_LOCK	1		/* block re-entering restart */

	int	nd_mask;		/* interrupt reg mask */
	int	nd_ba;			/* byte addr in buffer ram of inc pkt */
	int	nd_cur;			/* current page being filled */
	struct	prhdr	nd_ph;		/* hrdwr hdr of inc packet, ds8390.h */
	struct	ether_header nd_eh;	/* eth hdr of inc packet, neioctl.h */
	short	nd_txstart;		/* transmitter buffer start */
	u_short nd_rxend;               /* recevier buffer end */
	short	nd_port;		/* i/o port base */
	short	nd_mode;		/* word/byte mode */
	int	nd_filemode;		/* Open flags, eg O_WRONLY */
	int	nd_pad;			/* pad */
	int	nd_refc;		/* number of current opens */
	int	nd_reserved;		/* pad */
	event_t	nd_inevent;		/* poll events */
	event_t	nd_outevent;

	time_t	nd_ipsleep;		/* polling input timeout  */
	time_t	nd_opsleep;		/* polling output timeout */

		/* buffering is accomplished by having
		 * NE_NUMBUFS buffers for outgoing packets and
		 * for incoming packets.
		 * these are operated in a ring buffer method
		 * Race conditions between interrupt code and
		 * read write code is prevented by maintianing
		 * at least one open packet between the head and tail
		 * points. NE_NUMBUFS should be organized into
		 * a tuneable parameter in Space.c
		 *
		 */
	ebuf_t  nd_rq[NE_NUMBUFFS];		/* read buffers */
	int    nd_norq;				/* next open read side packet,
						 * this is where we put
						 * new arrivals coming
						 * off the wires
						 */
	int    nd_nrq;				/* next received packet
						 * This is the point from
						 * which we copy data to the
						 * user's data space in a read()
						 */

	ebuf_t  nd_wq[NE_NUMBUFFS];		/* write buffers */
	int    nd_nowq;				/* next open write side packet
						 * This is where we put data
						 * copied from the user's data
						 * space in a write call.
						 */
	int    nd_swq;				/* next packet to send
						 * this is where we will get
						 * the next data to put into
						 * the hardware's transmit
						 * buffer
						 */

	int	nd_state;		/* state */
#define NE_NOBOARD	-1		/* no ethernet hardware detected */
#define NE_NORMAL	0		/* normal state */
#define NE_DMAWAIT	1		/* waiting for dma to complete */
#define NE_XMIT	2			/* interrupt due in for xmit */
#define NE_RRUN 3			/* restart is running, no reentry */
	TIM	nd_tim;			/* timeout struct */
	int	nd_timwait;		/* keep timeout running */

		/* statistics on data flow through our wires, the list
		 * of stats needs to start at nd_collisions
		 * and needs to be synced with neioctl.h
		 */
	int	nd_collisions;		/* number of collisions */
	int	nd_oerrors;		/* number of xmit errors */
	int	nd_ierrors;		/* number of recv errors */
	int	nd_opackets;		/* xmitted packets */
	int	nd_ipackets;		/* recv'd packets */
	int	nd_idiscards;		/* incoming packets discarded */
	int	nd_lox;			/* lowest num of xmit empties */
	int	nd_lor;			/* lowest num of recv empties */
	int	nd_reentry;		/* true if intr is already executing */
} nedata_c[NNE] ;

#define	ENBUFSIZE	(sizeof(struct ether_header) + ETHERMTU + 2 + ETHER_MIN_LEN)
int counter, linenum;

#define NEPAUSE 	counter=1;while(counter--);

#define	PAT(n)	(0xa55a + 37*(n))

u_short boarddata[16];

/* replacement for nondev and nulldev */
void
ne_donone()
{
	return;
}

/* void
 * neopen( dev, mode )
 * dev_t dev;
 * int mode;
 *
 */
void
neopen(dev, mode, flags)
dev_t dev;
int mode, flags;
{
	struct aprcom *ifp;	/* we need to mark it as running or not */
	struct nedata_c *nd;	/* the nedata_c struct corresponding to unit */
	register neBase;	/* the hardware io ports */
	int unit;		/* current board */

		/* referenced in order to
		 * shut the compiler error messages off
		 */
	flags;

		/* the minor device number is also the board number */
	unit = minor( dev );

		/* there are 4 nedata_c structs so that the
		 * computer can support up to 4 ethernet boards.
		 * We use the minor number to determine which
		 * board shall be used.
		 */
	nd = &nedata_c[unit];

	if( unit > ( NNE - 1 ) || unit < 0 ) {
		  /* out of range card number */
		set_user_error( ENXIO );
		return;
	} 

		/* if there is no ethernet board present */
	if( nd->nd_state < 0 ) 	{
		set_user_error(ENODEV);
		return;
	}


		/* exclusive open only 
		 * /
	if( nd->nd_refc > 0) {
		set_user_error(EBUSY);
		return;
	}
		*/

	if( nd->nd_refc == 0) {
		/* first open determines access mode */
		nd->nd_filemode = mode;
	}

		/* set the buffer pointers */
	nd->nd_nrq = nd->nd_norq = nd->nd_nowq = nd->nd_swq = 0;

		/* initialize buffer supply statistics */
	nd->nd_lox = nd->nd_lor  = NE_NUMBUFFS;


		/* set the struct variables for later use */
	neBase = nd->nd_port = ne_iobase[unit];
	ifp = &nd->nd_ac;


		/* turn on the interrupts, but only if the output init
		 * routine has turned the runniing flag off 
		 */
	if (ifp->if_flags & IFF_RUNNING)
		;
	else neoinit(unit);

	nd->nd_timwait = 1;

	/* Schedule cycler so we get watchdog timeouts */
	necycle(unit);

		/* refc is available for the time when
		 * we will run the device driver with more than one
		 * concurrent process feeding it. refc allows us to
		 * turn off the interrupts when no other processes
		 * is using this net card.
		 */
	nd->nd_refc++;

	return;
}

/*
 * Initialization of interface; set up initialization block
 * and transmit/receive buffer descriptors. Performed at first open.
 */
neoinit(unit)
int unit;
{
	register struct nedata_c *nd = &nedata_c[unit];
	struct aprcom *ifp = &nd->nd_ac;
	int s;
	int i;
	register neBase = nd->nd_port;

	/***> is this interface already active
	 */
	if (ifp->if_flags & IFF_RUNNING) return;

	/* the older style interrupt disable. This should probably
	 * be updated
	 */
	s = sphi();

	/* set physical address on ethernet card */
	outb (neBase+ds_cmd, DSCM_NODMA|DSCM_PG1|DSCM_STOP);
		NEPAUSE

	for (i=0 ; i < 6 ; i++) outb(neBase+ds1_par0+i,nd->nd_addrp[i]);

	/* clear logical mulitcast address hash filter for now */
	for (i=0 ; i < 8 ; i++) outb(neBase+ds1_mar0+i,0xff);

	/* init regs */
	outb (neBase+ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_STOP);
		NEPAUSE
	outb (neBase+ds0_rbcr0, 0);
		NEPAUSE
	outb (neBase+ds0_rbcr1, 0);
		NEPAUSE
	outb (neBase+ds0_imr, 0);
		NEPAUSE
	outb (neBase+ds0_isr, 0xff);
		NEPAUSE

	/* Word Transfer select, Burst Mode Select, Fifo at 8 bytes */
	outb(neBase+ds0_dcr, nd->nd_mode);
		NEPAUSE
	outb(neBase+ds0_tcr, 0);
		NEPAUSE
	outb (neBase+ds0_rcr, DSRC_MON);
		NEPAUSE
	outb (neBase+ds0_tpsr, 0);
		NEPAUSE
	outb(neBase+ds0_pstart, (nd->nd_txstart+PKTSZ)/DS_PGSIZE);
		NEPAUSE
	outb(neBase+ds0_pstop, nd->nd_rxend/DS_PGSIZE);
		NEPAUSE
	outb(neBase+ds0_bnry, (nd->nd_txstart+PKTSZ)/DS_PGSIZE);
		NEPAUSE
	outb (neBase+ds_cmd, DSCM_NODMA|DSCM_PG1|DSCM_STOP);
		NEPAUSE
	outb(neBase+ds1_curr, (nd->nd_txstart+PKTSZ)/DS_PGSIZE);
		NEPAUSE
	nd->nd_cur = (nd->nd_txstart+PKTSZ)/DS_PGSIZE;
	outb (neBase+ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_START);
		NEPAUSE

	/* set non-promiscous mode, for now.  */
	outb (neBase+ds0_rcr, DSRC_AB);
		NEPAUSE

	/* word/byte */
	outb(neBase+ds0_dcr, nd->nd_mode);
		NEPAUSE

	/* enable interrupts */
	nd->nd_mask = DSIM_PRXE | DSIM_PTXE | DSIM_RXEE | DSIM_TXEE;
	outb (neBase+ds0_imr, (nd->nd_mask  & 0xff) );

	/* mark as running */
	nd->nd_if.if_flags |= (unsigned short) IFF_RUNNING;

	/* not actively outputing */
	nd->nd_flags &= ~DSF_LOCK;
	nd->nd_state = NE_NORMAL;

	spl(s);
}

/* void
 * neclose(dev)
 * dev_t dev;
 *
 */
void
neclose(dev)
dev_t dev;
{

	register struct nedata_c *nd;
	register neBase;
	struct aprcom *ifp;
	int unit, oldpri, ne_tries;

	unit = minor( dev );
	if( unit > ( NNE - 1 ) || unit < 0 ) { 
		 /* out of range card number */
		set_user_error(  ENXIO );
	} 

	nd = &nedata_c[unit];
	neBase = nd->nd_port;

		/* test if nd_refc is zero
		 * and leave the interrupts on if it is not.
		 */
	nd->nd_refc--;

	
	if( nd->nd_refc  == 0 ) {
			/* we wait for
			 * output to drain.
			 */
	        oldpri = sphi();
		for( ne_tries = 0; ne_tries < 10; ne_tries++ ) {
			/* is our data space available?
			 * number of empty slots is:
			 * not wrapped  */
		if( nd->nd_swq == nd->nd_nowq ) {
			   	spl(oldpri);
				break;
		} else {
			/* we want to be awakened */
		    if( (nd->nd_flags & DSF_LOCK == 0) || nd->nd_state == NE_NORMAL ) {
				int rcount;
				rcount = 100000;
				while( (nerestart(unit)) == 0 
					&& (nd->nd_swq != nd->nd_nowq )
					&& (--rcount > 0) )
						;
					if( rcount == 0 ) continue;
		      }
		}
				/* snooze: */
		   if( (x_sleep(nd->nd_rq,pritty,slpriSigCatch,
			   "packetwait" )) ==PROCESS_SIGNALLED ) {
				 /* awakened by signal */
				set_user_error( EINTR);
				spl(oldpri);
					break;
		    }
	      }
	
	
			/* turn off interrupts at board */
		outb (neBase+ds0_imr, 0x0);
	
			/* we mark the board info as not running so
			 * the next guy will reinitialize it.
			 */
	     	ifp = &nd->nd_ac;	
		ifp->if_flags &= ~IFF_RUNNING;
	
			/* if somebody deallocate the queue memory or
			 * gives it to somebody else, we don't want to
			 * try to use it in some spuroius interrupt.
			 */
		nd->nd_timwait = 1;
	
		/* turn off cycle. */
		timeout ( &nd->nd_tim, HZ / 10, NULL, unit);
	}
	return;
}

/* void
 * neread(dev,iop)
 * dev_t dev;
 * IO * iop;
 */
void
neread(dev, iop)
dev_t dev;
register IO * iop;
{
	register struct nedata_c *nd;
	register neBase;
	int unit, oldpri;

		/* validate the read paramters
		 * and set up the pointers
		 */
	unit = minor( dev );

	if( unit > ( NNE - 1 ) || unit < 0 ) {
		  /* out of range card number */
		set_user_error(  ENXIO );
	} 

	nd = &nedata_c[unit];
	neBase = nd->nd_port;

	if( iop->io_ioc < PKTSZ ) {
		/* we return data to user in chunks
		 * of one packet only.
		 */
		set_user_error( EINVAL );
		return;
	}

		/* see if it is legal to transfer data to user */
	if( (nd->nd_filemode & IPR) == IPR ) {
			/* read is ok, is there data available? */
		if( nd->nd_norq != nd->nd_nrq ) {
			/* yes, there is data */
			iowrite( iop, &nd->nd_rq[nd->nd_nrq].data[0],
				( nd->nd_rq[nd->nd_nrq].len - 3 ) );

			if( nd->nd_nrq > NE_NUMBUFFS - 2 )
				nd->nd_nrq = 0;
			else 
				nd->nd_nrq++;
		} else {

			/* no data, shall we block? */
			if( iop->io_flag & IONDLY ) {
				 /* don't block */
				set_user_error( EWOULDBLOCK );
				return;
			} else 	{ 
				/* do a block, 'til there's data */
				oldpri = splhi();
				while( 1 ) {
					/* snooze */
					if( (x_sleep(nd->nd_rq, 
					   prilo, slpriSigCatch,
					   "pktread" ) ) == 
					   PROCESS_SIGNALLED ) {
						/* were we awakened by
						 * a signal?
						 */
						set_user_error( EINTR );
						spl(oldpri);
						return;
					}
					/* well, if it wasn't a
					 * signal, was it the arrival
					 * of our data?
					 */
					if( nd->nd_norq != nd->nd_nrq )	{
						/* yes, there is data
						 */
						iowrite( iop,
						 &nd->nd_rq[nd->nd_nrq].data[0],
						(nd->nd_rq[nd->nd_nrq].len - 3)
						          );
							/* update the
							 * the index
							 */
						if( nd->nd_nrq > NE_NUMBUFFS - 2 )
							nd->nd_nrq = 0;
						else 
							nd->nd_nrq++;
						spl(oldpri);
						return;
					}
					/* no data, no signal, so
					 * loop again
					 */
				}
			}
		}
	} else	{
		set_user_error( EBADFD );
		return;
	}
}

/* void
 * newrite(dev,iop)
 * dev_t dev;
 * IO * iop;
 */
void
newrite(dev, iop)
dev_t dev;
register IO * iop;
{
	register struct nedata_c *nd;
	register neBase;
	int empty, unit, oldpri, ne_tries;

	unit = minor( dev );
	if( unit > ( NNE - 1 ) || unit < 0 ) {
		 /* out of range card number */
		set_user_error(  ENXIO );
	} 

	nd = &nedata_c[unit];
	neBase = nd->nd_port;

	if( iop->io_ioc < ETHER_MIN_LEN || iop->io_ioc > ETHER_MAX_LEN ) {
		/* invalid data length */
		set_user_error( EINVAL );
		return;
	}

		/* see if it is legal to transfer data to user */
	if( nd->nd_filemode & IPW ) {
		 /* since write is ok, is there data available? */
		 /* determine number of empty slots. */
		 if( nd->nd_swq < nd->nd_nowq ) 
			empty = NE_NUMBUFFS - (nd->nd_nowq - nd->nd_swq);	
		 else 	{
			/* nowq has wrapped, but not swq */
			if( nd->nd_swq > nd->nd_nowq ) empty = 
			NE_NUMBUFFS - ((nd->nd_nowq + NE_NUMBUFFS) - nd->nd_swq);
			else  empty = NE_NUMBUFFS;
		}

	if( empty < nd->nd_lox ) nd->nd_lox = empty;

 /****************** NOTES ON RACE CONDITIONS ****************
 *
 * At this point in the code, we know a number of empty pakcet
 * slots. However, by the time we have digested this number and
 * prepared to act on in it, the interrupt handler can easily
 * change it. We might fall asleep when no further interrupts
 * have been scheduled.
 *
 * our methods of preventing overwriting our own data
 * before transmission, is that we allow at least ne_lowwater
 * empty slots to intervene between the current write slot 
 * ( nd_nowq ) and the posisiton of the current send slot (nd_swq).
 *
 * Regarding the possible race condition between the
 * call to x_sleep and the wakeup issued by the interrupt
 * handler. We use sphi to avert this, and we also have set
 * up a safety net with necycle which calls wakeup every
 * 10 HZ. When we do the sleep loop, if we set
 * ne_tries. It is incremented each time through the
 * loop. 
 *
 ************************************************************/

		if( empty > ne_lowwater   ) {
			/* yes, there is room, so copy in the data */
			nd->nd_wq[nd->nd_nowq].len = (iop->io_ioc + 1 );
			ioread( iop, 
				&nd->nd_wq[nd->nd_nowq].data[0], 
				iop->io_ioc
				 );
			/* update index */
			if( nd->nd_nowq > NE_NUMBUFFS - 2 )
				nd->nd_nowq = 0;
			else 
				nd->nd_nowq++;

			if( nd->nd_state == NE_NORMAL )	{
				oldpri = sphi();
				nerestart(unit);
			   	spl(oldpri);
			}
		} else	{

			/* no room, shall we block? */
			if( iop->io_flag & IONDLY ) {
				 /* don't block */
				set_user_error(EWOULDBLOCK);
				return;
			} else	{ 
				/* do a block, 'til there's room */

				/* there's some possible races
				 * in this block. So, we ask that
				 * interrupts be withheld until
				 * we are asleep. We test for room
				 * after interrupts are disabled,
				 * but before our first sleep.
				 */

			        oldpri = sphi();
				for( ne_tries = 0; ne_tries < 10; ne_tries++ ) {
					/* is our data space available? */
				   if( nd->nd_swq < nd->nd_nowq ) 
					empty = NE_NUMBUFFS - (nd->nd_nowq - nd->nd_swq);	
				   else {
					if( nd->nd_swq > nd->nd_nowq ) empty = 
					NE_NUMBUFFS - ((nd->nd_nowq + NE_NUMBUFFS) - nd->nd_swq);
					else  empty = NE_NUMBUFFS;
				   }

				if( empty < nd->nd_lox ) nd->nd_lox = empty;


				   if( empty > ne_lowwater ) {
					/* there is room, copy in data */
					nd->nd_wq[nd->nd_nowq].len = iop->io_ioc;
					ioread( iop, &nd->nd_wq[nd->nd_nowq].data[0], iop->io_ioc );
					nd->nd_wq[nd->nd_nowq].len++;
					/* update the index */
					if( nd->nd_nowq > NE_NUMBUFFS - 2 )
						nd->nd_nowq = 0;
					else 
						nd->nd_nowq++;

						/* begin transmission 
						 * if we can
						 */
					if( nd->nd_state == NE_NORMAL )	{
						nerestart(unit);
					} else continue;

					/* turn interrupts back on */
 				   	spl(oldpri);
					return;

				} else {
						/* we want to be awakened */
					if( (nd->nd_flags & DSF_LOCK == 0) || nd->nd_state == NE_NORMAL ) {
						int rcount;

						/* nerestart is desgined
						 * to return 1 if and only
						 * if it has done something
						 * which will create an
						 * interrupt (which makes
						 * our wakeup call). rcount
						 * prevents lockup on deadnet.
						 */
						rcount = 100;

						/* begin transmission 
						 * if we can
						 */
						if( nd->nd_state == NE_NORMAL )	{
							while( 
							   (nerestart(unit)) == 0  &&
							   (nd->nd_swq != nd->nd_nowq)
							   && (--rcount > 0)
							    )
							    ; /* <- note semicolon */
						} 
						if( rcount == 0) {
							cmn_err( CE_NOTE, "newrite:restart failure\n" );
							continue;
						}
						 /* net may be down */
					}
				}
				if( nd->nd_state != NE_NORMAL )	{
	/* turn on watchdog */
	timeout ( &nd->nd_tim, HZ / 10, necycle, unit);

						/* snooze */
					if( (x_sleep(nd->nd_rq,prilo,slpriSigCatch,
					   "pktwrite" )) ==PROCESS_SIGNALLED ) {
					   	 /* awakened by signal */

						set_user_error( EINTR );
						spl(oldpri);
						return;
					}
				}
			}

				/* if we reach this spot, we have
				 * probably have a broken network, 
				 * because it doesn't take a whole
				 * second to clear the backlog.
				 */
				set_user_error( ENETDOWN );

			   	spl(oldpri);
				return;
			}
		}
	} else {
		set_user_error( EBADFD );
		return;
	}

}

/* int
 * neioctl(dev,com,arg)
 * dev_t dev;
 * int com;
 * neattr_t * arg;
 */
static int
neioctl(dev, com, arg)
dev_t dev;
int com;
register  char * arg;
{

	register struct nedata_c *nd;
	register neBase;
	struct aprcom *ifp;
	int unit;
	short *sh;
	
	unit = minor( dev );
	if( unit > ( NNE - 1 ) || unit < 0 ) {
		  /* out of range card number */
		set_user_error(  ENXIO );
	} 

	nd = &nedata_c[unit];
	neBase = nd->nd_port;

		/* interface data for address resolution */
	ifp = &nd->nd_ac;
	switch( com )	{
	case NEIOCGFLAGS:
		kucopy( ifp, arg,
	 		(sizeof(ifp->ac_enaddr) + sizeof(ifp->if_flags) ) 
			);
		return;
		break;

	case NEIOCSFLAGS:
		/* we don't reset the ethernet address, just the flags */
		sh = arg;
		sh += 6;
		ifp->if_flags = getusd(sh);
			/* set the card operation */
		if( (ifp->if_flags & IFF_PROMISC))
			outb (nd->nd_port+ds0_rcr, DSRC_AB | DSRC_PRO |
			      DSRC_AR | DSRC_SEP);
		else	outb (nd->nd_port+ds0_rcr, DSRC_AB);
		return;
		break;

		/* retreive stats */
	case NEIOCGSTATS:
		kucopy( &nd->nd_collisions, 
			arg,
	 		sizeof(struct nestats)
			);

			/* clear buffer supply statistics */
		nd->nd_lox = nd->nd_lor = NE_NUMBUFFS;

		return;
		break;

		/* retreive lowwater figure */
	case NEIOCGLOWWAT:
		kucopy( &ne_lowwater,
			arg,
	 		sizeof(ne_lowwater) 
			);
		return;
		break;
	case NEIOCSLOWWAT:
		ne_lowwater = getuwd(arg);
		if( ne_lowwater < 3 ) ne_lowwater = 3;
		if( ne_lowwater > (NE_NUMBUFFS - 1) )  ne_lowwater=(NE_NUMBUFFS - 1);
		return;
		break;
	default:
		set_user_error(  EINVAL );
		return;
		break;
	}
	return;
}

void
newatch()
{ }

/*
 * neload routine, runs a boot time.
 *
 * We try out the card to see if it will run in byte mode
 * or word mode.
 *
 * If neither, no card is present. If byte mode works, its a NE1000
 * If word mode works, its a NE2000. If one of the modes
 * works, then we extract the hardware ethernet address and
 * make an announcement on the console.
 *
 * We allow a small amount of time to elapse between io events
 * to the registers, and a fair amount of time after a reset
 * or dma call.
 *
 * we don't use the dev_t that accompanies to call to neload.
 *
 * void
 * neload(dev)
 * dev_t dev;
 */

void
neload()
{
	int val, i, ch, pat, board;
	register struct nedata_c *nd;
	register neBase;
	struct aprcom *ifp;
	long delay;
	int bytemode;


		/* fill in the values from Space.c */
	ne_irq[0] = ne0_irq;
	ne_irq[1] = ne1_irq;
	ne_irq[2] = ne2_irq;
	ne_irq[3] = ne3_irq;
	ne_iobase[0] = ne0_iobase;
	ne_iobase[1] = ne1_iobase;
	ne_iobase[2] = ne2_iobase;
	ne_iobase[3] = ne3_iobase; 
	ne_mem[0] = ne0_mem;
	ne_mem[0] = ne1_mem;
	ne_mem[0] = ne2_mem;
	ne_mem[0] = ne3_mem;


 	/* loop in order to probe all cards */
	for( board = 0; board < NNE; board++ ) {

		/* try byte mode first */
		bytemode = 1;

		/* our private data */
		nd = &nedata_c[board];

		/* our io port */
		neBase = nd->nd_port = ne_iobase[board];

			/* interface data for address resolution */
		ifp = &nd->nd_ac;

			/* the param in Space.c is ne_iobase[]. If
			 * the sys admin has set our io port to zero
			 * we do not wish to test for this board
			 */
		if( neBase == 0 ) {
			nd->nd_state = NE_NOBOARD;
			continue;
		}

		if (bytemode) {

			/* Byte Transfers, Burst Mode Select, Fifo at 8 bytes
			 * DSDC_BMS = burst mode sel, DSDC_FT1 = fifo threshold 
			 */
			nd->nd_mode = DSDC_BMS|DSDC_FT1;

			/*  statring loc of xmit buffer (8*1024) */
			nd->nd_txstart = TBUF8;
	
			/* end loc of rcv buffer (16*1024) */
			nd->nd_rxend = RBUFEND8;

		} else {
word:
			/* Word Transfers, Burst Mode Select, Fifo at 8 bytes */
			nd->nd_mode = DSDC_WTS|DSDC_BMS|DSDC_FT1;
			nd->nd_txstart = TBUF16; /* (16*1024) */
			nd->nd_rxend = RBUFEND16; /* (32*1024) */
			bytemode = 0;
		}

		/* Reset the board by reading the reset port and
		 * then writing to it.
		 */
		val = inb(neBase + ne_reset);
		/* delay for ??? */
		for( delay = 1000L; delay > 0; delay -- ) 
				;
		outb(neBase + ne_reset, val);
		for( delay = 1000L; delay > 0; delay-- ) 
			;
	
		outb(neBase + ds_cmd, DSCM_STOP|DSCM_NODMA);
			NEPAUSE
	
		/* now check the results of the reset. If it worked, we
		 * have board. Otherwise we don't
		 */
		i = 10000;
		while ((inb(neBase + ds0_isr) & DSIS_RESET) == 0 && i-- > 0);
		if (i < 0) { 
			nd->nd_state = NE_NOBOARD;
			return; 
		}
	
		outb(neBase + ds0_isr, 0xff);
			NEPAUSE
		outb(neBase + ds0_dcr, nd->nd_mode);
			NEPAUSE
	
		outb(neBase + ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_STOP);
		for( delay = 1000L; delay > 0; delay -- ) 
			;
	
		/* Check cmd reg and fail if not right */
		if ((i = inb(neBase + ds_cmd)) != (DSCM_NODMA|DSCM_PG0|DSCM_STOP)) { 
			nd->nd_state = NE_NOBOARD; 
			return;
		}
		NEPAUSE
	
			/* transmission control */
		outb(neBase + ds0_tcr, 0);
			NEPAUSE
	
			/* receiver control */
		outb(neBase + ds0_rcr, DSRC_MON);
			NEPAUSE
	
			/* first page of receive buffer */
		outb(neBase + ds0_pstart, (nd->nd_txstart+PKTSZ)/DS_PGSIZE);
			NEPAUSE
	
			/* end page of receive buffer */
		outb(neBase + ds0_pstop, nd->nd_rxend/DS_PGSIZE);
			NEPAUSE
	
			/* pointer to boundary between last packet
			 * and next one (last page of last packet).
			 * Here we've set it the end of the receive
			 * buffer.
			 */
		outb(neBase + ds0_bnry, nd->nd_rxend/DS_PGSIZE);
			NEPAUSE
	
			/* We turn off all interrupts from this
			 * board.
			 */
		outb(neBase + ds0_imr, 0);
			NEPAUSE
	
			/* zero out the interrupt status register
			 */
		outb(neBase + ds0_isr, 0);
			NEPAUSE
	
			/* flip to page 1 of registers */
		outb(neBase + ds_cmd, DSCM_NODMA|DSCM_PG1|DSCM_STOP);
			NEPAUSE
	
			/* The current transmit buffer begining page
			 * is stored in ds1_curr
			 */
		outb(neBase + ds1_curr, (nd->nd_txstart+PKTSZ)/DS_PGSIZE);
			NEPAUSE
	
			/* flip back to page 0 */
		outb(neBase + ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_STOP);
			NEPAUSE
	
		/*
		 * detect the presence of the card by using card dma
		 * to store the pattern in the transmit buffer
		 * then retrieve it using card dma and check it.
		 * In order for this to work, the byte vs. word
		 * mode must be correct and the hardware must be
		 * present.
		 */
		pat = PAT(0);
		nesput(nd, &pat, nd->nd_txstart, 4);
		nefetch(nd, &pat, nd->nd_txstart, 4);
	
		if (pat != PAT(0)) {
			if (bytemode) { 
				goto word; 
			} else 	{ 
			     	nd->nd_state = NE_NOBOARD;
			     	return; 
			}
		}
	
		/* set the interrupt vector to point at the correct handler
		 * entry point after validating it.
		 */
		if( (ne_irq[board] > 1) && neintf[board] != NULL ) {
			setivec( ne_irq[board], neintf[board] );
		} else {
		     	nd->nd_state = NE_NOBOARD;
		     	return; 		
		}
	
		/* Extract ethernet hardware address from board */ 
		nefetch (nd, &boarddata[0], 0, sizeof(boarddata));
	
		/* report to the console */
		if( bytemode ) 	cmn_err( CE_CONT, "ne%d: NE1000 ", board );
		else cmn_err( CE_CONT, "ne%d: NE2000 ", board );
	
		cmn_err( CE_CONT, "ethernet address [" );
		for(i=0; i < 6; i++) {
			nd->nd_addrp[i] = 
				ne_eaddr[board][i] = boarddata[i];
			ch = (unsigned int) nd->nd_addrp[i]; 
			cmn_err( CE_CONT, "%x", ch );
			if( i < 5 ) cmn_err( CE_CONT, ":" );
		}
		cmn_err( CE_CONT, "] IRQ:[%d] io base[0x%x]\n", ne_irq[board], ne_iobase[board]  );
		nd->nd_norq = nd->nd_nrq = 0;
	 	nd->nd_nowq = nd->nd_swq = 0;
	
		/* no processes can have us opened yet, so init refc */
		nd->nd_refc = 0;
		nd->nd_state = NE_NORMAL;
	}
	return;
}

/*
 * Fetch from onboard ROM/RAM. I/O for the NE card uses
 * an internal dma scheme to move data between our accessible
 * data port and the card's  internal memory buffers.
 */
static void
nefetch (nd, up, ad, len) 
struct nedata_c *nd; 
caddr_t up; 
{
	u_char cmd;
	int pport, t;
	register neBase = nd->nd_port;


		/* save the old cmd value */
	cmd = inb (neBase + ds_cmd);
		NEPAUSE

		/* flip to page 0 */
	outb (neBase + ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_START);
		NEPAUSE

		/* Setup remote dma */
	outb (neBase + ds0_isr, DSIS_RDC);
		NEPAUSE

		/* roundup our length to words if needed  */
	t = len;
	if ((nd->nd_mode & DSDC_WTS) &&  (t & 1) )
		t++;

		/* tell the card how much data (in bytes) to expect */
	outb (neBase+ds0_rbcr0, t);
		NEPAUSE

	outb (neBase+ds0_rbcr1, (t >> 8) );
		NEPAUSE

		/* tell the card where to get the data from */
	outb (neBase+ds0_rsar0, ad);
		NEPAUSE

	outb (neBase+ds0_rsar1, ad>>8);
		NEPAUSE

		/* Execute & extract from card */
	outb (neBase+ds_cmd, DSCM_RREAD|DSCM_PG0|DSCM_START);
		NEPAUSE

		/* insw and insb are assembler routines defined
		 * locore.s which use insb and insw to move data */

	if (nd->nd_mode & DSDC_WTS) {
		pport = neBase+ne_data;
			/* ee_insw is defined in wsub.s */
		ee_insw (pport, up, (int)t/2 );
	} else 	ee_insb (neBase+ne_data, up, len);

		counter = 10000;
	/* Wait till done, then shutdown remote dma */
	while ((inb (neBase+ds0_isr) & DSIS_RDC) == 0 && counter-- > 0)
		;
	outb (neBase+ds0_isr, DSIS_RDC);
		NEPAUSE

		/* restore the old cmd */
	outb (neBase+ds_cmd, cmd);
		NEPAUSE
}

/*
 * Put string to onboard RAM. For a description of card io, see
 * nefecth pre-function comments
 */

nesput (nd, up, ad, len) 
struct nedata_c *nd;
caddr_t up; 
{
	u_char cmd;
	int pport;
	register neBase = nd->nd_port;


		/* save the old cmd */
	cmd = inb(neBase+ds_cmd);

		/* flip to page 0 */
	outb (neBase+ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_START);
		NEPAUSE

		/* Setup for remote dma */
	outb (neBase+ds0_isr, DSIS_RDC);
		NEPAUSE

		/* roundup to words if needed */
	if ((nd->nd_mode & DSDC_WTS) && len&1)
		len++;

		/* tell the card how much data and where to put it */
	outb (neBase+ds0_rbcr0, len);
		NEPAUSE
	outb (neBase+ds0_rbcr1, len>>8);
		NEPAUSE
	outb (neBase+ds0_rsar0, ad);
		NEPAUSE
	outb (neBase+ds0_rsar1, ad>>8);
		NEPAUSE

	/* Execute & stuff to card */
	outb (neBase+ds_cmd, DSCM_RWRITE|DSCM_PG0|DSCM_START);
		NEPAUSE

	if (nd->nd_mode & DSDC_WTS) {
		pport = neBase+ne_data,
		ee_outsw ( pport, up, (int)len/2);
		if( len & 0x1 ) {
			unsigned short myword;
			myword = ( (((char)up[len-1]) << 8) & 0xff00);
			outw( pport, myword );
		}
	} else	ee_outsb (neBase+ne_data, up, len);

		counter=10000;
	/* Wait till done, then shutdown dma */
	while ((inb (neBase+ds0_isr) & DSIS_RDC) == 0 && counter-- > 0)
		;
	outb (neBase+ds0_isr, DSIS_RDC);
		NEPAUSE

		/* restore old cmd */
	outb (neBase+ds_cmd, cmd);
		NEPAUSE
}


/*void
 * neunload(dev)
 * dev_t dev;
 */
void
neunload()
{ /** none **/ }

/* 
 * int
 * nepoll(dev,ev,msec)
 * dev_t dev;
 * int ev;
 * int msec;
 */
static int
nepoll(dev, ev, msec)
dev_t dev;
int ev;
int msec;
{

	register struct nedata_c *nd;
	int unit, empty;

	unit = minor( dev );
	if( unit > ( NNE - 1 ) || unit < 0 ) {  
		/* out of range card number */
		set_user_error(  ENXIO );
	} 
	nd = &nedata_c[unit];

	/*
	 * We accept polls for POLLIN and POLLOUT. POLLIN gets a defered 
	 * pollwake in the interrupt code when data arrives.
	 * POLLOUT gets a defered wakeup from the interrupt code when
	 * the is more than ne_lowwater empty packet slots available.
	 *
	 * In addition, there is a pollwake call available in necycle
	 * that tests to see if the timeout time has elapsed. It runs
	 * in a 10 HZ granularity.
	 */

	ev &= (POLLIN | POLLOUT);

	if( (ev & POLLIN) != 0)	{
		if(nd->nd_norq == nd->nd_nrq ) {
			/* block for input */
			if( msec != 0 ) {
				/* if there is already a poll that will
				 * timeout sooner than ours, we don't
				 * bump our own watchdog timer.
				 */
			if( nd->nd_inevent.e_procp && 
				((lbolt + (msec/10)) < nd->nd_ipsleep) )
				nd->nd_ipsleep =  lbolt + (msec / 10 ) ;
			pollopen( &nd->nd_inevent );
			}

			/* 2nd look */
			if( nd->nd_norq != nd->nd_nrq ) {
				ev &= POLLIN;
			} else  ev &= ~POLLIN;
		}
	}


	/** calculate the number of empty slots **/
	 if( nd->nd_swq < nd->nd_nowq ) 
		empty = NE_NUMBUFFS - (nd->nd_nowq - nd->nd_swq);	
	 else {
		if( nd->nd_swq > nd->nd_nowq ) empty = 
		NE_NUMBUFFS - ((nd->nd_nowq + NE_NUMBUFFS) - nd->nd_swq);
		else  empty = NE_NUMBUFFS;
	}

	if( ((ev & POLLOUT) != 0) &&  ( empty < (ne_lowwater + 2 )) ) {
		/* block for input */
		if( msec != 0 )	{
			/* if there is already a poll that will
			 * timeout sooner than ours, we don't
			 * bump our own watchdog timer.
			 */
			if( nd->nd_outevent.e_procp && 
				((lbolt + (msec/10)) < nd->nd_opsleep) )
				nd->nd_opsleep =  lbolt + (msec / 10 ) ;

			pollopen(  &nd->nd_outevent );
		}

			/* 2nd look */
		 if( nd->nd_swq < nd->nd_nowq ) 
			empty = NE_NUMBUFFS - (nd->nd_nowq - nd->nd_swq);	
		else 	{
			if( nd->nd_swq > nd->nd_nowq ) empty = 
			NE_NUMBUFFS - ((nd->nd_nowq + NE_NUMBUFFS) - nd->nd_swq);
			else  empty = NE_NUMBUFFS;
		}

		if( empty > ne_lowwater ) 	ev &= POLLOUT;
		else 	ev &= ~POLLOUT;
	}
	return ev;
}

 /* the interrupt vectoring functions */
static void
ne0intr()
{
	neintr(0);
}

static void
ne1intr()
{
	neintr(1);
}

static void
ne2intr()
{
	neintr(2);
}

static void
ne3intr()
{
	neintr(3);
}

/* buffer successor/predecessor in ring? */
#define succ(n) (((n)+1 >= nd->nd_rxend/DS_PGSIZE) ? (nd->nd_txstart+PKTSZ)/DS_PGSIZE : (n)+1)
#define pred(n) (((n)-1 < (nd->nd_txstart+PKTSZ)/DS_PGSIZE) ? nd->nd_rxend/DS_PGSIZE-1 : (n)-1)

/*
 * Controller interrupt.
 */
void
neintr(unit)
{
	register struct nedata_c *nd;
	register neBase;
	unsigned char cmd, isr, pend, lastfree, nxt;
	int 
		len,  /* lenth of incoming packet */
		i,    /* generic iteration counter */
		rqf,   /* wakeup read side flag */
		wqf,   /* wakeup write side flag */
		oldstate, /* previous state */
		pri, 	  /* previous interrupt priority */
		rempty,	  /* number of empty slots in the read buffer array */
		empty,	  /* number of empty slots in write buffer array */
		oldpri,	  /* retained interrupt priority */
		vector;	  /* same as unit */


		/* let's see what this value is */
	oldpri = pri = sphi();
		/* set up a pointer to our private data */
	nd = &nedata_c[unit];

		/* bad pointer ?? */
	if( nd == NULL) return;

		/* check re-entrancy */
	if( nd->nd_reentry ) {
		cmn_err( CE_CONT, "reentering neintr\n" );
		return;
	}
	nd->nd_reentry = 1;

		/* get our io port base */
	neBase = nd->nd_port;

		/* stop any further card interrupts */
	outb (neBase+ds0_imr, (nd->nd_mask  & 0x00) );

		/* flag to enable wakeups */
	wqf = rqf = 0;

		/* save interrupt vector */
	i = vector = unit;


		/* store the current command */
	cmd = inb( neBase+ds_cmd );

loop:

		/* get the interrupt status */
	isr = inb( neBase + ds0_isr );

		/* clear the interrupt */
	outb(neBase + ds_cmd, DSCM_NODMA | DSCM_START );
		NEPAUSE
	outb(neBase + ds0_isr, isr );
		NEPAUSE

		/* if received with error, clear the error */
	if( isr & DSIS_RXE ) {
		nd->nd_ierrors++;
		NEPAUSE
		inb( neBase + ds0_rsr);
		NEPAUSE
		inb( neBase + 0xD );
		NEPAUSE
		inb( neBase + 0xE );
		NEPAUSE
		inb( neBase + 0xF );
	}

		/* if received, get it out of the board memory */
	if( isr & (DSIS_RXE | DSIS_RX | DSIS_ROVRN )) {
		nd->nd_ipackets++;

			/* flip to page 1 */
		outb( neBase + ds_cmd, DSCM_START | DSCM_NODMA | DSCM_PG1 );
		NEPAUSE
			/* store the current buffer page num */
		pend = inb( neBase + ds1_curr );
		NEPAUSE
			/* flip to page 0 */
		outb( neBase + ds_cmd, DSCM_START | DSCM_NODMA | DSCM_PG0 );
		NEPAUSE
			/* get the boundry pointer */
		lastfree = inb( neBase + ds0_bnry );

			/* if we have looped around the recv. ring buffer,
			 * adjust the pointers 
			 */
		if( lastfree >= nd->nd_rxend / DS_PGSIZE )
			lastfree = ( nd->nd_txstart+PKTSZ) / DS_PGSIZE ;
		if( pend < lastfree  && nd->nd_cur < pend )
			lastfree = nd->nd_cur;
		else
			if( nd->nd_cur > lastfree )
				lastfree = nd->nd_cur;

		while( pend != lastfree ) {
			if( nd->nd_state == NE_DMAWAIT )	{
				  /* This should not happen. We have
				   * locked off card interrupts, so there
				   * should not be another copy of neintr
				   * happening in the dma loop. nerestart
				   * should have turned off card interrupts
				   * before starting a dma and they shouldn't
				   * be turned back on until the dma is complete
				   * This is an error, so we are going to clear
				   * it, by taking the card out of dma mode.
				   */
				outb (neBase+ds0_isr, DSIS_RDC);
			}

				/* save state and then change it */
			oldstate = nd->nd_state;
			nd->nd_state = NE_DMAWAIT;

				/* get the ethernet packet header */
			nefetch( nd , &nd->nd_ph, lastfree * DS_PGSIZE,
				sizeof( nd->nd_ph ) );

				/* set up the byte address of the packet */
			nd->nd_ba = lastfree * DS_PGSIZE + sizeof( nd->nd_ph);

				/* check to be sure the receive is complete */
			if( nd->nd_ph.pr_status == DSRS_RPC ||
			  nd->nd_ph.pr_status == 0x21 ) {
				    /* get the data length */
			    len = nd->nd_ph.pr_sz0 + (nd->nd_ph.pr_sz1 << 8 );

				/* check for sane len */
			    if( len < ETHER_MIN_LEN || len > ETHER_MAX_LEN )
				goto brr;

				/* vaidate a slot to hold
				 * the data 
				 */

			     if( nd->nd_nrq < nd->nd_norq ) 
					rempty = NE_NUMBUFFS - (nd->nd_norq - nd->nd_nrq);	
			     else  {
					if( nd->nd_nrq > nd->nd_norq ) rempty = 
						NE_NUMBUFFS - ((nd->nd_norq + NE_NUMBUFFS) - nd->nd_nrq);
					else  rempty = NE_NUMBUFFS;
			     }

					/* update buffer supply statistic */
				if( rempty < nd->nd_lor ) 
					nd->nd_lor = rempty;

				/* if we have less than two buffers remaining
				 * in our array, we need to stop buffering
				 * input from the wire and return
				 * so that some of the build up can 
				 * be cleared.
				 */
			    if( rempty < 2 ) {
				rqf++; /* flag wakeup */
				goto brr;
			     }

					/* get the packet */
			     nefetch( nd, &nd->nd_rq[nd->nd_norq].data[0], 
					nd->nd_ba,
					len  - sizeof( nd->nd_ph) 
					);

			      nd->nd_rq[nd->nd_norq].len = len;

			      rqf++; /* flag wakeup */

					/* increment buffer index */
			      nd->nd_norq++;

					/* wrap buffer index if needed */
			      if( nd->nd_norq > NE_NUMBUFFS - 1)
					nd->nd_norq = 0;

					/* restore previous state */
			      nd->nd_state = oldstate;
			}
brr:
			/* adjust pointer to next packet */
			nxt = nd->nd_ph.pr_nxtpg;

				/* validate pointer, re-adj if needed */
			if( nxt >= (nd->nd_txstart+PKTSZ)/DS_PGSIZE
			   && nxt <= nd->nd_rxend/DS_PGSIZE 
			   && nxt <= pend )
			     	nd->nd_cur = nxt;
			else  
				nd->nd_cur = nxt = pend;
			   
			lastfree = nxt;

				/* reset the card's boundary pointer */
			outb( neBase + ds0_bnry, pred(nxt) );
			NEPAUSE
				/* flip to page 1 */
			outb(neBase+ds_cmd, DSCM_START|DSCM_NODMA|DSCM_PG1);
			NEPAUSE
				/* get a new pointer for next read */
			pend = inb(neBase+ds1_curr);
			NEPAUSE
				/* flip back to page 0 */
			outb(neBase+ds_cmd, DSCM_START|DSCM_NODMA|DSCM_PG0);
			NEPAUSE
		} /* while more incoming packets */

			/* turn off card dma */
		outb(neBase+ds_cmd, DSCM_START|DSCM_NODMA);
		NEPAUSE

	} /* if recd something */

	/* Transmit error */
	if (isr & DSIS_TXE) {
			/* Need to read these registers to clear error status */
		inb( neBase + 0xD );
		NEPAUSE
		inb( neBase + 0xE );
		NEPAUSE
		inb( neBase + 0xF );
		NEPAUSE
			/* update statistics */
		nd->nd_collisions +=  inb(neBase+ds0_tbcr0);
		nd->nd_oerrors++;

			/* set wakeups */
		wqf++;
	}

	/* Packet Transmitted */
	if (isr & DSIS_TX) {
			/* update statistics */
		++nd->nd_opackets;
		nd->nd_collisions += inb(neBase+ds0_tbcr0);
		NEPAUSE

			/* set wakeups */
		wqf++;
	}

	/* Receiver ovverun? */
	if (isr & DSIS_ROVRN) {
			/* in case of receive buffer overrun
			 * we have to go through this ritual
			 * in order to restore sanity to the board:
			 *
			 * Set the recv'd byte count to zero.
			 */
		outb(neBase+ds0_rbcr0, 0);
		NEPAUSE
		outb(neBase+ds0_rbcr1, 0);
		NEPAUSE
			/* set the transmission control register
			 * to loop back 0 mode.
			 */
		outb(neBase+ds0_tcr, DSTC_LB0);
		NEPAUSE
			/* set the recv control register to
			 * monitor mode
			 */
		outb(neBase+ds0_rcr, DSRC_MON);
		NEPAUSE
			/* start up the card listening to itself */
		outb(neBase+ds_cmd, DSCM_START|DSCM_NODMA);
		NEPAUSE
			/* Set the recv control register back
			 * back to normal
			 */
		outb(neBase+ds0_rcr, DSRC_AB);
		NEPAUSE
			/* turn off the tranmission control
			 * loop back mode.
			 */
		outb(neBase+ds0_tcr, 0);
		NEPAUSE
	}

		/* restore old cmd and interrupt mask */
	outb (neBase+ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_START);
		NEPAUSE
	outb (neBase+ds_cmd, cmd);
		NEPAUSE
	outb (neBase+ds0_imr, (nd->nd_mask  & 0xff) );
		NEPAUSE

		/* Any more to send? */
	if( nd->nd_swq != nd->nd_nowq ) {
		nerestart(unit);
		wqf++;
	} else {
			/* unlock the xmiter and q */
		nd->nd_flags &= ~DSF_LOCK;
		nd->nd_state = NE_NORMAL;
	}	


		/* Still more to do? */
	isr = inb (neBase+ds0_isr);
		NEPAUSE

	if(isr) {
		goto loop;
	}

		/* do read side wakeups */
	if( rqf ) {
		defer( wakeup, nd->nd_rq );
		rqf = 0;
		if( nd->nd_inevent.e_procp ) {
			defer( pollwake,  &nd->nd_inevent );
		}
	}
	
		/* write side wakeup */
	if( wqf ) {
		defer( wakeup, nd->nd_wq );
		wqf = 0;
	}

	/* calculate the number of empty slots, for pollwake */
	 if( nd->nd_swq < nd->nd_nowq ) 
		empty = NE_NUMBUFFS - (nd->nd_nowq - nd->nd_swq);	
	 else {
		if( nd->nd_swq > nd->nd_nowq ) empty = 
		NE_NUMBUFFS - ((nd->nd_nowq + NE_NUMBUFFS) - nd->nd_swq);
		else  empty = NE_NUMBUFFS;
	}

	if( empty > ( ne_lowwater + 2 ) && nd->nd_outevent.e_procp ) {
		defer( pollwake,  &nd->nd_outevent );
	}
	nd->nd_reentry = 0;
	spl(oldpri);
	return;
}

/*
 * Setup output on interface.
 * Get a datagram to send from nd_wq buffers,
 * and copy it to the xmit buffer before starting the output.
 * return true if an interrupt can be expected from what we are doing here
 */
static int
nerestart(unit)
int unit;
{
	register struct nedata_c *nd;
	int len, i, total,t, oldstate;
	register neBase;
	u_short word;
	u_char cmd;
	char n_cmd, n_isr, n_tsr;

	nd = &nedata_c[unit];

		/* lock things up right away */
	oldstate = nd->nd_state;
	nd->nd_state = NE_RRUN;

	if( oldstate == NE_RRUN ) return(1);

		/* our io port base */
	neBase = nd->nd_port;

		/* nothin to do ?? */
	if(  nd->nd_swq == nd->nd_nowq ) {
		nd->nd_state = oldstate;
		return(0);
	}

		/* if the board or structs haven't been initialized */
	if ((nd->nd_if.if_flags & IFF_RUNNING ) == 0) {
		nd->nd_state = oldstate;
		return(0);
	}

		/* if we haven't completed the last transmission
		 * return because we'll soon get an interrupt anyway
		 *
	if (nd->nd_flags & DSF_LOCK || nd->nd_state > NE_NORMAL ) {
		return(0);
	}
		*/

	cmd = inb(neBase+ds_cmd);
	NEPAUSE
	/*
	 * The DS8390 has only one transmit buffer, if it is busy we
	 * must wait until the transmit interrupt completes.
	 */
	outb(neBase+ds_cmd,DSCM_NODMA|DSCM_START);
	NEPAUSE

	if (inb(neBase+ds_cmd) & DSCM_TRANS) {
		nd->nd_state = oldstate;
		return(0);
	}

		/* look at state, if we are already waiting for dma
		 * we do not want to start another, this would indicate
		 * that the receiver is doing dma, so we don't want to
		 * replace the flag in oldstate 'cause that dma flag might
		 * have changed since we copied it.
		 */
	if( oldstate == NE_DMAWAIT ) { 
		return(0); 
	}

		/* go to work loading board. turn off interrupts */
		/* store old cmd */
	inb(neBase+ds_cmd);
	NEPAUSE
		/* flip to page 0 */
	outb (neBase+ds_cmd, DSCM_NODMA|DSCM_PG0|DSCM_START);
	NEPAUSE
		/* turn off interrupts on card */
	outb (neBase+ds0_imr, 0 );
	NEPAUSE

		/* double check . If recieve interrput code is
		 * doing dma wait til later, in case of race */
	if( nd->nd_state == NE_DMAWAIT ) {
		return(0); 
	}

		/* now, lock things up and we should have sole control
		 * of the card until we turn card interrupts back on.
		 */
	nd->nd_state = NE_DMAWAIT;	/* don't mess with wq */
	nd->nd_flags |= DSF_LOCK;	/* prevent re-entry */

		/* determine data length */
	i = t = nd->nd_wq[nd->nd_swq].len; 

		/* if we are a NE2000 round up dma size to words */
	if ((nd->nd_mode & DSDC_WTS) && (t & 1) )
		t++; 

		/* Setup for remote dma */
	outb (neBase+ds0_isr, DSIS_RDC);
	NEPAUSE
	outb (neBase+ds0_rbcr0, t);
	NEPAUSE
	outb (neBase+ds0_rbcr1, t>>8);
	NEPAUSE
	outb (neBase+ds0_rsar0, nd->nd_txstart);
	NEPAUSE
	outb (neBase+ds0_rsar1, nd->nd_txstart>>8);
	NEPAUSE
		/* Execute dma & stuff to card */
	outb (neBase+ds_cmd, DSCM_RWRITE|DSCM_PG0|DSCM_START);
	NEPAUSE
	total = t;
	counter = 10000;
	if (nd->nd_mode & DSDC_WTS) {
		        /* Word Mode */
		if (i > 1) {

			/* if the data is odd in len, add a pad for the dma */
			if (i & 1) {
					/* captuer odd byte into a short */
				word = (ushort) nd->nd_wq[ nd->nd_swq].data[ (nd->nd_wq[ nd->nd_swq ].len - 1) ] ;
				word = (ushort) ((word << 8 ) & 0xff00 );

					/* put the data into the buffer */
				ee_outsw(neBase+ne_data,
					&nd->nd_wq[ nd->nd_swq].data[0], 
					i / 2);

					/* now the final word */
					/* put the data into the buffer */
				ee_outsw(neBase+ne_data,
					&word, 
					1);

					/* Wait till done, then shutdown dma */
				while ((inb (neBase+ds0_isr) & DSIS_RDC) == 0 && counter-- > 0)
					;
				outb (neBase+ds0_isr, DSIS_RDC);
				NEPAUSE

					/* restore cmd */
				outb (neBase+ds_cmd, cmd);

					/* update the index */
				if( nd->nd_swq > NE_NUMBUFFS - 2 )
					nd->nd_swq = 0;
				else nd->nd_swq++;
			} else {
					/* length is even,
					 * no need for extra word */
				n_cmd = inb(neBase+ds_cmd );
				NEPAUSE
				n_isr = inb(neBase+ds0_isr);
				NEPAUSE
				n_tsr = inb(neBase+ds0_tsr);
				NEPAUSE

					/* put the data into the buffer */
				ee_outsw(neBase+ne_data,
					&nd->nd_wq[ nd->nd_swq].data[0], 
					i / 2);

					/* Wait till done, then shutdown dma */
				counter = 10000;
				while ((inb (neBase+ds0_isr) & DSIS_RDC) == 0 && counter-- > 0)
					;

				if( counter == 0 )
					cmn_err( CE_CONT, "<ne:counter=0>\n" );
				outb (neBase+ds0_isr, DSIS_RDC);
				NEPAUSE
				outb (neBase+ds_cmd, cmd);
				NEPAUSE

					/* update the index */
				if( nd->nd_swq > NE_NUMBUFFS - 2 )
					nd->nd_swq = 0;
				else nd->nd_swq++;
			}
		}
	} else {
	            /* Byte Mode */
		if (i > 0)
				/* put the data into the buffer */
			ee_outsw(neBase+ne_data,
				&nd->nd_wq[ nd->nd_swq].data[0], 
				i );

				/* Wait till done, then shutdown dma */
		while ((inb (neBase+ds0_isr) & DSIS_RDC) == 0 && counter-- > 0)
				;
		outb (neBase+ds0_isr, DSIS_RDC);
		outb (neBase+ds_cmd, cmd);

			/* update the index */
		if( nd->nd_swq > NE_NUMBUFFS - 2 )
			nd->nd_swq = 0;
		else nd->nd_swq++;
	}

	len = total = t;
	if (len < ETHER_MIN_LEN) len = ETHER_MIN_LEN;

		/* transmit length */
	outb(neBase+ds0_tbcr0,len&0xff);
	NEPAUSE
	outb(neBase+ds0_tbcr1,(len>>8)&0xff);
	NEPAUSE

		/* pointer to begining of transmit packet */
	outb(neBase+ds0_tpsr, nd->nd_txstart/DS_PGSIZE);
	NEPAUSE

		/* transmit complete interrupt will change
		 * state of nd_state, but this state indicator
		 * will keep from attempting to call
		 * nerestart again
		 */
	nd->nd_state = NE_XMIT;

		/* cmd = transmit */
	outb(neBase+ds_cmd, DSCM_TRANS|DSCM_NODMA|DSCM_START);
	NEPAUSE

	/*
	 * turn on card interrupts.
	 */
	outb (neBase+ds0_imr, (nd->nd_mask  & 0xff) );

		/* return 1 says we have done something which should
		 * cause a future interrupt which will awaken
		 * sleeping write() call
		 */		 
	return(1);
}

/*****/
/*
 * necycle()
 *
 * Do a wakeup of any sleeping ne's at regular intervals.
 */

static void 
necycle(unit)
int unit;
{
	register struct nedata_c *nd;

	nd = &nedata_c[unit];


	if (nd->nd_refc ) {
		wakeup (nd->nd_wq );
		wakeup (nd->nd_rq );

			/* restore sanity to polls and do any
			 * overdue pollwakes
			 */
		if( (nd->nd_opsleep  != 0) && nd->nd_opsleep < lbolt ) {
			nd->nd_opsleep = 0;
			if( nd->nd_outevent.e_procp )
				pollwake( &nd->nd_outevent );
		}

		if( (nd->nd_ipsleep != 0) && ( nd->nd_ipsleep < lbolt ) ) {
			nd->nd_ipsleep = 0;
			if( nd->nd_inevent.e_procp )
				pollwake( &nd->nd_inevent );
		}
	}

	/* Schedule next cycle. */
	if ( nd->nd_refc && nd->nd_timwait )
		timeout ( &nd->nd_tim, HZ / 10, necycle, unit);
}

/**** end of ne.c ****/

