diff options
Diffstat (limited to 'sys/dev/ic/smc90cx6.c')
-rw-r--r-- | sys/dev/ic/smc90cx6.c | 1231 |
1 files changed, 1231 insertions, 0 deletions
diff --git a/sys/dev/ic/smc90cx6.c b/sys/dev/ic/smc90cx6.c new file mode 100644 index 00000000000..afa12df2b80 --- /dev/null +++ b/sys/dev/ic/smc90cx6.c @@ -0,0 +1,1231 @@ +/* $NetBSD: smc90cx6.c,v 1.16 1996/03/20 13:28:50 is 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 Busines 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> +#include <net/bpfdesc.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 __P((struct device *, void *, void *)); +void bah_zbus_attach __P((struct device *, struct device *, void *)); +void bah_init __P((struct bah_softc *)); +void bah_reset __P((struct bah_softc *)); +void bah_stop __P((struct bah_softc *)); +void bah_start __P((struct ifnet *)); +int bahintr __P((struct bah_softc *sc)); +int bah_ioctl __P((struct ifnet *, unsigned long, caddr_t)); +void bah_watchdog __P((int)); +void movepout __P((u_char *from, u_char __volatile *to, int len)); +void movepin __P((u_char __volatile *from, u_char *to, int len)); +void bah_srint __P((void *vsc, void *dummy)); +void callstart __P((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); + + ifp->if_unit = sc->sc_dev.dv_unit; + ifp->if_name = bah_cd.cd_name; + 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; + + 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("bah%ld: reset: card reset, link addr = 0x%02x (%ld)\n", + ifp->if_unit, 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 becore 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 = bah_cd.cd_devs[ifp->if_unit]; + +#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; + } + + IF_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 interupt: */ + 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 situtations 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; + u_long 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 interupt 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 = bah_cd.cd_devs[ifp->if_unit]; + 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(unit) +int unit; +{ + struct bah_softc *sc; + struct ifnet *ifp; + + sc = bah_cd.cd_devs[unit]; + ifp = &(sc->sc_arccom.ac_if); + + sc->sc_base->command = ARC_TXDIS; + return; +} |