
/*
 * P_R_P_Q_# (C) COPYRIGHT IBM CORPORATION 1987
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header: if_un.c,v 12.3 89/08/14 16:41:21 mlewis Exp $ */

#ifndef lint
static char    *rcsid = "$Header: if_un.c,v 12.3 89/08/14 16:41:21 mlewis Exp $";
#endif lint


/*
 * this driver supports the NICPS2 adapter from Ungermann Bass
 * which is based on the Intel 82586 chip. See the Intel Microcomunications
 * handbook for hardware details.
 */


#define inb(port) IN(port)
#define outb(port,value) OUT(port,value)
#define swaph(value) ((((value)>>8)&0xff)|(((value)&0xff)<<8))
#define UNIRQ 3

#include	"un.h"
#include "../machine/pte.h"

#include "param.h"
#include "systm.h"
#include "mbuf.h"
#include "buf.h"
#include "protosw.h"
#include "socket.h"
#include "vmmac.h"
#include "ioctl.h"
#include "errno.h"

#include "../net/if.h"
#include "../net/netisr.h"
#include "../net/route.h"

#ifdef INET
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#include "../netinet/in_var.h"
#include "../netinet/ip.h"
#include "../netinet/if_ether.h"
#endif INET

#ifdef NS
#include "../netns/ns.h"
#include "../netns/ns_if.h"
#endif NS

#include "../machine/io.h"
#include "../machineio/ioccvar.h"
#include "time.h"
#include "kernel.h"
#include "../machine/debug.h"
#include "../ca_atr/pcif.h"


#define ETH_ADDR_SIZE	6

#define		UNCARDID	0xEFF5		/* POS register ID */
/* bits in the CSR registers */
#define		INTPNDG		0x80
#define		CHINTENB	0x08
#define		TMINTENB	0x04
#define		LOOPBACK	0x02
#define		RESET		0x01


/* Bits in the Transmit Control Blocks */
#define 	BBIT		0x4000		/* Busy executing command */
#define		DONEBIT		0x8000		/* command completed */
#define		OKBIT		0x2000		/* error-free completion */
#define		ABORTED		0x1000		/* command aborted */
#define		NOCARRIER	0x0400		/* no carrier sense */
#define		XMIT_BAD	0x0200		/* transmit failed */
#define		XMIT_BAD_DMA	0x0100		/* transmit failed (DMA) */
#define		XMIT_DEFER	0x0080		/* transmit failed (traffic) */
#define		XMIT_HEART	0x0040		/* transmit failed (heartbeat) */
#define		XMIT_MAX_COL_16	0x0020		/* more than 16 collisions */
#define		XMIT_NCOL(status) ((((status)&XMIT_MAX_COL_16)>>1)|((status)&0xf))	/* number of colisions */

/* Bits in the System Control Block */
#define		CX		0x8000		/* the CU finished a command */
#define		FR		0x4000		/* the RU got a frame */
#define		CNA		0x2000		/* the CU left the ACT state */
#define		RNR		0x1000		/* the RU left the RDY state */
#define		NO_RESOURCES	0x0020		/* we got an illegal packet */
/* command bits. */
#define		CUCSTART	0x100
#define		RUCSTART	0x10
#define		RUCRESUME	0x20

/* Bits in the Receive Buffer Descriptor (rbd) */
#define ACT_MASK	0x3fff	/* received count */
#define	EOFBIT		0x8000	/* End of a Frame */

/* Bits in the Frame descriptor command */
#define	FDSUSPEND	0x4000	/* Tell receive unit to suspend */
#define	FDEOL		0x8000

 /* States of Chip */
#define		CRESET		0x0001		/* we just reset the chip */
#define		CREADY		0x0002		/* turned on rcvr */
#define		CBROKEN		0x8000		/* something's screwed */

#define		FREE	0
#define		BUSY	1

#define		RBUFFS	15	/* number of rcvr buffers */

#if NUN > 0

/*
 * Ethernet software status.
 */

struct	un_softc {
	struct	arpcom es_ac;		/* common Ethernet structures */
#define es_if	es_ac.ac_if		/* network-visable interfaces */
#define es_addr	es_ac.ac_enaddr	/* hardware ethernet addresses */
	long	if_buf0;		/* number of uses of buf0 */
	long	if_buf1;		/* number of uses of buf0 */
	long	if_nobuf;		/* number of times no buffer */
	unsigned short	state;		/* state of card */
	unsigned short tbuffstate[2];	/* transmit buffer state */
	unsigned short	rec_nm_buf;		/* next receive buffer to fill */
	unsigned short	csr;			/* port address */
	short		us_owatch;	/* output watch timer */
	short		us_iwatch;	/* input watch timer */
	short		us_watch;	/* watchdog running */
	unsigned int rom_addr;		/* address of ROM */
	unsigned int ram_addr;		/* RAM address */
	unsigned int ram_seg;		/* RAM segment */
	struct	scb *scbptr;		/* pointer to our scb */
	struct	buff *tbufptr[2];	/* transmit buffer pointer */
	struct	buff *rbufptr[RBUFFS];	/* recieve buffer pointers */
	struct	fd *fdptr[RBUFFS];	/* data pointer */
	struct	tcb *tcbptr[2];
	struct	rbd *rbdptr[RBUFFS];
	struct	tbd *tbdptr[2];
	int	fill[2];		/* align it conveniently */
} un_softc[NUN];

int un_size = sizeof (struct un_softc);
/* patch un_last to large number to disable claiming of stray interrupts */
int un_last = -1;

#ifndef UBIWATCH
#define UBIWATCH	0
#endif

#ifndef UBOWATCH
#define UBOWATCH	30
#endif

int uniwatch = UBIWATCH;			/* no input watchdog */
int unowatch = UBOWATCH;			/* 30 second output watchdog */

typedef int spl_t;

struct iroot {
	unsigned short sysbus;
	unsigned short not_used1;
	unsigned short not_used2;
	unsigned short iscp_low;
	unsigned short iscp_high;
};

