/*	$OpenBSD: smc90cx6.c,v 1.7 2004/05/12 06:35:10 tedu Exp $ */
/*	$NetBSD: smc90cx6.c,v 1.17 1996/05/07 01:43:18 thorpej Exp $ */

/*
 * Copyright (c) 1994, 1995 Ignatios Souvatzis
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Ignatios Souvatzis
 *      for the NetBSD Project.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Driver for the Commodore Business Machines ARCnet card.
 */

#define BAHASMCOPY /**/
#define BAHSOFTCOPY /**/
#define BAHRETRANSMIT /**/
/* #define BAHTIMINGS */
/* #define BAH_DEBUG 3 */

/* zeroth version of M68060 support */

#if defined(M68060) && defined(BAHASMCOPY)
#undef BAHASMCOPY
#endif

#include "bpfilter.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/buf.h>
#include <sys/device.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/ioctl.h>
#include <sys/errno.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/netisr.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>
#include <netinet/if_arc.h>
#endif

#if NBPFILTER > 0
#include <net/bpf.h>
#endif

#include <sys/kernel.h>
#include <machine/cpu.h>
#include <machine/mtpr.h>

#include <amiga/amiga/device.h>
#include <amiga/amiga/isr.h>
#include <amiga/dev/zbusvar.h>
#include <amiga/dev/if_bahreg.h>

/* these should be elsewhere */

#define ARC_MIN_LEN 1
#define ARC_MIN_FORBID_LEN 254
#define ARC_MAX_FORBID_LEN 256
#define ARC_MAX_LEN 508
#define ARC_ADDR_LEN 1

/* for watchdog timer. This should be more than enough. */
#define ARCTIMEOUT (5*IFNET_SLOWHZ)

/*
 * This currently uses 2 bufs for tx, 2 for rx
 *
 * New rx protocol:
 *
 * rx has a fillcount variable. If fillcount > (NRXBUF-1), 
 * rx can be switched off from rx hard int. 
 * Else rx is restarted on the other receiver.
 * rx soft int counts down. if it is == (NRXBUF-1), it restarts
 * the receiver.
 * To ensure packet ordering (we need that for 1201 later), we have a counter
 * which is incremented modulo 256 on each receive and a per buffer
 * variable, which is set to the counter on filling. The soft int can
 * compare both values to determine the older packet.
 *
 * Transmit direction:
 * 
 * bah_start checks tx_fillcount
 * case 2: return
 *
 * else fill tx_act ^ 1 && inc tx_fillcount
 *
 * check tx_fillcount again.
 * case 2: set IFF_OACTIVE to stop arc_output from filling us.
 * case 1: start tx
 *
 * tint clears IFF_OCATIVE, decrements and checks tx_fillcount
 * case 1: start tx on tx_act ^ 1, softcall bah_start
 * case 0: softcall bah_start
 *
 * #define fill(i) get mbuf && copy mbuf to chip(i)
 */

#ifdef BAHTIMINGS
/*
 * ARCnet stats; per interface.
 */
struct bah_stats {
	u_long	mincopyin;
	u_long	maxcopyin;		/* divided by byte count */
	u_long	mincopyout;
	u_long	maxcopyout;
	u_long	minsend;
	u_long	maxsend;
	u_long	lasttxstart_mics;
	struct	timeval lasttxstart_tv;
};

#error BAHTIMINGS CODE IS BROKEN; use of clkread() is bogus
#endif

/*
 * Arcnet software status per interface
 */
struct bah_softc {
	struct	device	sc_dev;
	struct	arccom	sc_arccom;	/* Common arcnet structures */
	struct	isr	sc_isr;
	struct	a2060	*sc_base;
	u_long	sc_recontime;		/* seconds only, I'm lazy */
	u_long	sc_reconcount;		/* for the above */
	u_long	sc_reconcount_excessive; /* for the above */
#define ARC_EXCESSIVE_RECONS 20
#define ARC_EXCESSIVE_RECONS_REWARN 400
	u_char	sc_intmask;
	u_char	sc_rx_act;		/* 2..3 */
	u_char	sc_tx_act;		/* 0..1 */
	u_char	sc_rx_fillcount;
	u_char	sc_tx_fillcount;
	u_char	sc_broadcast[2];	/* is it a broadcast packet? */
	u_char	sc_retransmits[2];	/* unused at the moment */
#ifdef BAHTIMINGS
	struct	bah_stats sc_stats;
#endif
};

