/* THIS IS THE ex.c 4.2 DRIVER MODIFIED TO SUPPORT TCI/EXCELAN DECnet */

/*
 * (1)	Post multiple receives with mbuffers instead of copying
 *	from rbuf.
 *
 * (2)	Investigate recovery of mbuffers queued to EXOS controller
 *	when it is reset/initialized.
 *
 * (3)	Compress Valid's interfaces into a single one.
 *
 */

static char rcsid[] = "$Header: ex.c,v 820.1 86/12/04 19:55:21 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 		*
 *	Incoroporated.							*
 *									*
 *	The copyright notice appearing above is included to provide	*
 *	statutory protection in the event of unauthorized or 		*
 *	unintentional public disclosure.				*
 *									*
 ************************************************************************/

/*	ex.c	1.0	85/04/10	*/

/*
 * Excelan EXOS 201(Multibus) Link Level Ethernet Interface Driver
 */

#include "ex.h"			/* Configuration constants */
#include "xm.h"			/* Defines download driver */
#include "xd.h"			/* Defines (TCI/DECnet) logical link driver */

#include "../h/types.h"		/* Commonly used data types */
#include "../s32dev/exreg.h"	/* EXOS 201 Multibus Controller definitions */

#include "../machine/pte.h"	/* VM page table definitions */
#include "../h/param.h"		/* Process, memory and file sys parameters */
#include "../h/systm.h"		/* Kernel global variables */
#include "../h/mbuf.h"		/* Net/Comm buffer definitions */
#include "../h/socket.h"	/* Socket interface definitions */
#include "../h/vmmac.h"		/* Virtual memory address conversion macros */
#include "../h/map.h"		/* Resource allocation maps */
#include "../h/ioctl.h"		/* System wide IOCTL definitions */
#include "../h/dir.h"		/* File directory definitions */
#include "../h/buf.h"
#include "../h/inode.h"
#include "../h/uio.h"
#include "../h/user.h"		/* U Page definitions (per user blocks) */
				/*  ../h/user.h includes 
					../machine/pch.h
					../h/dmap.h
					../h/time.h
					../h/resource.h
					../h/errno.h
				 */
#include "../h/proc.h"
#include "../h/kernel.h"	/* Kernel variables */

#include "../net/if.h"		/* Network interface definitions */
#include "../net/netisr.h"	/* Network interrupt service definitions */
#include "../net/route.h"	/* Internet routing tables */
#include "../netinet/in.h"	/* Internet definitions */
#include "../netinet/in_systm.h"
#include "../netinet/ip.h"	/* DARPA IP (Internet Protocol) definitions */
#include "../netinet/ip_var.h"	/* IP global variables */

#include "../netinet/if_ether.h"	/* Address resolution protocol */


#include "../machine/cpu.h"	/* Machine specific memory layout and sizing */
#include "../s32dev/mbvar.h"	/* Multibus definitions (autoconfig) */
#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 "../s32dev/exioctl.h"	/* Definition of ioctl's */
#if NXD > 0
#include "../tci/tci.h"		/* TCI type defines */
#include "../tci/community.h"	/* TCI/DECnet CommUnity defines */
#endif NXD

#include "../s32dev/exdef.h"	/* EXOS 201 Driver definitions */

#define EXWATCHINTVL	20	/* call exWatch every x secs */
#define EXMAXOUTPUTQLEN	16	/* Maximum output queue length */


/****************************************************************
 * Forward routine declarations					*
 *								*
 ****************************************************************/

int exProbe();		/* EXOS controller existence test probe routine */
int exSlave();		/* Slave probe routine */
int exAttach();		/* Attach for protocol processing */
int exIntr();		/* Interrupt service routine */
int exInit();		/* Initialize controller */
int exOutput();		/* Output packet (through net interface) */
int exIoctl();		/* Network IOCTL processing */
int exWatch();		/* Watchdog processing */
struct exMessage *exGetCbuf();	/* Get EXOS buffer for command message */
int mbufGather();	/* Gather mbuffers into contiguous buffer */
#ifdef notdef
struct mbuf *mbufScatter();	/* Scatter contiguous buffer into mbuffers */
#endif notdef

/****************************************************************
 * Owned variables						*
 *								*
 ****************************************************************/

u_short exstd[] = {0};			/* Standard I/O addresses */
struct mb_device *EXINFO(NEX);		/* Multibus device descriptors */
struct mb_driver exdriver =		/* Driver software descriptor */
    {
    0,		/* No bus reset routine */
    exProbe,	/* Controller existence board routine */
    0,		/* No slave controllers */
    exAttach,	/* Protocol attach processing */
    exstd,	/* No standard CSR addresses */
    "ex", 	/* Device name */
    &EXINFO(0),	/* Pointer to device structures */
    0,		/* No controller name */
    0		/* No controller structures */
    };

struct ex_softc EX_SOFTC(NEX);		/* Controller state and data */

/* exProbe - probe for existence of a controller
 *
 * Synopsis:
 *	result = exProbe(&exaddr, &intr, unit);
 *	int result;
 *	struct exdevice exaddr;
 *	int intr ();
 *	int unit;
 *
 * Parameter Usage:
 *	result	0 => no controller, 1 => controller found.
 *	exaddr	Multibus memory address of controller registers.
 *	intr	Interrupt service routine address.
 *	unit	Unit number (zero relative).
 *
 * Called by:
 *	mbconf [s32/autoconf.c]
 *
 * Description:
 *	This routine is called during multibus autoconfiguration.
 *
 *	****** SAY MORE HERE *****
 *
 */