struct iscp {
	unsigned short busy;
	unsigned short scb_offset;
	unsigned short scb_base_low;
	unsigned short scb_base_high;
};

struct scb {
	unsigned short status;
	unsigned short command;
	unsigned short cbl_offset;
	unsigned short rfa_offset;
	unsigned short crcerrs;
	unsigned short alnerrs;
	unsigned short rscerrs;
	unsigned short ovrnerrs;
};

struct setup {
	unsigned short status;
	unsigned short command;
	unsigned short link_offset;
	unsigned short addr0;
	unsigned short addr1;
	unsigned short addr2;
};

struct tcb {
	unsigned short status;
	unsigned short command;
	unsigned short link_offset;
	unsigned short tbd_offset;
	unsigned char dest[6];
	unsigned short type;
};

struct romaddr {
	unsigned short bytea;
	unsigned short byteb;
	unsigned short bytec;
	unsigned short byted;
	unsigned short bytee;
	unsigned short bytef;
};

/*
 * see Page 1-10 figure 8 for overall structure of RFD's, FD's, RBD's
 * and DB's.
 *
 */



/* recieve frame descriptor (fd) */
struct fd {
	unsigned short status;
	unsigned short command;
	unsigned short link_offset;
	unsigned short rbd_offset;
	unsigned short dest_addr[3];
	unsigned short source_addr[3];
	unsigned short type;			/* also known as "length" */
};

/* receive buffer descriptor (see page 1-10) */
struct rbd {
	unsigned short status;			/* recieve frame status */
	unsigned short nxt_rbd_offset;		/* link field */
	unsigned short buff_addr_l;		/* buffer address */
	unsigned short buff_addr_h;		/*   "	    "     */
	unsigned short size;			/* size buffer can hold */
};

/* transmist buffer (frame) descriptor */
struct tbd {
	unsigned short status;		/* eof & act count */
	unsigned short nxt_tbd_offset;
	unsigned short buff_addr_l;
	unsigned short buff_addr_h;
};

struct buff {
	struct ether_header hdr;
	char data[1518];
};


int unintr();
#ifdef DEBUG
#define ncprintf if (undebug) printf
#else
#define ncprintf if (0) printf
#endif

int 
unprobe(), unattach();

extern int pcif_512_fw;
#define PTOKV(addr) (set_512_window(addr) + pcif_512_fw);

caddr_t         unstd[] = {(caddr_t) 0x00001550, (caddr_t) 0x00001554,
			   (caddr_t) 0x00001558, (caddr_t) 0x0000155c, 0};

struct iocc_device *uninfo[NUN];

int 
uninit(), unioctl(), unoutput(), unreset(), unwatch();
int unsuspend();

struct iocc_driver undriver =
{unprobe, 0, unattach, 0, unstd, "un", uninfo,
 /*     int   csr chanrel flags 		suspend */
 0, 0, unintr, 0,    0,    DRIVER_SUSPEND,   unsuspend }; 


#define Offset(off)	((us->ram_addr + off) & 0x0ffff)

int undebug = 0;	/* debugging flags */
int unnoint = 0;	/* don't attempt to cause interrupt */
int noun = 0;		/* don't use un */

#define UB_GSFTINT      0x20    /* Generate software interrupt */

#define MAX_PACKET	1518	/* MAX PACKET SIZE */
#define DATA_OFFSET	0x800	/* where to start data buffers */


/*
 *  unprobe - This adapter can only be at interrupt 3
 */
unprobe(p)
	register caddr_t p;
{
	printf("unprobe called: port=%x\n", p);
	if (noun)
		return(PROBE_BAD);
	if (unnoint)
		return(PROBE_NOINT);	/* didn't attempt hardware int */
	outb(p,UB_GSFTINT);
	PROBE_DELAY(200000);
	outb(p, 0);
	outb(p+1,0xFF);			/* CA */
	return (PROBE_OK);		/* attempted hardware interrupt */
}

int un_output(), un_ioctl();

/*
 *  unattach - make the interface available to the network software
 *  (if the auto-configuration software determines that the interface
 *  exists).  The system will initialize the interface when it is
 *  ready to accept packets.
 */
unattach(iod)
	register struct iocc_device *iod;
{
	int unit = iod->iod_unit;
	register struct un_softc *us = &un_softc[unit];
	register struct ifnet *ifp = &us->es_if;
	int	old_window = get_512_window();
	char	*p = (char *) iod->iod_addr;
	int	x = inb(p+1);
	int	y = inb(p+3);
	int	ram = ((x << 8) | ((y & 0x3) << 6)) << 8;
	int	rom;

	if (unit > un_last)
		un_last = unit;		/* remember last unit */
	us->csr = (short) iod->iod_addr;
	p = (char *) set_512_window(ram) + pcif_512_fw;
	rom = p[0x29] << 12;		/* get the rom addr */
	printf("unattach called: port=%x ca=%x pos5=%x ram=%x rom=%x\n", us->csr, x, y,
		ram,rom);
	if (rom >= 0xc0000 && rom < 0xdffff)
		us->rom_addr = rom;		/* use it if it looks valid */
	else
		printf("unattach: couldn't find rom address\n");
	us->ram_addr = ram;
	us->ram_seg = us->ram_addr >> 16;	/* high order bits */
	ifp->if_unit = unit;
	ifp->if_name = "un";		/* use "un0" etc */
	ifp->if_mtu = ETHERMTU;

	set_512_window(old_window);
	ifp->if_init = uninit;
	ifp->if_ioctl = un_ioctl;
	ifp->if_output = un_output;
	ifp->if_reset = unreset;
	ifp->if_flags = IFF_BROADCAST | IFF_NOTRAILERS;
	if_attach(ifp);
	DEBUGF(undebug, printf("un%d: attached\n", unit));
}


unsuspend(iod, idr, how)
	register struct iocc_device *iod;
	struct iocc_driver *idr;
{
	int unit = iod->iod_unit;
	register struct un_softc *us = &un_softc[unit];
	register struct ifnet *ifp = &us->es_if;