int	bah_zbus_match(struct device *, void *, void *);
void	bah_zbus_attach(struct device *, struct device *, void *);
void	bah_init(struct bah_softc *);
void	bah_reset(struct bah_softc *);
void	bah_stop(struct bah_softc *);
void	bah_start(struct ifnet *);
int	bahintr(struct bah_softc *sc);
int	bah_ioctl(struct ifnet *, unsigned long, caddr_t);
void	bah_watchdog(struct ifnet *);
void	movepout(u_char *from, u_char __volatile *to, int len);
void	movepin(u_char __volatile *from, u_char *to, int len);
void	bah_srint(void *vsc, void *dummy);
void	callstart(void *vsc, void *dummy);

#ifdef BAHTIMINGS
int	clkread();
#endif

struct cfattach bah_zbus_ca = {
	sizeof(struct bah_softc), bah_zbus_match, bah_zbus_attach
};

struct cfdriver bah_cd = {
	NULL, "bah", DV_IFNET
};

int
bah_zbus_match(parent, match, aux)
	struct device *parent;
	void *match, *aux;
{
	struct zbus_args *zap = aux;

	if ((zap->manid == 514 || zap->manid == 1053) && zap->prodid == 9)
		return (1);

	return (0);
}

void
bah_zbus_attach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct bah_softc *sc = (void *)self;
	struct zbus_args *zap = aux;
	struct ifnet *ifp = &sc->sc_arccom.ac_if;
	int s, linkaddress;

#if (defined(BAH_DEBUG) && (BAH_DEBUG > 2))
	printf("\n%s: attach(0x%x, 0x%x, 0x%x)\n",
	    sc->sc_dev.dv_xname, parent, self, aux);
#endif
	s = splhigh();
	sc->sc_base = zap->va;

	/*
	 * read the arcnet address from the board
	 */

	sc->sc_base->kick1 = 0x0;
	sc->sc_base->kick2 = 0x0;
	DELAY(200);

	sc->sc_base->kick1 = 0xFF;
	sc->sc_base->kick2 = 0xFF;
	do {
		DELAY(200);
	} while (!(sc->sc_base->status & ARC_POR)); 

	linkaddress = sc->sc_base->dipswitches;

#ifdef BAHTIMINGS
	printf(": link addr 0x%02x(%ld), with timer\n",
	    linkaddress, linkaddress);
#else
	printf(": link addr 0x%02x(%ld)\n", linkaddress, linkaddress);
#endif

	sc->sc_arccom.ac_anaddr = linkaddress;

	/* clear the int mask... */

	sc->sc_base->status = sc->sc_intmask = 0;

	sc->sc_base->command = ARC_CONF(CONF_LONG);
	sc->sc_base->command = ARC_CLR(CLR_POR|CLR_RECONFIG);
	sc->sc_recontime = sc->sc_reconcount = 0;

	/* and reenable kernel int level */
	splx(s);

	/*
	 * set interface to stopped condition (reset)
	 */
	bah_stop(sc); 

	bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ);
	ifp->if_softc = sc;
	ifp->if_output = arc_output;
	ifp->if_start = bah_start;
	ifp->if_ioctl = bah_ioctl;
	ifp->if_timer = 0;
	ifp->if_watchdog  = bah_watchdog;

	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX |
	    IFF_NOTRAILERS | IFF_NOARP;

	ifp->if_mtu = ARCMTU;
	IFQ_SET_READY(&ifp->if_snd);

	if_attach(ifp);
	arc_ifattach(ifp);

#if NBPFILTER > 0
	bpfattach(&ifp->if_bpf, ifp, DLT_ARCNET, ARC_HDRLEN);
#endif
	/* under heavy load we need four of them: */
	alloc_sicallback();
	alloc_sicallback();
	alloc_sicallback();
	alloc_sicallback();

	sc->sc_isr.isr_intr = bahintr;
	sc->sc_isr.isr_arg = sc;
	sc->sc_isr.isr_ipl = 2;
	add_isr(&sc->sc_isr);
}

/*
 * Initialize device
 *
 */
void
bah_init(sc)
	struct bah_softc *sc;
{
	struct ifnet *ifp;
	int s;

	ifp = &sc->sc_arccom.ac_if;

	if ((ifp->if_flags & IFF_RUNNING) == 0) {
		s = splnet();
		ifp->if_flags |= IFF_RUNNING;
		bah_reset(sc);
		bah_start(ifp);
		splx(s);
	}
}

/*
 * Reset the interface...
 *
 * this assumes that it is called inside a critical section...
 *
 */
void
bah_reset(sc)
	struct bah_softc *sc;
{
	struct ifnet *ifp;
	int linkaddress;

	ifp = &sc->sc_arccom.ac_if;

#ifdef BAH_DEBUG
	printf("%s: reset\n", sc->sc_dev.dv_xname);
#endif
	/* stop hardware in case it still runs */

	sc->sc_base->kick1 = 0;
	sc->sc_base->kick2 = 0;
	DELAY(200);

