/* $FreeBSD$ */ /* $NecBSD: dp83932.c,v 1.5 1999/07/29 05:08:44 kmatsuda Exp $ */ /* $NetBSD: if_snc.c,v 1.18 1998/04/25 21:27:40 scottr Exp $ */ /* * Copyright (c) 1997, 1998, 1999 * Kouichi Matsuda. 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 Kouichi Matsuda for * NetBSD/pc98. * 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. */ /* * Modified for FreeBSD(98) 4.0 from NetBSD/pc98 1.4.2 by Motomichi Matsuzaki. */ /* * Modified for NetBSD/pc98 1.2G from NetBSD/mac68k 1.2G by Kouichi Matsuda. * Make adapted for NEC PC-9801-83, 84, PC-9801-103, 104, PC-9801N-25 and * PC-9801N-J02, J02R, which uses National Semiconductor DP83934AVQB as * Ethernet Controller and National Semiconductor NS46C46 as * (64 * 16 bits) Microwire Serial EEPROM. */ /* * National Semiconductor DP8393X SONIC Driver * Copyright (c) 1991 Algorithmics Ltd (http://www.algor.co.uk) * You may use, copy, and modify this program so long as you retain the * copyright line. * * This driver has been substantially modified since Algorithmics donated * it. * * Denton Gentry * and also * Yanagisawa Takeshi * did the work to get this running on the Macintosh. */ #include "opt_inet.h" #include #include #include #include #include #include #include #include #if NRND > 0 #include #endif #include #include #include #include #include #include #include #include #include #include hide void sncwatchdog(struct ifnet *); hide void sncinit(void *); hide int sncstop(struct snc_softc *sc); hide int sncioctl(struct ifnet *ifp, u_long cmd, caddr_t data); hide void sncstart(struct ifnet *ifp); hide void sncreset(struct snc_softc *sc); hide void caminitialise(struct snc_softc *); hide void camentry(struct snc_softc *, int, u_char *ea); hide void camprogram(struct snc_softc *); hide void initialise_tda(struct snc_softc *); hide void initialise_rda(struct snc_softc *); hide void initialise_rra(struct snc_softc *); #ifdef SNCDEBUG hide void camdump(struct snc_softc *sc); #endif hide void sonictxint(struct snc_softc *); hide void sonicrxint(struct snc_softc *); hide u_int sonicput(struct snc_softc *sc, struct mbuf *m0, int mtd_next); hide int sonic_read(struct snc_softc *, u_int32_t, int); hide struct mbuf *sonic_get(struct snc_softc *, u_int32_t, int); int snc_enable(struct snc_softc *); void snc_disable(struct snc_softc *); int snc_mediachange(struct ifnet *); void snc_mediastatus(struct ifnet *, struct ifmediareq *); #ifdef NetBSD #if NetBSD <= 199714 struct cfdriver snc_cd = { NULL, "snc", DV_IFNET }; #endif #endif #undef assert #undef _assert #ifdef NDEBUG #define assert(e) ((void)0) #define _assert(e) ((void)0) #else #define _assert(e) assert(e) #ifdef __STDC__ #define assert(e) ((e) ? (void)0 : __assert("snc ", __FILE__, __LINE__, #e)) #else /* PCC */ #define assert(e) ((e) ? (void)0 : __assert("snc "__FILE__, __LINE__, "e")) #endif #endif #ifdef SNCDEBUG #define SNC_SHOWTXHDR 0x01 /* show tx ether_header */ #define SNC_SHOWRXHDR 0x02 /* show rx ether_header */ #define SNC_SHOWCAMENT 0x04 /* show CAM entry */ #endif /* SNCDEBUG */ int sncdebug = 0; void sncconfig(sc, media, nmedia, defmedia, myea) struct snc_softc *sc; int *media, nmedia, defmedia; u_int8_t *myea; { struct ifnet *ifp = &sc->sc_if; int i; #ifdef SNCDEBUG if ((sncdebug & SNC_SHOWCAMENT) != 0) { camdump(sc); } #endif device_printf(sc->sc_dev, "address %6D\n", myea, ":"); #ifdef SNCDEBUG device_printf(sc->sc_dev, "buffers: rra=0x%x cda=0x%x rda=0x%x tda=0x%x\n", sc->v_rra[0], sc->v_cda, sc->v_rda, sc->mtda[0].mtd_vtxp); #endif ifp->if_softc = sc; ifp->if_unit = device_get_unit(sc->sc_dev); ifp->if_name = "snc"; ifp->if_ioctl = sncioctl; ifp->if_output = ether_output; ifp->if_start = sncstart; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_watchdog = sncwatchdog; ifp->if_init = sncinit; ifp->if_mtu = ETHERMTU; ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; bcopy(myea, sc->sc_ethercom.ac_enaddr, ETHER_ADDR_LEN); /* Initialize media goo. */ ifmedia_init(&sc->sc_media, 0, snc_mediachange, snc_mediastatus); if (media != NULL) { for (i = 0; i < nmedia; i++) ifmedia_add(&sc->sc_media, media[i], 0, NULL); ifmedia_set(&sc->sc_media, defmedia); } else { ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_MANUAL, 0, NULL); ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_MANUAL); } ether_ifattach(ifp, myea); #if NRND > 0 rnd_attach_source(&sc->rnd_source, device_get_nameunit(sc->sc_dev), RND_TYPE_NET, 0); #endif } void sncshutdown(arg) void *arg; { sncstop((struct snc_softc *)arg); } /* * Media change callback. */ int snc_mediachange(ifp) struct ifnet *ifp; { struct snc_softc *sc = ifp->if_softc; if (sc->sc_mediachange) return ((*sc->sc_mediachange)(sc)); return (EINVAL); } /* * Media status callback. */ void snc_mediastatus(ifp, ifmr) struct ifnet *ifp; struct ifmediareq *ifmr; { struct snc_softc *sc = ifp->if_softc; if (sc->sc_enabled == 0) { ifmr->ifm_active = IFM_ETHER | IFM_NONE; ifmr->ifm_status = 0; return; } if (sc->sc_mediastatus) (*sc->sc_mediastatus)(sc, ifmr); } hide int sncioctl(ifp, cmd, data) struct ifnet *ifp; u_long cmd; caddr_t data; { struct ifreq *ifr; struct snc_softc *sc = ifp->if_softc; int s = splhardnet(), err = 0; int temp; switch (cmd) { 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. */ sncstop(sc); ifp->if_flags &= ~IFF_RUNNING; snc_disable(sc); } 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. */ if ((err = snc_enable(sc)) != 0) break; sncinit(sc); } else if (sc->sc_enabled) { /* * reset the interface to pick up any other changes * in flags */ temp = ifp->if_flags & IFF_UP; sncreset(sc); ifp->if_flags |= temp; sncstart(ifp); } break; case SIOCADDMULTI: case SIOCDELMULTI: if (sc->sc_enabled == 0) { err = EIO; break; } temp = ifp->if_flags & IFF_UP; sncreset(sc); ifp->if_flags |= temp; err = 0; break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: ifr = (struct ifreq *) data; err = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd); break; default: err = ether_ioctl(ifp, cmd, data); break; } splx(s); return (err); } /* * Encapsulate a packet of type family for the local net. */ hide void sncstart(ifp) struct ifnet *ifp; { struct snc_softc *sc = ifp->if_softc; struct mbuf *m; int mtd_next; if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING) return; outloop: /* Check for room in the xmit buffer. */ if ((mtd_next = (sc->mtd_free + 1)) == NTDA) mtd_next = 0; if (mtd_next == sc->mtd_hw) { ifp->if_flags |= IFF_OACTIVE; return; } IF_DEQUEUE(&ifp->if_snd, m); if (m == 0) return; /* We need the header for m_pkthdr.len. */ if ((m->m_flags & M_PKTHDR) == 0) panic("%s: sncstart: no header mbuf", device_get_nameunit(sc->sc_dev)); /* * If bpf is listening on this interface, let it * see the packet before we commit it to the wire. */ BPF_MTAP(ifp, m); /* * If there is nothing in the o/p queue, and there is room in * the Tx ring, then send the packet directly. Otherwise append * it to the o/p queue. */ if ((sonicput(sc, m, mtd_next)) == 0) { IF_PREPEND(&ifp->if_snd, m); return; } sc->mtd_prev = sc->mtd_free; sc->mtd_free = mtd_next; ifp->if_opackets++; /* # of pkts */ /* Jump back for possibly more punishment. */ goto outloop; } /* * reset and restart the SONIC. Called in case of fatal * hardware/software errors. */ hide void sncreset(sc) struct snc_softc *sc; { sncstop(sc); sncinit(sc); } hide void sncinit(xsc) void *xsc; { struct snc_softc *sc = xsc; u_long s_rcr; int s; if (sc->sc_if.if_flags & IFF_RUNNING) /* already running */ return; s = splhardnet(); NIC_PUT(sc, SNCR_CR, CR_RST); /* DCR only accessable in reset mode! */ /* config it */ NIC_PUT(sc, SNCR_DCR, (sc->sncr_dcr | (sc->bitmode ? DCR_DW32 : DCR_DW16))); NIC_PUT(sc, SNCR_DCR2, sc->sncr_dcr2); s_rcr = RCR_BRD | RCR_LBNONE; if (sc->sc_if.if_flags & IFF_PROMISC) s_rcr |= RCR_PRO; if (sc->sc_if.if_flags & IFF_ALLMULTI) s_rcr |= RCR_AMC; NIC_PUT(sc, SNCR_RCR, s_rcr); NIC_PUT(sc, SNCR_IMR, (IMR_PRXEN | IMR_PTXEN | IMR_TXEREN | IMR_LCDEN)); /* clear pending interrupts */ NIC_PUT(sc, SNCR_ISR, ISR_ALL); /* clear tally counters */ NIC_PUT(sc, SNCR_CRCT, -1); NIC_PUT(sc, SNCR_FAET, -1); NIC_PUT(sc, SNCR_MPT, -1); initialise_tda(sc); initialise_rda(sc); initialise_rra(sc); /* enable the chip */ NIC_PUT(sc, SNCR_CR, 0); wbflush(); /* program the CAM */ camprogram(sc); /* get it to read resource descriptors */ NIC_PUT(sc, SNCR_CR, CR_RRRA); wbflush(); while ((NIC_GET(sc, SNCR_CR)) & CR_RRRA) continue; /* enable rx */ NIC_PUT(sc, SNCR_CR, CR_RXEN); wbflush(); /* flag interface as "running" */ sc->sc_if.if_flags |= IFF_RUNNING; sc->sc_if.if_flags &= ~IFF_OACTIVE; splx(s); return; } /* * close down an interface and free its buffers * Called on final close of device, or if sncinit() fails * part way through. */ hide int sncstop(sc) struct snc_softc *sc; { struct mtd *mtd; int s = splhardnet(); /* stick chip in reset */ NIC_PUT(sc, SNCR_CR, CR_RST); wbflush(); /* free all receive buffers (currently static so nothing to do) */ /* free all pending transmit mbufs */ while (sc->mtd_hw != sc->mtd_free) { mtd = &sc->mtda[sc->mtd_hw]; if (mtd->mtd_mbuf) m_freem(mtd->mtd_mbuf); if (++sc->mtd_hw == NTDA) sc->mtd_hw = 0; } sc->sc_if.if_timer = 0; sc->sc_if.if_flags &= ~(IFF_RUNNING | IFF_UP); splx(s); return (0); } /* * Called if any Tx packets remain unsent after 5 seconds, * In all cases we just reset the chip, and any retransmission * will be handled by higher level protocol timeouts. */ hide void sncwatchdog(ifp) struct ifnet *ifp; { struct snc_softc *sc = ifp->if_softc; struct mtd *mtd; int temp; if (sc->mtd_hw != sc->mtd_free) { /* something still pending for transmit */ mtd = &sc->mtda[sc->mtd_hw]; if (SRO(sc, mtd->mtd_vtxp, TXP_STATUS) == 0) log(LOG_ERR, "%s: Tx - timeout\n", device_get_nameunit(sc->sc_dev)); else log(LOG_ERR, "%s: Tx - lost interrupt\n", device_get_nameunit(sc->sc_dev)); temp = ifp->if_flags & IFF_UP; sncreset(sc); ifp->if_flags |= temp; } } /* * stuff packet into sonic (at splnet) */ hide u_int sonicput(sc, m0, mtd_next) struct snc_softc *sc; struct mbuf *m0; int mtd_next; { struct mtd *mtdp; struct mbuf *m; u_int32_t buff; u_int32_t txp; u_int len = 0; u_int totlen = 0; #ifdef whyonearthwouldyoudothis if (NIC_GET(sc, SNCR_CR) & CR_TXP) return (0); #endif /* grab the replacement mtd */ mtdp = &sc->mtda[sc->mtd_free]; buff = mtdp->mtd_vbuf; /* this packet goes to mtdnext fill in the TDA */ mtdp->mtd_mbuf = m0; txp = mtdp->mtd_vtxp; /* Write to the config word. Every (NTDA/2)+1 packets we set an intr */ if (sc->mtd_pint == 0) { sc->mtd_pint = NTDA/2; SWO(sc, txp, TXP_CONFIG, TCR_PINT); } else { sc->mtd_pint--; SWO(sc, txp, TXP_CONFIG, 0); } for (m = m0; m; m = m->m_next) { len = m->m_len; totlen += len; (*sc->sc_copytobuf)(sc, mtod(m, caddr_t), buff, len); buff += len; } if (totlen >= TXBSIZE) { panic("%s: sonicput: packet overflow", device_get_nameunit(sc->sc_dev)); } SWO(sc, txp, TXP_FRAGOFF + (0 * TXP_FRAGSIZE) + TXP_FPTRLO, LOWER(mtdp->mtd_vbuf)); SWO(sc, txp, TXP_FRAGOFF + (0 * TXP_FRAGSIZE) + TXP_FPTRHI, UPPER(mtdp->mtd_vbuf)); if (totlen < ETHERMIN + sizeof(struct ether_header)) { int pad = ETHERMIN + sizeof(struct ether_header) - totlen; (*sc->sc_zerobuf)(sc, mtdp->mtd_vbuf + totlen, pad); totlen = ETHERMIN + sizeof(struct ether_header); } SWO(sc, txp, TXP_FRAGOFF + (0 * TXP_FRAGSIZE) + TXP_FSIZE, totlen); SWO(sc, txp, TXP_FRAGCNT, 1); SWO(sc, txp, TXP_PKTSIZE, totlen); /* link onto the next mtd that will be used */ SWO(sc, txp, TXP_FRAGOFF + (1 * TXP_FRAGSIZE) + TXP_FPTRLO, LOWER(sc->mtda[mtd_next].mtd_vtxp) | EOL); /* * The previous txp.tlink currently contains a pointer to * our txp | EOL. Want to clear the EOL, so write our * pointer to the previous txp. */ SWO(sc, sc->mtda[sc->mtd_prev].mtd_vtxp, sc->mtd_tlinko, LOWER(mtdp->mtd_vtxp)); /* make sure chip is running */ wbflush(); NIC_PUT(sc, SNCR_CR, CR_TXP); wbflush(); sc->sc_if.if_timer = 5; /* 5 seconds to watch for failing to transmit */ return (totlen); } /* * These are called from sonicioctl() when /etc/ifconfig is run to set * the address or switch the i/f on. */ /* * CAM support */ hide void caminitialise(sc) struct snc_softc *sc; { u_int32_t v_cda = sc->v_cda; int i; int camoffset; for (i = 0; i < MAXCAM; i++) { camoffset = i * CDA_CAMDESC; SWO(sc, v_cda, (camoffset + CDA_CAMEP), i); SWO(sc, v_cda, (camoffset + CDA_CAMAP2), 0); SWO(sc, v_cda, (camoffset + CDA_CAMAP1), 0); SWO(sc, v_cda, (camoffset + CDA_CAMAP0), 0); } SWO(sc, v_cda, CDA_ENABLE, 0); #ifdef SNCDEBUG if ((sncdebug & SNC_SHOWCAMENT) != 0) { camdump(sc); } #endif } hide void camentry(sc, entry, ea) int entry; u_char *ea; struct snc_softc *sc; { u_int32_t v_cda = sc->v_cda; int camoffset = entry * CDA_CAMDESC; SWO(sc, v_cda, camoffset + CDA_CAMEP, entry); SWO(sc, v_cda, camoffset + CDA_CAMAP2, (ea[5] << 8) | ea[4]); SWO(sc, v_cda, camoffset + CDA_CAMAP1, (ea[3] << 8) | ea[2]); SWO(sc, v_cda, camoffset + CDA_CAMAP0, (ea[1] << 8) | ea[0]); SWO(sc, v_cda, CDA_ENABLE, (SRO(sc, v_cda, CDA_ENABLE) | (1 << entry))); } hide void camprogram(sc) struct snc_softc *sc; { struct ifmultiaddr *ifma; struct ifnet *ifp; int timeout; int mcount = 0; caminitialise(sc); ifp = &sc->sc_if; /* Always load our own address first. */ camentry (sc, mcount, sc->sc_ethercom.ac_enaddr); mcount++; /* Assume we won't need allmulti bit. */ ifp->if_flags &= ~IFF_ALLMULTI; /* Loop through multicast addresses */ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; if (mcount == MAXCAM) { ifp->if_flags |= IFF_ALLMULTI; break; } /* program the CAM with the specified entry */ camentry(sc, mcount, LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); mcount++; } NIC_PUT(sc, SNCR_CDP, LOWER(sc->v_cda)); NIC_PUT(sc, SNCR_CDC, MAXCAM); NIC_PUT(sc, SNCR_CR, CR_LCAM); wbflush(); timeout = 10000; while ((NIC_GET(sc, SNCR_CR) & CR_LCAM) && timeout--) continue; if (timeout == 0) { /* XXX */ panic("%s: CAM initialisation failed\n", device_get_nameunit(sc->sc_dev)); } timeout = 10000; while (((NIC_GET(sc, SNCR_ISR) & ISR_LCD) == 0) && timeout--) continue; if (NIC_GET(sc, SNCR_ISR) & ISR_LCD) NIC_PUT(sc, SNCR_ISR, ISR_LCD); else device_printf(sc->sc_dev, "CAM initialisation without interrupt\n"); } #ifdef SNCDEBUG hide void camdump(sc) struct snc_softc *sc; { int i; printf("CAM entries:\n"); NIC_PUT(sc, SNCR_CR, CR_RST); wbflush(); for (i = 0; i < 16; i++) { ushort ap2, ap1, ap0; NIC_PUT(sc, SNCR_CEP, i); wbflush(); ap2 = NIC_GET(sc, SNCR_CAP2); ap1 = NIC_GET(sc, SNCR_CAP1); ap0 = NIC_GET(sc, SNCR_CAP0); printf("%d: ap2=0x%x ap1=0x%x ap0=0x%x\n", i, ap2, ap1, ap0); } printf("CAM enable 0x%x\n", NIC_GET(sc, SNCR_CEP)); NIC_PUT(sc, SNCR_CR, 0); wbflush(); } #endif hide void initialise_tda(sc) struct snc_softc *sc; { struct mtd *mtd; int i; for (i = 0; i < NTDA; i++) { mtd = &sc->mtda[i]; mtd->mtd_mbuf = 0; } sc->mtd_hw = 0; sc->mtd_prev = NTDA - 1; sc->mtd_free = 0; sc->mtd_tlinko = TXP_FRAGOFF + 1*TXP_FRAGSIZE + TXP_FPTRLO; sc->mtd_pint = NTDA/2; NIC_PUT(sc, SNCR_UTDA, UPPER(sc->mtda[0].mtd_vtxp)); NIC_PUT(sc, SNCR_CTDA, LOWER(sc->mtda[0].mtd_vtxp)); } hide void initialise_rda(sc) struct snc_softc *sc; { int i; u_int32_t vv_rda = 0; u_int32_t v_rda = 0; /* link the RDA's together into a circular list */ for (i = 0; i < (sc->sc_nrda - 1); i++) { v_rda = sc->v_rda + (i * RXPKT_SIZE(sc)); vv_rda = sc->v_rda + ((i+1) * RXPKT_SIZE(sc)); SWO(sc, v_rda, RXPKT_RLINK, LOWER(vv_rda)); SWO(sc, v_rda, RXPKT_INUSE, 1); } v_rda = sc->v_rda + ((sc->sc_nrda - 1) * RXPKT_SIZE(sc)); SWO(sc, v_rda, RXPKT_RLINK, LOWER(sc->v_rda) | EOL); SWO(sc, v_rda, RXPKT_INUSE, 1); /* mark end of receive descriptor list */ sc->sc_rdamark = sc->sc_nrda - 1; sc->sc_rxmark = 0; NIC_PUT(sc, SNCR_URDA, UPPER(sc->v_rda)); NIC_PUT(sc, SNCR_CRDA, LOWER(sc->v_rda)); wbflush(); } hide void initialise_rra(sc) struct snc_softc *sc; { int i; u_int v; int bitmode = sc->bitmode; if (bitmode) NIC_PUT(sc, SNCR_EOBC, RBASIZE(sc) / 2 - 2); else NIC_PUT(sc, SNCR_EOBC, RBASIZE(sc) / 2 - 1); NIC_PUT(sc, SNCR_URRA, UPPER(sc->v_rra[0])); NIC_PUT(sc, SNCR_RSA, LOWER(sc->v_rra[0])); /* rea must point just past the end of the rra space */ NIC_PUT(sc, SNCR_REA, LOWER(sc->v_rea)); NIC_PUT(sc, SNCR_RRP, LOWER(sc->v_rra[0])); NIC_PUT(sc, SNCR_RSC, 0); /* fill up SOME of the rra with buffers */ for (i = 0; i < NRBA; i++) { v = SONIC_GETDMA(sc->rbuf[i]); SWO(sc, sc->v_rra[i], RXRSRC_PTRHI, UPPER(v)); SWO(sc, sc->v_rra[i], RXRSRC_PTRLO, LOWER(v)); SWO(sc, sc->v_rra[i], RXRSRC_WCHI, UPPER(NBPG/2)); SWO(sc, sc->v_rra[i], RXRSRC_WCLO, LOWER(NBPG/2)); } sc->sc_rramark = NRBA; NIC_PUT(sc, SNCR_RWP, LOWER(sc->v_rra[sc->sc_rramark])); wbflush(); } void sncintr(arg) void *arg; { struct snc_softc *sc = (struct snc_softc *)arg; int isr; if (sc->sc_enabled == 0) return; while ((isr = (NIC_GET(sc, SNCR_ISR) & ISR_ALL)) != 0) { /* scrub the interrupts that we are going to service */ NIC_PUT(sc, SNCR_ISR, isr); wbflush(); if (isr & (ISR_BR | ISR_LCD | ISR_TC)) device_printf(sc->sc_dev, "unexpected interrupt status 0x%x\n", isr); if (isr & (ISR_TXDN | ISR_TXER | ISR_PINT)) sonictxint(sc); if (isr & ISR_PKTRX) sonicrxint(sc); if (isr & (ISR_HBL | ISR_RDE | ISR_RBE | ISR_RBAE | ISR_RFO)) { if (isr & ISR_HBL) /* * The repeater is not providing a heartbeat. * In itself this isn't harmful, lots of the * cheap repeater hubs don't supply a heartbeat. * So ignore the lack of heartbeat. Its only * if we can't detect a carrier that we have a * problem. */ ; if (isr & ISR_RDE) device_printf(sc->sc_dev, "receive descriptors exhausted\n"); if (isr & ISR_RBE) device_printf(sc->sc_dev, "receive buffers exhausted\n"); if (isr & ISR_RBAE) device_printf(sc->sc_dev, "receive buffer area exhausted\n"); if (isr & ISR_RFO) device_printf(sc->sc_dev, "receive FIFO overrun\n"); } if (isr & (ISR_CRC | ISR_FAE | ISR_MP)) { #ifdef notdef if (isr & ISR_CRC) sc->sc_crctally++; if (isr & ISR_FAE) sc->sc_faetally++; if (isr & ISR_MP) sc->sc_mptally++; #endif } sncstart(&sc->sc_if); #if NRND > 0 if (isr) rnd_add_uint32(&sc->rnd_source, isr); #endif } return; } /* * Transmit interrupt routine */ hide void sonictxint(sc) struct snc_softc *sc; { struct mtd *mtd; u_int32_t txp; unsigned short txp_status; int mtd_hw; struct ifnet *ifp = &sc->sc_if; mtd_hw = sc->mtd_hw; if (mtd_hw == sc->mtd_free) return; while (mtd_hw != sc->mtd_free) { mtd = &sc->mtda[mtd_hw]; txp = mtd->mtd_vtxp; if (SRO(sc, txp, TXP_STATUS) == 0) { break; /* it hasn't really gone yet */ } #ifdef SNCDEBUG if ((sncdebug & SNC_SHOWTXHDR) != 0) { struct ether_header eh; (*sc->sc_copyfrombuf)(sc, &eh, mtd->mtd_vbuf, sizeof(eh)); device_printf(sc->sc_dev, "xmit status=0x%x len=%d type=0x%x from %6D", SRO(sc, txp, TXP_STATUS), SRO(sc, txp, TXP_PKTSIZE), htons(eh.ether_type), eh.ether_shost, ":"); printf(" (to %6D)\n", eh.ether_dhost, ":"); } #endif /* SNCDEBUG */ ifp->if_flags &= ~IFF_OACTIVE; if (mtd->mtd_mbuf != 0) { m_freem(mtd->mtd_mbuf); mtd->mtd_mbuf = 0; } if (++mtd_hw == NTDA) mtd_hw = 0; txp_status = SRO(sc, txp, TXP_STATUS); ifp->if_collisions += (txp_status & TCR_EXC) ? 16 : ((txp_status & TCR_NC) >> 12); if ((txp_status & TCR_PTX) == 0) { ifp->if_oerrors++; device_printf(sc->sc_dev, "Tx packet status=0x%x\n", txp_status); /* XXX - DG This looks bogus */ if (mtd_hw != sc->mtd_free) { printf("resubmitting remaining packets\n"); mtd = &sc->mtda[mtd_hw]; NIC_PUT(sc, SNCR_CTDA, LOWER(mtd->mtd_vtxp)); NIC_PUT(sc, SNCR_CR, CR_TXP); wbflush(); break; } } } sc->mtd_hw = mtd_hw; return; } /* * Receive interrupt routine */ hide void sonicrxint(sc) struct snc_softc *sc; { u_int32_t rda; int orra; int len; int rramark; int rdamark; u_int16_t rxpkt_ptr; rda = sc->v_rda + (sc->sc_rxmark * RXPKT_SIZE(sc)); while (SRO(sc, rda, RXPKT_INUSE) == 0) { u_int status = SRO(sc, rda, RXPKT_STATUS); orra = RBASEQ(SRO(sc, rda, RXPKT_SEQNO)) & RRAMASK; rxpkt_ptr = SRO(sc, rda, RXPKT_PTRLO); /* * Do not trunc ether_header length. * Our sonic_read() and sonic_get() require it. */ len = SRO(sc, rda, RXPKT_BYTEC) - FCSSIZE; if (status & RCR_PRX) { /* XXX: Does PGOFSET require? */ u_int32_t pkt = sc->rbuf[orra & RBAMASK] + (rxpkt_ptr & PGOFSET); if (sonic_read(sc, pkt, len)) sc->sc_if.if_ipackets++; else sc->sc_if.if_ierrors++; } else sc->sc_if.if_ierrors++; /* * give receive buffer area back to chip. * * If this was the last packet in the RRA, give the RRA to * the chip again. * If sonic read didnt copy it out then we would have to * wait !! * (dont bother add it back in again straight away) * * Really, we're doing v_rra[rramark] = v_rra[orra] but * we have to use the macros because SONIC might be in * 16 or 32 bit mode. */ if (status & RCR_LPKT) { u_int32_t tmp1, tmp2; rramark = sc->sc_rramark; tmp1 = sc->v_rra[rramark]; tmp2 = sc->v_rra[orra]; SWO(sc, tmp1, RXRSRC_PTRLO, SRO(sc, tmp2, RXRSRC_PTRLO)); SWO(sc, tmp1, RXRSRC_PTRHI, SRO(sc, tmp2, RXRSRC_PTRHI)); SWO(sc, tmp1, RXRSRC_WCLO, SRO(sc, tmp2, RXRSRC_WCLO)); SWO(sc, tmp1, RXRSRC_WCHI, SRO(sc, tmp2, RXRSRC_WCHI)); /* zap old rra for fun */ SWO(sc, tmp2, RXRSRC_WCHI, 0); SWO(sc, tmp2, RXRSRC_WCLO, 0); sc->sc_rramark = (++rramark) & RRAMASK; NIC_PUT(sc, SNCR_RWP, LOWER(sc->v_rra[rramark])); wbflush(); } /* * give receive descriptor back to chip simple * list is circular */ rdamark = sc->sc_rdamark; SWO(sc, rda, RXPKT_INUSE, 1); SWO(sc, rda, RXPKT_RLINK, SRO(sc, rda, RXPKT_RLINK) | EOL); SWO(sc, (sc->v_rda + (rdamark * RXPKT_SIZE(sc))), RXPKT_RLINK, SRO(sc, (sc->v_rda + (rdamark * RXPKT_SIZE(sc))), RXPKT_RLINK) & ~EOL); sc->sc_rdamark = sc->sc_rxmark; if (++sc->sc_rxmark >= sc->sc_nrda) sc->sc_rxmark = 0; rda = sc->v_rda + (sc->sc_rxmark * RXPKT_SIZE(sc)); } } /* * sonic_read -- pull packet off interface and forward to * appropriate protocol handler */ hide int sonic_read(sc, pkt, len) struct snc_softc *sc; u_int32_t pkt; int len; { struct ifnet *ifp = &sc->sc_if; struct ether_header *et; struct mbuf *m; if (len <= sizeof(struct ether_header) || len > ETHERMTU + sizeof(struct ether_header)) { device_printf(sc->sc_dev, "invalid packet length %d bytes\n", len); return (0); } /* Pull packet off interface. */ m = sonic_get(sc, pkt, len); if (m == 0) { return (0); } /* We assume that the header fit entirely in one mbuf. */ et = mtod(m, struct ether_header *); #ifdef SNCDEBUG if ((sncdebug & SNC_SHOWRXHDR) != 0) { device_printf(sc->sc_dev, "rcvd 0x%x len=%d type=0x%x from %6D", pkt, len, htons(et->ether_type), et->ether_shost, ":"); printf(" (to %6D)\n", et->ether_dhost, ":"); } #endif /* SNCDEBUG */ /* Pass the packet up. */ (*ifp->if_input)(ifp, m); return (1); } /* * munge the received packet into an mbuf chain */ hide struct mbuf * sonic_get(sc, pkt, datalen) struct snc_softc *sc; u_int32_t pkt; int datalen; { struct mbuf *m, *top, **mp; int len; /* * Do not trunc ether_header length. * Our sonic_read() and sonic_get() require it. */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == 0) return (0); m->m_pkthdr.rcvif = &sc->sc_if; m->m_pkthdr.len = datalen; len = MHLEN; top = 0; mp = ⊤ while (datalen > 0) { if (top) { MGET(m, M_DONTWAIT, MT_DATA); if (m == 0) { m_freem(top); return (0); } len = MLEN; } if (datalen >= MINCLSIZE) { MCLGET(m, M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { if (top) m_freem(top); return (0); } len = MCLBYTES; } #if 0 /* XXX: Require? */ if (!top) { register int pad = ALIGN(sizeof(struct ether_header)) - sizeof(struct ether_header); m->m_data += pad; len -= pad; } #endif m->m_len = len = min(datalen, len); (*sc->sc_copyfrombuf)(sc, mtod(m, caddr_t), pkt, len); pkt += len; datalen -= len; *mp = m; mp = &m->m_next; } return (top); } /* * Enable power on the interface. */ int snc_enable(sc) struct snc_softc *sc; { #ifdef SNCDEBUG device_printf(sc->sc_dev, "snc_enable()\n"); #endif /* SNCDEBUG */ if (sc->sc_enabled == 0 && sc->sc_enable != NULL) { if ((*sc->sc_enable)(sc) != 0) { device_printf(sc->sc_dev, "device enable failed\n"); return (EIO); } } sc->sc_enabled = 1; return (0); } /* * Disable power on the interface. */ void snc_disable(sc) struct snc_softc *sc; { #ifdef SNCDEBUG device_printf(sc->sc_dev, "snc_disable()\n"); #endif /* SNCDEBUG */ if (sc->sc_enabled != 0 && sc->sc_disable != NULL) { (*sc->sc_disable)(sc); sc->sc_enabled = 0; } }