/* $OpenBSD: aic6915.c,v 1.4 2008/03/01 23:57:50 brad Exp $ */ /* $NetBSD: aic6915.c,v 1.15 2005/12/24 20:27:29 perry Exp $ */ /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe. * * 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 the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION OR CONTRIBUTORS * 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. */ /* * Device driver for the Adaptec AIC-6915 (``Starfire'') * 10/100 Ethernet controller. */ #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #include #endif #include #if NBPFILTER > 0 #include #endif #include #include #include #include void sf_start(struct ifnet *); void sf_watchdog(struct ifnet *); int sf_ioctl(struct ifnet *, u_long, caddr_t); int sf_init(struct ifnet *); void sf_stop(struct ifnet *, int); void sf_shutdown(void *); void sf_txintr(struct sf_softc *); void sf_rxintr(struct sf_softc *); void sf_stats_update(struct sf_softc *); void sf_reset(struct sf_softc *); void sf_macreset(struct sf_softc *); void sf_rxdrain(struct sf_softc *); int sf_add_rxbuf(struct sf_softc *, int); uint8_t sf_read_eeprom(struct sf_softc *, int); void sf_set_filter(struct sf_softc *); int sf_mii_read(struct device *, int, int); void sf_mii_write(struct device *, int, int, int); void sf_mii_statchg(struct device *); void sf_tick(void *); int sf_mediachange(struct ifnet *); void sf_mediastatus(struct ifnet *, struct ifmediareq *); uint32_t sf_reg_read(struct sf_softc *, bus_addr_t); void sf_reg_write(struct sf_softc *, bus_addr_t , uint32_t); void sf_set_filter_perfect(struct sf_softc *, int , uint8_t *); void sf_set_filter_hash(struct sf_softc *, uint8_t *); struct cfdriver sf_cd = { NULL, "sf", DV_IFNET }; #define sf_funcreg_read(sc, reg) \ bus_space_read_4((sc)->sc_st, (sc)->sc_sh_func, (reg)) #define sf_funcreg_write(sc, reg, val) \ bus_space_write_4((sc)->sc_st, (sc)->sc_sh_func, (reg), (val)) uint32_t sf_reg_read(struct sf_softc *sc, bus_addr_t reg) { if (__predict_false(sc->sc_iomapped)) { bus_space_write_4(sc->sc_st, sc->sc_sh, SF_IndirectIoAccess, reg); return (bus_space_read_4(sc->sc_st, sc->sc_sh, SF_IndirectIoDataPort)); } return (bus_space_read_4(sc->sc_st, sc->sc_sh, reg)); } void sf_reg_write(struct sf_softc *sc, bus_addr_t reg, uint32_t val) { if (__predict_false(sc->sc_iomapped)) { bus_space_write_4(sc->sc_st, sc->sc_sh, SF_IndirectIoAccess, reg); bus_space_write_4(sc->sc_st, sc->sc_sh, SF_IndirectIoDataPort, val); return; } bus_space_write_4(sc->sc_st, sc->sc_sh, reg, val); } #define sf_genreg_read(sc, reg) \ sf_reg_read((sc), (reg) + SF_GENREG_OFFSET) #define sf_genreg_write(sc, reg, val) \ sf_reg_write((sc), (reg) + SF_GENREG_OFFSET, (val)) /* * sf_attach: * * Attach a Starfire interface to the system. */ void sf_attach(struct sf_softc *sc) { struct ifnet *ifp = &sc->sc_arpcom.ac_if; int i, rseg, error; bus_dma_segment_t seg; u_int8_t enaddr[ETHER_ADDR_LEN]; timeout_set(&sc->sc_mii_timeout, sf_tick, sc); /* * If we're I/O mapped, the functional register handle is * the same as the base handle. If we're memory mapped, * carve off a chunk of the register space for the functional * registers, to save on arithmetic later. */ if (sc->sc_iomapped) sc->sc_sh_func = sc->sc_sh; else { if ((error = bus_space_subregion(sc->sc_st, sc->sc_sh, SF_GENREG_OFFSET, SF_FUNCREG_SIZE, &sc->sc_sh_func)) != 0) { printf("%s: unable to sub-region functional " "registers, error = %d\n", sc->sc_dev.dv_xname, error); return; } } /* * Initialize the transmit threshold for this interface. The * manual describes the default as 4 * 16 bytes. We start out * at 10 * 16 bytes, to avoid a bunch of initial underruns on * several platforms. */ sc->sc_txthresh = 10; /* * Allocate the control data structures, and create and load the * DMA map for it. */ if ((error = bus_dmamem_alloc(sc->sc_dmat, sizeof(struct sf_control_data), PAGE_SIZE, 0, &seg, 1, &rseg, BUS_DMA_NOWAIT)) != 0) { printf("%s: unable to allocate control data, error = %d\n", sc->sc_dev.dv_xname, error); goto fail_0; } if ((error = bus_dmamem_map(sc->sc_dmat, &seg, rseg, sizeof(struct sf_control_data), (caddr_t *)&sc->sc_control_data, BUS_DMA_NOWAIT|BUS_DMA_COHERENT)) != 0) { printf("%s: unable to map control data, error = %d\n", sc->sc_dev.dv_xname, error); goto fail_1; } if ((error = bus_dmamap_create(sc->sc_dmat, sizeof(struct sf_control_data), 1, sizeof(struct sf_control_data), 0, BUS_DMA_NOWAIT, &sc->sc_cddmamap)) != 0) { printf("%s: unable to create control data DMA map, " "error = %d\n", sc->sc_dev.dv_xname, error); goto fail_2; } if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_cddmamap, sc->sc_control_data, sizeof(struct sf_control_data), NULL, BUS_DMA_NOWAIT)) != 0) { printf("%s: unable to load control data DMA map, error = %d\n", sc->sc_dev.dv_xname, error); goto fail_3; } /* * Create the transmit buffer DMA maps. */ for (i = 0; i < SF_NTXDESC; i++) { if ((error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, SF_NTXFRAGS, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->sc_txsoft[i].ds_dmamap)) != 0) { printf("%s: unable to create tx DMA map %d, " "error = %d\n", sc->sc_dev.dv_xname, i, error); goto fail_4; } } /* * Create the receive buffer DMA maps. */ for (i = 0; i < SF_NRXDESC; i++) { if ((error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->sc_rxsoft[i].ds_dmamap)) != 0) { printf("%s: unable to create rx DMA map %d, " "error = %d\n", sc->sc_dev.dv_xname, i, error); goto fail_5; } } /* * Reset the chip to a known state. */ sf_reset(sc); /* * Read the Ethernet address from the EEPROM. */ for (i = 0; i < ETHER_ADDR_LEN; i++) enaddr[i] = sf_read_eeprom(sc, (15 + (ETHER_ADDR_LEN - 1)) - i); printf(", address %s\n", ether_sprintf(enaddr)); #ifdef DEBUG if (sf_funcreg_read(sc, SF_PciDeviceConfig) & PDC_System64) printf("%s: 64-bit PCI slot detected\n", sc->sc_dev.dv_xname); #endif /* * Initialize our media structures and probe the MII. */ sc->sc_mii.mii_ifp = ifp; sc->sc_mii.mii_readreg = sf_mii_read; sc->sc_mii.mii_writereg = sf_mii_write; sc->sc_mii.mii_statchg = sf_mii_statchg; ifmedia_init(&sc->sc_mii.mii_media, IFM_IMASK, sf_mediachange, sf_mediastatus); mii_attach(&sc->sc_dev, &sc->sc_mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (LIST_FIRST(&sc->sc_mii.mii_phys) == NULL) { ifmedia_add(&sc->sc_mii.mii_media, IFM_ETHER|IFM_NONE, 0, NULL); ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER|IFM_NONE); } else ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER|IFM_AUTO); bcopy(enaddr, sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN); bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ); ifp = &sc->sc_arpcom.ac_if; ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = sf_ioctl; ifp->if_start = sf_start; ifp->if_watchdog = sf_watchdog; IFQ_SET_MAXLEN(&ifp->if_snd, SF_NTXDESC_MASK); IFQ_SET_READY(&ifp->if_snd); /* * Attach the interface. */ if_attach(ifp); ether_ifattach(ifp); /* * Make sure the interface is shutdown during reboot. */ sc->sc_sdhook = shutdownhook_establish(sf_shutdown, sc); if (sc->sc_sdhook == NULL) printf("%s: WARNING: unable to establish shutdown hook\n", sc->sc_dev.dv_xname); return; /* * Free any resources we've allocated during the failed attach * attempt. Do this in reverse order an fall through. */ fail_5: for (i = 0; i < SF_NRXDESC; i++) { if (sc->sc_rxsoft[i].ds_dmamap != NULL) bus_dmamap_destroy(sc->sc_dmat, sc->sc_rxsoft[i].ds_dmamap); } fail_4: for (i = 0; i < SF_NTXDESC; i++) { if (sc->sc_txsoft[i].ds_dmamap != NULL) bus_dmamap_destroy(sc->sc_dmat, sc->sc_txsoft[i].ds_dmamap); } bus_dmamap_unload(sc->sc_dmat, sc->sc_cddmamap); fail_3: bus_dmamap_destroy(sc->sc_dmat, sc->sc_cddmamap); fail_2: bus_dmamem_unmap(sc->sc_dmat, (caddr_t) sc->sc_control_data, sizeof(struct sf_control_data)); fail_1: bus_dmamem_free(sc->sc_dmat, &seg, rseg); fail_0: return; } /* * sf_shutdown: * * Shutdown hook -- make sure the interface is stopped at reboot. */ void sf_shutdown(void *arg) { struct sf_softc *sc = arg; sf_stop(&sc->sc_arpcom.ac_if, 1); } /* * sf_start: [ifnet interface function] * * Start packet transmission on the interface. */ void sf_start(struct ifnet *ifp) { struct sf_softc *sc = ifp->if_softc; struct mbuf *m0, *m; struct sf_txdesc0 *txd; struct sf_descsoft *ds; bus_dmamap_t dmamap; int error, producer, last = -1, opending, seg; /* * Remember the previous number of pending transmits. */ opending = sc->sc_txpending; /* * Find out where we're sitting. */ producer = SF_TXDINDEX_TO_HOST( TDQPI_HiPrTxProducerIndex_get( sf_funcreg_read(sc, SF_TxDescQueueProducerIndex))); /* * Loop through the send queue, setting up transmit descriptors * until we drain the queue, or use up all available transmit * descriptors. Leave a blank one at the end for sanity's sake. */ while (sc->sc_txpending < (SF_NTXDESC - 1)) { /* * Grab a packet off the queue. */ IFQ_POLL(&ifp->if_snd, m0); if (m0 == NULL) break; m = NULL; /* * Get the transmit descriptor. */ txd = &sc->sc_txdescs[producer]; ds = &sc->sc_txsoft[producer]; dmamap = ds->ds_dmamap; /* * Load the DMA map. If this fails, the packet either * didn't fit in the allotted number of frags, or we were * short on resources. In this case, we'll copy and try * again. */ if (bus_dmamap_load_mbuf(sc->sc_dmat, dmamap, m0, BUS_DMA_WRITE|BUS_DMA_NOWAIT) != 0) { MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) { printf("%s: unable to allocate Tx mbuf\n", sc->sc_dev.dv_xname); break; } if (m0->m_pkthdr.len > MHLEN) { MCLGET(m, M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { printf("%s: unable to allocate Tx " "cluster\n", sc->sc_dev.dv_xname); m_freem(m); break; } } m_copydata(m0, 0, m0->m_pkthdr.len, mtod(m, caddr_t)); m->m_pkthdr.len = m->m_len = m0->m_pkthdr.len; error = bus_dmamap_load_mbuf(sc->sc_dmat, dmamap, m, BUS_DMA_WRITE|BUS_DMA_NOWAIT); if (error) { printf("%s: unable to load Tx buffer, " "error = %d\n", sc->sc_dev.dv_xname, error); break; } } /* * WE ARE NOW COMMITTED TO TRANSMITTING THE PACKET. */ IFQ_DEQUEUE(&ifp->if_snd, m0); if (m != NULL) { m_freem(m0); m0 = m; } /* Initialize the descriptor. */ txd->td_word0 = htole32(TD_W0_ID | TD_W0_CRCEN | m0->m_pkthdr.len); if (producer == (SF_NTXDESC - 1)) txd->td_word0 |= TD_W0_END; txd->td_word1 = htole32(dmamap->dm_nsegs); for (seg = 0; seg < dmamap->dm_nsegs; seg++) { txd->td_frags[seg].fr_addr = htole32(dmamap->dm_segs[seg].ds_addr); txd->td_frags[seg].fr_len = htole32(dmamap->dm_segs[seg].ds_len); } /* Sync the descriptor and the DMA map. */ SF_CDTXDSYNC(sc, producer, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE); /* * Store a pointer to the packet so we can free it later. */ ds->ds_mbuf = m0; /* Advance the Tx pointer. */ sc->sc_txpending++; last = producer; producer = SF_NEXTTX(producer); #if NBPFILTER > 0 /* * Pass the packet to any BPF listeners. */ if (ifp->if_bpf) bpf_mtap(ifp->if_bpf, m0, BPF_DIRECTION_OUT); #endif } if (sc->sc_txpending == (SF_NTXDESC - 1)) { /* No more slots left; notify upper layer. */ ifp->if_flags |= IFF_OACTIVE; } if (sc->sc_txpending != opending) { KASSERT(last != -1); /* * We enqueued packets. Cause a transmit interrupt to * happen on the last packet we enqueued, and give the * new descriptors to the chip by writing the new * producer index. */ sc->sc_txdescs[last].td_word0 |= TD_W0_INTR; SF_CDTXDSYNC(sc, last, BUS_DMASYNC_PREWRITE); sf_funcreg_write(sc, SF_TxDescQueueProducerIndex, TDQPI_HiPrTxProducerIndex(SF_TXDINDEX_TO_CHIP(producer))); /* Set a watchdog timer in case the chip flakes out. */ ifp->if_timer = 5; } } /* * sf_watchdog: [ifnet interface function] * * Watchdog timer handler. */ void sf_watchdog(struct ifnet *ifp) { struct sf_softc *sc = ifp->if_softc; printf("%s: device timeout\n", sc->sc_dev.dv_xname); ifp->if_oerrors++; (void) sf_init(ifp); /* Try to get more packets going. */ sf_start(ifp); } /* * sf_ioctl: [ifnet interface function] * * Handle control requests from the operator. */ int sf_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct sf_softc *sc = (struct sf_softc *)ifp->if_softc; struct ifaddr *ifa = (struct ifaddr *)data; struct ifreq *ifr = (struct ifreq *) data; int s, error = 0; s = splnet(); if ((error = ether_ioctl(ifp, &sc->sc_arpcom, cmd, data)) > 0) { splx(s); return (error); } switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; if (!(ifp->if_flags & IFF_RUNNING)) sf_init(ifp); #ifdef INET if (ifa->ifa_addr->sa_family == AF_INET) arp_ifinit(&sc->sc_arpcom, ifa); #endif break; case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (ifp->if_flags & IFF_RUNNING && ((ifp->if_flags ^ sc->sc_flags) & IFF_PROMISC)) { sf_set_filter(sc); } else { if (!(ifp->if_flags & IFF_RUNNING)) sf_init(ifp); } } else { if (ifp->if_flags & IFF_RUNNING) sf_stop(ifp, 1); } sc->sc_flags = ifp->if_flags; break; case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > ifp->if_hardmtu) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) ifp->if_mtu = ifr->ifr_mtu; break; case SIOCADDMULTI: case SIOCDELMULTI: ifr = (struct ifreq *)data; error = (cmd == SIOCADDMULTI) ? ether_addmulti(ifr, &sc->sc_arpcom) : ether_delmulti(ifr, &sc->sc_arpcom); if (error == ENETRESET) { if (ifp->if_flags & IFF_RUNNING) sf_set_filter(sc); error = 0; } break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->sc_mii.mii_media, cmd); break; default: error = ENOTTY; } /* Try to get more packets going. */ sf_start(ifp); splx(s); return (error); } /* * sf_intr: * * Interrupt service routine. */ int sf_intr(void *arg) { struct sf_softc *sc = arg; uint32_t isr; int handled = 0, wantinit = 0; for (;;) { /* Reading clears all interrupts we're interested in. */ isr = sf_funcreg_read(sc, SF_InterruptStatus); if ((isr & IS_PCIPadInt) == 0) break; handled = 1; /* Handle receive interrupts. */ if (isr & IS_RxQ1DoneInt) sf_rxintr(sc); /* Handle transmit completion interrupts. */ if (isr & (IS_TxDmaDoneInt|IS_TxQueueDoneInt)) sf_txintr(sc); /* Handle abnormal interrupts. */ if (isr & IS_AbnormalInterrupt) { /* Statistics. */ if (isr & IS_StatisticWrapInt) sf_stats_update(sc); /* DMA errors. */ if (isr & IS_DmaErrInt) { wantinit = 1; printf("%s: WARNING: DMA error\n", sc->sc_dev.dv_xname); } /* Transmit FIFO underruns. */ if (isr & IS_TxDataLowInt) { if (sc->sc_txthresh < 0xff) sc->sc_txthresh++; #ifdef DEBUG printf("%s: transmit FIFO underrun, new " "threshold: %d bytes\n", sc->sc_dev.dv_xname, sc->sc_txthresh * 16); #endif sf_funcreg_write(sc, SF_TransmitFrameCSR, sc->sc_TransmitFrameCSR | TFCSR_TransmitThreshold(sc->sc_txthresh)); sf_funcreg_write(sc, SF_TxDescQueueCtrl, sc->sc_TxDescQueueCtrl | TDQC_TxHighPriorityFifoThreshold( sc->sc_txthresh)); } } } if (handled) { /* Reset the interface, if necessary. */ if (wantinit) sf_init(&sc->sc_arpcom.ac_if); /* Try and get more packets going. */ sf_start(&sc->sc_arpcom.ac_if); } return (handled); } /* * sf_txintr: * * Helper -- handle transmit completion interrupts. */ void sf_txintr(struct sf_softc *sc) { struct ifnet *ifp = &sc->sc_arpcom.ac_if; struct sf_descsoft *ds; uint32_t cqci, tcd; int consumer, producer, txidx; try_again: cqci = sf_funcreg_read(sc, SF_CompletionQueueConsumerIndex); consumer = CQCI_TxCompletionConsumerIndex_get(cqci); producer = CQPI_TxCompletionProducerIndex_get( sf_funcreg_read(sc, SF_CompletionQueueProducerIndex)); if (consumer == producer) return; ifp->if_flags &= ~IFF_OACTIVE; while (consumer != producer) { SF_CDTXCSYNC(sc, consumer, BUS_DMASYNC_POSTREAD); tcd = letoh32(sc->sc_txcomp[consumer].tcd_word0); txidx = SF_TCD_INDEX_TO_HOST(TCD_INDEX(tcd)); #ifdef DIAGNOSTIC if ((tcd & TCD_PR) == 0) printf("%s: Tx queue mismatch, index %d\n", sc->sc_dev.dv_xname, txidx); #endif /* * NOTE: stats are updated later. We're just * releasing packets that have been DMA'd to * the chip. */ ds = &sc->sc_txsoft[txidx]; SF_CDTXDSYNC(sc, txidx, BUS_DMASYNC_POSTWRITE); bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0, ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE); m_freem(ds->ds_mbuf); ds->ds_mbuf = NULL; consumer = SF_NEXTTCD(consumer); sc->sc_txpending--; } /* XXXJRT -- should be KDASSERT() */ KASSERT(sc->sc_txpending >= 0); /* If all packets are done, cancel the watchdog timer. */ if (sc->sc_txpending == 0) ifp->if_timer = 0; /* Update the consumer index. */ sf_funcreg_write(sc, SF_CompletionQueueConsumerIndex, (cqci & ~CQCI_TxCompletionConsumerIndex(0x7ff)) | CQCI_TxCompletionConsumerIndex(consumer)); /* Double check for new completions. */ goto try_again; } /* * sf_rxintr: * * Helper -- handle receive interrupts. */ void sf_rxintr(struct sf_softc *sc) { struct ifnet *ifp = &sc->sc_arpcom.ac_if; struct sf_descsoft *ds; struct sf_rcd_full *rcd; struct mbuf *m; uint32_t cqci, word0; int consumer, producer, bufproducer, rxidx, len; try_again: cqci = sf_funcreg_read(sc, SF_CompletionQueueConsumerIndex); consumer = CQCI_RxCompletionQ1ConsumerIndex_get(cqci); producer = CQPI_RxCompletionQ1ProducerIndex_get( sf_funcreg_read(sc, SF_CompletionQueueProducerIndex)); bufproducer = RXQ1P_RxDescQ1Producer_get( sf_funcreg_read(sc, SF_RxDescQueue1Ptrs)); if (consumer == producer) return; while (consumer != producer) { rcd = &sc->sc_rxcomp[consumer]; SF_CDRXCSYNC(sc, consumer, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE); SF_CDRXCSYNC(sc, consumer, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); word0 = letoh32(rcd->rcd_word0); rxidx = RCD_W0_EndIndex(word0); ds = &sc->sc_rxsoft[rxidx]; consumer = SF_NEXTRCD(consumer); bufproducer = SF_NEXTRX(bufproducer); if ((word0 & RCD_W0_OK) == 0) { SF_INIT_RXDESC(sc, rxidx); continue; } bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0, ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD); /* * No errors; receive the packet. Note that we have * configured the Starfire to NOT transfer the CRC * with the packet. */ len = RCD_W0_Length(word0); #ifndef __STRICT_ALIGNMENT /* * Allocate a new mbuf cluster. If that fails, we are * out of memory, and must drop the packet and recycle * the buffer that's already attached to this descriptor. */ m = ds->ds_mbuf; if (sf_add_rxbuf(sc, rxidx) != 0) { ifp->if_ierrors++; SF_INIT_RXDESC(sc, rxidx); bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0, ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD); continue; } #else /* * The Starfire's receive buffer must be 4-byte aligned. * But this means that the data after the Ethernet header * is misaligned. We must allocate a new buffer and * copy the data, shifted forward 2 bytes. */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) { dropit: ifp->if_ierrors++; SF_INIT_RXDESC(sc, rxidx); bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0, ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD); continue; } if (len > (MHLEN - 2)) { MCLGET(m, M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { m_freem(m); goto dropit; } } m->m_data += 2; /* * Note that we use cluster for incoming frames, so the * buffer is virtually contiguous. */ memcpy(mtod(m, caddr_t), mtod(ds->ds_mbuf, caddr_t), len); /* Allow the receive descriptor to continue using its mbuf. */ SF_INIT_RXDESC(sc, rxidx); bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0, ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD); #endif /* __STRICT_ALIGNMENT */ m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = len; #if NBPFILTER > 0 /* * Pass this up to any BPF listeners. */ if (ifp->if_bpf) bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN); #endif /* NBPFILTER > 0 */ /* Pass it on. */ ether_input_mbuf(ifp, m); ifp->if_ipackets++; } /* Update the chip's pointers. */ sf_funcreg_write(sc, SF_CompletionQueueConsumerIndex, (cqci & ~CQCI_RxCompletionQ1ConsumerIndex(0x7ff)) | CQCI_RxCompletionQ1ConsumerIndex(consumer)); sf_funcreg_write(sc, SF_RxDescQueue1Ptrs, RXQ1P_RxDescQ1Producer(bufproducer)); /* Double-check for any new completions. */ goto try_again; } /* * sf_tick: * * One second timer, used to tick the MII and update stats. */ void sf_tick(void *arg) { struct sf_softc *sc = arg; int s; s = splnet(); mii_tick(&sc->sc_mii); sf_stats_update(sc); splx(s); timeout_add(&sc->sc_mii_timeout, hz); } /* * sf_stats_update: * * Read the statitistics counters. */ void sf_stats_update(struct sf_softc *sc) { struct sf_stats stats; struct ifnet *ifp = &sc->sc_arpcom.ac_if; uint32_t *p; u_int i; p = &stats.TransmitOKFrames; for (i = 0; i < (sizeof(stats) / sizeof(uint32_t)); i++) { *p++ = sf_genreg_read(sc, SF_STATS_BASE + (i * sizeof(uint32_t))); sf_genreg_write(sc, SF_STATS_BASE + (i * sizeof(uint32_t)), 0); } ifp->if_opackets += stats.TransmitOKFrames; ifp->if_collisions += stats.SingleCollisionFrames + stats.MultipleCollisionFrames; ifp->if_oerrors += stats.TransmitAbortDueToExcessiveCollisions + stats.TransmitAbortDueToExcessingDeferral + stats.FramesLostDueToInternalTransmitErrors; ifp->if_ipackets += stats.ReceiveOKFrames; ifp->if_ierrors += stats.ReceiveCRCErrors + stats.AlignmentErrors + stats.ReceiveFramesTooLong + stats.ReceiveFramesTooShort + stats.ReceiveFramesJabbersError + stats.FramesLostDueToInternalReceiveErrors; } /* * sf_reset: * * Perform a soft reset on the Starfire. */ void sf_reset(struct sf_softc *sc) { int i; sf_funcreg_write(sc, SF_GeneralEthernetCtrl, 0); sf_macreset(sc); sf_funcreg_write(sc, SF_PciDeviceConfig, PDC_SoftReset); for (i = 0; i < 1000; i++) { delay(10); if ((sf_funcreg_read(sc, SF_PciDeviceConfig) & PDC_SoftReset) == 0) break; } if (i == 1000) { printf("%s: reset failed to complete\n", sc->sc_dev.dv_xname); sf_funcreg_write(sc, SF_PciDeviceConfig, 0); } delay(1000); } /* * sf_macreset: * * Reset the MAC portion of the Starfire. */ void sf_macreset(struct sf_softc *sc) { sf_genreg_write(sc, SF_MacConfig1, sc->sc_MacConfig1 | MC1_SoftRst); delay(1000); sf_genreg_write(sc, SF_MacConfig1, sc->sc_MacConfig1); } /* * sf_init: [ifnet interface function] * * Initialize the interface. Must be called at splnet(). */ int sf_init(struct ifnet *ifp) { struct sf_softc *sc = ifp->if_softc; struct sf_descsoft *ds; int error = 0; u_int i; /* * Cancel any pending I/O. */ sf_stop(ifp, 0); /* * Reset the Starfire to a known state. */ sf_reset(sc); /* Clear the stat counters. */ for (i = 0; i < sizeof(struct sf_stats); i += sizeof(uint32_t)) sf_genreg_write(sc, SF_STATS_BASE + i, 0); /* * Initialize the transmit descriptor ring. */ memset(sc->sc_txdescs, 0, sizeof(sc->sc_txdescs)); sf_funcreg_write(sc, SF_TxDescQueueHighAddr, 0); sf_funcreg_write(sc, SF_HiPrTxDescQueueBaseAddr, SF_CDTXDADDR(sc, 0)); sf_funcreg_write(sc, SF_LoPrTxDescQueueBaseAddr, 0); /* * Initialize the transmit completion ring. */ for (i = 0; i < SF_NTCD; i++) { sc->sc_txcomp[i].tcd_word0 = TCD_DMA_ID; SF_CDTXCSYNC(sc, i, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); } sf_funcreg_write(sc, SF_CompletionQueueHighAddr, 0); sf_funcreg_write(sc, SF_TxCompletionQueueCtrl, SF_CDTXCADDR(sc, 0)); /* * Initialize the receive descriptor ring. */ for (i = 0; i < SF_NRXDESC; i++) { ds = &sc->sc_rxsoft[i]; if (ds->ds_mbuf == NULL) { if ((error = sf_add_rxbuf(sc, i)) != 0) { printf("%s: unable to allocate or map rx " "buffer %d, error = %d\n", sc->sc_dev.dv_xname, i, error); /* * XXX Should attempt to run with fewer receive * XXX buffers instead of just failing. */ sf_rxdrain(sc); goto out; } } else SF_INIT_RXDESC(sc, i); } sf_funcreg_write(sc, SF_RxDescQueueHighAddress, 0); sf_funcreg_write(sc, SF_RxDescQueue1LowAddress, SF_CDRXDADDR(sc, 0)); sf_funcreg_write(sc, SF_RxDescQueue2LowAddress, 0); /* * Initialize the receive completion ring. */ for (i = 0; i < SF_NRCD; i++) { sc->sc_rxcomp[i].rcd_word0 = RCD_W0_ID; sc->sc_rxcomp[i].rcd_word1 = 0; sc->sc_rxcomp[i].rcd_word2 = 0; sc->sc_rxcomp[i].rcd_timestamp = 0; SF_CDRXCSYNC(sc, i, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); } sf_funcreg_write(sc, SF_RxCompletionQueue1Ctrl, SF_CDRXCADDR(sc, 0) | RCQ1C_RxCompletionQ1Type(3)); sf_funcreg_write(sc, SF_RxCompletionQueue2Ctrl, 0); /* * Initialize the Tx CSR. */ sc->sc_TransmitFrameCSR = 0; sf_funcreg_write(sc, SF_TransmitFrameCSR, sc->sc_TransmitFrameCSR | TFCSR_TransmitThreshold(sc->sc_txthresh)); /* * Initialize the Tx descriptor control register. */ sc->sc_TxDescQueueCtrl = TDQC_SkipLength(0) | TDQC_TxDmaBurstSize(4) | /* default */ TDQC_MinFrameSpacing(3) | /* 128 bytes */ TDQC_TxDescType(0); sf_funcreg_write(sc, SF_TxDescQueueCtrl, sc->sc_TxDescQueueCtrl | TDQC_TxHighPriorityFifoThreshold(sc->sc_txthresh)); /* * Initialize the Rx descriptor control registers. */ sf_funcreg_write(sc, SF_RxDescQueue1Ctrl, RDQ1C_RxQ1BufferLength(MCLBYTES) | RDQ1C_RxDescSpacing(0)); sf_funcreg_write(sc, SF_RxDescQueue2Ctrl, 0); /* * Initialize the Tx descriptor producer indices. */ sf_funcreg_write(sc, SF_TxDescQueueProducerIndex, TDQPI_HiPrTxProducerIndex(0) | TDQPI_LoPrTxProducerIndex(0)); /* * Initialize the Rx descriptor producer indices. */ sf_funcreg_write(sc, SF_RxDescQueue1Ptrs, RXQ1P_RxDescQ1Producer(SF_NRXDESC - 1)); sf_funcreg_write(sc, SF_RxDescQueue2Ptrs, RXQ2P_RxDescQ2Producer(0)); /* * Initialize the Tx and Rx completion queue consumer indices. */ sf_funcreg_write(sc, SF_CompletionQueueConsumerIndex, CQCI_TxCompletionConsumerIndex(0) | CQCI_RxCompletionQ1ConsumerIndex(0)); sf_funcreg_write(sc, SF_RxHiPrCompletionPtrs, 0); /* * Initialize the Rx DMA control register. */ sf_funcreg_write(sc, SF_RxDmaCtrl, RDC_RxHighPriorityThreshold(6) | /* default */ RDC_RxBurstSize(4)); /* default */ /* * Set the receive filter. */ sc->sc_RxAddressFilteringCtl = 0; sf_set_filter(sc); /* * Set MacConfig1. When we set the media, MacConfig1 will * actually be written and the MAC part reset. */ sc->sc_MacConfig1 = MC1_PadEn; /* * Set the media. */ mii_mediachg(&sc->sc_mii); /* * Initialize the interrupt register. */ sc->sc_InterruptEn = IS_PCIPadInt | IS_RxQ1DoneInt | IS_TxQueueDoneInt | IS_TxDmaDoneInt | IS_DmaErrInt | IS_StatisticWrapInt; sf_funcreg_write(sc, SF_InterruptEn, sc->sc_InterruptEn); sf_funcreg_write(sc, SF_PciDeviceConfig, PDC_IntEnable | PDC_PCIMstDmaEn | (1 << PDC_FifoThreshold_SHIFT)); /* * Start the transmit and receive processes. */ sf_funcreg_write(sc, SF_GeneralEthernetCtrl, GEC_TxDmaEn|GEC_RxDmaEn|GEC_TransmitEn|GEC_ReceiveEn); /* Start the on second clock. */ timeout_add(&sc->sc_mii_timeout, hz); /* * Note that the interface is now running. */ ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; out: if (error) { ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); ifp->if_timer = 0; printf("%s: interface not running\n", sc->sc_dev.dv_xname); } return (error); } /* * sf_rxdrain: * * Drain the receive queue. */ void sf_rxdrain(struct sf_softc *sc) { struct sf_descsoft *ds; int i; for (i = 0; i < SF_NRXDESC; i++) { ds = &sc->sc_rxsoft[i]; if (ds->ds_mbuf != NULL) { bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap); m_freem(ds->ds_mbuf); ds->ds_mbuf = NULL; } } } /* * sf_stop: [ifnet interface function] * * Stop transmission on the interface. */ void sf_stop(struct ifnet *ifp, int disable) { struct sf_softc *sc = ifp->if_softc; struct sf_descsoft *ds; int i; /* Stop the one second clock. */ timeout_del(&sc->sc_mii_timeout); /* Down the MII. */ mii_down(&sc->sc_mii); /* Disable interrupts. */ sf_funcreg_write(sc, SF_InterruptEn, 0); /* Stop the transmit and receive processes. */ sf_funcreg_write(sc, SF_GeneralEthernetCtrl, 0); /* * Release any queued transmit buffers. */ for (i = 0; i < SF_NTXDESC; i++) { ds = &sc->sc_txsoft[i]; if (ds->ds_mbuf != NULL) { bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap); m_freem(ds->ds_mbuf); ds->ds_mbuf = NULL; } } sc->sc_txpending = 0; if (disable) sf_rxdrain(sc); /* * Mark the interface down and cancel the watchdog timer. */ ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); ifp->if_timer = 0; } /* * sf_read_eeprom: * * Read from the Starfire EEPROM. */ uint8_t sf_read_eeprom(struct sf_softc *sc, int offset) { uint32_t reg; reg = sf_genreg_read(sc, SF_EEPROM_BASE + (offset & ~3)); return ((reg >> (8 * (offset & 3))) & 0xff); } /* * sf_add_rxbuf: * * Add a receive buffer to the indicated descriptor. */ int sf_add_rxbuf(struct sf_softc *sc, int idx) { struct sf_descsoft *ds = &sc->sc_rxsoft[idx]; struct mbuf *m; int error; MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); MCLGET(m, M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { m_freem(m); return (ENOBUFS); } if (ds->ds_mbuf != NULL) bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap); ds->ds_mbuf = m; error = bus_dmamap_load(sc->sc_dmat, ds->ds_dmamap, m->m_ext.ext_buf, m->m_ext.ext_size, NULL, BUS_DMA_READ|BUS_DMA_NOWAIT); if (error) { printf("%s: can't load rx DMA map %d, error = %d\n", sc->sc_dev.dv_xname, idx, error); panic("sf_add_rxbuf"); /* XXX */ } bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0, ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD); SF_INIT_RXDESC(sc, idx); return (0); } void sf_set_filter_perfect(struct sf_softc *sc, int slot, uint8_t *enaddr) { uint32_t reg0, reg1, reg2; reg0 = enaddr[5] | (enaddr[4] << 8); reg1 = enaddr[3] | (enaddr[2] << 8); reg2 = enaddr[1] | (enaddr[0] << 8); sf_genreg_write(sc, SF_PERFECT_BASE + (slot * 0x10) + 0, reg0); sf_genreg_write(sc, SF_PERFECT_BASE + (slot * 0x10) + 4, reg1); sf_genreg_write(sc, SF_PERFECT_BASE + (slot * 0x10) + 8, reg2); } void sf_set_filter_hash(struct sf_softc *sc, uint8_t *enaddr) { uint32_t hash, slot, reg; hash = ether_crc32_be(enaddr, ETHER_ADDR_LEN) >> 23; slot = hash >> 4; reg = sf_genreg_read(sc, SF_HASH_BASE + (slot * 0x10)); reg |= 1 << (hash & 0xf); sf_genreg_write(sc, SF_HASH_BASE + (slot * 0x10), reg); } /* * sf_set_filter: * * Set the Starfire receive filter. */ void sf_set_filter(struct sf_softc *sc) { struct arpcom *ac = &sc->sc_arpcom; struct ifnet *ifp = &sc->sc_arpcom.ac_if; struct ether_multi *enm; struct ether_multistep step; int i; /* Start by clearing the perfect and hash tables. */ for (i = 0; i < SF_PERFECT_SIZE; i += sizeof(uint32_t)) sf_genreg_write(sc, SF_PERFECT_BASE + i, 0); for (i = 0; i < SF_HASH_SIZE; i += sizeof(uint32_t)) sf_genreg_write(sc, SF_HASH_BASE + i, 0); /* * Clear the perfect and hash mode bits. */ sc->sc_RxAddressFilteringCtl &= ~(RAFC_PerfectFilteringMode(3) | RAFC_HashFilteringMode(3)); if (ifp->if_flags & IFF_BROADCAST) sc->sc_RxAddressFilteringCtl |= RAFC_PassBroadcast; else sc->sc_RxAddressFilteringCtl &= ~RAFC_PassBroadcast; if (ifp->if_flags & IFF_PROMISC) { sc->sc_RxAddressFilteringCtl |= RAFC_PromiscuousMode; goto allmulti; } else sc->sc_RxAddressFilteringCtl &= ~RAFC_PromiscuousMode; /* * Set normal perfect filtering mode. */ sc->sc_RxAddressFilteringCtl |= RAFC_PerfectFilteringMode(1); /* * First, write the station address to the perfect filter * table. */ sf_set_filter_perfect(sc, 0, LLADDR(ifp->if_sadl)); /* * Now set the hash bits for each multicast address in our * list. */ ETHER_FIRST_MULTI(step, ac, enm); if (enm == NULL) goto done; while (enm != NULL) { if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) { /* * We must listen to a range of multicast addresses. * For now, just accept all multicasts, rather than * trying to set only those filter bits needed to match * the range. (At this time, the only use of address * ranges is for IP multicast routing, for which the * range is big enough to require all bits set.) */ goto allmulti; } sf_set_filter_hash(sc, enm->enm_addrlo); ETHER_NEXT_MULTI(step, enm); } /* * Set "hash only multicast dest, match regardless of VLAN ID". */ sc->sc_RxAddressFilteringCtl |= RAFC_HashFilteringMode(2); goto done; allmulti: /* * XXX RAFC_PassMulticast is sub-optimal if using VLAN mode. */ sc->sc_RxAddressFilteringCtl |= RAFC_PassMulticast; ifp->if_flags |= IFF_ALLMULTI; done: sf_funcreg_write(sc, SF_RxAddressFilteringCtl, sc->sc_RxAddressFilteringCtl); } /* * sf_mii_read: [mii interface function] * * Read from the MII. */ int sf_mii_read(struct device *self, int phy, int reg) { struct sf_softc *sc = (void *) self; uint32_t v; int i; for (i = 0; i < 1000; i++) { v = sf_genreg_read(sc, SF_MII_PHY_REG(phy, reg)); if (v & MiiDataValid) break; delay(1); } if ((v & MiiDataValid) == 0) return (0); if (MiiRegDataPort(v) == 0xffff) return (0); return (MiiRegDataPort(v)); } /* * sf_mii_write: [mii interface function] * * Write to the MII. */ void sf_mii_write(struct device *self, int phy, int reg, int val) { struct sf_softc *sc = (void *) self; int i; sf_genreg_write(sc, SF_MII_PHY_REG(phy, reg), val); for (i = 0; i < 1000; i++) { if ((sf_genreg_read(sc, SF_MII_PHY_REG(phy, reg)) & MiiBusy) == 0) return; delay(1); } printf("%s: MII write timed out\n", sc->sc_dev.dv_xname); } /* * sf_mii_statchg: [mii interface function] * * Callback from the PHY when the media changes. */ void sf_mii_statchg(struct device *self) { struct sf_softc *sc = (void *) self; uint32_t ipg; if (sc->sc_mii.mii_media_active & IFM_FDX) { sc->sc_MacConfig1 |= MC1_FullDuplex; ipg = 0x15; } else { sc->sc_MacConfig1 &= ~MC1_FullDuplex; ipg = 0x11; } sf_genreg_write(sc, SF_MacConfig1, sc->sc_MacConfig1); sf_macreset(sc); sf_genreg_write(sc, SF_BkToBkIPG, ipg); } /* * sf_mediastatus: [ifmedia interface function] * * Callback from ifmedia to request current media status. */ void sf_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct sf_softc *sc = ifp->if_softc; mii_pollstat(&sc->sc_mii); ifmr->ifm_status = sc->sc_mii.mii_media_status; ifmr->ifm_active = sc->sc_mii.mii_media_active; } /* * sf_mediachange: [ifmedia interface function] * * Callback from ifmedia to request new media setting. */ int sf_mediachange(struct ifnet *ifp) { struct sf_softc *sc = ifp->if_softc; if (ifp->if_flags & IFF_UP) mii_mediachg(&sc->sc_mii); return (0); }