exProbe(exaddr, intr, unit)
    register struct exdevice * exaddr;
    int (* intr) ();
    int unit;
{
    register int i;		/* Loop index and scratch cell */
    extern int cvec;		/* Interrupt vector used during config */

/* Existence test by reading I/O port A; also resets the controller */

    i = exaddr->porta;

/* Wait for at least 2 seconds for self-test to complete */

    i += 1000000;
    while (((exaddr->portb & EX_TESTOK) == 0) && --i)
	;

/* Check if self-test completed ok */

    if ((exaddr->portb & EX_TESTOK) == 0)
	return (0);

/*****************************/
/*???????????????????????????*/
/* Hardcode interrupt vector */
/*???????????????????????????*/
/*****************************/

    cvec = 5 - 1;

/* Indicate success in probing for board */

    return (1);
}

/*
 * Interface exists: make available by filling in network interface record.  
 * System will initialize the interface when it is ready to accept packets.  
 * A NET_ADDRS command is done to get the ethernet address.
 */

exAttach(mb)
    struct mb_device *mb;
{
    int unit;				/* Controller unit number */
    register struct ex_softc *xs;	/* Driver/Controller data structure */
    register struct ifnet *ifp;		/* Interface structure */
    register struct exdevice *exaddr;	/* Controller registers */
    struct sockaddr_in *sin;		/* Socket address structure */
    register struct net_addrs *bp;	/* EXOS message pointer */

/* */
    unit = mb->md_unit;
    xs = &EX_SOFTC(unit);
    ifp = &xs->xs_if;
    exaddr = (struct exdevice *)(mb->md_addr);

/* */
/**********************************************************/
/**???????????????????????????????????????????????????????*/
/* IS THIS THE CORRECT PLACE TO STASH THE exinfo ENTRIES? */
/**???????????????????????????????????????????????????????*/
/**********************************************************/

    EXINFO(unit) = mb;

/* */
    ifp->if_unit = unit;
    ifp->if_name = "ex";
    ifp->if_mtu = ETHERMTU;

/* Configure EXOS into DLL operational mode; without interrupts */
/* If configuration fails, abort attach */

    if (exConfig(unit, xs, exaddr, EX_INTnone, EX_OPMDLL))
	return (0);

/* Allocate and format LLNET_ADDRS request */

    bp = (struct net_addrs *)exGetCbuf(xs, HSAP_DLL, EXSAP_DLL);
    if(!bp)
	panic("LLNET_ADDRS");
    bp->request = LLNET_ADDRS;
    bp->mask = READ_OBJ;
    bp->slot = PHYSSLOT;

/* Post message to EXOS */

    bp->status |= MH_EXOS;
    exaddr->portb = 0;

/* Wait for response */

    bp = (struct net_addrs *)xs->x2hnext;
    while ((bp->status & MH_OWNER) == MH_EXOS)
	;

    printf("\t%02x-%02x-%02x-%02x-%02x-%02x HW %c.%c NX %c.%c\n",
	    bp->addrs[0], bp->addrs[1], bp->addrs[2], 
	    bp->addrs[3], bp->addrs[4], bp->addrs[5],
	    xs->cm.vc[2], xs->cm.vc[3], xs->cm.vc[0], xs->cm.vc[1]);

    bcopy((caddr_t)&bp->addrs[0], (caddr_t)&xs->xs_addr[0], 6);
/*
printf("xs->xs_addr = %02x-%02x-%02x-%02x-%02x-%02x\n",
	xs->xs_addr[0], xs->xs_addr[1], xs->xs_addr[2],
	xs->xs_addr[3], xs->xs_addr[4], xs->xs_addr[5]);
*/

    sin = (struct sockaddr_in *)&ifp->if_addr;
    sin->sin_family = AF_INET;
    ifp->if_init = exInit;
    ifp->if_output = exOutput;
    ifp->if_ioctl = exIoctl;
    ifp->if_reset = 0;
    if_attach(ifp);

/* Start ARP timeout services for this interface */

    arpattach(&xs->ac);

#ifdef VALIDnet
	/* VALID BULK */

#ifdef notdef
	ifp = &xs->if_bulk;
	ifp->if_unit = mb->md_unit;
	ifp->if_name = "ex-bulk";
	ifp->if_mtu = ETHERMTU;
	ifp->if_init = exInit;
	ifp->if_ioctl = exIoctl;
	ifp->if_output = exOutput;
	ifp->if_reset = 0;
	if_attach(ifp);
#endif notdef

	/* VALID CONN */

	ifp = &xs->if_conn;
	ifp->if_unit = mb->md_unit;
	ifp->if_name = "ex-conn";
	ifp->if_mtu = ETHERMTU;
	ifp->if_init = exInit;
	ifp->if_ioctl = exIoctl;
	ifp->if_output = exOutput;
	ifp->if_reset = 0;
	if_attach(ifp);

	/* VALID RPC */

	ifp = &xs->if_rpc;
	ifp->if_unit = mb->md_unit;
	ifp->if_name = "ex-rpc";
	ifp->if_mtu = ETHERMTU;
	ifp->if_init = exInit;
	ifp->if_ioctl = exIoctl;
	ifp->if_output = exOutput;
	ifp->if_reset = 0;
	if_attach(ifp);

#endif VALIDnet

}
 
/*
 * Initialization of interface; clear recorded pending operations, and 
 * reinitialize Multibus usage. Called at boot time, and at ifconfig time via 
 * exIoctl, with interrupts disabled.
 *
 ******************************
 * Check use of return value! *
 ******************************
 */