	/* and restart it */
	sc->sc_base->kick1 = 0xFF;
	sc->sc_base->kick2 = 0xFF;

	do {
		DELAY(200);
	} while (!(sc->sc_base->status & ARC_POR)); 

	linkaddress = sc->sc_base->dipswitches;

#if defined(BAH_DEBUG) && (BAH_DEBUG > 2)
	printf("%s: reset: card reset, link addr = 0x%02x (%ld)\n",
	    sc->sc_dev.dv_xname, linkaddress, linkaddress);
#endif
	sc->sc_arccom.ac_anaddr = linkaddress;

	/* tell the routing level about the (possibly changed) link address */
	arc_ifattach(ifp);

	/* POR is NMI, but we need it below: */
	sc->sc_intmask = ARC_RECON|ARC_POR;
	sc->sc_base->status	= sc->sc_intmask;
	sc->sc_base->command = ARC_CONF(CONF_LONG);
	
#ifdef BAH_DEBUG
	printf("%s: reset: chip configured, status=0x%02x\n",
	    sc->sc_dev.dv_xname, sc->sc_base->status);
#endif

	sc->sc_base->command = ARC_CLR(CLR_POR|CLR_RECONFIG);

#ifdef BAH_DEBUG
	printf("%s: reset: bits cleared, status=0x%02x\n",
	    sc->sc_dev.dv_xname, sc->sc_base->status);
#endif

	sc->sc_reconcount_excessive = ARC_EXCESSIVE_RECONS;

	/* start receiver */

	sc->sc_intmask  |= ARC_RI;
	sc->sc_rx_fillcount = 0;
	sc->sc_rx_act = 2;

	sc->sc_base->command = ARC_RXBC(2);
	sc->sc_base->status	= sc->sc_intmask;

#ifdef BAH_DEBUG
	printf("%s: reset: started receiver, status=0x%02x\n",
	    sc->sc_dev.dv_xname, sc->sc_base->status);
#endif

	/* and init transmitter status */
	sc->sc_tx_act = 0;
	sc->sc_tx_fillcount = 0;

	ifp->if_flags |= IFF_RUNNING;
	ifp->if_flags &= ~IFF_OACTIVE;

#ifdef BAHTIMINGS
	bzero((caddr_t)&(sc->sc_stats), sizeof(sc->sc_stats));
	sc->sc_stats.mincopyin =
	    sc->sc_stats.mincopyout =
	    sc->sc_stats.minsend = ULONG_MAX;
#endif

	bah_start(ifp);
}

/*
 * Take interface offline
 */
void
bah_stop(sc)
	struct bah_softc *sc;
{
	/* Stop the interrupts */
	sc->sc_base->status = 0;

	/* Stop the interface */
	sc->sc_base->kick1 = 0;
	sc->sc_base->kick2 = 0;

	/* Stop watchdog timer */
	sc->sc_arccom.ac_if.if_timer = 0;

#ifdef BAHTIMINGS
	log(LOG_DEBUG,"%s: to board: %6lu .. %6lu ns/byte\n",
	    sc->sc_dev.dv_xname,
	    sc->sc_stats.mincopyout, sc->sc_stats.maxcopyout);

	log(LOG_DEBUG,"%s: from board: %6lu .. %6lu ns/byte\n",
	    sc->sc_dev.dv_xname,
	    sc->sc_stats.mincopyin, sc->sc_stats.maxcopyin);
	
	log(LOG_DEBUG,"%s: send time: %6lu .. %6lu mics/byte\n",
	    sc->sc_dev.dv_xname,
	    sc->sc_stats.minsend, sc->sc_stats.maxsend);

	sc->sc_stats.minsend = 
	    sc->sc_stats.mincopyout = 
	    sc->sc_stats.mincopyin = ULONG_MAX;
	sc->sc_stats.maxsend = 
	    sc->sc_stats.maxcopyout = 
	    sc->sc_stats.maxcopyin = 0;
#endif
}

