/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION  1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:if_mc.c 12.1$ */
/* $ACIS:if_mc.c 12.1$ */
/* $Source: /ibm/acis/usr/sys/ca_atr/RCS/if_mc.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char    *rcsid = "$Header:if_mc.c 12.1$";
#endif lint

#include "mc.h"
#if NMC > 0

#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 "if_unreg.h"
#include "../machineio/ioccvar.h"
#include "time.h"
#include "kernel.h"
#include "../machine/debug.h"
#include "../ca_atr/pcif.h"
#include "../pc_code/mc.h"

#define MCIWATCH	60
#define MCOWATCH	30

int 
mcprobe(), mcattach();

caddr_t         mcstd[] = {0};

struct iocc_device *mcinfo[NMC];

int 
mcint(), mcinit(), mcioctl(), mcoutput(), mcreset(), mcwatch();
int mcsuspend();

struct iocc_driver mcdriver =
{mcprobe, 0, mcattach, 0, mcstd, "mc", mcinfo,
 /*     int   csr chanrel flags 		suspend */
 0, 0, mcint, 0,    0,     DRIVER_SUSPEND,   mcsuspend };

struct mbuf    *mcget();

/*
 * Ethernet software status per adapter.
 */
struct mc_softc {
	struct arpcom   us_ac;	/* generic network interface stuff */
#define	us_if	us_ac.ac_if	/* ifnet struct */
#define	us_addr	us_ac.ac_enaddr	/* hardware (i.e. ethernet) address */
	short           us_oactive;	/* 1 => output active */
	short           us_xfull[2];	/* 1 => a full xmt buf */
	char            mc_rcvbuf[MAX_PAC_LEN];	/* 1 receive buffer per
						 * adapter */
	char            mc_xmtbuf[MAX_PAC_LEN];	/* 1 transmit buffer
						 * per adapter */
	int             mcextra_ints;	/* Interrupt count for stray handling */
	short		us_owatch;	/* output watch timer */
	short		us_iwatch;	/* input watch timer */
	short		us_watch;	/* watchdog running */
	int		mc_win;		/* pc window address */
}               mc_softc[NMC];

#ifdef DEBUG
int            mcdebug = 0;
#endif DEBUG

/*
 *  mcprobe - This adapter can only be at interrupt 3
 */
mcprobe(p)
	register caddr_t p;
{
	unsigned int mc_win = get_pc_cb(MCENT);
	int rc = PROBE_BAD;

	if (mc_win) {
		set_pcvec_map(MCIRQ, 1);	/* defensive tell pc to allow ints */
		if (pc_req(CB_MCREQ, MC_CMD_NOP, MCENT) < 0) {
			panic("pc req fail, help!");
		}
		PROBE_DELAY(500000);		/* allow .5 seconds for interrupt */
		rc = PROBE_OK;
	}
	return(rc);
}

/*
 *  mcattach - 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.
 */
mcattach(iod)
	register struct iocc_device *iod;
{
	register struct mc_softc *us = &mc_softc[iod->iod_unit];
	register struct ifnet *ifp = &us->us_if;
	register int    i;
	int	old_window = get_512_window();
	unsigned int mc_win = get_pc_cb(MCENT);
	struct mcdev *mc_pc = (struct mcdev *)(set_512_window(mc_win) + pcif_512_fw);
	us->mc_win = mc_win;
	ifp->if_unit = iod->iod_unit;
	ifp->if_name = "mc";
	ifp->if_mtu = ETHERMTU;

	/*
	 * Read the ethernet address off the board. We do an
	 * INIT command so that we can get the address.
	 * We will then save the address. 
	 */
	mcinitcmd(mc_pc,ifp);

	/*
	 * If the init command takes to long we time out and return so we
	 * don't hang the system If this happens, there is most likely a
	 * hardware problem with the adapter. 
	 */
	if (mc_pc->result == 0) {
		printf("mc%d: init timed out - not attached\n", iod->iod_unit);
		return;
	}
	if (mc_pc->result != MC_RESULT_OK) {
		printf("mc%d: not init'ed rc=%x\n", iod->iod_unit, mc_pc->result);
		return;
	}
	/*
	 * If the init command takes to long we time out and return so we
	 * don't hang the system If this happens, there is most likely a
	 * hardware problem with the adapter. 
	 */
	if (mc_pc->result == 0) {
		printf("mc%d didn't init - not attached\n", iod->iod_unit);
		return;
	}
	for (i = 0; i < ETH_ADDR_SIZE; i++) {
		us->us_addr[i] = mc_pc->addr[i];
	}
	set_512_window(old_window);
	printf("mc%d: ethernet address ", ifp->if_unit);
	mcprintethaddr(us->us_addr);
	printf("\n");
	ifp->if_init = mcinit;
	ifp->if_ioctl = mcioctl;
	ifp->if_output = mcoutput;
	ifp->if_reset = mcreset;
	ifp->if_flags = IFF_BROADCAST;
	if_attach(ifp);
	DEBUGF(mcdebug, printf("mc%d: attached\n", iod->iod_unit));
}