exInit(unit)
    int unit;
{
    register struct ex_softc *xs;	/* Driver/Controller data structure */
    register struct mb_device *mb;	/* Multibus device definition */
    register struct exdevice *exaddr;	/* Controller registers */
    register struct ifnet *ifp;		/* Network interface structure */
    register struct sockaddr_in *sin;	/* Socket address */
    register struct net_mode *bp;	/* EXOS message buffer */
    int s;				/* Saved interrupt priority level */

/* */
    xs = &EX_SOFTC(unit);
    mb = EXINFO(unit);
    exaddr = (struct exdevice *)(mb->md_addr);
    ifp = &xs->xs_if;

    /*
     * Initialize the output queue maximum length field
     */
    xs->output.ifq_maxlen = EXMAXOUTPUTQLEN;

    if(xs->flags & EX_TCInet)
	return(0);

/* */
    sin = (struct sockaddr_in *)&ifp->if_addr;
    if (sin->sin_addr.s_addr == 0)
	return (0);

    if (ifp->if_flags & IFF_RUNNING)
	goto justarp;

    if (exConfig(unit, xs, exaddr, EX_INTlevel, EX_OPMDLL))
	return (0);

/* Allocate and format LLNET_MODE request; select DLL mode */

    bp = (struct net_mode *)exGetCbuf(xs, HSAP_DLL, EXSAP_DLL);
    if(!bp)
	panic("LLNET_MODE");
    bp->request = LLNET_MODE;
    bp->mask = WRITE_OBJ;
    bp->options = EX_OPMDLL;
    bp->mode = MODE_PERF;

/* Post message to EXOS */

    bp->status |= MH_EXOS;
    exaddr->portb = 0;

/* Wait for response */

    bp = (struct net_mode *)xs->x2hnext;
    while ((bp->status & MH_OWNER) == MH_EXOS)
	;

/* Release response back to EXOS */

    bp->length = MBDATALEN;
    bp->status |= MH_EXOS;
    exaddr->portb = 0;
    xs->x2hnext = xs->x2hnext->next;

/* Start watchdog, post receive, and start any transmits */

    ifp->if_watchdog = exWatch;
    ifp->if_timer = EXWATCHINTVL;
    s = splnet();
    exHangRcv(xs, exaddr);
    exStart(xs, exaddr);
    xs->xs_if.if_flags |= IFF_UP|IFF_RUNNING;
    splx(s);

justarp:
    if_rtinit(&xs->xs_if, RTF_UP);
    arpwhohas(&xs->ac, &sin->sin_addr);
}

/*
 * Reset, test, and configure EXOS. 
 * It is called by exInit, and exAttach.
 * itype = 0 -> no interrupts; itype = 1 -> enable interrupts.
 * Returns 0 if successful, 1 if self-test failed.
 */

#define PhysOffset(addr) (xs->physBaseOffset + ((u_char*)(addr) - xs->virtBaseAddr))

