/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. * Copyright (C) 2009-2015 Semihalf * Copyright (C) 2015 Stormshield * All rights reserved. * * Developed by Semihalf. * * 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. Neither the name of MARVELL nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY 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 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "miibus_if.h" #include "mdio_if.h" #define MGE_DELAY(x) pause("SMI access sleep", (x) / tick_sbt) static int mge_probe(device_t dev); static int mge_attach(device_t dev); static int mge_detach(device_t dev); static int mge_shutdown(device_t dev); static int mge_suspend(device_t dev); static int mge_resume(device_t dev); static int mge_miibus_readreg(device_t dev, int phy, int reg); static int mge_miibus_writereg(device_t dev, int phy, int reg, int value); static int mge_mdio_readreg(device_t dev, int phy, int reg); static int mge_mdio_writereg(device_t dev, int phy, int reg, int value); static int mge_ifmedia_upd(if_t ifp); static void mge_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr); static void mge_init(void *arg); static void mge_init_locked(void *arg); static void mge_start(if_t ifp); static void mge_start_locked(if_t ifp); static void mge_watchdog(struct mge_softc *sc); static int mge_ioctl(if_t ifp, u_long command, caddr_t data); static uint32_t mge_tfut_ipg(uint32_t val, int ver); static uint32_t mge_rx_ipg(uint32_t val, int ver); static void mge_ver_params(struct mge_softc *sc); static void mge_intrs_ctrl(struct mge_softc *sc, int enable); static void mge_intr_rxtx(void *arg); static void mge_intr_rx(void *arg); static void mge_intr_rx_check(struct mge_softc *sc, uint32_t int_cause, uint32_t int_cause_ext); static int mge_intr_rx_locked(struct mge_softc *sc, int count); static void mge_intr_tx(void *arg); static void mge_intr_tx_locked(struct mge_softc *sc); static void mge_intr_misc(void *arg); static void mge_intr_sum(void *arg); static void mge_intr_err(void *arg); static void mge_stop(struct mge_softc *sc); static void mge_tick(void *msc); static uint32_t mge_set_port_serial_control(uint32_t media); static void mge_get_mac_address(struct mge_softc *sc, uint8_t *addr); static void mge_set_mac_address(struct mge_softc *sc); static void mge_set_ucast_address(struct mge_softc *sc, uint8_t last_byte, uint8_t queue); static void mge_set_prom_mode(struct mge_softc *sc, uint8_t queue); static int mge_allocate_dma(struct mge_softc *sc); static int mge_alloc_desc_dma(struct mge_softc *sc, struct mge_desc_wrapper* desc_tab, uint32_t size, bus_dma_tag_t *buffer_tag); static int mge_new_rxbuf(bus_dma_tag_t tag, bus_dmamap_t map, struct mbuf **mbufp, bus_addr_t *paddr); static void mge_get_dma_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error); static void mge_free_dma(struct mge_softc *sc); static void mge_free_desc(struct mge_softc *sc, struct mge_desc_wrapper* tab, uint32_t size, bus_dma_tag_t buffer_tag, uint8_t free_mbufs); static void mge_offload_process_frame(if_t ifp, struct mbuf *frame, uint32_t status, uint16_t bufsize); static void mge_offload_setup_descriptor(struct mge_softc *sc, struct mge_desc_wrapper *dw); static uint8_t mge_crc8(uint8_t *data, int size); static void mge_setup_multicast(struct mge_softc *sc); static void mge_set_rxic(struct mge_softc *sc); static void mge_set_txic(struct mge_softc *sc); static void mge_add_sysctls(struct mge_softc *sc); static int mge_sysctl_ic(SYSCTL_HANDLER_ARGS); static device_method_t mge_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mge_probe), DEVMETHOD(device_attach, mge_attach), DEVMETHOD(device_detach, mge_detach), DEVMETHOD(device_shutdown, mge_shutdown), DEVMETHOD(device_suspend, mge_suspend), DEVMETHOD(device_resume, mge_resume), /* MII interface */ DEVMETHOD(miibus_readreg, mge_miibus_readreg), DEVMETHOD(miibus_writereg, mge_miibus_writereg), /* MDIO interface */ DEVMETHOD(mdio_readreg, mge_mdio_readreg), DEVMETHOD(mdio_writereg, mge_mdio_writereg), { 0, 0 } }; DEFINE_CLASS_0(mge, mge_driver, mge_methods, sizeof(struct mge_softc)); static int switch_attached = 0; DRIVER_MODULE(mge, simplebus, mge_driver, 0, 0); DRIVER_MODULE(miibus, mge, miibus_driver, 0, 0); DRIVER_MODULE(mdio, mge, mdio_driver, 0, 0); MODULE_DEPEND(mge, ether, 1, 1, 1); MODULE_DEPEND(mge, miibus, 1, 1, 1); MODULE_DEPEND(mge, mdio, 1, 1, 1); static struct resource_spec res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_IRQ, 1, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_IRQ, 2, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; static struct { driver_intr_t *handler; char * description; } mge_intrs[MGE_INTR_COUNT + 1] = { { mge_intr_rxtx,"GbE aggregated interrupt" }, { mge_intr_rx, "GbE receive interrupt" }, { mge_intr_tx, "GbE transmit interrupt" }, { mge_intr_misc,"GbE misc interrupt" }, { mge_intr_sum, "GbE summary interrupt" }, { mge_intr_err, "GbE error interrupt" }, }; /* SMI access interlock */ static struct sx sx_smi; static uint32_t mv_read_ge_smi(device_t dev, int phy, int reg) { uint32_t timeout; uint32_t ret; struct mge_softc *sc; sc = device_get_softc(dev); KASSERT(sc != NULL, ("NULL softc ptr!")); timeout = MGE_SMI_WRITE_RETRIES; MGE_SMI_LOCK(); while (--timeout && (MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_BUSY)) MGE_DELAY(MGE_SMI_WRITE_DELAY); if (timeout == 0) { device_printf(dev, "SMI write timeout.\n"); ret = ~0U; goto out; } MGE_WRITE(sc, MGE_REG_SMI, MGE_SMI_MASK & (MGE_SMI_READ | (reg << 21) | (phy << 16))); /* Wait till finished. */ timeout = MGE_SMI_WRITE_RETRIES; while (--timeout && !((MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_READVALID))) MGE_DELAY(MGE_SMI_WRITE_DELAY); if (timeout == 0) { device_printf(dev, "SMI write validation timeout.\n"); ret = ~0U; goto out; } /* Wait for the data to update in the SMI register */ MGE_DELAY(MGE_SMI_DELAY); ret = MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_DATA_MASK; out: MGE_SMI_UNLOCK(); return (ret); } static void mv_write_ge_smi(device_t dev, int phy, int reg, uint32_t value) { uint32_t timeout; struct mge_softc *sc; sc = device_get_softc(dev); KASSERT(sc != NULL, ("NULL softc ptr!")); MGE_SMI_LOCK(); timeout = MGE_SMI_READ_RETRIES; while (--timeout && (MGE_READ(sc, MGE_REG_SMI) & MGE_SMI_BUSY)) MGE_DELAY(MGE_SMI_READ_DELAY); if (timeout == 0) { device_printf(dev, "SMI read timeout.\n"); goto out; } MGE_WRITE(sc, MGE_REG_SMI, MGE_SMI_MASK & (MGE_SMI_WRITE | (reg << 21) | (phy << 16) | (value & MGE_SMI_DATA_MASK))); out: MGE_SMI_UNLOCK(); } static int mv_read_ext_phy(device_t dev, int phy, int reg) { uint32_t retries; struct mge_softc *sc; uint32_t ret; sc = device_get_softc(dev); MGE_SMI_LOCK(); MGE_WRITE(sc->phy_sc, MGE_REG_SMI, MGE_SMI_MASK & (MGE_SMI_READ | (reg << 21) | (phy << 16))); retries = MGE_SMI_READ_RETRIES; while (--retries && !(MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_READVALID)) DELAY(MGE_SMI_READ_DELAY); if (retries == 0) device_printf(dev, "Timeout while reading from PHY\n"); ret = MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_DATA_MASK; MGE_SMI_UNLOCK(); return (ret); } static void mv_write_ext_phy(device_t dev, int phy, int reg, int value) { uint32_t retries; struct mge_softc *sc; sc = device_get_softc(dev); MGE_SMI_LOCK(); MGE_WRITE(sc->phy_sc, MGE_REG_SMI, MGE_SMI_MASK & (MGE_SMI_WRITE | (reg << 21) | (phy << 16) | (value & MGE_SMI_DATA_MASK))); retries = MGE_SMI_WRITE_RETRIES; while (--retries && MGE_READ(sc->phy_sc, MGE_REG_SMI) & MGE_SMI_BUSY) DELAY(MGE_SMI_WRITE_DELAY); if (retries == 0) device_printf(dev, "Timeout while writing to PHY\n"); MGE_SMI_UNLOCK(); } static void mge_get_mac_address(struct mge_softc *sc, uint8_t *addr) { uint32_t mac_l, mac_h; uint8_t lmac[6]; int i, valid; /* * Retrieve hw address from the device tree. */ i = OF_getprop(sc->node, "local-mac-address", (void *)lmac, 6); if (i == 6) { valid = 0; for (i = 0; i < 6; i++) if (lmac[i] != 0) { valid = 1; break; } if (valid) { bcopy(lmac, addr, 6); return; } } /* * Fall back -- use the currently programmed address. */ mac_l = MGE_READ(sc, MGE_MAC_ADDR_L); mac_h = MGE_READ(sc, MGE_MAC_ADDR_H); addr[0] = (mac_h & 0xff000000) >> 24; addr[1] = (mac_h & 0x00ff0000) >> 16; addr[2] = (mac_h & 0x0000ff00) >> 8; addr[3] = (mac_h & 0x000000ff); addr[4] = (mac_l & 0x0000ff00) >> 8; addr[5] = (mac_l & 0x000000ff); } static uint32_t mge_tfut_ipg(uint32_t val, int ver) { switch (ver) { case 1: return ((val & 0x3fff) << 4); case 2: default: return ((val & 0xffff) << 4); } } static uint32_t mge_rx_ipg(uint32_t val, int ver) { switch (ver) { case 1: return ((val & 0x3fff) << 8); case 2: default: return (((val & 0x8000) << 10) | ((val & 0x7fff) << 7)); } } static void mge_ver_params(struct mge_softc *sc) { uint32_t d, r; soc_id(&d, &r); if (d == MV_DEV_88F6281 || d == MV_DEV_88F6781 || d == MV_DEV_88F6282 || d == MV_DEV_MV78100 || d == MV_DEV_MV78100_Z0 || (d & MV_DEV_FAMILY_MASK) == MV_DEV_DISCOVERY) { sc->mge_ver = 2; sc->mge_mtu = 0x4e8; sc->mge_tfut_ipg_max = 0xFFFF; sc->mge_rx_ipg_max = 0xFFFF; sc->mge_tx_arb_cfg = 0xFC0000FF; sc->mge_tx_tok_cfg = 0xFFFF7FFF; sc->mge_tx_tok_cnt = 0x3FFFFFFF; } else { sc->mge_ver = 1; sc->mge_mtu = 0x458; sc->mge_tfut_ipg_max = 0x3FFF; sc->mge_rx_ipg_max = 0x3FFF; sc->mge_tx_arb_cfg = 0x000000FF; sc->mge_tx_tok_cfg = 0x3FFFFFFF; sc->mge_tx_tok_cnt = 0x3FFFFFFF; } if (d == MV_DEV_88RC8180) sc->mge_intr_cnt = 1; else sc->mge_intr_cnt = 2; if (d == MV_DEV_MV78160 || d == MV_DEV_MV78260 || d == MV_DEV_MV78460) sc->mge_hw_csum = 0; else sc->mge_hw_csum = 1; } static void mge_set_mac_address(struct mge_softc *sc) { char *if_mac; uint32_t mac_l, mac_h; MGE_GLOBAL_LOCK_ASSERT(sc); if_mac = (char *)if_getlladdr(sc->ifp); mac_l = (if_mac[4] << 8) | (if_mac[5]); mac_h = (if_mac[0] << 24)| (if_mac[1] << 16) | (if_mac[2] << 8) | (if_mac[3] << 0); MGE_WRITE(sc, MGE_MAC_ADDR_L, mac_l); MGE_WRITE(sc, MGE_MAC_ADDR_H, mac_h); mge_set_ucast_address(sc, if_mac[5], MGE_RX_DEFAULT_QUEUE); } static void mge_set_ucast_address(struct mge_softc *sc, uint8_t last_byte, uint8_t queue) { uint32_t reg_idx, reg_off, reg_val, i; last_byte &= 0xf; reg_idx = last_byte / MGE_UCAST_REG_NUMBER; reg_off = (last_byte % MGE_UCAST_REG_NUMBER) * 8; reg_val = (1 | (queue << 1)) << reg_off; for (i = 0; i < MGE_UCAST_REG_NUMBER; i++) { if ( i == reg_idx) MGE_WRITE(sc, MGE_DA_FILTER_UCAST(i), reg_val); else MGE_WRITE(sc, MGE_DA_FILTER_UCAST(i), 0); } } static void mge_set_prom_mode(struct mge_softc *sc, uint8_t queue) { uint32_t port_config; uint32_t reg_val, i; /* Enable or disable promiscuous mode as needed */ if (if_getflags(sc->ifp) & IFF_PROMISC) { port_config = MGE_READ(sc, MGE_PORT_CONFIG); port_config |= PORT_CONFIG_UPM; MGE_WRITE(sc, MGE_PORT_CONFIG, port_config); reg_val = ((1 | (queue << 1)) | (1 | (queue << 1)) << 8 | (1 | (queue << 1)) << 16 | (1 | (queue << 1)) << 24); for (i = 0; i < MGE_MCAST_REG_NUMBER; i++) { MGE_WRITE(sc, MGE_DA_FILTER_SPEC_MCAST(i), reg_val); MGE_WRITE(sc, MGE_DA_FILTER_OTH_MCAST(i), reg_val); } for (i = 0; i < MGE_UCAST_REG_NUMBER; i++) MGE_WRITE(sc, MGE_DA_FILTER_UCAST(i), reg_val); } else { port_config = MGE_READ(sc, MGE_PORT_CONFIG); port_config &= ~PORT_CONFIG_UPM; MGE_WRITE(sc, MGE_PORT_CONFIG, port_config); for (i = 0; i < MGE_MCAST_REG_NUMBER; i++) { MGE_WRITE(sc, MGE_DA_FILTER_SPEC_MCAST(i), 0); MGE_WRITE(sc, MGE_DA_FILTER_OTH_MCAST(i), 0); } mge_set_mac_address(sc); } } static void mge_get_dma_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error) { u_int32_t *paddr; KASSERT(nseg == 1, ("wrong number of segments, should be 1")); paddr = arg; *paddr = segs->ds_addr; } static int mge_new_rxbuf(bus_dma_tag_t tag, bus_dmamap_t map, struct mbuf **mbufp, bus_addr_t *paddr) { struct mbuf *new_mbuf; bus_dma_segment_t seg[1]; int error; int nsegs; KASSERT(mbufp != NULL, ("NULL mbuf pointer!")); new_mbuf = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (new_mbuf == NULL) return (ENOBUFS); new_mbuf->m_len = new_mbuf->m_pkthdr.len = new_mbuf->m_ext.ext_size; if (*mbufp) { bus_dmamap_sync(tag, map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(tag, map); } error = bus_dmamap_load_mbuf_sg(tag, map, new_mbuf, seg, &nsegs, BUS_DMA_NOWAIT); KASSERT(nsegs == 1, ("Too many segments returned!")); if (nsegs != 1 || error) panic("mge_new_rxbuf(): nsegs(%d), error(%d)", nsegs, error); bus_dmamap_sync(tag, map, BUS_DMASYNC_PREREAD); (*mbufp) = new_mbuf; (*paddr) = seg->ds_addr; return (0); } static int mge_alloc_desc_dma(struct mge_softc *sc, struct mge_desc_wrapper* tab, uint32_t size, bus_dma_tag_t *buffer_tag) { struct mge_desc_wrapper *dw; bus_addr_t desc_paddr; int i, error; desc_paddr = 0; for (i = size - 1; i >= 0; i--) { dw = &(tab[i]); error = bus_dmamem_alloc(sc->mge_desc_dtag, (void**)&(dw->mge_desc), BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, &(dw->desc_dmap)); if (error) { if_printf(sc->ifp, "failed to allocate DMA memory\n"); dw->mge_desc = NULL; return (ENXIO); } error = bus_dmamap_load(sc->mge_desc_dtag, dw->desc_dmap, dw->mge_desc, sizeof(struct mge_desc), mge_get_dma_addr, &(dw->mge_desc_paddr), BUS_DMA_NOWAIT); if (error) { if_printf(sc->ifp, "can't load descriptor\n"); bus_dmamem_free(sc->mge_desc_dtag, dw->mge_desc, dw->desc_dmap); dw->mge_desc = NULL; return (ENXIO); } /* Chain descriptors */ dw->mge_desc->next_desc = desc_paddr; desc_paddr = dw->mge_desc_paddr; } tab[size - 1].mge_desc->next_desc = desc_paddr; /* Allocate a busdma tag for mbufs. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ MCLBYTES, 1, /* maxsize, nsegments */ MCLBYTES, 0, /* maxsegsz, flags */ NULL, NULL, /* lockfunc, lockfuncarg */ buffer_tag); /* dmat */ if (error) { if_printf(sc->ifp, "failed to create busdma tag for mbufs\n"); return (ENXIO); } /* Create TX busdma maps */ for (i = 0; i < size; i++) { dw = &(tab[i]); error = bus_dmamap_create(*buffer_tag, 0, &dw->buffer_dmap); if (error) { if_printf(sc->ifp, "failed to create map for mbuf\n"); return (ENXIO); } dw->buffer = (struct mbuf*)NULL; dw->mge_desc->buffer = (bus_addr_t)NULL; } return (0); } static int mge_allocate_dma(struct mge_softc *sc) { struct mge_desc_wrapper *dw; int i; /* Allocate a busdma tag and DMA safe memory for TX/RX descriptors. */ bus_dma_tag_create(bus_get_dma_tag(sc->dev), /* parent */ 16, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ sizeof(struct mge_desc), 1, /* maxsize, nsegments */ sizeof(struct mge_desc), 0, /* maxsegsz, flags */ NULL, NULL, /* lockfunc, lockfuncarg */ &sc->mge_desc_dtag); /* dmat */ mge_alloc_desc_dma(sc, sc->mge_tx_desc, MGE_TX_DESC_NUM, &sc->mge_tx_dtag); mge_alloc_desc_dma(sc, sc->mge_rx_desc, MGE_RX_DESC_NUM, &sc->mge_rx_dtag); for (i = 0; i < MGE_RX_DESC_NUM; i++) { dw = &(sc->mge_rx_desc[i]); mge_new_rxbuf(sc->mge_rx_dtag, dw->buffer_dmap, &dw->buffer, &dw->mge_desc->buffer); } sc->tx_desc_start = sc->mge_tx_desc[0].mge_desc_paddr; sc->rx_desc_start = sc->mge_rx_desc[0].mge_desc_paddr; return (0); } static void mge_free_desc(struct mge_softc *sc, struct mge_desc_wrapper* tab, uint32_t size, bus_dma_tag_t buffer_tag, uint8_t free_mbufs) { struct mge_desc_wrapper *dw; int i; for (i = 0; i < size; i++) { /* Free RX mbuf */ dw = &(tab[i]); if (dw->buffer_dmap) { if (free_mbufs) { bus_dmamap_sync(buffer_tag, dw->buffer_dmap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(buffer_tag, dw->buffer_dmap); } bus_dmamap_destroy(buffer_tag, dw->buffer_dmap); if (free_mbufs) m_freem(dw->buffer); } /* Free RX descriptors */ if (dw->desc_dmap) { bus_dmamap_sync(sc->mge_desc_dtag, dw->desc_dmap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->mge_desc_dtag, dw->desc_dmap); bus_dmamem_free(sc->mge_desc_dtag, dw->mge_desc, dw->desc_dmap); } } } static void mge_free_dma(struct mge_softc *sc) { /* Free descriptors and mbufs */ mge_free_desc(sc, sc->mge_rx_desc, MGE_RX_DESC_NUM, sc->mge_rx_dtag, 1); mge_free_desc(sc, sc->mge_tx_desc, MGE_TX_DESC_NUM, sc->mge_tx_dtag, 0); /* Destroy mbuf dma tag */ bus_dma_tag_destroy(sc->mge_tx_dtag); bus_dma_tag_destroy(sc->mge_rx_dtag); /* Destroy descriptors tag */ bus_dma_tag_destroy(sc->mge_desc_dtag); } static void mge_reinit_rx(struct mge_softc *sc) { struct mge_desc_wrapper *dw; int i; MGE_RECEIVE_LOCK_ASSERT(sc); mge_free_desc(sc, sc->mge_rx_desc, MGE_RX_DESC_NUM, sc->mge_rx_dtag, 1); mge_alloc_desc_dma(sc, sc->mge_rx_desc, MGE_RX_DESC_NUM, &sc->mge_rx_dtag); for (i = 0; i < MGE_RX_DESC_NUM; i++) { dw = &(sc->mge_rx_desc[i]); mge_new_rxbuf(sc->mge_rx_dtag, dw->buffer_dmap, &dw->buffer, &dw->mge_desc->buffer); } sc->rx_desc_start = sc->mge_rx_desc[0].mge_desc_paddr; sc->rx_desc_curr = 0; MGE_WRITE(sc, MGE_RX_CUR_DESC_PTR(MGE_RX_DEFAULT_QUEUE), sc->rx_desc_start); /* Enable RX queue */ MGE_WRITE(sc, MGE_RX_QUEUE_CMD, MGE_ENABLE_RXQ(MGE_RX_DEFAULT_QUEUE)); } #ifdef DEVICE_POLLING static poll_handler_t mge_poll; static int mge_poll(if_t ifp, enum poll_cmd cmd, int count) { struct mge_softc *sc = if_getsoftc(ifp); uint32_t int_cause, int_cause_ext; int rx_npkts = 0; MGE_RECEIVE_LOCK(sc); if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) { MGE_RECEIVE_UNLOCK(sc); return (rx_npkts); } if (cmd == POLL_AND_CHECK_STATUS) { int_cause = MGE_READ(sc, MGE_PORT_INT_CAUSE); int_cause_ext = MGE_READ(sc, MGE_PORT_INT_CAUSE_EXT); /* Check for resource error */ if (int_cause & MGE_PORT_INT_RXERRQ0) mge_reinit_rx(sc); if (int_cause || int_cause_ext) { MGE_WRITE(sc, MGE_PORT_INT_CAUSE, ~int_cause); MGE_WRITE(sc, MGE_PORT_INT_CAUSE_EXT, ~int_cause_ext); } } rx_npkts = mge_intr_rx_locked(sc, count); MGE_RECEIVE_UNLOCK(sc); MGE_TRANSMIT_LOCK(sc); mge_intr_tx_locked(sc); MGE_TRANSMIT_UNLOCK(sc); return (rx_npkts); } #endif /* DEVICE_POLLING */ static int mge_attach(device_t dev) { struct mge_softc *sc; struct mii_softc *miisc; if_t ifp; uint8_t hwaddr[ETHER_ADDR_LEN]; int i, error, phy; sc = device_get_softc(dev); sc->dev = dev; sc->node = ofw_bus_get_node(dev); phy = 0; if (fdt_get_phyaddr(sc->node, sc->dev, &phy, (void **)&sc->phy_sc) == 0) { device_printf(dev, "PHY%i attached, phy_sc points to %s\n", phy, device_get_nameunit(sc->phy_sc->dev)); sc->phy_attached = 1; } else { device_printf(dev, "PHY not attached.\n"); sc->phy_attached = 0; sc->phy_sc = sc; } if (fdt_find_compatible(sc->node, "mrvl,sw", 1) != 0) { device_printf(dev, "Switch attached.\n"); sc->switch_attached = 1; /* additional variable available across instances */ switch_attached = 1; } else { sc->switch_attached = 0; } if (device_get_unit(dev) == 0) { sx_init(&sx_smi, "mge_tick() SMI access threads interlock"); } /* Set chip version-dependent parameters */ mge_ver_params(sc); /* Initialize mutexes */ mtx_init(&sc->transmit_lock, device_get_nameunit(dev), "mge TX lock", MTX_DEF); mtx_init(&sc->receive_lock, device_get_nameunit(dev), "mge RX lock", MTX_DEF); /* Allocate IO and IRQ resources */ error = bus_alloc_resources(dev, res_spec, sc->res); if (error) { device_printf(dev, "could not allocate resources\n"); mge_detach(dev); return (ENXIO); } /* Allocate DMA, buffers, buffer descriptors */ error = mge_allocate_dma(sc); if (error) { mge_detach(dev); return (ENXIO); } sc->tx_desc_curr = 0; sc->rx_desc_curr = 0; sc->tx_desc_used_idx = 0; sc->tx_desc_used_count = 0; /* Configure defaults for interrupts coalescing */ sc->rx_ic_time = 768; sc->tx_ic_time = 768; mge_add_sysctls(sc); /* Allocate network interface */ ifp = sc->ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "if_alloc() failed\n"); mge_detach(dev); return (ENOMEM); } if_initname(ifp, device_get_name(dev), device_get_unit(dev)); if_setsoftc(ifp, sc); if_setflags(ifp, IFF_SIMPLEX | IFF_MULTICAST | IFF_BROADCAST); if_setcapabilities(ifp, IFCAP_VLAN_MTU); if (sc->mge_hw_csum) { if_setcapabilitiesbit(ifp, IFCAP_HWCSUM, 0); if_sethwassist(ifp, MGE_CHECKSUM_FEATURES); } if_setcapenable(ifp, if_getcapabilities(ifp)); #ifdef DEVICE_POLLING /* Advertise that polling is supported */ if_setcapabilitiesbit(ifp, IFCAP_POLLING, 0); #endif if_setinitfn(ifp, mge_init); if_setstartfn(ifp, mge_start); if_setioctlfn(ifp, mge_ioctl); if_setsendqlen(ifp, MGE_TX_DESC_NUM - 1); if_setsendqready(ifp); mge_get_mac_address(sc, hwaddr); ether_ifattach(ifp, hwaddr); callout_init(&sc->wd_callout, 1); /* Attach PHY(s) */ if (sc->phy_attached) { error = mii_attach(dev, &sc->miibus, ifp, mge_ifmedia_upd, mge_ifmedia_sts, BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); if (error) { device_printf(dev, "MII failed to find PHY\n"); if_free(ifp); sc->ifp = NULL; mge_detach(dev); return (error); } sc->mii = device_get_softc(sc->miibus); /* Tell the MAC where to find the PHY so autoneg works */ miisc = LIST_FIRST(&sc->mii->mii_phys); MGE_WRITE(sc, MGE_REG_PHYDEV, miisc->mii_phy); } else { /* no PHY, so use hard-coded values */ ifmedia_init(&sc->mge_ifmedia, 0, mge_ifmedia_upd, mge_ifmedia_sts); ifmedia_add(&sc->mge_ifmedia, IFM_ETHER | IFM_1000_T | IFM_FDX, 0, NULL); ifmedia_set(&sc->mge_ifmedia, IFM_ETHER | IFM_1000_T | IFM_FDX); } /* Attach interrupt handlers */ /* TODO: review flags, in part. mark RX as INTR_ENTROPY ? */ for (i = 1; i <= sc->mge_intr_cnt; ++i) { error = bus_setup_intr(dev, sc->res[i], INTR_TYPE_NET | INTR_MPSAFE, NULL, *mge_intrs[(sc->mge_intr_cnt == 1 ? 0 : i)].handler, sc, &sc->ih_cookie[i - 1]); if (error) { device_printf(dev, "could not setup %s\n", mge_intrs[(sc->mge_intr_cnt == 1 ? 0 : i)].description); mge_detach(dev); return (error); } } if (sc->switch_attached) { MGE_WRITE(sc, MGE_REG_PHYDEV, MGE_SWITCH_PHYDEV); device_add_child(dev, "mdio", -1); bus_generic_attach(dev); } return (0); } static int mge_detach(device_t dev) { struct mge_softc *sc; int error,i; sc = device_get_softc(dev); /* Stop controller and free TX queue */ if (sc->ifp) mge_shutdown(dev); /* Wait for stopping ticks */ callout_drain(&sc->wd_callout); /* Stop and release all interrupts */ for (i = 0; i < sc->mge_intr_cnt; ++i) { if (!sc->ih_cookie[i]) continue; error = bus_teardown_intr(dev, sc->res[1 + i], sc->ih_cookie[i]); if (error) device_printf(dev, "could not release %s\n", mge_intrs[(sc->mge_intr_cnt == 1 ? 0 : i + 1)].description); } /* Detach network interface */ if (sc->ifp) { ether_ifdetach(sc->ifp); if_free(sc->ifp); } /* Free DMA resources */ mge_free_dma(sc); /* Free IO memory handler */ bus_release_resources(dev, res_spec, sc->res); /* Destroy mutexes */ mtx_destroy(&sc->receive_lock); mtx_destroy(&sc->transmit_lock); if (device_get_unit(dev) == 0) sx_destroy(&sx_smi); return (0); } static void mge_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct mge_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); MGE_GLOBAL_LOCK(sc); if (!sc->phy_attached) { ifmr->ifm_active = IFM_1000_T | IFM_FDX | IFM_ETHER; ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; goto out_unlock; } mii = sc->mii; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; out_unlock: MGE_GLOBAL_UNLOCK(sc); } static uint32_t mge_set_port_serial_control(uint32_t media) { uint32_t port_config; port_config = PORT_SERIAL_RES_BIT9 | PORT_SERIAL_FORCE_LINK_FAIL | PORT_SERIAL_MRU(PORT_SERIAL_MRU_1552); if (IFM_TYPE(media) == IFM_ETHER) { switch(IFM_SUBTYPE(media)) { case IFM_AUTO: break; case IFM_1000_T: port_config |= (PORT_SERIAL_GMII_SPEED_1000 | PORT_SERIAL_AUTONEG | PORT_SERIAL_AUTONEG_FC | PORT_SERIAL_SPEED_AUTONEG); break; case IFM_100_TX: port_config |= (PORT_SERIAL_MII_SPEED_100 | PORT_SERIAL_AUTONEG | PORT_SERIAL_AUTONEG_FC | PORT_SERIAL_SPEED_AUTONEG); break; case IFM_10_T: port_config |= (PORT_SERIAL_AUTONEG | PORT_SERIAL_AUTONEG_FC | PORT_SERIAL_SPEED_AUTONEG); break; } if (media & IFM_FDX) port_config |= PORT_SERIAL_FULL_DUPLEX; } return (port_config); } static int mge_ifmedia_upd(if_t ifp) { struct mge_softc *sc = if_getsoftc(ifp); /* * Do not do anything for switch here, as updating media between * MGE MAC and switch MAC is hardcoded in PCB. Changing it here would * break the link. */ if (sc->phy_attached) { MGE_GLOBAL_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { sc->mge_media_status = sc->mii->mii_media.ifm_media; mii_mediachg(sc->mii); /* MGE MAC needs to be reinitialized. */ mge_init_locked(sc); } MGE_GLOBAL_UNLOCK(sc); } return (0); } static void mge_init(void *arg) { struct mge_softc *sc; sc = arg; MGE_GLOBAL_LOCK(sc); mge_init_locked(arg); MGE_GLOBAL_UNLOCK(sc); } static void mge_init_locked(void *arg) { struct mge_softc *sc = arg; struct mge_desc_wrapper *dw; volatile uint32_t reg_val; int i, count; uint32_t media_status; MGE_GLOBAL_LOCK_ASSERT(sc); /* Stop interface */ mge_stop(sc); /* Disable interrupts */ mge_intrs_ctrl(sc, 0); /* Set MAC address */ mge_set_mac_address(sc); /* Setup multicast filters */ mge_setup_multicast(sc); if (sc->mge_ver == 2) { MGE_WRITE(sc, MGE_PORT_SERIAL_CTRL1, MGE_RGMII_EN); MGE_WRITE(sc, MGE_FIXED_PRIO_CONF, MGE_FIXED_PRIO_EN(0)); } /* Initialize TX queue configuration registers */ MGE_WRITE(sc, MGE_TX_TOKEN_COUNT(0), sc->mge_tx_tok_cnt); MGE_WRITE(sc, MGE_TX_TOKEN_CONF(0), sc->mge_tx_tok_cfg); MGE_WRITE(sc, MGE_TX_ARBITER_CONF(0), sc->mge_tx_arb_cfg); /* Clear TX queue configuration registers for unused queues */ for (i = 1; i < 7; i++) { MGE_WRITE(sc, MGE_TX_TOKEN_COUNT(i), 0); MGE_WRITE(sc, MGE_TX_TOKEN_CONF(i), 0); MGE_WRITE(sc, MGE_TX_ARBITER_CONF(i), 0); } /* Set default MTU */ MGE_WRITE(sc, sc->mge_mtu, 0); /* Port configuration */ MGE_WRITE(sc, MGE_PORT_CONFIG, PORT_CONFIG_RXCS | PORT_CONFIG_DFLT_RXQ(0) | PORT_CONFIG_ARO_RXQ(0)); MGE_WRITE(sc, MGE_PORT_EXT_CONFIG , 0x0); /* Configure promisc mode */ mge_set_prom_mode(sc, MGE_RX_DEFAULT_QUEUE); media_status = sc->mge_media_status; if (sc->switch_attached) { media_status &= ~IFM_TMASK; media_status |= IFM_1000_T; } /* Setup port configuration */ reg_val = mge_set_port_serial_control(media_status); MGE_WRITE(sc, MGE_PORT_SERIAL_CTRL, reg_val); /* Setup SDMA configuration */ MGE_WRITE(sc, MGE_SDMA_CONFIG , MGE_SDMA_RX_BYTE_SWAP | MGE_SDMA_TX_BYTE_SWAP | MGE_SDMA_RX_BURST_SIZE(MGE_SDMA_BURST_16_WORD) | MGE_SDMA_TX_BURST_SIZE(MGE_SDMA_BURST_16_WORD)); MGE_WRITE(sc, MGE_TX_FIFO_URGENT_TRSH, 0x0); MGE_WRITE(sc, MGE_TX_CUR_DESC_PTR, sc->tx_desc_start); MGE_WRITE(sc, MGE_RX_CUR_DESC_PTR(MGE_RX_DEFAULT_QUEUE), sc->rx_desc_start); /* Reset descriptor indexes */ sc->tx_desc_curr = 0; sc->rx_desc_curr = 0; sc->tx_desc_used_idx = 0; sc->tx_desc_used_count = 0; /* Enable RX descriptors */ for (i = 0; i < MGE_RX_DESC_NUM; i++) { dw = &sc->mge_rx_desc[i]; dw->mge_desc->cmd_status = MGE_RX_ENABLE_INT | MGE_DMA_OWNED; dw->mge_desc->buff_size = MCLBYTES; bus_dmamap_sync(sc->mge_desc_dtag, dw->desc_dmap, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } /* Enable RX queue */ MGE_WRITE(sc, MGE_RX_QUEUE_CMD, MGE_ENABLE_RXQ(MGE_RX_DEFAULT_QUEUE)); /* Enable port */ reg_val = MGE_READ(sc, MGE_PORT_SERIAL_CTRL); reg_val |= PORT_SERIAL_ENABLE; MGE_WRITE(sc, MGE_PORT_SERIAL_CTRL, reg_val); count = 0x100000; for (;;) { reg_val = MGE_READ(sc, MGE_PORT_STATUS); if (reg_val & MGE_STATUS_LINKUP) break; DELAY(100); if (--count == 0) { if_printf(sc->ifp, "Timeout on link-up\n"); break; } } /* Setup interrupts coalescing */ mge_set_rxic(sc); mge_set_txic(sc); /* Enable interrupts */ #ifdef DEVICE_POLLING /* * * ...only if polling is not turned on. Disable interrupts explicitly * if polling is enabled. */ if (if_getcapenable(sc->ifp) & IFCAP_POLLING) mge_intrs_ctrl(sc, 0); else #endif /* DEVICE_POLLING */ mge_intrs_ctrl(sc, 1); /* Activate network interface */ if_setdrvflagbits(sc->ifp, IFF_DRV_RUNNING, 0); if_setdrvflagbits(sc->ifp, 0, IFF_DRV_OACTIVE); sc->wd_timer = 0; /* Schedule watchdog timeout */ if (sc->phy_attached) callout_reset(&sc->wd_callout, hz, mge_tick, sc); } static void mge_intr_rxtx(void *arg) { struct mge_softc *sc; uint32_t int_cause, int_cause_ext; sc = arg; MGE_GLOBAL_LOCK(sc); #ifdef DEVICE_POLLING if (if_getcapenable(sc->ifp) & IFCAP_POLLING) { MGE_GLOBAL_UNLOCK(sc); return; } #endif /* Get interrupt cause */ int_cause = MGE_READ(sc, MGE_PORT_INT_CAUSE); int_cause_ext = MGE_READ(sc, MGE_PORT_INT_CAUSE_EXT); /* Check for Transmit interrupt */ if (int_cause_ext & (MGE_PORT_INT_EXT_TXBUF0 | MGE_PORT_INT_EXT_TXUR)) { MGE_WRITE(sc, MGE_PORT_INT_CAUSE_EXT, ~(int_cause_ext & (MGE_PORT_INT_EXT_TXBUF0 | MGE_PORT_INT_EXT_TXUR))); mge_intr_tx_locked(sc); } MGE_TRANSMIT_UNLOCK(sc); /* Check for Receive interrupt */ mge_intr_rx_check(sc, int_cause, int_cause_ext); MGE_RECEIVE_UNLOCK(sc); } static void mge_intr_err(void *arg) { struct mge_softc *sc; if_t ifp; sc = arg; ifp = sc->ifp; if_printf(ifp, "%s\n", __FUNCTION__); } static void mge_intr_misc(void *arg) { struct mge_softc *sc; if_t ifp; sc = arg; ifp = sc->ifp; if_printf(ifp, "%s\n", __FUNCTION__); } static void mge_intr_rx(void *arg) { struct mge_softc *sc; uint32_t int_cause, int_cause_ext; sc = arg; MGE_RECEIVE_LOCK(sc); #ifdef DEVICE_POLLING if (if_getcapenable(sc->ifp) & IFCAP_POLLING) { MGE_RECEIVE_UNLOCK(sc); return; } #endif /* Get interrupt cause */ int_cause = MGE_READ(sc, MGE_PORT_INT_CAUSE); int_cause_ext = MGE_READ(sc, MGE_PORT_INT_CAUSE_EXT); mge_intr_rx_check(sc, int_cause, int_cause_ext); MGE_RECEIVE_UNLOCK(sc); } static void mge_intr_rx_check(struct mge_softc *sc, uint32_t int_cause, uint32_t int_cause_ext) { /* Check for resource error */ if (int_cause & MGE_PORT_INT_RXERRQ0) { mge_reinit_rx(sc); MGE_WRITE(sc, MGE_PORT_INT_CAUSE, ~(int_cause & MGE_PORT_INT_RXERRQ0)); } int_cause &= MGE_PORT_INT_RXQ0; int_cause_ext &= MGE_PORT_INT_EXT_RXOR; if (int_cause || int_cause_ext) { MGE_WRITE(sc, MGE_PORT_INT_CAUSE, ~int_cause); MGE_WRITE(sc, MGE_PORT_INT_CAUSE_EXT, ~int_cause_ext); mge_intr_rx_locked(sc, -1); } } static int mge_intr_rx_locked(struct mge_softc *sc, int count) { if_t ifp = sc->ifp; uint32_t status; uint16_t bufsize; struct mge_desc_wrapper* dw; struct mbuf *mb; int rx_npkts = 0; MGE_RECEIVE_LOCK_ASSERT(sc); while (count != 0) { dw = &sc->mge_rx_desc[sc->rx_desc_curr]; bus_dmamap_sync(sc->mge_desc_dtag, dw->desc_dmap, BUS_DMASYNC_POSTREAD); /* Get status */ status = dw->mge_desc->cmd_status; bufsize = dw->mge_desc->buff_size; if ((status & MGE_DMA_OWNED) != 0) break; if (dw->mge_desc->byte_count && ~(status & MGE_ERR_SUMMARY)) { bus_dmamap_sync(sc->mge_rx_dtag, dw->buffer_dmap, BUS_DMASYNC_POSTREAD); mb = m_devget(dw->buffer->m_data, dw->mge_desc->byte_count - ETHER_CRC_LEN, 0, ifp, NULL); if (mb == NULL) /* Give up if no mbufs */ break; mb->m_len -= 2; mb->m_pkthdr.len -= 2; mb->m_data += 2; mb->m_pkthdr.rcvif = ifp; mge_offload_process_frame(ifp, mb, status, bufsize); MGE_RECEIVE_UNLOCK(sc); if_input(ifp, mb); MGE_RECEIVE_LOCK(sc); rx_npkts++; } dw->mge_desc->byte_count = 0; dw->mge_desc->cmd_status = MGE_RX_ENABLE_INT | MGE_DMA_OWNED; sc->rx_desc_curr = (++sc->rx_desc_curr % MGE_RX_DESC_NUM); bus_dmamap_sync(sc->mge_desc_dtag, dw->desc_dmap, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); if (count > 0) count -= 1; } if_inc_counter(ifp, IFCOUNTER_IPACKETS, rx_npkts); return (rx_npkts); } static void mge_intr_sum(void *arg) { struct mge_softc *sc = arg; if_t ifp; ifp = sc->ifp; if_printf(ifp, "%s\n", __FUNCTION__); } static void mge_intr_tx(void *arg) { struct mge_softc *sc = arg; uint32_t int_cause_ext; MGE_TRANSMIT_LOCK(sc); #ifdef DEVICE_POLLING if (if_getcapenable(sc->ifp) & IFCAP_POLLING) { MGE_TRANSMIT_UNLOCK(sc); return; } #endif /* Ack the interrupt */ int_cause_ext = MGE_READ(sc, MGE_PORT_INT_CAUSE_EXT); MGE_WRITE(sc, MGE_PORT_INT_CAUSE_EXT, ~(int_cause_ext & (MGE_PORT_INT_EXT_TXBUF0 | MGE_PORT_INT_EXT_TXUR))); mge_intr_tx_locked(sc); MGE_TRANSMIT_UNLOCK(sc); } static void mge_intr_tx_locked(struct mge_softc *sc) { if_t ifp = sc->ifp; struct mge_desc_wrapper *dw; struct mge_desc *desc; uint32_t status; int send = 0; MGE_TRANSMIT_LOCK_ASSERT(sc); /* Disable watchdog */ sc->wd_timer = 0; while (sc->tx_desc_used_count) { /* Get the descriptor */ dw = &sc->mge_tx_desc[sc->tx_desc_used_idx]; desc = dw->mge_desc; bus_dmamap_sync(sc->mge_desc_dtag, dw->desc_dmap, BUS_DMASYNC_POSTREAD); /* Get descriptor status */ status = desc->cmd_status; if (status & MGE_DMA_OWNED) break; sc->tx_desc_used_idx = (++sc->tx_desc_used_idx) % MGE_TX_DESC_NUM; sc->tx_desc_used_count--; /* Update collision statistics */ if (status & MGE_ERR_SUMMARY) { if ((status & MGE_ERR_MASK) == MGE_TX_ERROR_LC) if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1); if ((status & MGE_ERR_MASK) == MGE_TX_ERROR_RL) if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 16); } bus_dmamap_sync(sc->mge_tx_dtag, dw->buffer_dmap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mge_tx_dtag, dw->buffer_dmap); m_freem(dw->buffer); dw->buffer = (struct mbuf*)NULL; send++; if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } if (send) { /* Now send anything that was pending */ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); mge_start_locked(ifp); } } static int mge_ioctl(if_t ifp, u_long command, caddr_t data) { struct mge_softc *sc = if_getsoftc(ifp); struct ifreq *ifr = (struct ifreq *)data; int mask, error; uint32_t flags; error = 0; switch (command) { case SIOCSIFFLAGS: MGE_GLOBAL_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { flags = if_getflags(ifp) ^ sc->mge_if_flags; if (flags & IFF_PROMISC) mge_set_prom_mode(sc, MGE_RX_DEFAULT_QUEUE); if (flags & IFF_ALLMULTI) mge_setup_multicast(sc); } else mge_init_locked(sc); } else if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) mge_stop(sc); sc->mge_if_flags = if_getflags(ifp); MGE_GLOBAL_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { MGE_GLOBAL_LOCK(sc); mge_setup_multicast(sc); MGE_GLOBAL_UNLOCK(sc); } break; case SIOCSIFCAP: mask = if_getcapenable(ifp) ^ ifr->ifr_reqcap; if (mask & IFCAP_HWCSUM) { if_setcapenablebit(ifp, 0, IFCAP_HWCSUM); if_setcapenablebit(ifp, IFCAP_HWCSUM & ifr->ifr_reqcap, 0); if (if_getcapenable(ifp) & IFCAP_TXCSUM) if_sethwassist(ifp, MGE_CHECKSUM_FEATURES); else if_sethwassist(ifp, 0); } #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { error = ether_poll_register(mge_poll, ifp); if (error) return(error); MGE_GLOBAL_LOCK(sc); mge_intrs_ctrl(sc, 0); if_setcapenablebit(ifp, IFCAP_POLLING, 0); MGE_GLOBAL_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); MGE_GLOBAL_LOCK(sc); mge_intrs_ctrl(sc, 1); if_setcapenablebit(ifp, 0, IFCAP_POLLING); MGE_GLOBAL_UNLOCK(sc); } } #endif break; case SIOCGIFMEDIA: /* fall through */ case SIOCSIFMEDIA: /* * Setting up media type via ioctls is *not* supported for MAC * which is connected to switch. Use etherswitchcfg. */ if (!sc->phy_attached && (command == SIOCSIFMEDIA)) return (0); else if (!sc->phy_attached) { error = ifmedia_ioctl(ifp, ifr, &sc->mge_ifmedia, command); break; } if (IFM_SUBTYPE(ifr->ifr_media) == IFM_1000_T && !(ifr->ifr_media & IFM_FDX)) { device_printf(sc->dev, "1000baseTX half-duplex unsupported\n"); return 0; } error = ifmedia_ioctl(ifp, ifr, &sc->mii->mii_media, command); break; default: error = ether_ioctl(ifp, command, data); } return (error); } static int mge_miibus_readreg(device_t dev, int phy, int reg) { KASSERT(!switch_attached, ("miibus used with switch attached")); return (mv_read_ext_phy(dev, phy, reg)); } static int mge_miibus_writereg(device_t dev, int phy, int reg, int value) { KASSERT(!switch_attached, ("miibus used with switch attached")); mv_write_ext_phy(dev, phy, reg, value); return (0); } static int mge_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "mrvl,ge")) return (ENXIO); device_set_desc(dev, "Marvell Gigabit Ethernet controller"); return (BUS_PROBE_DEFAULT); } static int mge_resume(device_t dev) { device_printf(dev, "%s\n", __FUNCTION__); return (0); } static int mge_shutdown(device_t dev) { struct mge_softc *sc = device_get_softc(dev); MGE_GLOBAL_LOCK(sc); #ifdef DEVICE_POLLING if (if_getcapenable(sc->ifp) & IFCAP_POLLING) ether_poll_deregister(sc->ifp); #endif mge_stop(sc); MGE_GLOBAL_UNLOCK(sc); return (0); } static int mge_encap(struct mge_softc *sc, struct mbuf *m0) { struct mge_desc_wrapper *dw = NULL; bus_dma_segment_t segs[MGE_TX_DESC_NUM]; bus_dmamap_t mapp; int error; int seg, nsegs; int desc_no; /* Fetch unused map */ desc_no = sc->tx_desc_curr; dw = &sc->mge_tx_desc[desc_no]; mapp = dw->buffer_dmap; /* Create mapping in DMA memory */ error = bus_dmamap_load_mbuf_sg(sc->mge_tx_dtag, mapp, m0, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { m_freem(m0); return (error); } /* Only one segment is supported. */ if (nsegs != 1) { bus_dmamap_unload(sc->mge_tx_dtag, mapp); m_freem(m0); return (-1); } bus_dmamap_sync(sc->mge_tx_dtag, mapp, BUS_DMASYNC_PREWRITE); /* Everything is ok, now we can send buffers */ for (seg = 0; seg < nsegs; seg++) { dw->mge_desc->byte_count = segs[seg].ds_len; dw->mge_desc->buffer = segs[seg].ds_addr; dw->buffer = m0; dw->mge_desc->cmd_status = 0; if (seg == 0) mge_offload_setup_descriptor(sc, dw); dw->mge_desc->cmd_status |= MGE_TX_LAST | MGE_TX_FIRST | MGE_TX_ETH_CRC | MGE_TX_EN_INT | MGE_TX_PADDING | MGE_DMA_OWNED; } bus_dmamap_sync(sc->mge_desc_dtag, dw->desc_dmap, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); sc->tx_desc_curr = (++sc->tx_desc_curr) % MGE_TX_DESC_NUM; sc->tx_desc_used_count++; return (0); } static void mge_tick(void *msc) { struct mge_softc *sc = msc; KASSERT(sc->phy_attached == 1, ("mge_tick while PHY not attached")); MGE_GLOBAL_LOCK(sc); /* Check for TX timeout */ mge_watchdog(sc); mii_tick(sc->mii); /* Check for media type change */ if(sc->mge_media_status != sc->mii->mii_media.ifm_media) mge_ifmedia_upd(sc->ifp); MGE_GLOBAL_UNLOCK(sc); /* Schedule another timeout one second from now */ callout_reset(&sc->wd_callout, hz, mge_tick, sc); return; } static void mge_watchdog(struct mge_softc *sc) { if_t ifp; ifp = sc->ifp; if (sc->wd_timer == 0 || --sc->wd_timer) { return; } if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if_printf(ifp, "watchdog timeout\n"); mge_stop(sc); mge_init_locked(sc); } static void mge_start(if_t ifp) { struct mge_softc *sc = if_getsoftc(ifp); MGE_TRANSMIT_LOCK(sc); mge_start_locked(ifp); MGE_TRANSMIT_UNLOCK(sc); } static void mge_start_locked(if_t ifp) { struct mge_softc *sc; struct mbuf *m0, *mtmp; uint32_t reg_val, queued = 0; sc = if_getsoftc(ifp); MGE_TRANSMIT_LOCK_ASSERT(sc); if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; for (;;) { /* Get packet from the queue */ m0 = if_dequeue(ifp); if (m0 == NULL) break; if (m0->m_pkthdr.csum_flags & (CSUM_IP|CSUM_TCP|CSUM_UDP) || m0->m_flags & M_VLANTAG) { if (M_WRITABLE(m0) == 0) { mtmp = m_dup(m0, M_NOWAIT); m_freem(m0); if (mtmp == NULL) continue; m0 = mtmp; } } /* The driver support only one DMA fragment. */ if (m0->m_next != NULL) { mtmp = m_defrag(m0, M_NOWAIT); if (mtmp != NULL) m0 = mtmp; } /* Check for free descriptors */ if (sc->tx_desc_used_count + 1 >= MGE_TX_DESC_NUM) { if_sendq_prepend(ifp, m0); if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0); break; } if (mge_encap(sc, m0) != 0) break; queued++; BPF_MTAP(ifp, m0); } if (queued) { /* Enable transmitter and watchdog timer */ reg_val = MGE_READ(sc, MGE_TX_QUEUE_CMD); MGE_WRITE(sc, MGE_TX_QUEUE_CMD, reg_val | MGE_ENABLE_TXQ); sc->wd_timer = 5; } } static void mge_stop(struct mge_softc *sc) { if_t ifp; volatile uint32_t reg_val, status; struct mge_desc_wrapper *dw; struct mge_desc *desc; int count; ifp = sc->ifp; if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) return; /* Stop tick engine */ callout_stop(&sc->wd_callout); /* Disable interface */ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)); sc->wd_timer = 0; /* Disable interrupts */ mge_intrs_ctrl(sc, 0); /* Disable Rx and Tx */ reg_val = MGE_READ(sc, MGE_TX_QUEUE_CMD); MGE_WRITE(sc, MGE_TX_QUEUE_CMD, reg_val | MGE_DISABLE_TXQ); MGE_WRITE(sc, MGE_RX_QUEUE_CMD, MGE_DISABLE_RXQ_ALL); /* Remove pending data from TX queue */ while (sc->tx_desc_used_idx != sc->tx_desc_curr && sc->tx_desc_used_count) { /* Get the descriptor */ dw = &sc->mge_tx_desc[sc->tx_desc_used_idx]; desc = dw->mge_desc; bus_dmamap_sync(sc->mge_desc_dtag, dw->desc_dmap, BUS_DMASYNC_POSTREAD); /* Get descriptor status */ status = desc->cmd_status; if (status & MGE_DMA_OWNED) break; sc->tx_desc_used_idx = (++sc->tx_desc_used_idx) % MGE_TX_DESC_NUM; sc->tx_desc_used_count--; bus_dmamap_sync(sc->mge_tx_dtag, dw->buffer_dmap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mge_tx_dtag, dw->buffer_dmap); m_freem(dw->buffer); dw->buffer = (struct mbuf*)NULL; } /* Wait for end of transmission */ count = 0x100000; while (count--) { reg_val = MGE_READ(sc, MGE_PORT_STATUS); if ( !(reg_val & MGE_STATUS_TX_IN_PROG) && (reg_val & MGE_STATUS_TX_FIFO_EMPTY)) break; DELAY(100); } if (count == 0) if_printf(ifp, "%s: timeout while waiting for end of transmission\n", __FUNCTION__); reg_val = MGE_READ(sc, MGE_PORT_SERIAL_CTRL); reg_val &= ~(PORT_SERIAL_ENABLE); MGE_WRITE(sc, MGE_PORT_SERIAL_CTRL ,reg_val); } static int mge_suspend(device_t dev) { device_printf(dev, "%s\n", __FUNCTION__); return (0); } static void mge_offload_process_frame(if_t ifp, struct mbuf *frame, uint32_t status, uint16_t bufsize) { int csum_flags = 0; if (if_getcapenable(ifp) & IFCAP_RXCSUM) { if ((status & MGE_RX_L3_IS_IP) && (status & MGE_RX_IP_OK)) csum_flags |= CSUM_IP_CHECKED | CSUM_IP_VALID; if ((bufsize & MGE_RX_IP_FRAGMENT) == 0 && (MGE_RX_L4_IS_TCP(status) || MGE_RX_L4_IS_UDP(status)) && (status & MGE_RX_L4_CSUM_OK)) { csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; frame->m_pkthdr.csum_data = 0xFFFF; } frame->m_pkthdr.csum_flags = csum_flags; } } static void mge_offload_setup_descriptor(struct mge_softc *sc, struct mge_desc_wrapper *dw) { struct mbuf *m0 = dw->buffer; struct ether_vlan_header *eh = mtod(m0, struct ether_vlan_header *); int csum_flags = m0->m_pkthdr.csum_flags; int cmd_status = 0; struct ip *ip; int ehlen, etype; if (csum_flags != 0) { if (eh->evl_encap_proto == htons(ETHERTYPE_VLAN)) { etype = ntohs(eh->evl_proto); ehlen = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN; csum_flags |= MGE_TX_VLAN_TAGGED; } else { etype = ntohs(eh->evl_encap_proto); ehlen = ETHER_HDR_LEN; } if (etype != ETHERTYPE_IP) { if_printf(sc->ifp, "TCP/IP Offload enabled for unsupported " "protocol!\n"); return; } ip = (struct ip *)(m0->m_data + ehlen); cmd_status |= MGE_TX_IP_HDR_SIZE(ip->ip_hl); cmd_status |= MGE_TX_NOT_FRAGMENT; } if (csum_flags & CSUM_IP) cmd_status |= MGE_TX_GEN_IP_CSUM; if (csum_flags & CSUM_TCP) cmd_status |= MGE_TX_GEN_L4_CSUM; if (csum_flags & CSUM_UDP) cmd_status |= MGE_TX_GEN_L4_CSUM | MGE_TX_UDP; dw->mge_desc->cmd_status |= cmd_status; } static void mge_intrs_ctrl(struct mge_softc *sc, int enable) { if (enable) { MGE_WRITE(sc, MGE_PORT_INT_MASK , MGE_PORT_INT_RXQ0 | MGE_PORT_INT_EXTEND | MGE_PORT_INT_RXERRQ0); MGE_WRITE(sc, MGE_PORT_INT_MASK_EXT , MGE_PORT_INT_EXT_TXERR0 | MGE_PORT_INT_EXT_RXOR | MGE_PORT_INT_EXT_TXUR | MGE_PORT_INT_EXT_TXBUF0); } else { MGE_WRITE(sc, MGE_INT_CAUSE, 0x0); MGE_WRITE(sc, MGE_INT_MASK, 0x0); MGE_WRITE(sc, MGE_PORT_INT_CAUSE, 0x0); MGE_WRITE(sc, MGE_PORT_INT_CAUSE_EXT, 0x0); MGE_WRITE(sc, MGE_PORT_INT_MASK, 0x0); MGE_WRITE(sc, MGE_PORT_INT_MASK_EXT, 0x0); } } static uint8_t mge_crc8(uint8_t *data, int size) { uint8_t crc = 0; static const uint8_t ct[256] = { 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 }; while(size--) crc = ct[crc ^ *(data++)]; return(crc); } struct mge_hash_maddr_ctx { uint32_t smt[MGE_MCAST_REG_NUMBER]; uint32_t omt[MGE_MCAST_REG_NUMBER]; }; static u_int mge_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { static const uint8_t special[5] = { 0x01, 0x00, 0x5E, 0x00, 0x00 }; struct mge_hash_maddr_ctx *ctx = arg; static const uint8_t v = (MGE_RX_DEFAULT_QUEUE << 1) | 1; uint8_t *mac; int i; mac = LLADDR(sdl); if (memcmp(mac, special, sizeof(special)) == 0) { i = mac[5]; ctx->smt[i >> 2] |= v << ((i & 0x03) << 3); } else { i = mge_crc8(mac, ETHER_ADDR_LEN); ctx->omt[i >> 2] |= v << ((i & 0x03) << 3); } return (1); } static void mge_setup_multicast(struct mge_softc *sc) { struct mge_hash_maddr_ctx ctx; if_t ifp = sc->ifp; static const uint8_t v = (MGE_RX_DEFAULT_QUEUE << 1) | 1; int i; if (if_getflags(ifp) & IFF_ALLMULTI) { for (i = 0; i < MGE_MCAST_REG_NUMBER; i++) ctx.smt[i] = ctx.omt[i] = (v << 24) | (v << 16) | (v << 8) | v; } else { memset(&ctx, 0, sizeof(ctx)); if_foreach_llmaddr(ifp, mge_hash_maddr, &ctx); } for (i = 0; i < MGE_MCAST_REG_NUMBER; i++) { MGE_WRITE(sc, MGE_DA_FILTER_SPEC_MCAST(i), ctx.smt[i]); MGE_WRITE(sc, MGE_DA_FILTER_OTH_MCAST(i), ctx.omt[i]); } } static void mge_set_rxic(struct mge_softc *sc) { uint32_t reg; if (sc->rx_ic_time > sc->mge_rx_ipg_max) sc->rx_ic_time = sc->mge_rx_ipg_max; reg = MGE_READ(sc, MGE_SDMA_CONFIG); reg &= ~mge_rx_ipg(sc->mge_rx_ipg_max, sc->mge_ver); reg |= mge_rx_ipg(sc->rx_ic_time, sc->mge_ver); MGE_WRITE(sc, MGE_SDMA_CONFIG, reg); } static void mge_set_txic(struct mge_softc *sc) { uint32_t reg; if (sc->tx_ic_time > sc->mge_tfut_ipg_max) sc->tx_ic_time = sc->mge_tfut_ipg_max; reg = MGE_READ(sc, MGE_TX_FIFO_URGENT_TRSH); reg &= ~mge_tfut_ipg(sc->mge_tfut_ipg_max, sc->mge_ver); reg |= mge_tfut_ipg(sc->tx_ic_time, sc->mge_ver); MGE_WRITE(sc, MGE_TX_FIFO_URGENT_TRSH, reg); } static int mge_sysctl_ic(SYSCTL_HANDLER_ARGS) { struct mge_softc *sc = (struct mge_softc *)arg1; uint32_t time; int error; time = (arg2 == MGE_IC_RX) ? sc->rx_ic_time : sc->tx_ic_time; error = sysctl_handle_int(oidp, &time, 0, req); if (error != 0) return(error); MGE_GLOBAL_LOCK(sc); if (arg2 == MGE_IC_RX) { sc->rx_ic_time = time; mge_set_rxic(sc); } else { sc->tx_ic_time = time; mge_set_txic(sc); } MGE_GLOBAL_UNLOCK(sc); return(0); } static void mge_add_sysctls(struct mge_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *children; struct sysctl_oid *tree; ctx = device_get_sysctl_ctx(sc->dev); children = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)); tree = SYSCTL_ADD_NODE(ctx, children, OID_AUTO, "int_coal", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "MGE Interrupts coalescing"); children = SYSCTL_CHILDREN(tree); SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "rx_time", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, MGE_IC_RX, mge_sysctl_ic, "I", "IC RX time threshold"); SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "tx_time", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, MGE_IC_TX, mge_sysctl_ic, "I", "IC TX time threshold"); } static int mge_mdio_writereg(device_t dev, int phy, int reg, int value) { mv_write_ge_smi(dev, phy, reg, value); return (0); } static int mge_mdio_readreg(device_t dev, int phy, int reg) { int ret; ret = mv_read_ge_smi(dev, phy, reg); return (ret); }