__inline void 
movepout(from, to, len)
	u_char *from;
	__volatile u_char *to;
	int len;
{
#ifdef BAHASMCOPY
	u_short shortd;
	u_long longd, longd1, longd2, longd3, longd4;

	if ((len > 3) && ((long)from) & 3) {
		switch (((long)from) & 3) {
		case 3:
			*to = *from++;
			to += 2; --len;
			break;
		case 1:
			*to = *from++;
			to += 2; --len;
		case 2:
			shortd = *((u_short *)from)++;
			asm("movepw %0,%1@(0)" : : "d"(shortd), "a"(to));
			to += 4; len -= 2;
			break;
		default:
		}

		while (len >= 32) {
			longd1 = *((u_long *)from)++;
			longd2 = *((u_long *)from)++;
			longd3 = *((u_long *)from)++;
			longd4 = *((u_long *)from)++;
			asm("movepl %0,%1@(0)"  : : "d"(longd1), "a"(to));
			asm("movepl %0,%1@(8)"  : : "d"(longd2), "a"(to));
			asm("movepl %0,%1@(16)" : : "d"(longd3), "a"(to));
			asm("movepl %0,%1@(24)" : : "d"(longd4), "a"(to));

			longd1 = *((u_long *)from)++;
			longd2 = *((u_long *)from)++;
			longd3 = *((u_long *)from)++;
			longd4 = *((u_long *)from)++;
			asm("movepl %0,%1@(32)" : : "d"(longd1), "a"(to));
			asm("movepl %0,%1@(40)" : : "d"(longd2), "a"(to));
			asm("movepl %0,%1@(48)" : : "d"(longd3), "a"(to));
			asm("movepl %0,%1@(56)" : : "d"(longd4), "a"(to));

			to += 64; len -= 32;
		}
		while (len > 0) {
			longd = *((u_long *)from)++;
			asm("movepl %0,%1@(0)" : : "d"(longd), "a"(to));
			to += 8; len -= 4;
		}
	}
#endif
	while (len > 0) {
		*to = *from++;
		to += 2;
		--len;
	}
}

/*
 * Start output on interface. Get another datagram to send
 * off the interface queue, and copy it to the
 * interface before starting the output.
 *
 * this assumes that it is called inside a critical section...
 * XXX hm... does it still?
 *
 */
void
bah_start(ifp)
	struct ifnet *ifp;
{
	struct bah_softc *sc;
	struct mbuf *m,*mp;
	__volatile u_char *bah_ram_ptr;
	int len, tlen, offset, s, buffer;
#ifdef BAHTIMINGS
	u_long copystart, lencopy, perbyte;
#endif

	sc = ifp->if_softc;

#if defined(BAH_DEBUG) && (BAH_DEBUG > 3)
	printf("%s: start(0x%x)\n", sc->sc_dev.dv_xname, ifp);
#endif

	if ((ifp->if_flags & IFF_RUNNING) == 0)
		return;

	s = splnet();

	if (sc->sc_tx_fillcount >= 2) {
		splx(s);
		return;
	}

	IFQ_DEQUEUE(&ifp->if_snd, m);
	buffer = sc->sc_tx_act ^ 1;

	splx(s);

	if (m == 0)
		return;

#if NBPFILTER > 0
	/*
	 * If bpf is listening on this interface, let it
	 * see the packet before we commit it to the wire
	 *
	 * (can't give the copy in A2060 card RAM to bpf, because
	 * that RAM is just accessed as on every other byte)
	 */
	if (ifp->if_bpf)
		bpf_mtap(ifp->if_bpf, m);
#endif

#ifdef BAH_DEBUG
	m = m_pullup(m,3);	/* gcc does structure padding */
	printf("%s: start: filling %ld from %ld to %ld type %ld\n",
	    sc->sc_dev.dv_xname, buffer, mtod(m, u_char *)[0],
	    mtod(m, u_char *)[1], mtod(m, u_char *)[2]);
#else
	m = m_pullup(m, 2);
#endif
	bah_ram_ptr = sc->sc_base->buffers + buffer*512*2;

	/* write the addresses to RAM and throw them away */

	/*
	 * Hardware does this: Yet Another Microsecond Saved.
	 * (btw, timing code says usually 2 microseconds)
	 * bah_ram_ptr[0*2] = mtod(m, u_char *)[0];
	 */
	bah_ram_ptr[1 * 2] = mtod(m, u_char *)[1];
	m_adj(m, 2);
		
	/* get total length left at this point */
	tlen = m->m_pkthdr.len;
	if (tlen < ARC_MIN_FORBID_LEN) {
		offset = 256 - tlen;
		bah_ram_ptr[2 * 2] = offset;
	} else {
		bah_ram_ptr[2 * 2] = 0;
		if (tlen <= ARC_MAX_FORBID_LEN)
			offset = 255;		/* !!! */
		else {
			if (tlen > ARC_MAX_LEN)
				tlen = ARC_MAX_LEN;
			offset = 512 - tlen;
		}
		bah_ram_ptr[3 * 2] = offset;

	}
	bah_ram_ptr += offset * 2;

	/* lets loop through the mbuf chain */