exConfig(unit, xs, exaddr, itype, mode)
    register int unit;			/* Controller unit number */
    register struct ex_softc *xs;	/* Driver/Controller data structure */
    register struct exdevice *exaddr;	/* Controller registers */
    int itype;				/* Type of EXOS interrupt */
    u_long mode;			/* Mode for EXOS controller */
{
    register struct confmsg *cm;	/* Configuration message */
    register union exEXOSmessage *bp;	/* EXOS message buffer */
    int i;				/* Loop index and scratch cell */
    u_long shiftreg;			/* Serialization register for config */
    static u_char cmaddr[8] = {0xFF, 0xFF, 0, 0};

    cm = &xs->cm;

#ifdef XND > 0
    if(mode == EX_OPMFEP)
	xs->flags = EX_TCInet;
    else
#endif XND
	xs->flags = 0;

/* Reset EXOS, wait for self-test to complete */
/* Value of porta is added to keep compiler from optimizing out "read" */

    i = exaddr->porta + 200000;
    while (((exaddr->portb & EX_TESTOK) == 0) && i--)
	;

    if ((exaddr->portb & EX_TESTOK) == 0)
	{
	printf("ex%d: reset in configuration failed\n", unit);
	return (1);
	}

/* Set up configuration message */

    cm->rsrv1 = 1;		/* 1=> New style config message */
    cm->cc = 0xFF;		/* Clear completion code */
    cm->opmode = mode;
    cm->dfo[0] = EX_DFOadapt;	/* Enable host data order conversion */
    cm->dfo[1] = EX_DFOadapt;
    cm->dcn[0] = 1;
    cm->dcn[1] = 0;
    cm->dcn[2] = 0;
    cm->ham = EX_HAMabsolute | EX_HAMset;
    cm->rsrv3 = 0;
    cm->mapsiz = 0;

    cm->byteptrn[0] = 0x01;	/* EXOS deduces data order of host */
    cm->byteptrn[1] = 0x03;	/*  by looking at this pattern */
    cm->byteptrn[2] = 0x07;
    cm->byteptrn[3] = 0x0F;
    cm->wordptrn[0] = 0x0103;
    cm->wordptrn[1] = 0x070F;
    cm->lwordptrn = 0x0103070F;
    for (i=0; i<20; i++)
	cm->rsrvd[i] = 0;

    cm->mba = 0xFFFFFFFF;
    cm->nproc = 0xFF;
    cm->nmbox = 0xFF;
    cm->nmcast = 0xFF;
    cm->nhost = 1;

/* Initialize virtual to physical mapping of communication area */

    xs->virtBaseAddr = (u_char *)&xs->h2xhdr;
    xs->physBaseSeg = vtop(xs->virtBaseAddr);
    xs->physBaseOffset = xs->physBaseSeg & 0xf;
    xs->physBaseSeg = xs->physBaseSeg & 0xfffff0;
    printf("ex%d: virtBase:%x, physBase:%x, physOffset:%x\n", unit,
	xs->virtBaseAddr, xs->physBaseSeg, xs->physBaseOffset);

/* Set up request queue header and buffers; level interrupt notification */

    cm->h2xba = xs->physBaseSeg;
    cm->h2xhdr = PhysOffset(&xs->h2xhdr);
    cm->h2xtyp = itype;

    for (bp = &xs->h2xmsg[0]; bp < &xs->h2xmsg[NH2X]; bp++)
	{
	bp->exMessage.link = PhysOffset(bp+1) + sizeof(struct exMessage *);
	bp->exMessage.rsrv = 0;
	bp->exMessage.length = MBDATALEN;
	bp->exMessage.status = MH_HOST;
	bp->exMessage.next = (struct exMessage *)(bp+1);
	}
    
    xs->h2xmsg[NH2X-1].exMessage.link = PhysOffset(&xs->h2xmsg[0]) +
						sizeof(struct exMessage *);
    xs->h2xhdr = PhysOffset(&xs->h2xmsg[0]) + sizeof(struct exMessage *);
    xs->h2xmsg[NH2X-1].exMessage.next = &xs->h2xmsg[0].exMessage;
    xs->h2xnext = &xs->h2xmsg[0].exMessage;

/* Set up reply queue header and buffers; level interrupt notification */

    cm->x2hba = xs->physBaseSeg;
    cm->x2hhdr = PhysOffset(&xs->x2hhdr);
    cm->x2htyp = itype;

    for (bp = &xs->x2hmsg[0]; bp < &xs->x2hmsg[NX2H]; bp++)
	{
	bp->exMessage.link = PhysOffset(bp+1) + sizeof(struct exMessage *);
	bp->exMessage.rsrv = 0;
	bp->exMessage.length = MBDATALEN;
	bp->exMessage.status = MH_EXOS;
	bp->exMessage.next = (struct exMessage *)(bp+1);
	}
    
    xs->x2hmsg[NX2H-1].exMessage.link = PhysOffset(&xs->x2hmsg[0])
					+ sizeof(struct exMessage *);
    xs->x2hhdr = PhysOffset(&xs->x2hmsg[0]) + sizeof(struct exMessage *);
    xs->x2hmsg[NX2H-1].exMessage.next = &xs->x2hmsg[0].exMessage;
    xs->x2hnext = &xs->x2hmsg[0].exMessage;

/*
 * Write config msg address to EXOS and wait for configuration to 
 * complete (guaranteed response within 2 seconds).
 */

    shiftreg = vtop(&xs->cm);
    printf("ex%d: configuration message at v:%x p:%x\n",
		unit, &xs->cm, shiftreg);
    for (i = 4; i < 8; i++)
	{
	cmaddr[i] = (u_char)(shiftreg & 0xFF);
	shiftreg >>= 8;
	}

    for (i = 0; i < 8; i++)
	{
	while (exaddr->portb & EX_UNREADY) 
	    ;
	DELAY(200);
	exaddr->portb = cmaddr[i];
	}

    for (i = 100000; (cm->cc == 0xFF) && i; --i)
	DELAY(10);

    if (cm->cc)
	{
	printf("ex%d: configuration failed; cc=%x\n", unit, cm->cc);
	return (1);
	}

    return (0);
}

/*
 * Start or re-start output on interface. Get another datagram to send off of 
 * the interface queue, and map it to the interface before starting the output.
 * This routine is called by exInit(), exOutput().  In all cases,
 * interrupts by EXOS are disabled on entry.
 */
exStart(xs, exaddr)
    register struct ex_softc *xs;	/* Driver/Controller data structure */
    register struct exdevice *exaddr;	/* Controller registers */
{
    register struct enet_xmit *bp;	/* Current EXOS message buffer */
    register struct mbuf *m;		/* Current mbuffer */
    int len;
    struct mbuf *m0;
    register int nb;		/* Number of EXOS fragments */
    register int tlen;		/* Total packet length */

/* */

    IF_DEQUEUE(&xs->output, m);
    if (m == 0)
	return;
    /*
     * Get a transmit request.
     */
    bp = (struct enet_xmit *)exGetCbuf(xs, HSAP_DLL, EXSAP_DLL);
    if(!bp)
	panic("LLTRANSMIT");
    bp->request = LLRTRANSMIT;

    nb = 0;
    tlen = 0;
    m0 = 0;

    bp->mid = (int)m;	/* save mbuf pointer to free when xmt done */

    /*
     * point directly to the first group of mbufs to be transmitted. The
     * hardware can only support NFRAGMENTS descriptors.
     */
    while (m && ((nb < NFRAGMENTS-1) || (m->m_next == 0)) )
	{
	bp->blks[nb].length = (u_short)m->m_len;
	*(u_long *)bp->blks[nb].address = vtop(mtod(m, char *));
	tlen += m->m_len;
	m0 = m;
	m = m->m_next;
	nb++;
	}

    /* null end of chain pointed to by mb_mid, to be freed when xmit done */
    if (m0)
	m0->m_next = 0;

    /*
     * if not all of the descriptors would fit then merge remaining data
     * into the transmit buffer, and point to it.  Note: the mbufs are freed
     * during the merge, they do not have to be freed when we get the 
     * transmit interrupt.
     */
    if (m)
	{
	len = mbufGather(xs->tbuf, sizeof(xs->tbuf), m);
	bp->blks[nb].length = (u_short)len;
	*(u_long *)bp->blks[nb].address = vtop(xs->tbuf);
	tlen += len;
	nb++;
	}

    /*
     * If the total length of the packet is too small, pad the last frag
     */
     /*************************************************/
     /**************** CHECK THIS OUT *****************/
     /*************************************************/

    if (tlen - sizeof(struct ether_header) < ETHERMIN)
	{
	len = (ETHERMIN + sizeof(struct ether_header)) - tlen;
	bp->blks[nb-1].length += (u_short)len;
	tlen += len;
	}

    /* set number of fragments in descriptor */

    bp->nblock= nb;

    xs->flags |= EX_XPENDING;
    bp->status |= MH_EXOS;
    exaddr->portb = 0;
}