	if (how == SUSPEND_START) {
		if (ifp->if_flags & IFF_RUNNING) {
			unzap(unit);
			ifp->if_flags |= IFF_RUNNING;	/* remember we were running */
		}
	} else if (how == SUSPEND_DONE) {
		if (ifp->if_flags & IFF_RUNNING) {
			ifp->if_flags &= ~IFF_RUNNING;	/* pretend not running */
			uninit(unit);
		}
	}
}

uninit(iv)
int iv;
{
	spl_t ospl;
	int i; 
	int ii;
	int	old_window = get_512_window();
	struct un_softc *us = &un_softc[iv];
	struct ifnet *ifp = &us->es_if;
	struct iroot *initptr;
	struct iscp *iscpptr;
	struct romaddr *romaddrptr;
	struct setup *setupptr;

	if (ifp->if_addrlist == (struct ifaddr *) 0) {
		/* no address */
		return;
	}
	if (ifp->if_flags & IFF_RUNNING)
		return;		/* already running! */

	DEBUGF(undebug, printf("un%d: uninit ram=%x rom=%x\n", iv,us->ram_addr,us->rom_addr));
	ifp->if_flags |= IFF_RUNNING;

	/* 							 */
	/* System		D7FDE	STATUS			 */
	/* Control		D7FE0	COMMAND			 */
	/* Block		D7FE2	CBL OFFSET		 */
	/* 			D7FE4	RFA OFFSET		 */
	/* 			D7FE6	CRCERRS			 */
	/* 			D7FE8	ALNERRS			 */
	/* 			D7FEA	RSCERRS			 */
	/* 			D7FEC	OVRNERRS		 */
	/* 							 */
	/* Intermediate		D7FEE	BUSY = 1 (reset)	 */
	/* System		D7FF0	SCB OFFSET = 0x7FDE	 */
	/* Configuration	D7FF2	SCB BASE (lo) = 0x0000	 */
	/* Pointer		D7FF4	SCB BASE (hi) = 0x000D 	 */
	/* 							 */
	/* Initialization	D7FF6	SYSBUS = 0 (16 bit bus)	 */
	/* Root			D7FF8	not used		 */
	/* 			D7FFA	not used		 */
	/* 			D7FFC	ISCP ADDR (lo) = 0x7FEE	 */
	/* 			D7FFE	ISCP ADDR (hi) = 0x000D	 */
	/* 							 */
	set_pcvec_map(UNIRQ, 0);	/* disable int forwarding */
	initptr = (struct iroot *) PTOKV(us->ram_addr + 0x7FF6);
	initptr->sysbus = swaph(0);		/* 16 bit bus */
	initptr->not_used1 = swaph(0);
	initptr->not_used2 = swaph(0);
	initptr->iscp_low = swaph(Offset(0x7FEE));
	initptr->iscp_high = swaph(us->ram_seg);
	iscpptr = (struct iscp *) PTOKV(us->ram_addr + 0x7FEE);
	iscpptr->busy = swaph(0x01);		/* init in progress */
	iscpptr->scb_offset = swaph(Offset(0x7FDE));
	iscpptr->scb_base_low = swaph(Offset(0x0));
	iscpptr->scb_base_high = swaph(us->ram_seg);
	us->scbptr = (struct scb *) PTOKV(us->ram_addr + 0x7FDE);
	us->scbptr->status = 0;
	us->scbptr->command = 0;
	us->scbptr->cbl_offset = 0;
	us->scbptr->rfa_offset = 0;
	us->scbptr->crcerrs = 0;
	us->scbptr->alnerrs = 0;
	us->scbptr->rscerrs = 0;
	us->scbptr->ovrnerrs = 0;
	outb(us->csr,(CHINTENB | RESET));
	DELAY(10);
	outb(us->csr, CHINTENB);
	outb(us->csr+1,0xFF);			/* CA */
	while (!(inb(us->csr) & INTPNDG));
	DEBUGF(undebug, printf("un%d: uninit reset done\n", iv));
	us->state = CRESET;
	us->rec_nm_buf = 0;			/* which rcvr buffer we use */
	romaddrptr = (struct romaddr *) PTOKV(us->rom_addr + 0x10);
	setupptr = (struct setup *) PTOKV(us->ram_addr + 0x7FD2);
	setupptr->status = 0;
	setupptr->command = swaph(0x8001);    /* EL + SETUP */
	setupptr->link_offset = 0;
	setupptr->addr0 = ((romaddrptr->byteb) >> 8) | romaddrptr->bytea;
	setupptr->addr1 = ((romaddrptr->byted) >> 8) | romaddrptr->bytec;
	setupptr->addr2 = ((romaddrptr->bytef) >> 8) | romaddrptr->bytee;

#define LAN_ADDR_SIZE ETH_ADDR_SIZE
	bcopy((char *) &setupptr->addr0, us->es_addr, LAN_ADDR_SIZE);

	printf("un%d: ethernet address ", iv);
	unprintethaddr(us->es_addr);
	printf("\n");

	ospl = splimp();
	while (us->scbptr->command != 0);
	us->scbptr->command = swaph(CUCSTART);
	us->scbptr->cbl_offset = swaph(Offset(0x7FD2));
	outb(us->csr+1,0xFF);			/* CA */
	splx(ospl);
	DEBUGF(undebug, printf("un%d: uninit cucstart done\n", iv));
	for (i=0; !(setupptr->status & swaph(OKBIT));++i)
		if (i > 1000) {
			printf("un%d: timed out waiting for OKBIT\n",iv);
			break;
		} else
			delay(1);
	/* build fd's backwards from offset 7FBC */
	us->fdptr[0] = (struct fd *) PTOKV(us->ram_addr + 0x7FBC);
	for (i = 1; i < RBUFFS; i++)
		us->fdptr[i] = us->fdptr[i-1] - 1;
	/* allocate rbd's just before fd's */
	us->rbdptr[0] = ((struct rbd *) us->fdptr[RBUFFS-1]) - 1;
	for (i = 1; i < RBUFFS; i++)
		us->rbdptr[i] = us->rbdptr[i-1] - 1;
	/* allocate tcb's before rbd's */
	us->tcbptr[0] = ((struct tcb *) us->rbdptr[RBUFFS-1]) - 1;
	us->tcbptr[1] = us->tcbptr[0] - 1;
	/* allocate tbd's before tcb's */
	us->tbdptr[0] = ((struct tbd *) us->tcbptr[1]) - 1;
	us->tbdptr[1] = us->tbdptr[0] - 1;
	/* point scb's rfa_offset to first fd */
	us->scbptr->rfa_offset = swaph((unsigned short) us->fdptr[0]);
	/* chain the fd's together via the link_offset */
	for (i=0 ; i < RBUFFS; i++)
		us->fdptr[i]->link_offset = swaph((ushort) us->fdptr[((i+1)%RBUFFS)]);
	/* initialize the fd's to point to the appropriate rbd's */
	for (i = 0; i < RBUFFS; i++) {
		us->fdptr[i]->rbd_offset = swaph((ushort) us->rbdptr[i]);
		us->fdptr[i]->status = 0;
		us->fdptr[i]->command = 0;
		us->fdptr[i]->type = 0;
		for (ii = 0; ii < 3; ii++) {
			us->fdptr[i]->dest_addr[ii] = 0;
			us->fdptr[i]->source_addr[ii] = 0;
		}
	}
	/* suspend if we use the last fd */
	us->fdptr[RBUFFS-1]->command |= swaph(FDSUSPEND);	/* Overrun protection */
	/* initialize the first data buffer pointer to start of ram */
	us->rbufptr[0] = (struct buff *) PTOKV(us->ram_addr+DATA_OFFSET);
	/* initialize the rest of the data buffer pointers */
	for( i = 1; i < RBUFFS; i++)
		us->rbufptr[i] = us->rbufptr[i-1] + 1;
	for (i = 0; i < RBUFFS; i++) {
		us->rbdptr[i]->buff_addr_l = swaph((ushort) &(us->rbufptr[i]->data[0]));
		us->rbdptr[i]->buff_addr_h = swaph(us->ram_seg);
		us->rbdptr[i]->status = 0;
		us->rbdptr[i]->nxt_rbd_offset = 0;
		us->rbdptr[i]->size = swaph(0x85EE);   /* 1518(0x5ee)+last RBD(0x8000)*/
	}
	/* transmit buffers follows receive buffers */
	us->tbufptr[0] = us->rbufptr[RBUFFS-1] + 1;
	us->tbufptr[1] = us->tbufptr[0] + 1;
	DEBUGF(undebug, printf("un%d: free buffer space=%d\n", iv,
		(caddr_t) (us->tbdptr[1] - 1) - (caddr_t) (us->tbufptr[1]+1)));
	us->tbuffstate[0] = FREE;	/* mark xmit buffers as free */
	us->tbuffstate[1] = FREE;
	us->tcbptr[0]->status = 0;
	us->tcbptr[1]->status = 0;

	DEBUGF(undebug, printf("un%d: uninit calling ostart\n", iv));
	/* Attach this device to the outside world! */

	un_ostart(&us->es_if);

	if (!us->us_watch) {
		timeout(unwatch, (caddr_t) iv, hz);
		us->us_watch++;		/* timer running */
	}

done:
	set_pcvec_map(UNIRQ, 1);	/* enable int forwarding */
	set_512_window(old_window);
	DEBUGF(undebug, printf("un%d: uninit done\n", iv));
}