	for (mp = m; mp; mp = mp->m_next) {
		if ((len = mp->m_len)) {		/* YAMS */
#ifdef BAHTIMINGS
			lencopy = len;
			copystart = clkread();
#endif
			movepout(mtod(mp, caddr_t), bah_ram_ptr, len);

#ifdef BAHTIMINGS
			perbyte = 1000 * (clkread() - copystart) / lencopy;
			sc->sc_stats.mincopyout = 
			    ulmin(sc->sc_stats.mincopyout, perbyte);
			sc->sc_stats.maxcopyout =
			    ulmax(sc->sc_stats.maxcopyout, perbyte);
#endif
			bah_ram_ptr += len*2;
		}
	}

	sc->sc_broadcast[buffer] = (m->m_flags & M_BCAST) != 0;
	sc->sc_retransmits[buffer] = (m->m_flags & M_BCAST) ? 1 : 5;

	/* actually transmit the packet */
	s = splnet();

	if (++sc->sc_tx_fillcount > 1) { 
		/*
		 * We are filled up to the rim. No more bufs for the moment,
		 * please.
		 */
		ifp->if_flags |= IFF_OACTIVE;
	} else {
#ifdef BAH_DEBUG
		printf("%s: start: starting transmitter on buffer %d\n", 
		    sc->sc_dev.dv_xname, buffer);
#endif
		/* Transmitter was off, start it */
		sc->sc_tx_act = buffer;

		/*
		 * We still can accept another buf, so don't:
		 * ifp->if_flags |= IFF_OACTIVE;
		 */
		sc->sc_intmask |= ARC_TA;
		sc->sc_base->command = ARC_TX(buffer);
		sc->sc_base->status  = sc->sc_intmask;

		sc->sc_arccom.ac_if.if_timer = ARCTIMEOUT;
#ifdef BAHTIMINGS
		bcopy((caddr_t)&time,
		    (caddr_t)&(sc->sc_stats.lasttxstart_tv),
		    sizeof(struct timeval));

		sc->sc_stats.lasttxstart_mics = clkread();
#endif
	}
	splx(s);
	m_freem(m);

	/*
	 * After 10 times reading the docs, I realized
	 * that in the case the receiver NAKs the buffer request,
	 * the hardware retries till shutdown.
	 * This is integrated now in the code above.
	 */

	return;
}

void 
callstart(vsc, dummy)
	void *vsc, *dummy;
{
	struct bah_softc *sc;

	sc = (struct bah_softc *)vsc;
	bah_start(&sc->sc_arccom.ac_if);
}

__inline void
movepin(from, to, len)
	__volatile u_char *from;
	u_char *to;
	int len;
{
#ifdef BAHASMCOPY
	unsigned long	longd, longd1, longd2, longd3, longd4;
	ushort		shortd;

	if ((len > 3) && (((long)to) & 3)) {
		switch (((long)to) & 3) {
		case 3: *to++ = *from;
			from += 2; --len;
			break;
		case 1: *to++ = *from;
			from += 2; --len;
		case 2:	asm ("movepw %1@(0),%0": "=d" (shortd) : "a" (from));
			*((ushort *)to)++ = shortd;
			from += 4; len -= 2;
			break;
		default:
		}

		while (len >= 32) {
			asm("movepl %1@(0),%0"  : "=d"(longd1) : "a" (from));
			asm("movepl %1@(8),%0"  : "=d"(longd2) : "a" (from));
			asm("movepl %1@(16),%0" : "=d"(longd3) : "a" (from));
			asm("movepl %1@(24),%0" : "=d"(longd4) : "a" (from));
			*((unsigned long *)to)++ = longd1;
			*((unsigned long *)to)++ = longd2;
			*((unsigned long *)to)++ = longd3;
			*((unsigned long *)to)++ = longd4;

			asm("movepl %1@(32),%0" : "=d"(longd1) : "a" (from));
			asm("movepl %1@(40),%0" : "=d"(longd2) : "a" (from));
			asm("movepl %1@(48),%0" : "=d"(longd3) : "a" (from));
			asm("movepl %1@(56),%0" : "=d"(longd4) : "a" (from));
			*((unsigned long *)to)++ = longd1;
			*((unsigned long *)to)++ = longd2;
			*((unsigned long *)to)++ = longd3;
			*((unsigned long *)to)++ = longd4;

			from += 64; len -= 32;
		}
		while (len > 0) {
			asm("movepl %1@(0),%0" : "=d"(longd) : "a" (from));
			*((unsigned long *)to)++ = longd;
			from += 8; len -= 4;
		}

	}
#endif /* BAHASMCOPY */
	while (len > 0) {
		*to++ = *from;
		from += 2;
		--len;
	}

}

/*
 * Arcnet interface receiver soft interrupt:
 * get the stuff out of any filled buffer we find.
 */