/*
 * interrupt service routine.
 */

exIntr(unit)
    int unit;
{
    struct mb_device *mb;		/* Multibus device descriptor */
    register struct ex_softc *xs;	/* Driver/Controller data structure */
    register struct exdevice *exaddr;	/* Controller registers */
    register struct exDLLhdr *bp;	/* Current EXOS message buffer */
    register struct mbuf *m;		/* Current mbuffer */
    int len;
    struct mbuf *m0;
    register int nb;		/* Number of EXOS fragments */
    register int tlen;		/* Total packet length */

/* Init locals */

    mb = EXINFO(unit);
    xs = &EX_SOFTC(unit);
    exaddr = (struct exdevice *)(mb->md_addr);
    bp = (struct exDLLhdr *)xs->x2hnext;

/* Check for and clear interrupt from this controller */

    if (!(exaddr->portb & EX_INTERRUPT))
	return(0);
    exaddr->porta = 0;

/* Service interrupt request, process all messages outstanding to host */

    while ((bp->status & MH_OWNER) == MH_HOST)
	{
	switch(bp->hsap)
	    {
	    case HSAP_DLL:
		switch (bp->request)
		    {
		    case LLRECEIVE:
			exRecv(unit, xs, bp);
			exHangRcv(xs, exaddr);
			break;

		    case LLTRANSMIT:
		    case LLRTRANSMIT:
			if ((xs->flags & EX_XPENDING) == 0)
			    printf("ex%d: xmt complete, wasn't pending!\n", unit);

			xs->flags &= ~EX_XPENDING;
			xs->xs_if.if_opackets++;
			if (bp->reply != LL_OK)
			    if (bp->reply & LLXM_1RTRY)
				xs->xs_if.if_collisions++;
			    else
				if (bp->reply & LLXM_RTRYS)
				    xs->xs_if.if_collisions += 2; /* guess */
				else
				    if (bp->reply & LLXM_ERROR)
					if ((xs->xs_if.if_oerrors++ % 100) == 0)
					    printf("ex%d: 100 transmit errors=%b\n",
						    unit, bp->reply, XMIT_BITS);

			if (bp->mid)
			    m_freem((struct mbuf *)bp->mid);
			exStart(xs, exaddr);
			break;

		    case LLNET_STSTCS:
			xs->xs_if.if_ierrors += xs->xsa.crc;
			xs->flags &= ~EX_STATPENDING;
			break;

#if NXM > 0
		    case NET_DLOAD:
		    case NET_START:
			xmintr(xs, bp);
			break;

#endif NXM

		    default:
			printf("ex%d: DLL msg from EXOS unknown; req:0x%x reply:0x%x\n",
			    unit, bp->request, bp->reply);
		    }
		break;

#if NXD > 0
	    case HSAP_TCI:
		llintr(xs, bp);
		break;
#endif NXD

	    default:
		printf("ex%d: HSAP from EXOS unknown %d\n", bp->hsap);
	    }

	bp->length = MBDATALEN;
	bp->status |= MH_EXOS;		/* free up buffer */
	exaddr->portb = 0;		/* tell EXOS about it */
	bp = (struct exDLLhdr *)bp->next;
    }
    xs->x2hnext = (struct exMessage *)bp;

/* Interrupt was serviced */

    return(1);
}

/*
 * Get a request buffer, fill in standard values, advance pointer.
 */
struct exMessage *
exGetCbuf(xs, hsap, exsap)
    struct ex_softc *xs;
    u_char hsap;
    u_char exsap;
{
    register struct exMessage *bp;
    
    bp = xs->h2xnext;

    if((bp->status & (MH_EXOS|MH_DONE)) != 0)
	return(0);

    bp->status |= MH_DONE;
    bp->hsap = hsap;
    bp->exsap = exsap;

    bp->length = MBDATALEN;
    xs->h2xnext = xs->h2xnext->next;
    return(bp);
}

/*
 * Process Ethernet receive completion:  If input error just drop packet, 
 * otherwise examine packet to determine type.  If can't determine length from 
 * type, then have to drop packet, otherwise decapsulate packet based on type 
 * and pass to type-specific higher-level input routine.
 */