/*
 * note: since this adapter appears to cause stray interrupts that it
 * later disclaims, we use un_last to determine if we are the last un adapter
 * and always claims interrupts in this case.  This does not work if there is
 * anything else at this interrupt level, so the un addapter must be configured
 * so that it ends up last on the chain.
 */

unintr(iv)
int iv;		/* board we are going to service */
{
	unsigned short build_comm;
	int	old_window = get_512_window();
	struct un_softc *us = &un_softc[iv];
	struct scb *scb = us->scbptr;	/* get scb pointer */
	register struct ifnet *ifp = &us->es_if;

	if (! (inb(us->csr) & INTPNDG)) {
		DEBUGF(undebug&SHOW_INTR, printf("un%d: unintr - no int\n",iv));
		return(iv != un_last);		/* 1 = not our interrupt */
	}

	(void) set_512_window(us->ram_addr);	/* set to proper 512k window */
	DEBUGF(undebug&SHOW_INTR, printf("un%d: unintr\n",iv));
	build_comm = 0;
	if (us->state & CRESET) {
		ncprintf("unintr: CRESET was set (starting receiver)\n");
		ncprintf("unintr: CRESET board %d\n",iv);
		us->state = CREADY;
		while (scb->command != 0)
			;
		scb->command = swaph(CX | CNA | RUCSTART);	/* start rcvr */
		outb(us->csr+1, 0xff);		/* CA */
		while (scb->command != 0)
			;
		goto out;
	}

	if (scb->status & swaph(FR)) {     /* rcvd a frame */
		build_comm |= FR;
		unrint(iv);
	}
	if (scb->status & swaph(CX)) {     /* xmitted a frame */
		us->us_owatch = 0;	/* set watchdog timer */
		build_comm |= CX;
		if ((us->tbuffstate[0] == BUSY) && (us->tcbptr[0]->status & swaph(DONEBIT))) {
			int status = swaph(us->tcbptr[0]->status);
			us->tbuffstate[0] = FREE;
			ifp->if_collisions += XMIT_NCOL(status);
			if (status&OKBIT)
				ifp->if_opackets++;
			else
				ifp->if_oerrors++;
			us->tcbptr[0]->status = 0;
		}
		if ((us->tbuffstate[1] == BUSY) && (us->tcbptr[1]->status & swaph(DONEBIT))) {
			int status = swaph(us->tcbptr[1]->status);
			ifp->if_collisions += XMIT_NCOL(status);
			if (status&OKBIT)
				ifp->if_opackets++;
			else
				ifp->if_oerrors++;
			us->tbuffstate[1] = FREE;
			us->tcbptr[1]->status = 0;
		}
	}
	if (scb->status & swaph(CNA)) {
		build_comm |= CNA;
	}
	if (scb->status & swaph(NO_RESOURCES)) {
	       build_comm |= (RUCSTART|RNR);
	       scb->rfa_offset=swaph((unsigned short)us->fdptr[us->rec_nm_buf]);
	       ncprintf("unintr: restart after non-802.3 packet\n");
	}
	if ((scb->status & swaph(RNR)) && 
			(!(scb->status & swaph(NO_RESOURCES)))) {
		/* The receive unit ran out of frames... restart it, since
		 * unrint must have read in all the packets.
		 */
		ncprintf("unintr: restarting receiver (ran out of bufs)\n");
		build_comm |= (RUCRESUME|RNR);	/* resume receiving */
	}
	while (scb->command != 0)
		;
	scb->command = swaph(build_comm);
	outb(us->csr+1,0xFF);		/* CA */
	if (build_comm & CX)
		un_ostart(&us->es_if);
out:
	DEBUGF(undebug&SHOW_INTR, printf("un%d: unintr done crc=%d ovrn=%d aln=%d rsc=%d\n",iv,swaph(scb->crcerrs),swaph(scb->ovrnerrs),swaph(scb->alnerrs),swaph(scb->rscerrs)));
	if (scb->crcerrs || scb->alnerrs)
	{	/* got some input errors - count them */
		ifp->if_ierrors += swaph(scb->crcerrs) + swaph(scb->alnerrs);
		scb->crcerrs = 0;
		scb->alnerrs = 0;
	}
	set_512_window(old_window);
	return(0);
}


