/* $OpenBSD: mtd8xx.c,v 1.27 2015/06/24 09:40:54 mpi Exp $ */ /* * Copyright (c) 2003 Oleg Safiullin * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ #include "bpfilter.h" #include #include #include #include #include #include #include #include #if NBPFILTER > 0 #include #endif #include #include #include #include #include #include #include #include static int mtd_ifmedia_upd(struct ifnet *); static void mtd_ifmedia_sts(struct ifnet *, struct ifmediareq *); static u_int32_t mtd_mii_command(struct mtd_softc *, int, int, int); static int mtd_miibus_readreg(struct device *, int, int); static void mtd_miibus_writereg(struct device *, int, int, int); static void mtd_miibus_statchg(struct device *); static void mtd_setmulti(struct mtd_softc *); static int mtd_encap(struct mtd_softc *, struct mbuf *, u_int32_t *); static int mtd_list_rx_init(struct mtd_softc *); static void mtd_list_tx_init(struct mtd_softc *); static int mtd_newbuf(struct mtd_softc *, int, struct mbuf *); static void mtd_reset(struct mtd_softc *sc); static int mtd_ioctl(struct ifnet *, u_long, caddr_t); static void mtd_init(struct ifnet *); static void mtd_start(struct ifnet *); static void mtd_stop(struct ifnet *); static void mtd_watchdog(struct ifnet *); static int mtd_rxeof(struct mtd_softc *); static int mtd_rx_resync(struct mtd_softc *); static void mtd_txeof(struct mtd_softc *); void mtd_attach(struct mtd_softc *sc) { struct ifnet *ifp = &sc->sc_arpcom.ac_if; u_int32_t enaddr[2]; int i; /* Reset the adapter. */ mtd_reset(sc); if (bus_dmamem_alloc(sc->sc_dmat, sizeof(struct mtd_list_data), PAGE_SIZE, 0, sc->sc_listseg, 1, &sc->sc_listnseg, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0) { printf(": can't alloc list mem\n"); return; } if (bus_dmamem_map(sc->sc_dmat, sc->sc_listseg, sc->sc_listnseg, sizeof(struct mtd_list_data), &sc->sc_listkva, BUS_DMA_NOWAIT) != 0) { printf(": can't map list mem\n"); return; } if (bus_dmamap_create(sc->sc_dmat, sizeof(struct mtd_list_data), 1, sizeof(struct mtd_list_data), 0, BUS_DMA_NOWAIT, &sc->sc_listmap) != 0) { printf(": can't alloc list map\n"); return; } if (bus_dmamap_load(sc->sc_dmat, sc->sc_listmap, sc->sc_listkva, sizeof(struct mtd_list_data), NULL, BUS_DMA_NOWAIT) != 0) { printf(": can't load list map\n"); return; } sc->mtd_ldata = (struct mtd_list_data *)sc->sc_listkva; for (i = 0; i < MTD_RX_LIST_CNT; i++) { if (bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->mtd_cdata.mtd_rx_chain[i].sd_map) != 0) { printf(": can't create rx map\n"); return; } } if (bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->sc_rx_sparemap) != 0) { printf(": can't create rx spare map\n"); return; } for (i = 0; i < MTD_TX_LIST_CNT; i++) { if (bus_dmamap_create(sc->sc_dmat, MCLBYTES, MTD_TX_LIST_CNT - 5, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->mtd_cdata.mtd_tx_chain[i].sd_map) != 0) { printf(": can't create tx map\n"); return; } } if (bus_dmamap_create(sc->sc_dmat, MCLBYTES, MTD_TX_LIST_CNT - 5, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->sc_tx_sparemap) != 0) { printf(": can't create tx spare map\n"); return; } /* Get station address. */ enaddr[0] = letoh32(CSR_READ_4(MTD_PAR0)); enaddr[1] = letoh32(CSR_READ_4(MTD_PAR4)); bcopy(enaddr, sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN); printf(" address %s\n", ether_sprintf(sc->sc_arpcom.ac_enaddr)); /* Initialize interface */ ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = mtd_ioctl; ifp->if_start = mtd_start; ifp->if_watchdog = mtd_watchdog; IFQ_SET_READY(&ifp->if_snd); bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ); ifp->if_capabilities = IFCAP_VLAN_MTU; /* * Initialize our media structures and probe the MII. */ sc->sc_mii.mii_ifp = ifp; sc->sc_mii.mii_readreg = mtd_miibus_readreg; sc->sc_mii.mii_writereg = mtd_miibus_writereg; sc->sc_mii.mii_statchg = mtd_miibus_statchg; ifmedia_init(&sc->sc_mii.mii_media, 0, mtd_ifmedia_upd, mtd_ifmedia_sts); 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); /* * Attach us everywhere */ if_attach(ifp); ether_ifattach(ifp); } static int mtd_ifmedia_upd(struct ifnet *ifp) { struct mtd_softc *sc = ifp->if_softc; return (mii_mediachg(&sc->sc_mii)); } static void mtd_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct mtd_softc *sc = ifp->if_softc; mii_pollstat(&sc->sc_mii); ifmr->ifm_active = sc->sc_mii.mii_media_active; ifmr->ifm_status = sc->sc_mii.mii_media_status; } static u_int32_t mtd_mii_command(struct mtd_softc *sc, int opcode, int phy, int reg) { u_int32_t miir, mask, data; int i; miir = (CSR_READ_4(MTD_MIIMGT) & ~MIIMGT_MASK) | MIIMGT_WRITE | MIIMGT_MDO; for (i = 0; i < 32; i++) { miir &= ~MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); miir |= MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); } data = opcode | (phy << 7) | (reg << 2); for (mask = 0; mask; mask >>= 1) { miir &= ~(MIIMGT_MDC | MIIMGT_MDO); if (mask & data) miir |= MIIMGT_MDO; CSR_WRITE_4(MTD_MIIMGT, miir); miir |= MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); DELAY(30); if (mask == 0x4 && opcode == MII_OPCODE_RD) miir &= ~MIIMGT_WRITE; } return (miir); } static int mtd_miibus_readreg(struct device *self, int phy, int reg) { struct mtd_softc *sc = (void *)self; if (sc->sc_devid == PCI_PRODUCT_MYSON_MTD803) return (phy ? 0 : (int)CSR_READ_2(MTD_PHYCSR + (reg << 1))); else { u_int32_t miir, mask, data; miir = mtd_mii_command(sc, MII_OPCODE_RD, phy, reg); for (mask = 0x8000, data = 0; mask; mask >>= 1) { miir &= ~MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); miir = CSR_READ_4(MTD_MIIMGT); if (miir & MIIMGT_MDI) data |= mask; miir |= MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); DELAY(30); } miir &= ~MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); return ((int)data); } } static void mtd_miibus_writereg(struct device *self, int phy, int reg, int val) { struct mtd_softc *sc = (void *)self; if (sc->sc_devid == PCI_PRODUCT_MYSON_MTD803) { if (!phy) CSR_WRITE_2(MTD_PHYCSR + (reg << 1), val); } else { u_int32_t miir, mask; miir = mtd_mii_command(sc, MII_OPCODE_WR, phy, reg); for (mask = 0x8000; mask; mask >>= 1) { miir &= ~(MIIMGT_MDC | MIIMGT_MDO); if (mask & (u_int32_t)val) miir |= MIIMGT_MDO; CSR_WRITE_4(MTD_MIIMGT, miir); miir |= MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); DELAY(1); } miir &= ~MIIMGT_MDC; CSR_WRITE_4(MTD_MIIMGT, miir); } } static void mtd_miibus_statchg(struct device *self) { /* NOTHING */ } void mtd_setmulti(struct mtd_softc *sc) { struct arpcom *ac = &sc->sc_arpcom; struct ifnet *ifp = &sc->sc_arpcom.ac_if; u_int32_t rxfilt, crc, hash[2] = { 0, 0 }; struct ether_multistep step; struct ether_multi *enm; int mcnt = 0; if (ac->ac_multirangecnt > 0) ifp->if_flags |= IFF_ALLMULTI; rxfilt = CSR_READ_4(MTD_TCRRCR) & ~RCR_AM; if (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) { rxfilt |= RCR_AM; CSR_WRITE_4(MTD_TCRRCR, rxfilt); CSR_WRITE_4(MTD_MAR0, 0xffffffff); CSR_WRITE_4(MTD_MAR4, 0xffffffff); return; } /* First, zot all the existing hash bits. */ CSR_WRITE_4(MTD_MAR0, 0); CSR_WRITE_4(MTD_MAR4, 0); /* Now program new ones. */ ETHER_FIRST_MULTI(step, ac, enm); while (enm != NULL) { crc = ether_crc32_be(enm->enm_addrlo, ETHER_ADDR_LEN) >> 26; hash[crc >> 5] |= 1 << (crc & 0xf); ++mcnt; ETHER_NEXT_MULTI(step, enm); } if (mcnt) rxfilt |= RCR_AM; CSR_WRITE_4(MTD_MAR0, hash[0]); CSR_WRITE_4(MTD_MAR4, hash[1]); CSR_WRITE_4(MTD_TCRRCR, rxfilt); } /* * Encapsulate an mbuf chain in a descriptor by coupling the mbuf data * pointers to the fragment pointers. */ int mtd_encap(struct mtd_softc *sc, struct mbuf *m_head, u_int32_t *txidx) { struct mtd_tx_desc *f = NULL; int frag, cur, cnt = 0, i, total_len = 0; bus_dmamap_t map; /* * Start packing the mbufs in this chain into * the fragment pointers. Stop when we run out * of fragments or hit the end of the mbuf chain. */ map = sc->sc_tx_sparemap; if (bus_dmamap_load_mbuf(sc->sc_dmat, map, m_head, BUS_DMA_NOWAIT) != 0) return (1); cur = frag = *txidx; for (i = 0; i < map->dm_nsegs; i++) { if ((MTD_TX_LIST_CNT - (sc->mtd_cdata.mtd_tx_cnt + cnt)) < 5) { bus_dmamap_unload(sc->sc_dmat, map); return (1); } f = &sc->mtd_ldata->mtd_tx_list[frag]; f->td_tcw = htole32(map->dm_segs[i].ds_len); total_len += map->dm_segs[i].ds_len; if (cnt == 0) { f->td_tsw = 0; f->td_tcw |= htole32(TCW_FD | TCW_CRC | TCW_PAD); } else f->td_tsw = htole32(TSW_OWN); f->td_buf = htole32(map->dm_segs[i].ds_addr); cur = frag; frag = (frag + 1) % MTD_TX_LIST_CNT; cnt++; } sc->mtd_cdata.mtd_tx_cnt += cnt; sc->mtd_cdata.mtd_tx_chain[cur].sd_mbuf = m_head; sc->sc_tx_sparemap = sc->mtd_cdata.mtd_tx_chain[cur].sd_map; sc->mtd_cdata.mtd_tx_chain[cur].sd_map = map; sc->mtd_ldata->mtd_tx_list[cur].td_tcw |= htole32(TCW_LD | TCW_IC); if (sc->sc_devid == PCI_PRODUCT_MYSON_MTD891) sc->mtd_ldata->mtd_tx_list[cur].td_tcw |= htole32(TCW_EIC | TCW_RTLC); bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize, BUS_DMASYNC_PREWRITE); sc->mtd_ldata->mtd_tx_list[*txidx].td_tsw = htole32(TSW_OWN); sc->mtd_ldata->mtd_tx_list[*txidx].td_tcw |= htole32(total_len << TCW_PKTS_SHIFT); bus_dmamap_sync(sc->sc_dmat, sc->sc_listmap, offsetof(struct mtd_list_data, mtd_tx_list[0]), sizeof(struct mtd_tx_desc) * MTD_TX_LIST_CNT, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); *txidx = frag; return (0); } /* * Initialize the transmit descriptors. */ static void mtd_list_tx_init(struct mtd_softc *sc) { struct mtd_chain_data *cd; struct mtd_list_data *ld; int i; cd = &sc->mtd_cdata; ld = sc->mtd_ldata; for (i = 0; i < MTD_TX_LIST_CNT; i++) { cd->mtd_tx_chain[i].sd_mbuf = NULL; ld->mtd_tx_list[i].td_tsw = 0; ld->mtd_tx_list[i].td_tcw = 0; ld->mtd_tx_list[i].td_buf = 0; ld->mtd_tx_list[i].td_next = htole32( sc->sc_listmap->dm_segs[0].ds_addr + offsetof(struct mtd_list_data, mtd_tx_list[(i + 1) % MTD_TX_LIST_CNT])); } cd->mtd_tx_prod = cd->mtd_tx_cons = cd->mtd_tx_cnt = 0; } /* * Initialize the RX descriptors and allocate mbufs for them. Note that * we arrange the descriptors in a closed ring, so that the last descriptor * points back to the first. */ static int mtd_list_rx_init(struct mtd_softc *sc) { struct mtd_list_data *ld; int i; ld = sc->mtd_ldata; for (i = 0; i < MTD_RX_LIST_CNT; i++) { if (mtd_newbuf(sc, i, NULL)) return (1); ld->mtd_rx_list[i].rd_next = htole32( sc->sc_listmap->dm_segs[0].ds_addr + offsetof(struct mtd_list_data, mtd_rx_list[(i + 1) % MTD_RX_LIST_CNT]) ); } sc->mtd_cdata.mtd_rx_prod = 0; return (0); } /* * Initialize an RX descriptor and attach an MBUF cluster. */ static int mtd_newbuf(struct mtd_softc *sc, int i, struct mbuf *m) { struct mbuf *m_new = NULL; struct mtd_rx_desc *c; bus_dmamap_t map; c = &sc->mtd_ldata->mtd_rx_list[i]; if (m == NULL) { MGETHDR(m_new, M_DONTWAIT, MT_DATA); if (m_new == NULL) return (1); MCLGET(m_new, M_DONTWAIT); if (!(m_new->m_flags & M_EXT)) { m_freem(m_new); return (1); } m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; if (bus_dmamap_load(sc->sc_dmat, sc->sc_rx_sparemap, mtod(m_new, caddr_t), MCLBYTES, NULL, BUS_DMA_NOWAIT) != 0) { m_freem(m_new); return (1); } map = sc->mtd_cdata.mtd_rx_chain[i].sd_map; sc->mtd_cdata.mtd_rx_chain[i].sd_map = sc->sc_rx_sparemap; sc->sc_rx_sparemap = map; } else { m_new = m; m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; m_new->m_data = m_new->m_ext.ext_buf; } m_adj(m_new, sizeof(u_int64_t)); bus_dmamap_sync(sc->sc_dmat, sc->mtd_cdata.mtd_rx_chain[i].sd_map, 0, sc->mtd_cdata.mtd_rx_chain[i].sd_map->dm_mapsize, BUS_DMASYNC_PREREAD); sc->mtd_cdata.mtd_rx_chain[i].sd_mbuf = m_new; c->rd_buf = htole32( sc->mtd_cdata.mtd_rx_chain[i].sd_map->dm_segs[0].ds_addr + sizeof(u_int64_t)); c->rd_rcw = htole32(ETHER_MAX_DIX_LEN); c->rd_rsr = htole32(RSR_OWN); bus_dmamap_sync(sc->sc_dmat, sc->sc_listmap, offsetof(struct mtd_list_data, mtd_rx_list[i]), sizeof(struct mtd_rx_desc), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } static void mtd_reset(struct mtd_softc *sc) { int i; /* Set software reset bit */ CSR_WRITE_4(MTD_BCR, BCR_SWR); /* * Wait until software reset completed. */ for (i = 0; i < MTD_TIMEOUT; ++i) { DELAY(10); if (!(CSR_READ_4(MTD_BCR) & BCR_SWR)) { /* * Wait a little while for the chip to get * its brains in order. */ DELAY(1000); return; } } /* Reset timed out. */ printf("%s: reset never completed!\n", sc->sc_dev.dv_xname); } static int mtd_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct mtd_softc *sc = ifp->if_softc; struct ifaddr *ifa = (struct ifaddr *)data; struct ifreq *ifr = (struct ifreq *)data; int s, error = 0; s = splnet(); switch (command) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; mtd_init(ifp); switch (ifa->ifa_addr->sa_family) { case AF_INET: arp_ifinit(&sc->sc_arpcom, ifa); break; } break; case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) mtd_init(ifp); else { if (ifp->if_flags & IFF_RUNNING) mtd_stop(ifp); } error = 0; break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->sc_mii.mii_media, command); break; default: error = ether_ioctl(ifp, &sc->sc_arpcom, command, data); } if (error == ENETRESET) { if (ifp->if_flags & IFF_RUNNING) mtd_setmulti(sc); error = 0; } splx(s); return (error); } static void mtd_init(struct ifnet *ifp) { struct mtd_softc *sc = ifp->if_softc; int s; s = splnet(); /* * Cancel pending I/O and free all RX/TX buffers. */ mtd_stop(ifp); /* * Reset the chip to a known state. */ mtd_reset(sc); /* * Set cache alignment and burst length. */ CSR_WRITE_4(MTD_BCR, BCR_PBL8); CSR_WRITE_4(MTD_TCRRCR, TCR_TFTSF | RCR_RBLEN | RCR_RPBL512); if (sc->sc_devid == PCI_PRODUCT_MYSON_MTD891) { CSR_SETBIT(MTD_BCR, BCR_PROG); CSR_SETBIT(MTD_TCRRCR, TCR_ENHANCED); } if (ifp->if_flags & IFF_PROMISC) CSR_SETBIT(MTD_TCRRCR, RCR_PROM); else CSR_CLRBIT(MTD_TCRRCR, RCR_PROM); if (ifp->if_flags & IFF_BROADCAST) CSR_SETBIT(MTD_TCRRCR, RCR_AB); else CSR_CLRBIT(MTD_TCRRCR, RCR_AB); mtd_setmulti(sc); if (mtd_list_rx_init(sc)) { printf("%s: can't allocate memeory for rx buffers\n", sc->sc_dev.dv_xname); splx(s); return; } mtd_list_tx_init(sc); CSR_WRITE_4(MTD_RXLBA, sc->sc_listmap->dm_segs[0].ds_addr + offsetof(struct mtd_list_data, mtd_rx_list[0])); CSR_WRITE_4(MTD_TXLBA, sc->sc_listmap->dm_segs[0].ds_addr + offsetof(struct mtd_list_data, mtd_tx_list[0])); /* * Enable interrupts. */ CSR_WRITE_4(MTD_IMR, IMR_INTRS); CSR_WRITE_4(MTD_ISR, 0xffffffff); /* Enable receiver and transmitter */ CSR_SETBIT(MTD_TCRRCR, TCR_TE | RCR_RE); CSR_WRITE_4(MTD_RXPDR, 0xffffffff); ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; splx(s); } /* * Main transmit routine. To avoid having to do mbuf copies, we put pointers * to the mbuf data regions directly in the transmit lists. We also save a * copy of the pointers since the transmit list fragment pointers are * physical addresses. */ static void mtd_start(struct ifnet *ifp) { struct mtd_softc *sc = ifp->if_softc; struct mbuf *m_head = NULL; int idx; if (sc->mtd_cdata.mtd_tx_cnt) { ifp->if_flags |= IFF_OACTIVE; return; } idx = sc->mtd_cdata.mtd_tx_prod; while (sc->mtd_cdata.mtd_tx_chain[idx].sd_mbuf == NULL) { IFQ_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; if (mtd_encap(sc, m_head, &idx)) { ifp->if_flags |= IFF_OACTIVE; break; } /* * If there's a BPF listener, bounce a copy of this frame * to him. */ #if NBPFILTER > 0 if (ifp->if_bpf != NULL) bpf_mtap(ifp->if_bpf, m_head, BPF_DIRECTION_OUT); #endif } if (idx == sc->mtd_cdata.mtd_tx_prod) return; /* Transmit */ sc->mtd_cdata.mtd_tx_prod = idx; CSR_WRITE_4(MTD_TXPDR, 0xffffffff); /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; } static void mtd_stop(struct ifnet *ifp) { struct mtd_softc *sc = ifp->if_softc; int i; ifp->if_timer = 0; ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); CSR_CLRBIT(MTD_TCRRCR, (RCR_RE | TCR_TE)); CSR_WRITE_4(MTD_IMR, 0); CSR_WRITE_4(MTD_TXLBA, 0); CSR_WRITE_4(MTD_RXLBA, 0); /* * Free data in the RX lists. */ for (i = 0; i < MTD_RX_LIST_CNT; i++) { if (sc->mtd_cdata.mtd_rx_chain[i].sd_map->dm_nsegs != 0) { bus_dmamap_t map = sc->mtd_cdata.mtd_rx_chain[i].sd_map; bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_dmat, map); } if (sc->mtd_cdata.mtd_rx_chain[i].sd_mbuf != NULL) { m_freem(sc->mtd_cdata.mtd_rx_chain[i].sd_mbuf); sc->mtd_cdata.mtd_rx_chain[i].sd_mbuf = NULL; } } bzero(&sc->mtd_ldata->mtd_rx_list, sizeof(sc->mtd_ldata->mtd_rx_list)); /* * Free the TX list buffers. */ for (i = 0; i < MTD_TX_LIST_CNT; i++) { if (sc->mtd_cdata.mtd_tx_chain[i].sd_map->dm_nsegs != 0) { bus_dmamap_t map = sc->mtd_cdata.mtd_tx_chain[i].sd_map; bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, map); } if (sc->mtd_cdata.mtd_tx_chain[i].sd_mbuf != NULL) { m_freem(sc->mtd_cdata.mtd_tx_chain[i].sd_mbuf); sc->mtd_cdata.mtd_tx_chain[i].sd_mbuf = NULL; } } bzero(&sc->mtd_ldata->mtd_tx_list, sizeof(sc->mtd_ldata->mtd_tx_list)); } static void mtd_watchdog(struct ifnet *ifp) { struct mtd_softc *sc = ifp->if_softc; ifp->if_oerrors++; printf("%s: watchdog timeout\n", sc->sc_dev.dv_xname); mtd_init(ifp); if (!IFQ_IS_EMPTY(&ifp->if_snd)) mtd_start(ifp); } int mtd_intr(void *xsc) { struct mtd_softc *sc = xsc; struct ifnet *ifp = &sc->sc_arpcom.ac_if; u_int32_t status; int claimed = 0; /* Suppress unwanted interrupts */ if (!(ifp->if_flags & IFF_RUNNING)) { if (CSR_READ_4(MTD_ISR) & ISR_INTRS) mtd_stop(ifp); return (claimed); } /* Disable interrupts. */ CSR_WRITE_4(MTD_IMR, 0); while((status = CSR_READ_4(MTD_ISR)) & ISR_INTRS) { claimed = 1; CSR_WRITE_4(MTD_ISR, status); /* RX interrupt. */ if (status & ISR_RI) { if (mtd_rxeof(sc) == 0) while(mtd_rx_resync(sc)) mtd_rxeof(sc); } /* RX error interrupt. */ if (status & (ISR_RXERI | ISR_RBU)) ifp->if_ierrors++; /* TX interrupt. */ if (status & (ISR_TI | ISR_ETI | ISR_TBU)) mtd_txeof(sc); /* Fatal bus error interrupt. */ if (status & ISR_FBE) { mtd_reset(sc); mtd_start(ifp); } } /* Re-enable interrupts. */ CSR_WRITE_4(MTD_IMR, IMR_INTRS); if (!IFQ_IS_EMPTY(&ifp->if_snd)) mtd_start(ifp); return (claimed); } /* * A frame has been uploaded: pass the resulting mbuf chain up to * the higher level protocols. */ static int mtd_rxeof(struct mtd_softc *sc) { struct mbuf_list ml = MBUF_LIST_INITIALIZER(); struct mbuf *m; struct ifnet *ifp; struct mtd_rx_desc *cur_rx; int i, total_len = 0, consumed = 0; u_int32_t rxstat; ifp = &sc->sc_arpcom.ac_if; i = sc->mtd_cdata.mtd_rx_prod; while(!(sc->mtd_ldata->mtd_rx_list[i].rd_rsr & htole32(RSR_OWN))) { struct mbuf *m0 = NULL; bus_dmamap_sync(sc->sc_dmat, sc->sc_listmap, offsetof(struct mtd_list_data, mtd_rx_list[i]), sizeof(struct mtd_rx_desc), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); cur_rx = &sc->mtd_ldata->mtd_rx_list[i]; rxstat = letoh32(cur_rx->rd_rsr); m = sc->mtd_cdata.mtd_rx_chain[i].sd_mbuf; total_len = RSR_FLNG_GET(rxstat); sc->mtd_cdata.mtd_rx_chain[i].sd_mbuf = NULL; /* * If an error occurs, update stats, clear the * status word and leave the mbuf cluster in place: * it should simply get re-used next time this descriptor * comes up in the ring. */ if (rxstat & RSR_RXER) { ifp->if_ierrors++; mtd_newbuf(sc, i, m); if (rxstat & RSR_CRC) { i = (i + 1) % MTD_RX_LIST_CNT; continue; } else { mtd_init(ifp); break; } } /* No errors; receive the packet. */ total_len -= ETHER_CRC_LEN; bus_dmamap_sync(sc->sc_dmat, sc->mtd_cdata.mtd_rx_chain[i].sd_map, 0, sc->mtd_cdata.mtd_rx_chain[i].sd_map->dm_mapsize, BUS_DMASYNC_POSTREAD); m0 = m_devget(mtod(m, char *), total_len, ETHER_ALIGN); mtd_newbuf(sc, i, m); i = (i + 1) % MTD_RX_LIST_CNT; if (m0 == NULL) { ifp->if_ierrors++; continue; } m = m0; consumed++; ml_enqueue(&ml, m); } if_input(ifp, &ml); sc->mtd_cdata.mtd_rx_prod = i; return (consumed); } /* * This routine searches the RX ring for dirty descriptors in the * event that the rxeof routine falls out of sync with the chip's * current descriptor pointer. This may happen sometimes as a result * of a "no RX buffer available" condition that happens when the chip * consumes all of the RX buffers before the driver has a chance to * process the RX ring. This routine may need to be called more than * once to bring the driver back in sync with the chip, however we * should still be getting RX DONE interrupts to drive the search * for new packets in the RX ring, so we should catch up eventually. */ static int mtd_rx_resync(sc) struct mtd_softc *sc; { int i, pos; struct mtd_rx_desc *cur_rx; pos = sc->mtd_cdata.mtd_rx_prod; for (i = 0; i < MTD_RX_LIST_CNT; i++) { bus_dmamap_sync(sc->sc_dmat, sc->sc_listmap, offsetof(struct mtd_list_data, mtd_rx_list[pos]), sizeof(struct mtd_rx_desc), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); cur_rx = &sc->mtd_ldata->mtd_rx_list[pos]; if (!(cur_rx->rd_rsr & htole32(RSR_OWN))) break; pos = (pos + 1) % MTD_RX_LIST_CNT; } /* If the ring really is empty, then just return. */ if (i == MTD_RX_LIST_CNT) return (0); /* We've fallen behind the chip: catch it. */ sc->mtd_cdata.mtd_rx_prod = pos; return (EAGAIN); } /* * A frame was downloaded to the chip. It's safe for us to clean up * the list buffers. */ static void mtd_txeof(struct mtd_softc *sc) { struct mtd_tx_desc *cur_tx = NULL; struct ifnet *ifp = &sc->sc_arpcom.ac_if; int idx; /* Clear the timeout timer. */ ifp->if_timer = 0; /* * Go through our tx list and free mbufs for those * frames that have been transmitted. */ idx = sc->mtd_cdata.mtd_tx_cons; while(idx != sc->mtd_cdata.mtd_tx_prod) { u_int32_t txstat; bus_dmamap_sync(sc->sc_dmat, sc->sc_listmap, offsetof(struct mtd_list_data, mtd_tx_list[idx]), sizeof(struct mtd_tx_desc), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); cur_tx = &sc->mtd_ldata->mtd_tx_list[idx]; txstat = letoh32(cur_tx->td_tsw); if (txstat & TSW_OWN || txstat == TSW_UNSENT) break; if (!(cur_tx->td_tcw & htole32(TCW_LD))) { sc->mtd_cdata.mtd_tx_cnt--; idx = (idx + 1) % MTD_TX_LIST_CNT; continue; } if (CSR_READ_4(MTD_TCRRCR) & TCR_ENHANCED) ifp->if_collisions += TSR_NCR_GET(CSR_READ_4(MTD_TSR)); else { if (txstat & TSW_TXERR) { ifp->if_oerrors++; if (txstat & TSW_EC) ifp->if_collisions++; if (txstat & TSW_LC) ifp->if_collisions++; } ifp->if_collisions += TSW_NCR_GET(txstat); } ifp->if_opackets++; if (sc->mtd_cdata.mtd_tx_chain[idx].sd_map->dm_nsegs != 0) { bus_dmamap_t map = sc->mtd_cdata.mtd_tx_chain[idx].sd_map; bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, map); } if (sc->mtd_cdata.mtd_tx_chain[idx].sd_mbuf != NULL) { m_freem(sc->mtd_cdata.mtd_tx_chain[idx].sd_mbuf); sc->mtd_cdata.mtd_tx_chain[idx].sd_mbuf = NULL; } sc->mtd_cdata.mtd_tx_cnt--; idx = (idx + 1) % MTD_TX_LIST_CNT; } if (cur_tx != NULL) { ifp->if_flags &= ~IFF_OACTIVE; sc->mtd_cdata.mtd_tx_cons = idx; } else if (sc->mtd_ldata->mtd_tx_list[idx].td_tsw == htole32(TSW_UNSENT)) { sc->mtd_ldata->mtd_tx_list[idx].td_tsw = htole32(TSW_OWN); ifp->if_timer = 5; CSR_WRITE_4(MTD_TXPDR, 0xffffffff); } } struct cfdriver mtd_cd = { 0, "mtd", DV_IFNET };