/*
 * send an mc initialize command
 */
mcinitcmd(mc_pc,ifp)
	struct mcdev *mc_pc;
	struct ifnet *ifp;
{
	register int    i;

	mc_pc->result = 0;
	if (pc_req(CB_MCREQ, MC_CMD_OPEN, MCENT) < 0) {
		panic("pc req fail, help!");
	}
	for (i = 100; mc_pc->result == 0 && i > 0; --i)
		delay(1);

	DEBUGF(mcdebug, printf("mc%d: init result=%x status=%x\n", ifp->if_unit, mc_pc->result));
}

/*
 * handle suspend request to return control back to DOS, and then
 * getting it back again.
 */
mcsuspend(iod, idr, how)
	register struct iocc_device *iod;
	struct iocc_driver *idr;
{
	int unit = iod->iod_unit;
	register struct mc_softc *us = &mc_softc[unit];
	register struct ifnet *ifp = &us->us_if;
	int	old_window = get_512_window();
	unsigned int mc_win = get_pc_cb(MCENT);
	struct mcdev *mc_pc = (struct mcdev *)(set_512_window(mc_win) + pcif_512_fw);


	if (how == SUSPEND_START) {
		if (mc_softc[unit].us_if.if_flags & IFF_RUNNING)
			mczap(unit);
	} else if (how == SUSPEND_DONE) {
		us->mc_win = mc_win;
		mcinitcmd(mc_pc,ifp);
		if (ifp->if_flags & IFF_RUNNING) {
			ifp->if_flags &= ~IFF_RUNNING;	/* pretend not running */
			mcinit(unit);
		}
	}

	set_512_window(old_window);
}

/*
 *  mcreset - reset interface
 *		only called from mcareset which is only called on halt
 *		when returning to DOS.
 */
mcreset(unit)
	register unsigned int unit;
{
	register struct iocc_device *iod;

	if (unit < NMC && (iod = mcinfo[unit]) != 0 && iod->iod_alive != 0) {
		if (mc_softc[unit].us_if.if_flags & IFF_RUNNING) {
			mc_softc[unit].us_if.if_flags &= ~IFF_RUNNING;
			DEBUGF(mcdebug, printf("mc%d: reset\n", unit));
			mczap(unit);
		}
	}
}

/*
 * 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.
 */

mcwatch(arg)
caddr_t arg;
{
	register int    unit = (int) arg;
	register struct mc_softc *us = &mc_softc[unit];
	register struct ifnet *ifp = &us->us_if;

	if ((ifp->if_flags & IFF_RUNNING) != 0) {
		if (us->us_owatch && --us->us_owatch==0) {
			printf("mc%d: lost output interrupt - calling mcstart\n", unit);
			mcstart(us);
		}
		if (us->us_iwatch && --us->us_iwatch==0) {
			printf("mc%d: lost input interrupt?? - calling mcenrcv\n", unit);
			mcenrcv(us, unit);
		}
		timeout(mcwatch, (caddr_t) unit, hz);
	} else
		us->us_watch = 0;	/* if not running don't watch it */
}

/*
 *  mcinit - initialize interface, enable packet reception, start any
 *  pending writes
 */
mcinit(unit)
	register int    unit;
{
	register struct mc_softc *us = &mc_softc[unit];
	register struct ifnet *ifp = &us->us_if;
	register int    s;
	register char  *bp;

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

		s = splimp();
		if (!us->us_watch) {
			timeout(mcwatch, (caddr_t) unit, hz);
			us->us_watch++;		/* timer running */
		}
		/* re-program ethernet address here if XNS supported */
		/* turn adapter on here */
		us->us_oactive = 0;
		ifp->if_flags |= IFF_RUNNING;
		/*
		 * turn on packet reception here - We do that by sending the
		 */
		bp = ((char *) (&us->mc_rcvbuf));
		if (pc_req(CB_MCREQ, MC_CMD_RCVEN, MCENT) < 0) {
			panic("pc req fail, help!");
		}
		if (ifp->if_snd.ifq_head) {	/* anything on send queue */
			struct mbuf    *m;

			IF_DEQUEUE(&ifp->if_snd, m);
			mcput(us, m, unit);
			mcstart(us);
		}
		splx(s);
	}
	DEBUGF(mcdebug, printf("mc%d: init'ed\n", unit));
}