/*
 * Pegasus receive interrupt.
 */
unrint(iv)
int iv;		/* which board */
{
	spl_t s;
	int stat;
	int len, len_or_type;
	int prev_nm_buf;
	register struct ether_header *ehp;
	struct un_softc *us = &un_softc[iv];

	us->us_iwatch = uniwatch;		/* got an interrupt */
	DEBUGF(undebug&SHOW_INTR, printf("un%d: unrint\n",iv));
	for (; len = (us->rbdptr[us->rec_nm_buf]->status);
				us->rec_nm_buf=(us->rec_nm_buf+1)%RBUFFS) {
		len = swaph(len);		/* byte swap len */
		stat = (us->fdptr[us->rec_nm_buf]->status);
		stat = swaph(stat);	/* swap it */
		if (!(stat & DONEBIT)) {
			ncprintf("unrint: stat %x, not complete\n", stat);
			break;
		}
		/* NEEDSWORK: receiver should be shut down */
		if (! (us->es_if.if_flags & IFF_UP))
			goto discard;
		if (!(stat & (OKBIT|BBIT))) {
			/* Discard bad (not "ok") frames. */
			ncprintf("unintr: got not OK packet %x\n", stat);
			goto discard;
		}
		/*
		 * copy in the header to prefix the packet data to
		 * make access simple.
		 */
		bcopy(&(us->fdptr[us->rec_nm_buf]->dest_addr[0]),
			us->rbufptr[us->rec_nm_buf], sizeof(*ehp));
		/* clear flag bits in "len": its now length of data portion */
		len &= ACT_MASK;

		/*
		 * At this point we have the frame and the header
		 * If we want the whole frame we use rbufptr[x]
		 * If we want just the data rbufptr[x]->data[0]
		 * the packet type is in fdptr[x]->type
		 * as well as being in the header
		 * the size of the frame is in rbdptr[x]->status
		 * where x = rec_nm_buf
		 */

		/*
		 * Process the packet
		 */
		s = splimp();
		ehp = (struct ether_header *) us->rbufptr[us->rec_nm_buf];
		DEBUGF(undebug&SHOW_DATA, unprintpacket("unrint: ",ehp,"\n"));
#define eth_type ether_type
		len_or_type = ntohs(* (u_short *) &ehp->eth_type);
		if (len_or_type > ETHERMTU) {	/* KLUDGE!!! */
			struct mbuf *m = NULL;
			struct mbuf *unget();

			switch (len_or_type) {
#define LANTYPE_IP ETHERTYPE_IP
			case LANTYPE_IP:
				m = unget((char *) (ehp + 1), len,
					  &us->es_if);
				if (m == NULL)
					break;
				if (IF_QFULL(&ipintrq)) {
					IF_DROP(&ipintrq);
					m_freem(m);
					break;
				}
				IF_ENQUEUE(&ipintrq, m);
				schednetisr(NETISR_IP);
				break;
#define LANTYPE_ARP ETHERTYPE_ARP
			case LANTYPE_ARP:
				m = unget((char *) (ehp + 1), len,
					  &us->es_if);
				if (m == NULL)
					break;
				arpinput(&us->es_ac, m);
				break;
			default:
				DEBUGF(undebug, printf("un%d: unknown packet type %x\n",iv,len_or_type));

				break;
			}
		}
		else 
			DEBUGF(undebug, printf("un%d: unknown len or type %x\n",iv,len_or_type));
#ifdef notdef
		{
			struct mbuf *m = NULL;
			struct mbuf *unget();
#define eth_802_header ether_header
			struct eth_802_header *eh;

			eh = (struct eth_802_header *) ehp;
			len += sizeof(struct ether_header);
			if (len < sizeof(*eh)) {
				printf("unrint: short packet %d\n", len);
				splx(s);
				goto discard;
			}
#ifdef TEST_CONTROL
			switch (eh->eth_llc.llc_ctrl & ~CTRL_P_OR_F) {
			case TEST_CONTROL:
				m = unget((char *) eh, len,&us->es_if);
				if (m == NULL) {
					splx(s);
					goto discard;
				}
				lan_test_recv(&us->es_if, m);
				goto discard;
			case XID_CONTROL:
				m = unget((char *) eh, len,&us->es_if);
				if (m == NULL) {
					splx(s);
					goto discard;
				}
				lan_xid_recv(&us->es_if, m);
				goto discard;
			case UI_CONTROL:
				break;
			default:
				printf("unrint: bad LLC control field %d\n",
					eh->eth_llc.llc_ctrl);
				splx(s);
				goto discard;
			}
#endif
			switch (ntohs(* (u_short *) eh->eth_snap.snap_type)) {
#define eth_snap ether_snap
			case LANTYPE_IP:
				m = unget((char *) (eh + 1), len - sizeof(*eh),
					  &us->es_if);
				if (m == NULL)
					break;
				if (IF_QFULL(&ipintrq)) {
					IF_DROP(&ipintrq);
					m_freem(m);
					break;
				}
				IF_ENQUEUE(&ipintrq, m);
				schednetisr(NETISR_IP);
				break;
			case LANTYPE_ARP:
				m = unget((char *) (eh + 1), len - sizeof(*eh),
					  &us->es_if);
				if (m == NULL)
					break;
				arpinput(&us->es_if, m);
				break;
			default:
				break;
			}
		}
#endif notdef
		splx(s);
discard:
		/* So this driver knows we've read this frame. */
		us->rbdptr[us->rec_nm_buf]->status = 0;
		/* Now advance the "SUSPEND" indicator from previous buffer to
		 * the receive buffer we just read in.
		 */
		prev_nm_buf = us->rec_nm_buf? us->rec_nm_buf - 1: RBUFFS-1;
		us->fdptr[prev_nm_buf]->command &= ~swaph(FDSUSPEND);
		us->fdptr[us->rec_nm_buf]->command |= swaph(FDSUSPEND);
	}
}