exRecv(unit, xs, bp)
    int unit;
    register struct ex_softc *xs;
    register struct enet_recv *bp;
{
    register struct ether_header *eh;
    register struct mbuf *m;
    int len, off, resid;
    register struct ifqueue *inq;
    int s;

    xs->xs_if.if_ipackets++;
    /*     total length            - header                - crc */
    len = bp->blks[0].length - sizeof(struct ether_header) - 4;
    if (bp->reply != LL_OK)
	{
	if (xs->xs_if.if_ierrors++ % 100 == 0)
	    printf("ex%d: 100 receive errors=%b\n", unit, bp->reply, RECV_BITS);
	    return;
	}
    eh = (struct ether_header *)(xs->rbuf);

    /*
     * Deal with trailer protocol: if type is PUP trailer get true type from
     * first 16-bit word past data.  Remember that type was trailer by 
     * setting off.
     */
    eh->etherType = ntohs((u_short)eh->etherType);
#ifdef 	EXDEBUG
    {
	char *s;
	char *sd=(char *)&eh->destAddr;
	char *ss=(char *)&eh->sourceAddr;
	int i,j;
	printf("%x ",eh->etherType);
	printf("R ");
	for (i=0 ; i < 6 ; i++ )
	    printf("%x ",sd[i]&0xFF);
	printf("	from	");
	for (i=0 ; i < 6 ; i++ )
	    printf("%x ",ss[i]&0xFF);
	printf(" dlen = %x\n        ",len);
	s = (char *)&xs->rbuf[sizeof(struct ether_header)];
	j = /*MIN(36,*/len/*)*/;
	for (i = 0 ; i < j ; i++)
	    printf("%x ",s[i]&0xFF);
	printf("\n");
    }
#endif 	EXDEBUG
#define	exdataaddr(eh, off, type)	((type)(((caddr_t)((eh)+1)+(off))))
    if (eh->etherType >= ETHERTYPE_TRAILER &&
	eh->etherType < ETHERTYPE_TRAILER+NUM_TRAILER)
	{
	off = (eh->etherType - ETHERTYPE_TRAILER) * 512;
	if (off >= ETHERMTU)
	    return;			/* sanity */
	eh->etherType = ntohs(*exdataaddr(eh, off, u_short *));
	resid = ntohs(*(exdataaddr(eh, off+2, u_short *)));
	if (off + resid > len)
	    return;			/* sanity */
	len = off + resid;
	}
    else
	off = 0;
    if (len == 0)
	return;

    /*
     * Pull packet off controller.  Off is nonzero if packet has trailing 
     * header; if_rqbget will then force this header information to be at 
     * the front, but we still have to drop the type and length which are 
     * at the front of any trailer data.
     */
/*    m = mbufScatter(xs->rbuf, len, off, sizeof(struct ether_header)); */
    m = (struct mbuf *)ecGet(((caddr_t)xs->rbuf)-2, len, off);

    if (m == 0)
	return;
    if (off)
	{
	m->m_off += 2 * sizeof (u_short);
	m->m_len -= 2 * sizeof (u_short);
	}
    switch (eh->etherType)
	{
#ifdef INET
	case ETHERTYPE_IP:
	    schednetisr(NETISR_IP);
	    inq = &ipintrq;
	    break;

	case ETHERTYPE_ARP:
	    arpinput(&xs->ac, m);
	    return;
#endif
#ifdef VALIDnet
	    case ETHERTYPE_VALID:
		{
		struct validDLLheader * vhdr;
		vhdr = mtod(m, struct validDLLheader *);
		if (vhdr -> reserved != 0) {
		    m_freem(m);
		    return;
		}

		m->m_off += sizeof(struct validDLLheader);
		m->m_len -= sizeof(struct validDLLheader);

		switch (vhdr -> pktType) {
		    case VALIDnet_CONN:
			inq = &conn_intrq;
			schednetisr(NETISR_CONN);
			break;

		    case VALIDnet_RPC:
			rpc_input(m);
			return;

#ifdef notdef
		    case VALIDnet_BULK:
			inq = &bulk_intrq;
			schednetisr(NETISR_BULK);
			break;
#endif notdef

		    default:
			m_freem(m);
			return;
		    }
		}
		break;
#endif VALIDnet
	default:
	    m_freem(m);
	    return;
	}

    if (IF_QFULL(inq))
	{
	IF_DROP(inq);
	m_freem(m);
	}
    else
	IF_ENQUEUE(inq, m);
}

/*
 * Hang a receive request. This routine is called by exInit
 * with interrupts disabled in both cases.
 *
 ************************************
 ***** What if exGetCbuf fails?? ****
 ***** Is exaddr and xs recalculation needed? Does caller already have them? *
 ************************************
 */
exHangRcv(xs, exaddr)
    register struct ex_softc *xs;
    register struct exdevice *exaddr;
{
    register struct enet_recv *bp;

/* Allocate and format LLRECEIVE request */
	
    bp = (struct enet_recv *)exGetCbuf(xs, HSAP_DLL, EXSAP_DLL);
    if(!bp)
	panic("LLRECEIVE");
    bp->request = LLRECEIVE;
    bp->nblock = 1;
    bp->blks[0].length = EXMAXRBUF;
    *(u_long *)bp->blks[0].address = vtop(xs->rbuf);

/* Post message to EXOS */

    bp->status |= MH_EXOS;
    exaddr->portb = 0;
}

/*
 * Ethernet output routine. Encapsulate a packet of type family for the local 
 * net.  Use trailer local net encapsulation if enough data in first packet 
 * leaves a multiple of 512 bytes of data in remainder.
 */

exOutput(ifp, m0, dst)
    register struct ifnet *ifp;
    struct mbuf *m0;
    struct sockaddr *dst;
{
    int type, s, error;
    u_char edst[6];
    struct in_addr idst;
    register struct ex_softc *xs = &EX_SOFTC(ifp->if_unit);
    register struct mbuf *m = m0;
    register struct ether_header *eh;
    register int off;
    int dllHeaderSize = 0;		/* Size of DLL header (0 => none) */
    struct validDLLheader dllHeader;	/* DLL header */
    node_t *n;