/*
 *  mcstart - start output from one of the adapter's 2 transmit buffers
 */
mcstart(us)
	register struct mc_softc *us;
{

	us->us_oactive = 1;
	us->us_owatch = MCOWATCH;	/* set watchdog timer */
	if (pc_req(CB_MCREQ, MC_CMD_XMIT, MCENT) < 0) {
		panic("pc req fail, help!");
	}
}

/*
 *  mcint - interrupt handler.  find the cause of the interrupt and
 *  dispatch an appropriate handler routine.
 */
mcint(unit,irq,info)
	register int    unit;
{
	register struct mc_softc *us = &mc_softc[unit];
	register char   status;
	int	old_window = get_512_window();
	struct mcdev *mc_pc = (struct mcdev *)(set_512_window(us->mc_win) + pcif_512_fw);
	int	rc = 1;

	switch(IRQ_GET_INFO(info)) {
	case MC_RCV_INTR:
		{
		register u_short length;

		status = mc_pc->rstatus;
		if (status == 0) {
			printf("stray rcv intr\n");
			rc = 1;
			break;
		}
		length = mc_pc->rlength;	/* get byte swapped length */
		bcopy((char *) mc_pc->rbuff,
			(char *)(&us->mc_rcvbuf),length);
		mcrint(unit, us, status, length);
		pc_copy_in += length;		/* read from pc */
		rc = 0;
		}
		break;
	case MC_XMIT_INTR:
		status = mc_pc->xstatus;
		DEBUGF(mcdebug&0x02, printf("mc%d: xmit intr %x\n",unit,status));
		if (status == 0) {
			printf("stray xmit intr\n");
			rc = 1;
			break;
		}
		mc_pc->xstatus = 0;
		mcxint(unit, us, status);
		rc = 0;
		break;
	default:
		printf("mc%d: Illegal intr field in mc_pc (%d)\n",unit,
					 		IRQ_GET_INFO(info));
	case 0:
		if (mc_pc->result)
			rc = 0;		/* other command interrupt */
		break;
	}
	set_512_window(old_window);
	return (rc);
}

/*
 *  mcrint - interrupt handler for packet reception.
 *
 *  log error if the result field was not correct. If the return code was
 *  MC_CMD_CAN (command canceled) we chuckit but we don't add one to the error
 *  log. Examine packet to determine type, if can't determine packet length
 *  from type, drop packet. otherwise decapsulate packet based on type and 
 *  pass to an appropriate higher-level input routine.
 */

static int             maxethlen = 0;