/*
 * unxmit -- transmit a buffer -- before calling, you must:
 *	find a free buffer by checking tbuffstate[bufnum] ( 0 = free )
 *	copy your entire eth packet into tbufptr[bufnum]->{hdr,data}.
 */
unxmit(bufnum, len, iv)
u_int bufnum;
u_int len;	/* Length of entire packet, including header */
u_int iv;	/* which board */
{
	register struct tcb *tcbp;
	register struct tbd *tbdp;
	register struct buff *tbp;
	struct un_softc *us = &un_softc[iv];

	DEBUGF(undebug&SHOW_IO, printf("un%d: unxmit bufnum=%d len=%d\n",iv,bufnum,len));
	if (bufnum > 1)
		panic("unxmit");
	tcbp = us->tcbptr[bufnum];
	tbp = us->tbufptr[bufnum];
	tbdp = us->tbdptr[bufnum];

	/* Copy dest & type to where card expects to find them. */
#define eth_dhost ether_dhost
#define eth_shost ether_shost
	bcopy(tbp->hdr.eth_dhost, tcbp->dest, ETH_ADDR_SIZE);
/*	bcopy(&tbp->hdr.eth_type, tcbp->type, 2);	*/
	tcbp->type = (tbp->hdr.eth_type);	/* length ?? */
	DEBUGF(undebug&SHOW_IO, unprintpacket("unxmit: ",&tbp->hdr,"\n"));

	tcbp->command = swaph(0xA004);	/* EL & I & XMT */
	tcbp->link_offset = 0;
	tcbp->tbd_offset = swaph((unsigned short) tbdp);
/*	tbdp->status = swaph((len - sizeof(struct ether_header)) | EOFBIT); */
	tbdp->status = swaph((len) | EOFBIT);
	tbdp->nxt_tbd_offset = 0;
	tbdp->buff_addr_l = swaph((unsigned short) tbp->data);
	tbdp->buff_addr_h = swaph(us->ram_seg);
	while (us->scbptr->command != 0);
	us->scbptr->command = swaph(CUCSTART);
	us->scbptr->cbl_offset = swaph((unsigned short) tcbp);
	outb(us->csr+1,0xFF);	/* CA */
	us->us_owatch = unowatch;
}

struct mbuf *
unget(addr, totlen, ifp)
register u_char *addr;
register int totlen;
struct ifnet *ifp;
{
	register int len;
	register struct mbuf *m;
	struct mbuf *top = NULL, **mp = &top;
	u_char *mcp;

	++ifp->if_ipackets;

	DEBUGF(undebug&SHOW_IO, {
		int i;
		printf("un%d: unget addr=%x",ifp->if_unit,addr);
		if(undebug&SHOW_DATA)
			for (i=0; i<16; ++i)
				printf(" %x",addr[i]);
		printf("\n");});

	while (totlen > 0) {
		MGET(m, M_DONTWAIT, MT_DATA);
		if (m == NULL)
			goto bad;
		len = totlen;
		if (ifp != NULL)
			len += sizeof(ifp);
#ifdef notdef
		/* Should we try to use a cluster for efficiency? */
		if (len >= mincluster) {
			MCLGET(m);
			if (m->m_len == CLBYTES)
				m->m_len = len = MIN(CLBYTES, len);
			else
				m->m_len = len = MIN(MLEN, len);
		}
		else
#endif
		{
			m->m_len = len = MIN(MLEN, len);
			m->m_off = MMINOFF;
		}
		mcp = mtod(m, u_char *);
		if (ifp != NULL) {
			* (mtod(m, struct ifnet **)) = ifp;
			mcp += sizeof(ifp);
			len -= sizeof(ifp);
			ifp = NULL;
		}
		bcopy(addr, mcp, len);
		addr += len;
		*mp = m;
		mp = &m->m_next;
		totlen -= len;
	}
	return top;
bad:
	m_freem(top);
	DEBUGF(undebug&SHOW_IO, printf("un%d: unget BAD\n",ifp->if_unit));
	return NULL;
}