void
bah_srint(vsc, dummy)
	void *vsc, *dummy;
{
	struct bah_softc *sc;
	int buffer, len, len1, amount, offset, s, i, type;
	u_char __volatile *bah_ram_ptr;
	struct mbuf *m, *dst, *head;
	struct arc_header *ah;
	struct ifnet *ifp;
#ifdef BAHTIMINGS
	u_long copystart, lencopy, perbyte;
#endif
	sc = (struct bah_softc *)vsc;
	ifp = &sc->sc_arccom.ac_if;
	head = 0;

	s = splnet();
	buffer = sc->sc_rx_act ^ 1;
	splx(s);

	/* Allocate header mbuf */
	MGETHDR(m, M_DONTWAIT, MT_DATA);

	if (m == 0) {
		/* 
	 	 * in case s.th. goes wrong with mem, drop it
	 	 * to make sure the receiver can be started again
		 * count it as input error (we dont have any other
		 * detectable)
	 	 */
		ifp->if_ierrors++;
		goto cleanup;
	}
			
	m->m_pkthdr.rcvif = ifp;

	/*
	 * Align so that IP packet will be longword aligned. Here we
	 * assume that m_data of new packet is longword aligned.
	 * When implementing PHDS, we might have to change it to 2,
	 * (2*sizeof(ulong) - ARC_HDRNEWLEN)), packet type dependent.
	 */

	bah_ram_ptr = sc->sc_base->buffers + buffer*512*2;
	offset = bah_ram_ptr[2*2];
	if (offset)
		len = 256 - offset;
	else {
		offset = bah_ram_ptr[3*2];
		len = 512 - offset;
	}
	type = bah_ram_ptr[offset*2];
	m->m_data += 1 + arc_isphds(type);

	head = m;
	ah = mtod(head, struct arc_header *);
		
	ah->arc_shost = bah_ram_ptr[0*2];
	ah->arc_dhost = bah_ram_ptr[1*2];

	m->m_pkthdr.len = len+2; /* whole packet length */
	m->m_len = 2;		 /* mbuf filled with ARCnet addresses */
	bah_ram_ptr += offset*2; /* ram buffer continues there */

	while (len > 0) {
	
		len1 = len;
		amount = M_TRAILINGSPACE(m);

		if (amount == 0) {
			dst = m;
			MGET(m, M_DONTWAIT, MT_DATA);
		
			if (m == 0) {
				ifp->if_ierrors++;
				goto cleanup;
			}
		
			if (len1 >= MINCLSIZE)
				MCLGET(m, M_DONTWAIT);
	
			m->m_len = 0;
			dst->m_next = m;
			amount = M_TRAILINGSPACE(m);
		}

		if (amount < len1)
			len1 = amount;

#ifdef BAHTIMINGS
		lencopy = len;
		copystart = clkread();
#endif

		movepin(bah_ram_ptr, mtod(m, u_char *) + m->m_len, len1);

#ifdef BAHTIMINGS
		perbyte = 1000 * (clkread() - copystart) / lencopy;
		sc->sc_stats.mincopyin =
		    ulmin(sc->sc_stats.mincopyin, perbyte);
		sc->sc_stats.maxcopyin =
		    ulmax(sc->sc_stats.maxcopyin, perbyte);
#endif

		m->m_len += len1;
		bah_ram_ptr += len1*2;
		len -= len1;
	}

#if NBPFILTER > 0
	if (ifp->if_bpf)
		bpf_mtap(ifp->if_bpf, head);
#endif

	arc_input(&sc->sc_arccom.ac_if, head);

	/* arc_input has freed it, we dont need to... */

	head = NULL;
	ifp->if_ipackets++;
	
cleanup:

	if (head != NULL)
		m_freem(head);

	/* mark buffer as invalid by source id 0 */
	sc->sc_base->buffers[buffer*512*2] = 0;
	s = splnet();

	if (--sc->sc_rx_fillcount == 2 - 1) {

		/* was off, restart it on buffer just emptied */
		sc->sc_rx_act = buffer;
		sc->sc_intmask |= ARC_RI;

		/* this also clears the RI flag interrupt: */
		sc->sc_base->command = ARC_RXBC(buffer);
		sc->sc_base->status = sc->sc_intmask;

#ifdef BAH_DEBUG
		printf("%s: srint: restarted rx on buf %ld\n",
		    sc->sc_dev.dv_xname, buffer);
#endif
	}
	splx(s);
}