mcrint(unit, us, result, len)
	int             unit;
	register struct mc_softc *us;
	register char   result;
	register int    len;
{
	register struct ifnet *ifp = &us->us_if;
	register struct ether_header *eh;
	register struct mbuf *m;
	int             resid;
	int             off;
	struct ifqueue *inq;
	u_short         type;

	us->us_iwatch = 0;		/* got an interrupt */
	if ((result & MC_RECV_DONE) == 0) {
		us->us_if.if_ierrors++;
		goto chuckit;
	}
	us->us_if.if_ipackets++;
	if (len >= maxethlen) {
		maxethlen = len;
	}
	if (len > MAX_PAC_LEN) {
		printf("mc%d: huge packet!\n", unit);
		goto chuckit;
	}
	/*
	 * Process the packet 
	 */
	eh = (struct ether_header *) & us->mc_rcvbuf;
	DEBUGF(mcdebug & 0x2,
	       {
		char            cbuf[6];
		printf(" %d bytes from = ", len);
		bcopy(eh->ether_shost, cbuf, sizeof(cbuf));
		mcprintethaddr(cbuf);
		printf("  to = ");
		bcopy(eh->ether_dhost, cbuf, sizeof(cbuf));
		mcprintethaddr(cbuf);
		printf(" ");
	}
	)
		len -= sizeof(struct ether_header);
	type = ntohs((u_short) eh->ether_type);
	/*
	 * The ETHERTYPE_NTRAILER packet types starting at ETHERTYPE_TRAIL
	 * have (type - ETHERTYPE_TRAIL) * 512 bytes of data followed by a
	 * type field and then a (variable length) header 
	 */
	if (type >= ETHERTYPE_TRAIL &&
	    type < ETHERTYPE_TRAIL + ETHERTYPE_NTRAILER) {
		off = (type - ETHERTYPE_TRAIL) * 512;
		if (off >= ETHERMTU) {
			goto chuckit;
		}
		type = ntohs(*(u_short *) ((caddr_t) (eh + 1) + off));
		resid = ntohs(*(u_short *) ((caddr_t) (eh + 1) + off + 2));
		if (off + resid > len) {
			goto chuckit;
		}
		len = off + resid;
	} else {
		off = 0;
	}
	if (len == 0) {
		goto chuckit;
	}
	/*
	 * pull packet off interface.  if off is non-zero, the packet has a
	 * trailing "header".  unget will move this header to the front, but
	 * we still have to remove the type and length fields from the front
	 * of the data. 
	 */
	m = mcget((char *) eh, len, off, &us->us_if);
	if (m != 0) {
		if (off) {
			struct ifnet   *ifp2;

			/*
			 * bcopy is used since word moves must be on 4 byte
			 * boundaries on the RT PC 
			 */
			bcopy(mtod(m, char *), (char *) &ifp2, sizeof(ifp2));
			m->m_off += 2 * sizeof(u_short);
			m->m_len -= 2 * sizeof(u_short);
			bcopy((char *) &ifp2, mtod(m, char *), sizeof(ifp2));
		}
		switch (type) {
#ifdef INET
		case ETHERTYPE_IP:
 		    {
			int s;
			DEBUGF(mcdebug & 0x2, printf("ip packet");
			)
			schednetisr(NETISR_IP);
			s = splimp();
			inq = &ipintrq;
			if (IF_QFULL(inq)) {
				DEBUGF(mcdebug & 0x2, printf(" qfull\n");
				)
					IF_DROP(inq);
				m_freem(m);
			} else {
				IF_ENQUEUE(inq, m);
				DEBUGF(mcdebug & 0x2, printf(" queued\n");
				)
			}
			splx(s);
			break;
		     }

		case ETHERTYPE_ARP:
			DEBUGF(mcdebug & 0x2, printf("arp packet\n");
			)
				arpinput(&us->us_ac, m);	/* arpinput frees m */
			break;
#endif INET
#ifdef NS
		case ETHERTYPE_NS:
			DEBUGF(mcdebug & 0x2, printf("ns packet\n");
			)
				schednetisr(NETISR_NS);
			inq = &nsintrq;
			break;
#endif NS
		default:
			DEBUGF(mcdebug & 0x2, printf("unknown packet\n");
			)
				m_freem(m);
			break;
		}
	}
chuckit:
	/*
	 * If the board is down we do not want to send the FCI another
	 * receive zcb 
	 */
	if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) {
		mcenrcv(us, unit);
	}
	return;
}

/*
 *  mcxint -  interrupt handler for transmit ready
 */
mcxint(unit, us, result)
	register int    unit;
	register struct mc_softc *us;
	register char   result;
{

	if ((result & MC_XMIT_DONE) == 0) {
		us->us_if.if_oerrors++;
	}
	us->us_if.if_opackets++;
	us->us_owatch = 0;		/* got an interrupt */
	/* if the next xmt buffer is full then start transmitting */
	if (us->us_if.if_snd.ifq_head) {
		struct mbuf    *m;

		IF_DEQUEUE(&us->us_if.if_snd, m);
		mcput(us, m, unit);
		mcstart(us);		/* start output */
	} else {
		us->us_oactive = 0;
	}
}

/*
 *  mcoutput - ethernet output routine.  encapsulate a packet of type
 *  family for the local net.  use trailer local net encapsulation if
 *  the number of bytes in the mbufs after the first is a multiple of
 *  512.
 */