un_output(ifp, m0, dst)
struct ifnet *ifp;
struct mbuf *m0;
struct sockaddr *dst;
{
	int error;
	struct mbuf *m = m0, *mcopy = NULL;
	register struct ether_header *ec;
#ifdef notdef
	struct lan_arp *ah;
#endif
	char edst[LAN_ADDR_SIZE];
	int usetrailers;	/* N.B. this is currently ignored */
	struct in_addr idst;
	short type;
	spl_t s;
	extern struct ifnet loif;
	int	old_window = get_512_window();
	int iv = ifp->if_unit;
	struct un_softc *us = &un_softc[iv];

	(void) set_512_window(us->ram_addr);	/* set to proper 512k window */
	DEBUGF(undebug&SHOW_IO, printf("un%d: unoutput\n",iv));

	if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
		error = ENETDOWN;
		goto bad;
	}

	switch (dst->sa_family) {
#ifdef INET
	case AF_INET:
		idst = ((struct sockaddr_in *) dst)->sin_addr;
		if (! arpresolve(&us->es_ac,m,&idst,edst,&usetrailers)) {
			set_512_window(old_window);
			return 0;		/* not resolved */
		}
		if (! bcmp(edst, etherbroadcastaddr, ETH_ADDR_SIZE))
			mcopy = m_copy(m, 0, M_COPYALL);
		type = LANTYPE_IP;
		break;
#endif INET
#ifdef AF_ARP
	case AF_ARP:
		ah = mtod(m, struct lan_arp *);
		bcopy(ah->arp_tha, edst, LAN_ADDR_SIZE);
		type = LANTYPE_ARP;
		break;
#endif
	case AF_UNSPEC:
		ec = (struct ether_header *) dst->sa_data;
		bcopy((caddr_t) ec->eth_dhost, (caddr_t) edst, sizeof(edst));
#ifdef notdef
		type = NO_SNAP;
#else
		type = ec->ether_type;
#endif
		break;
	default:
		ncprintf("un_output: cannot handle address family 0x%x\n",
			dst->sa_family);
		error = EAFNOSUPPORT;
		goto bad;
	}
#ifdef IFF_IEEE
	if (!(ifp->if_flags & IFF_IEEE)) /* Xerox ethernet header */
#endif
	{
		if ((m->m_off > MMAXOFF)
		||  ((MMINOFF + sizeof(struct ether_header)) > m->m_off)) {
			m = m_get(M_DONTWAIT, MT_HEADER);
			if (m == NULL) {
				error = ENOBUFS;
				goto bad;
			}
			m->m_next = m0;
			m->m_off = MMINOFF;
			m->m_len = sizeof(struct ether_header);
		}
		else {
			m->m_off -= sizeof(struct ether_header);
			m->m_len += sizeof(struct ether_header);
		}
		ec = mtod(m, struct ether_header *);
		bcopy(edst, (caddr_t) ec->eth_dhost, LAN_ADDR_SIZE);
		bcopy(us->es_addr, (caddr_t) ec->eth_shost, LAN_ADDR_SIZE);
		ec->eth_type = htons(type);
	}
#ifdef IFF_IEEE
	else
	{		/* IEEE802.3 header */
		register struct eth_mac_header *mh;
		register int header_size;

		header_size = sizeof(struct lan_llc_header)
				+ sizeof(struct lan_snap_header);

		switch (type) {
		case LANTYPE_IP:
		case LANTYPE_ARP:
			if ((m->m_off > MMAXOFF)
			||  ((MMINOFF + header_size) > m->m_off)) {
				m = m_get(M_DONTWAIT, MT_HEADER);
				if (m == NULL) {
					error = ENOBUFS;
					goto bad;
				}
				m->m_next = m0;
				m->m_off = MMINOFF;
				m->m_len = header_size;
				m0 = m;
			}
			else {
				m->m_off -= header_size;
				m->m_len += header_size;
			}
			fill_llc_header(mtod(m, struct lan_llc_header *), type);
			/* FALLSTHROUGH */
		case NO_SNAP:
			if ((m->m_off > MMAXOFF)
			||  ((MMINOFF + sizeof(*mh)) > m->m_off)) {
				m = m_get(M_DONTWAIT, MT_HEADER);
				if (m == NULL) {
					error = ENOBUFS;
					goto bad;
				}
				m->m_next = m0;
				m->m_off = MMINOFF;
				m->m_len = sizeof(*mh);
			}
			else {
				m->m_off -= sizeof(*mh);
				m->m_len += sizeof(*mh);
			}
			/* Fill in ethernet MAC header */
			mh = mtod(m, struct eth_mac_header *);
			bcopy(edst, (caddr_t) mh->eth_dhost, LAN_ADDR_SIZE);
			bcopy(us->es_addr, (caddr_t) mh->eth_shost,
				LAN_ADDR_SIZE);
			/* mh->eth_len = XXX; */	/* Set later */
		}
	}
#endif

	s = splimp();
	if (IF_QFULL(&ifp->if_snd)) {
		IF_DROP(&ifp->if_snd);
		error = ENOBUFS;
		m0 = m;
		splx(s);
		goto bad;
	}
	IF_ENQUEUE(&ifp->if_snd, m);
	un_ostart(ifp);
	splx(s);
	set_512_window(old_window);
	return (mcopy ? looutput(&loif, mcopy, dst) : NULL);
bad:
	m_freem(m0);
	if (mcopy)
		m_freem(mcopy);
	set_512_window(old_window);
	return error;
}