__inline static void
bah_tint(sc, isr)
	struct bah_softc *sc;
	int isr;
{
	struct ifnet *ifp;

	int buffer;
#ifdef BAHTIMINGS
	int clknow;
#endif

	ifp = &(sc->sc_arccom.ac_if);
	buffer = sc->sc_tx_act;

	/*
	 * retransmit code:  
	 * Normal situations first for fast path:
	 * If acknowledgement received ok or broadcast, we're ok.
	 * else if 
	 */ 

	if (isr & ARC_TMA || sc->sc_broadcast[buffer])
		sc->sc_arccom.ac_if.if_opackets++;
#ifdef BAHRETRANSMIT
	else if (ifp->if_flags & IFF_LINK2 && ifp->if_timer > 0 
	    && --sc->sc_retransmits[buffer] > 0) {
		/* retransmit same buffer */
		sc->sc_base->command = ARC_TX(buffer);
		return;
	}
#endif
	else
		ifp->if_oerrors++;
		
		
#ifdef BAHTIMINGS
	clknow = clkread();

	sc->sc_stats.minsend = ulmin(sc->sc_stats.minsend,
	    clknow - sc->sc_stats.lasttxstart_mics);

	sc->sc_stats.maxsend = ulmax(sc->sc_stats.maxsend,
	    clknow - sc->sc_stats.lasttxstart_mics);
#endif

	/* We know we can accept another buffer at this point. */
	ifp->if_flags &= ~IFF_OACTIVE;

	if (--sc->sc_tx_fillcount > 0) {

		/* 
		 * start tx on other buffer.
		 * This also clears the int flag
		 */
		buffer ^= 1;
		sc->sc_tx_act = buffer;

		/*
		 * already given:
		 * sc->sc_intmask |= ARC_TA; 
		 * sc->sc_base->status = sc->sc_intmask;
		 */
		sc->sc_base->command = ARC_TX(buffer);
		/* init watchdog timer */
		ifp->if_timer = ARCTIMEOUT;

#ifdef BAHTIMINGS
		bcopy((caddr_t)&time,
		    (caddr_t)&(sc->sc_stats.lasttxstart_tv),
		    sizeof(struct timeval));

		sc->sc_stats.lasttxstart_mics = clkread();
#endif
 
#if defined(BAH_DEBUG) && (BAH_DEBUG > 1)
		printf("%s: tint: starting tx on buffer %d, status 0x%02x\n", 
		    sc->sc_dev.dv_xname, buffer, sc->sc_base->status);
#endif
	} else {
		/* have to disable TX interrupt */
		sc->sc_intmask &= ~ARC_TA;
		sc->sc_base->status = sc->sc_intmask;
		/* ... and watchdog timer */
		ifp->if_timer = 0;

#ifdef BAH_DEBUG
		printf("%s: tint: no more buffers to send, status 0x%02x\n",
		    sc->sc_dev.dv_xname, sc->sc_base->status);
#endif
	}

#ifdef BAHSOFTCOPY
	/* schedule soft int to fill a new buffer for us */
	add_sicallback((sifunc_t)callstart, sc, NULL);
#else
	/* call it directly */
	callstart(sc, NULL);
#endif
}

/*
 * Our interrupt routine
 */
int
bahintr(sc)
	struct bah_softc *sc;
{
	u_char isr, maskedisr;
	int buffer;
	time_t newsec;

	isr = sc->sc_base->status;
	maskedisr = isr & sc->sc_intmask;
	if (!maskedisr) 
		return (0);

#if defined(BAH_DEBUG) && (BAH_DEBUG>1)
	printf("%s: intr: status 0x%02x, intmask 0x%02x\n",
	    sc->sc_dev.dv_xname, isr, sc->sc_intmask);
#endif

	if (maskedisr & ARC_POR) {
		sc->sc_arccom.ac_anaddr = sc->sc_base->dipswitches;
		sc->sc_base->command = ARC_CLR(CLR_POR);
		log(LOG_WARNING, "%s: intr: got spurious power on reset int\n",
		    sc->sc_dev.dv_xname);
	}

	if (maskedisr & ARC_RECON) {
		/*
		 * we dont need to:
		 * sc->sc_base->command = ARC_CONF(CONF_LONG);
		 */
		sc->sc_base->command = ARC_CLR(CLR_RECONFIG);
		sc->sc_arccom.ac_if.if_collisions++;

		/*
		 * If more than 2 seconds per reconfig:
		 *	Reset time and counter.
		 * else:
		 *	If more than ARC_EXCESSIVE_RECONFIGS reconfigs
		 *	since last burst, complain and set treshold for
		 *	warnings to ARC_EXCESSIVE_RECONS_REWARN.
		 *
		 * This allows for, e.g., new stations on the cable, or
		 * cable switching as long as it is over after (normally)
		 * 16 seconds.
		 *
		 * XXX TODO: check timeout bits in status word and double
		 * time if necessary.
		 */

		newsec = time.tv_sec;
		if (newsec - sc->sc_recontime > 2 * sc->sc_reconcount) {
			sc->sc_recontime = newsec;
			sc->sc_reconcount = 0;
			sc->sc_reconcount_excessive = ARC_EXCESSIVE_RECONS;
		} else if (++sc->sc_reconcount > sc->sc_reconcount_excessive) {
			sc->sc_reconcount_excessive = 
			    ARC_EXCESSIVE_RECONS_REWARN;
			log(LOG_WARNING,
			    "%s: excessive token losses, cable problem?\n",
			    sc->sc_dev.dv_xname);
			sc->sc_recontime = newsec;
			sc->sc_reconcount = 0;
		}
	}