mcoutput(ifp, m0, dst)
	register struct ifnet *ifp;
	register struct mbuf *m0;
	register struct sockaddr *dst;
{
	int             type;
	int             s;
	int             error;
	char            edst[ETH_ADDR_SIZE];
	struct in_addr  idst;
	register struct mc_softc *us = &mc_softc[ifp->if_unit];
	register struct mbuf *m = m0;
	register struct ether_header *eh;
	int             off;
	struct mbuf    *m_get();
	int             usetrailers;

	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->us_ac, m, &idst, edst, &usetrailers)) {
			/* not resolved */
			return (0);
		}
		off = ntohs((u_short) mtod(m, struct ip *)->ip_len) - m->m_len;
		if (usetrailers && off > 0 && (off & 0x1ff) == 0 &&
		    m->m_off >= MMINOFF + 2 * sizeof(u_short)) {
			type = ETHERTYPE_TRAIL + (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);
			/*
			 * Packet to be sent with 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;
		} else {
			type = ETHERTYPE_IP;
		}
		break;
#endif INET
#ifdef NS
	case AF_NS:
		bcopy((caddr_t) & (((struct sockaddr_ns *) dst)->sns_addr.x_host),
		      (caddr_t) edst, sizeof(edst));
		type = ETHERTYPE_NS;
		off = 0;
		break;
#endif NS
	case AF_UNSPEC:
		eh = (struct ether_header *) dst->sa_data;
		bcopy((char *) eh->ether_dhost, (caddr_t) edst, sizeof(edst));
		type = eh->ether_type;
		break;
	default:
		printf("mc%d: can't handle af%d\n", ifp->if_unit,
		       dst->sa_family);
		error = EAFNOSUPPORT;
		goto bad;
	}
	/*
	 * Add local net header.  If no space in first mbuf, allocate
	 * another. 
	 */
	if (m->m_off > MMAXOFF ||
	    MMINOFF + sizeof(struct ether_header) > m->m_off) {
		m = m_get(M_DONTWAIT, MT_HEADER);
		/*
		 * Note:  m_get, m_freem etc. guard against concurrent
		 * updates to the list of free mbufs. 
		 */
		if (m == 0) {
			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);
	}
	eh = mtod(m, struct ether_header *);
	bcopy((caddr_t) edst, (caddr_t) eh->ether_dhost, sizeof(edst));
	bcopy((caddr_t) us->us_addr, (caddr_t) eh->ether_shost,
	      sizeof(eh->ether_shost));
	eh->ether_type = htons((u_short) type);
	/*
	 * queue packet for transmission.  if there is an empty transmit
	 * buffer on the adapter, use it. 
	 */
	s = splimp();
	if (IF_QFULL(&ifp->if_snd)) {
		IF_DROP(&ifp->if_snd);
		error = ENOBUFS;
		goto qfull;
	}
	if (us->us_oactive == 0) {
		mcput(us, m, ifp->if_unit);
		mcstart(us);
	} else {
		IF_ENQUEUE(&ifp->if_snd, m);
	}
	splx(s);
	return (0);
qfull:
	m0 = m;
	splx(s);
bad:
	m_freem(m0);
	return (error);
}

/*
 * mcenrcv - send the PC another receive enable
 */
mcenrcv(us, unit)
	struct mc_softc *us;
	int             unit;
{
	register int    s = splimp();


	us->us_iwatch = MCIWATCH;	/* set input timeout */
	if (pc_req(CB_MCREQ, MC_CMD_RCVEN, MCENT) < 0) {
		panic("pc req fail, help!");
	}
	splx(s);
}

/*
 *  mcput -  copy packet from an  mbuf chain to one of the adapter's softc
 *  transmit buffers.  the packet is extended to the minimum legal
 *  size if necessary.  the extra bytes could be zeroed out to improve
 *  security but are not to maximize performance.
 */
mcput(us, m, unit)
	struct mc_softc *us;
	register struct mbuf *m;
	int             unit;
{
	register struct mbuf *mp;
	register char  *bp;
	int             total_len = 0;
	int old_window = get_512_window();
	struct mcdev *mc_pc = (struct mcdev *)(set_512_window(us->mc_win) + pcif_512_fw);

	/* bp will point to the appropriate softc transmit buffers */
	bp = ((char *) (&us->mc_xmtbuf[0]));
	for (mp = m; mp; mp = mp->m_next) {
		register unsigned len = mp->m_len;

		bcopy(mtod(mp, char *), bp, len);
		bp += len;
		total_len += len;
		/*
		 * we put the address of the transmit buffer in the zcb down
		 * below so we have to make sure that it is pointing to the
		 * front of the buffer 
		 */
	}
	bp = ((char *) (&us->mc_xmtbuf[0]));
	DEBUGF(mcdebug&2, printf("unput: packet length = %d\n", total_len);
	)
	mc_pc->xstatus = 0;
	mc_pc->xlength = total_len;
	bcopy(bp, mc_pc->xbuff, total_len);
	pc_copy_out += total_len;	/* bytes written to pc */
	set_512_window(old_window);
	m_freem(m);
}

/*
 *  mcget - copy packet from adapter's softc receive buffers
 *  into a chain of mbufs
 */