un_ostart(ifp)
register struct ifnet *ifp;
{
	int iv = ifp->if_unit;
	register struct un_softc *us = &un_softc[iv];
	int bufnum;
	struct mbuf *m, *m0;
	register char *bufp;
	register unsigned short length;
	spl_t s;

	s = splimp();

	DEBUGF(undebug&SHOW_IO, printf("un%d: unostart\n",iv));

#define IF_EMPTYQUEUE(queue) ((queue)->ifq_head == 0)
	while (! IF_EMPTYQUEUE(&ifp->if_snd)) {
		/* find a free buffer to put a packet in */
		if (us->tbuffstate[0] == FREE) {
			us->tbuffstate[0] = BUSY;
			bufnum = 0;
			++us->if_buf0;
		}
		else if (us->tbuffstate[1] == FREE) {
			us->tbuffstate[1] = BUSY;
			bufnum = 1;
			++us->if_buf1;
		}
		else {
			++us->if_nobuf;
			break;
		}
		IF_DEQUEUE(&ifp->if_snd, m);

		bufp = (char *) us->tbufptr[bufnum];
		length = 0;
		for (m0 = m; m0 != NULL; m0 = m0->m_next) {
			bcopy(mtod(m0, char *), bufp, m0->m_len);
			bufp += m0->m_len;
			length += m0->m_len;
		}
#ifdef IFF_IEEE
		/* For IEEE802.3 we need to fill in the length */
		if (ifp->if_flags & IFF_IEEE) {
			bufp = (char *) us->tbufptr[bufnum];
			((struct eth_mac_header *) bufp)->eth_len =
				htons(length - sizeof(struct eth_mac_header));
		}
#endif IFF_IEEE
		length = MAX(length, ETHERMIN);
		m_freem(m);

		unxmit(bufnum, length, iv); 
	}
	splx(s);
	DEBUGF(undebug&SHOW_IO, printf("un%d: unostart done\n",iv));
	return;
}

un_ioctl(ifp, cmd, data)
struct ifnet *ifp;
int cmd;
caddr_t data;
{
	register struct ifaddr *ifa = (struct ifaddr *) data;
	int error = 0;
	spl_t s;

	DEBUGF(undebug&SHOW_IO, printf("un%d: unioctl cmd=%x\n",ifp->if_unit,cmd));
	s = splimp();
	switch (cmd) {
	case SIOCSIFADDR:
		ifp->if_flags |= IFF_UP;

		switch (ifa->ifa_addr.sa_family) {
#ifdef INET
		case AF_INET:
			uninit(ifp->if_unit);	/* before arpwhohas */
			((struct arpcom *)ifp)->ac_ipaddr=IA_SIN(ifa)->sin_addr;
			arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr);
			break;
#endif INET
		default:
			uninit(ifp->if_unit);
			break;

		}
		break;
        case SIOCSIFFLAGS:
		ifp->if_flags |= IFF_NOTRAILERS;	/* kill trailers */
                if ((ifp->if_flags & IFF_UP) == 0 && ifp->if_flags &
                    IFF_RUNNING) {
                        unzap(ifp->if_unit);
                } else if (ifp->if_flags & IFF_UP && (ifp->if_flags &
                                                      IFF_RUNNING) == 0)
                        uninit(ifp->if_unit);
                break;
	default:
		error = EINVAL;
		break;
	}
	splx(s);
	DEBUGF(undebug&SHOW_IO, printf("un%d: unioctl done\n",ifp->if_unit));
	return error;
}

unreset(unit)
        register unsigned int unit;
{
        register struct iocc_device *iod;
	if (unit < NUN && (iod = uninfo[unit]) != 0 && iod->iod_alive != 0) {
                if (un_softc[unit].es_if.if_flags & IFF_RUNNING) {
                        DEBUGF(undebug, printf("un%d: reset\n", unit));
                        unzap(unit);
                }

	}
}


/*
 * turn off the hardware via a reset thru the CSR 
 */
static unzap(unit)
        register unsigned int unit;
{
	register struct un_softc *us = &un_softc[unit];
	DEBUGF(undebug, printf("un%d: unzap\n", unit));

	un_softc[unit].es_if.if_flags &= ~IFF_RUNNING;	/* not running */
	outb(us->csr,(RESET));
	DELAY(10);
	outb(us->csr, 0);
	outb(us->csr+1,0xFF);			/* CA */
	delay(1);
	DEBUGF(undebug, printf("un%d: unzap done\n", unit));
}

/*
 *  unprintethaddr - print an ethernet address
 */
static unprintethaddr(p)
	register char  *p;
{
	register int    i;

	for (i = 0; i < ETH_ADDR_SIZE; i++) {
		if (i != 0)
			printf(":");
		printf("%x", *p++);
	}
}


#ifdef DEBUG
static char *ethertype(type)
{
	if (type == ETHERTYPE_IP)
		return("ip");
	if (type == ETHERTYPE_ARP)
		return("arp");
	return("?");
}

static unprintpacket(prefix,eh,postfix)
	register struct ether_header *eh;
	char *prefix, *postfix;
{
	char            cbuf[6];
	printf("%sfrom = ",prefix);
	bcopy(eh->ether_shost, cbuf, sizeof(cbuf));
	unprintethaddr(cbuf);
	printf("  to = ");
	bcopy(eh->ether_dhost, cbuf, sizeof(cbuf));
	unprintethaddr(cbuf);
	printf(" type=%s(%x) %s",ethertype(eh->ether_type),eh->ether_type,postfix);
}
#endif	/* DEBUG */

/*
 * watch routine. We check to see if we've not gotten any transmit or 
 * receive interrupts lately and attempt to restart if necessary.
 * We assume that on an ethernet with at least one other active machine
 * that we will get at least one broadcast packet per timeout period.
 */

unwatch(arg)
caddr_t arg;
{
	register int    unit = (int) arg;
	register struct un_softc *us = &un_softc[unit];
	register struct ifnet *ifp = &us->es_if;
	int restart = 0;

	if ((ifp->if_flags & IFF_RUNNING) != 0) {
		if (us->us_owatch && --us->us_owatch==0) {
			printf("un%d: lost output interrupt - restarting.\n", unit);
			++restart;
		}
		if (us->us_iwatch && --us->us_iwatch==0) {
			DEBUGF(undebug,
		 printf("un%d: lost input interrupt?? - restarting.\n", unit););
			++restart;
		}
		if (restart) {
			unzap(unit);	/* clears RUNNING */
			uninit(unit);
		}
		timeout(unwatch, (caddr_t) unit, hz);
	} else
		us->us_watch = 0;	/* if not running don't watch it */
}
#endif /* NUN > 0 */