	if (maskedisr & ARC_RI) {

#if defined(BAH_DEBUG) && (BAH_DEBUG > 1)
		printf("%s: intr: hard rint, act %ld\n",
		    sc->sc_dev.dv_xname, sc->sc_rx_act);
#endif
	
		buffer = sc->sc_rx_act;
		/* look if buffer is marked invalid: */
		if (sc->sc_base->buffers[buffer*512*2] == 0) {
	/* invalid marked buffer (or illegally configured sender) */
			log(LOG_WARNING, 
			    "%s: spurious RX interrupt or sender 0 (ignored)\n",
			    sc->sc_dev.dv_xname);
			/*
			 * restart receiver on same buffer.
			 */
			sc->sc_base->command = ARC_RXBC(buffer);

		} else if (++sc->sc_rx_fillcount > 1) {
			sc->sc_intmask &= ~ARC_RI;
			sc->sc_base->status = sc->sc_intmask;
		} else {

			buffer ^= 1;
			sc->sc_rx_act = buffer;

			/*
			 * Start receiver on other receive buffer.
			 * This also clears the RI interrupt flag.
			 */
			sc->sc_base->command = ARC_RXBC(buffer);
			/* we are in the RX intr, so mask is ok for RX */

#ifdef BAH_DEBUG
			printf("%s: started rx for buffer %ld, status 0x%02x\n",
			    sc->sc_dev.dv_xname, sc->sc_rx_act,
			    sc->sc_base->status);
#endif
		}

#ifdef BAHSOFTCOPY
		/* this one starts a soft int to copy out of the hw */
		add_sicallback((sifunc_t)bah_srint, sc,NULL);
#else
		/* this one does the copy here */
		bah_srint(sc,NULL);
#endif
	}

	if (maskedisr & ARC_TA) 
		bah_tint(sc, isr);

	return (1);
}

/*
 * Process an ioctl request. 
 * This code needs some work - it looks pretty ugly.
 */
int
bah_ioctl(ifp, command, data)
	register struct ifnet *ifp;
	u_long command;
	caddr_t data;
{
	struct bah_softc *sc;
	register struct ifaddr *ifa;
	int s, error;

	error = 0;
	sc = ifp->if_softc;
	ifa = (struct ifaddr *)data;
	s = splnet();

#if defined(BAH_DEBUG) && (BAH_DEBUG > 2) 
	printf("%s: ioctl() called, cmd = 0x%x\n",
	    sc->sc_dev.dv_xname, command);
#endif

	switch (command) {
	case SIOCSIFADDR:
		ifp->if_flags |= IFF_UP;
		switch (ifa->ifa_addr->sa_family) {
#ifdef INET
		case AF_INET:
			bah_init(sc);
			break;
#endif
		default:
			bah_init(sc);
			break;
		}

	case SIOCSIFFLAGS:
		if ((ifp->if_flags & IFF_UP) == 0 &&
		    (ifp->if_flags & IFF_RUNNING) != 0) {
			/*
			 * If interface is marked down and it is running, 
			 * then stop it.
			 */
			bah_stop(sc);
			ifp->if_flags &= ~IFF_RUNNING;
		} else if ((ifp->if_flags & IFF_UP) != 0 &&
			   (ifp->if_flags & IFF_RUNNING) == 0) {
			/*
			 * If interface is marked up and it is stopped, then
			 * start it.
			 */
			bah_init(sc);
		} 
		break;

		/* Multicast not supported */

	default:
		error = EINVAL;
	}

	splx(s);
	return (error);
}

/*
 * watchdog routine for transmitter.
 *
 * We need this, because else a receiver whose hardware is alive, but whose
 * software has not enabled the Receiver, would make our hardware wait forever
 * Discovered this after 20 times reading the docs.
 *
 * Only thing we do is disable transmitter. We'll get an transmit timeout,
 * and the int handler will have to decide not to retransmit (in case
 * retransmission is implemented).
 *
 * This one assumes being called inside splnet(), and that net >= ipl2
 */

void
bah_watchdog(ifp)
	struct ifnet *ifp;
{
	struct bah_softc *sc = ifp->if_softc;

	sc->sc_base->command = ARC_TXDIS;
	return;
}