struct mbuf    *
mcget(mcbuf, totlen, off0, ifp)
	char           *mcbuf;
	register int    totlen;
	int             off0;
	struct ifnet   *ifp;
{
	register struct mbuf *m;
	struct mbuf    *top = 0;
	register struct mbuf **mp = &top;
	register int    off = off0;
	register int    len;
	register char  *cp;

	cp = mcbuf + sizeof(struct ether_header);
	while (totlen > 0) {
		char           *mcp;

		MGET(m, M_DONTWAIT, MT_DATA);
		if (m == 0)
			goto bad;
		if (off) {	/* trailer exists */
			len = totlen - off;
			cp = mcbuf + sizeof(struct ether_header) + off;
		} else
			len = totlen;
		if (ifp)
			len += sizeof(ifp);
		if (len >= NBPG) {
			MCLGET(m);
			if (m->m_len == CLBYTES)
				m->m_len = len = MIN(len, CLBYTES);
			else
				m->m_len = len = MIN(MLEN, len);
		} else {
			m->m_len = len = MIN(MLEN, len);
			m->m_off = MMINOFF;
		}
		mcp = mtod(m, char *);
		if (ifp) {
			/* prepend ifp to first mbuf */
			/*
			 * bcopy is used since since word moves must be on 4
			 * byte boundaries on the RT PC 
			 */
			bcopy((char *) &ifp, mcp, sizeof(ifp));
			mcp += sizeof(ifp);
			len -= sizeof(ifp);
			ifp = (struct ifnet *) 0;
		}
		bcopy(cp, mcp, len);
		cp += len;
		*mp = m;
		mp = &m->m_next;
		if (off == 0) {
			totlen -= len;
			continue;
		}
		off += len;
		if (off == totlen) {
			cp = mcbuf + sizeof(struct ether_header);
			off = 0;
			totlen = off0;
		}
	}
	return (top);
bad:
	m_freem(top);
	return (0);
}

/*
 *  ioctl - process an ioctl request.
 */
mcioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	register int    cmd;
	register caddr_t data;
{
	register struct ifaddr *ifa = (struct ifaddr *) data;
	register int    s = splimp();
	register int    error = 0;

	switch (cmd) {
	case SIOCSIFADDR:
		ifp->if_flags |= IFF_UP;

		switch (ifa->ifa_addr.sa_family) {
#ifdef INET
		case AF_INET:
			mcinit(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
#ifdef NS
		case AF_NS:
			{
				struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr);
				struct mc_softc *us = &mc_softc[ifp->if_unit];

				if (ns_nullhost(*ina))
					ina->x_host = *(union ns_host *) (us->us_addr);
				else {
					ifp->if_flags &= ~IFF_RUNNING;
					bcopy((caddr_t) ina->x_host.c_host,
					      (caddr_t) us->us_addr, sizeof(us->us_addr));
					/*
					 * the mcinit will set the hardware
					 * address since the IFF_RUNNING flag
					 * is off 
					 */
				}
				mcinit(ifp->if_unit);
				break;
			}
#endif NS
		default:
			mcinit(ifp->if_unit);
			break;
		}
		break;
	case SIOCSIFFLAGS:
		if ((ifp->if_flags & IFF_UP) == 0 && ifp->if_flags &
		    IFF_RUNNING) {
			mczap(ifp->if_unit);
			ifp->if_flags &= ~IFF_RUNNING;
		} else if (ifp->if_flags & IFF_UP && (ifp->if_flags &
						      IFF_RUNNING) == 0)
			mcinit(ifp->if_unit);
		break;
	default:
		error = EINVAL;
	}
	splx(s);
	return (error);
}

/*
 *  mczap - send a cancel receive's zcb to the PC 
 *  
 */
mczap(unit)
	register int    unit;
{
	register struct mc_softc *us = &mc_softc[unit];
	int	old_window = get_512_window();
	struct mcdev *mc_pc = (struct mcdev *)(set_512_window(us->mc_win) + pcif_512_fw);

	DEBUGF(mcdebug, printf("mc%d: Zap the board\n", unit));
	mc_pc->result = 0;
	if (pc_req(CB_MCREQ, MC_CMD_CANRCV, MCENT) < 0) {
		panic("pc req fail, help!");
	}
	while (mc_pc->result == 0) {
		delay(10);
	}
	if (mc_pc->result != MC_RESULT_OK)
		DEBUGF(mcdebug, printf("mc%d: Receives not Cancelled\n", unit));
	set_512_window(old_window);
}

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

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