    dllHeader.reserved = 0;

    switch (dst->sa_family)
	{
#ifdef INET
	case AF_INET:
	    idst = ((struct sockaddr_in *)dst)->sin_addr;
	    if (!arpresolve(&xs->ac, m, &idst, &edst[0]))
		return (0);	/* if not yet resolved */
	    off = ntohs((u_short)mtod(m, struct ip *)->ip_len) - m->m_len;
	    /* need per host negotiation */
	    if ((ifp->if_flags & IFF_NOTRAILERS) == 0 &&
		off > 0 && (off & 0x1ff) == 0 &&
		m->m_off >= MMINOFF + 2 * sizeof (u_short))
		{
		type = ETHERTYPE_TRAILER + (off>>9);
		m->m_off -= 2 * sizeof (u_short);
		m->m_len += 2 * sizeof (u_short);
		*mtod(m, u_short *) = htons((u_short)ETHERTYPE_IP);
		*(mtod(m, u_short *) + 1) = htons((u_short)m->m_len);
		goto gottrailertype;
		}
	    type = ETHERTYPE_IP;
	    off = 0;
	    goto gottype;
#endif
	case AF_UNSPEC:
	    eh = (struct ether_header *)dst->sa_data;
	    bcopy((caddr_t)&eh->destAddr[0], (caddr_t)&edst[0], 6);
/*
printf("edst = %02x-%02x-%02x-%02x-%02x-%02x\n",
	edst[0], edst[1], edst[2],
	edst[3], edst[4], edst[5]);
*/
	    type = eh->etherType;
	    goto gottype;

#ifdef VALIDnet
	case AF_CONN:
	    long2ea(&edst[0], ((struct sockaddr_rpc *)dst)->node.host.high);
	    long2ea(&edst[3], ((struct sockaddr_rpc *)dst)->node.host.high);
	    dllHeader.pktType = VALIDnet_CONN;
	    dllHeaderSize = sizeof(struct validDLLheader);
	    type = ETHERTYPE_VALID;
	    goto gottype;
	    break;

	case AF_RPC:
	    long2ea(&edst[0], ((struct sockaddr_rpc *)dst)->node.host.high);
	    long2ea(&edst[3], ((struct sockaddr_rpc *)dst)->node.host.low);
	    dllHeader.pktType = VALIDnet_RPC;
	    dllHeaderSize = sizeof(struct validDLLheader);
	    type = ETHERTYPE_VALID;
	    goto gottype;
	    break;

#ifdef notdef
	case AF_BULK:
	    long2ea(&edst[0], ((struct sockaddr_rpc *)dst)->node.host.high);
	    long2ea(&edst[3], ((struct sockaddr_rpc *)dst)->node.host.low);
	    dllHeader.pktType = VALIDnet_BULK;
	    dllHeaderSize = sizeof(struct validDLLheader);
	    type = ETHERTYPE_VALID;
	    goto gottype;
	    break;
#endif notdef
#endif VALIDnet

	default:
	    printf("ex%d: can't handle af%d\n", ifp->if_unit, dst->sa_family);
	    error = EAFNOSUPPORT;
	    goto bad;
	}

gottrailertype:
    /*
     * Packet to be sent as trailer: move first packet (control information)
     * to end of chain.
     */
    while (m->m_next)
	m = m->m_next;
    m->m_next = m0;
    m = m0->m_next;
    m0->m_next = 0;
    m0 = m;

gottype:
/*
 * Add Ethernet net header.  If no space in first mbuf,
 * allocate another.
 */
    if ((m->m_off > MMAXOFF) ||
	((MMINOFF + sizeof(struct ether_header) + dllHeaderSize) > m->m_off))
	{
	m = m_get(M_DONTWAIT, MT_HEADER);
	if (m == 0)
	    {
	    error = ENOBUFS;
	    goto bad;
	    }
	m->m_next = m0;
	m->m_off = MMINOFF;
	m->m_len = sizeof(struct ether_header) + dllHeaderSize;
	}
    else
	{
	m->m_off -= sizeof(struct ether_header) + dllHeaderSize;
	m->m_len += sizeof(struct ether_header) + dllHeaderSize;
	}

    eh = mtod(m, struct ether_header *);
    eh->etherType = htons((u_short)type);
    bcopy((caddr_t)&edst[0], (caddr_t)&eh->destAddr[0], 6);
/*
printf("eh->destAddr = %02x-%02x-%02x-%02x-%02x-%02x\n",
	eh->destAddr[0], eh->destAddr[1], eh->destAddr[2],
	eh->destAddr[3], eh->destAddr[4], eh->destAddr[5]);
*/

/* Add data link layer header if one exists */

    if (dllHeaderSize)
	bcopy((caddr_t)&dllHeader,
	      (caddr_t)eh + sizeof(struct ether_header),
	      dllHeaderSize);


  gotheader:
    bcopy((caddr_t)&xs->xs_addr[0], (caddr_t)&eh->sourceAddr[0], 6);
/*
printf("eh->sourceAddr = %02x-%02x-%02x-%02x-%02x-%02x\n",
	eh->sourceAddr[0], eh->sourceAddr[1], eh->sourceAddr[2],
	eh->sourceAddr[3], eh->sourceAddr[4], eh->sourceAddr[5]);
*/

    /*
     * Queue message on interface, and start output if interface not yet 
     * active.
     */
    s = splnet();
    if (IF_QFULL(&xs->output))
	{
	IF_DROP(&xs->output);
	splx(s);
	m_freem(m);
	return (ENOBUFS);
	}
    IF_ENQUEUE(&xs->output, m);

    /*
     * If transmit request not already pending, then kick the back end.
     */
    if ((xs->flags & EX_XPENDING) == 0)
	exStart(xs, EXINFO(ifp->if_unit)->md_addr);
    splx(s);
    return (0);

bad:
    m_freem(m0);
    return (error);
}

/*
 * Watchdog routine (currently not used). Might use this to get stats from EXOS.
 */
exWatch(unit)
    int unit;
{
#ifdef notdef
    struct exdevice *exaddr = (struct exdevice *)(EXINFO(unit)->md_addr);
    register struct ex_softc *xs = &EX_SOFTC(unit);
    register struct net_ststcs *bp;
    int s = spl7();

    if (xs->flags & EX_STATPENDING)
	goto exspnd;

/* Allocate and format LLNET_STSTCS request */
    bp = (struct net_ststcs *)exGetCbuf(xs, HSAP_DLL, EXSAP_DLL);
    if(!bp)
	panic("LLNET_STSTCS");
    xs->flags |= EX_STATPENDING;
    bp->request = LLNET_STSTCS;
    bp->mask = READ_OBJ;
    bp->rsrv = 0;
    bp->numobj = 8;
    bp->objindx = 0;
    bp->bufptr = vtop(xs->xsa);

/* Post message to EXOS */

    bp->status |= MH_EXOS;
    exaddr->portb = 0;

exspnd:	splx(s);
    xs->xs_if.if_timer = EXWATCHINTVL;
#endif notdef
}

/*
 * Process an ioctl request.
 */

exIoctl(ifp, cmd, data)
    register struct ifnet *ifp;
    int cmd;
    caddr_t data;
{
    register struct ifreq *ifr = (struct ifreq *)data;
    register struct ex_softc *xs = &EX_SOFTC(ifp->if_unit);
    int error = 0;
    int s = splnet();

    switch (cmd)
	{
	case SIOCSIFADDR:
	/* Delete previous route */
	    if (ifp->if_flags & IFF_RUNNING)
		if_rtinit(ifp, -1);

	/* Set internet address */
	    exSetAddr(xs, ifp, (struct sockaddr_in *)&ifr->ifr_addr);

	/* Reinitialize the controller */
	    exInit(ifp->if_unit);
	    break;

	default:
	    error = EINVAL;
	}

    splx(s);
    return (error);
}

exSetAddr(xs, ifp, sin)
    register struct ex_softc *xs;
    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_family = AF_INET;
    sin->sin_addr = if_makeaddr(ifp->if_net, INADDR_ANY);
    ifp->if_flags |= IFF_BROADCAST;

#ifdef VALIDnet
	{
	register struct sockaddr_vnet *svnet;
	node_t n, broad;

	n.net = 0;
	ea2long(&xs->xs_addr[0], n.host.high);
	ea2long(&xs->xs_addr[3], n.host.low);
	broad.net = 0;
	ea2long(&etherbroadcastaddr[0], broad.host.high);
	ea2long(&etherbroadcastaddr[3], broad.host.low);

#ifdef notdef
	ifp = &xs->if_bulk;
	ifp->if_host[0] = n.host.low;
	ifp->if_net = n.host.high;
	svnet = (struct sockaddr_vnet *)&ifp->if_addr;
	svnet->sa_family = AF_BULK;
	svnet->sa_node = n;
	svnet = (struct sockaddr_vnet *)&ifp->if_broadaddr;
	svnet->sa_node = broad;
	svnet->sa_family = AF_BULK;
#endif notdef

	ifp = &xs->if_conn;
	ifp->if_host[0] = n.host.low;
	ifp->if_net = n.host.high;
	svnet = (struct sockaddr_vnet *)&ifp->if_addr;
	svnet->sa_family = AF_CONN;
	svnet->sa_node = n;
	svnet = (struct sockaddr_vnet *)&ifp->if_broadaddr;
	svnet->sa_node = broad;
	svnet->sa_family = AF_CONN;

	ifp = &xs->if_rpc;
	ifp->if_host[0] = n.host.low;
	ifp->if_net = n.host.high;
	svnet = (struct sockaddr_vnet *)&ifp->if_addr;
	svnet->sa_family = AF_RPC;
	svnet->sa_node = n;
	svnet = (struct sockaddr_vnet *)&ifp->if_broadaddr;
	svnet->sa_node = broad;
	svnet->sa_family = AF_RPC;
	}
#endif VALIDnet
}

int
mbufGather(buffer, size, bufchain)
    u_char * buffer;
    int size;
    struct mbuf * bufchain;
{
    int copied;		/* Bytes copied into buffer */
    struct mbuf * cbuf;	/* Pointer to current mbuffer */
    u_char * mdata;	/* Data pointer into current mbuffer */
    int mlen;		/* Bytes in current mbuffer */

    copied = 0;
    cbuf = bufchain;
    while((size > 0) && (cbuf != 0))
	{
	mdata = mtod(cbuf, u_char *);
	mlen = cbuf->m_len;
	while((size > 0) && (mlen > 0))
	    {
	    *buffer++ = *mdata++;
	    size--;
	    mlen--;
	    copied++;
	    }

	if(size <= 0)
	    break;

	cbuf = cbuf->m_next;
	}

    m_freem(bufchain);
    return(copied);
}
