#include #include /* * Copyright 2012 - 2015 Freescale Semiconductor Inc. * Copyright (c) 2016 embedded brains GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Freescale Semiconductor nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * ALTERNATIVELY, this software may be distributed under the terms of the * GNU General Public License ("GPL") as published by the Free Software * Foundation, either version 2 of that License or (at your option) any * later version. * * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``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 Freescale Semiconductor 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 #include "if_fmanmac.h" #include #include #include #include #include #include "../../../../../../../../linux/drivers/net/ethernet/freescale/dpaa/dpaa_eth.h" #define FMAN_MAC_LOCK(sc) mtx_lock(&(sc)->mtx) #define FMAN_MAC_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define FMAN_MAC_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define FMAN_MAC_CSUM (CSUM_IP | CSUM_TCP | CSUM_UDP | CSUM_TCP_IPV6 | \ CSUM_UDP_IPV6) struct fman_mac_sgt { char priv[DPAA_TX_PRIV_DATA_SIZE]; struct fman_prs_result prs; struct qm_sg_entry sg[DPAA_SGT_MAX_ENTRIES]; struct mbuf *m; }; static uma_zone_t fman_mac_sgt_zone; static void fman_mac_sgt_init(void) { fman_mac_sgt_zone = uma_zcreate("FMan MAC SGT", sizeof(struct fman_mac_sgt), NULL, NULL, NULL, NULL, 16, 0); if (fman_mac_sgt_zone == NULL) { panic("Cannot create FMan MAC SGT zone"); } } SYSINIT(fman_mac_sgt, SI_SUB_MBUF, SI_ORDER_ANY, fman_mac_sgt_init, NULL); static void fman_mac_enable_tx_csum(struct mbuf *m, struct qm_fd *fd, struct fman_prs_result *prs) { int csum_flags = m->m_pkthdr.csum_flags; if ((csum_flags & FMAN_MAC_CSUM) == 0) { return; } memset(prs, 0, sizeof(*prs)); if ((csum_flags & FMAN_MAC_CSUM) == CSUM_IP) { prs->l3r = FM_L3_PARSE_RESULT_IPV4; } else if ((csum_flags & CSUM_TCP) != 0) { prs->l3r = FM_L3_PARSE_RESULT_IPV4; prs->l4r = FM_L4_PARSE_RESULT_TCP; } else if ((csum_flags & CSUM_UDP) != 0) { prs->l3r = FM_L3_PARSE_RESULT_IPV4; prs->l4r = FM_L4_PARSE_RESULT_UDP; } else if ((csum_flags & CSUM_TCP_IPV6) != 0) { prs->l3r = FM_L3_PARSE_RESULT_IPV6; prs->l4r = FM_L4_PARSE_RESULT_TCP; } else if ((csum_flags & CSUM_UDP_IPV6) != 0) { prs->l3r = FM_L3_PARSE_RESULT_IPV6; prs->l4r = FM_L4_PARSE_RESULT_UDP; } else { BSD_ASSERT(0); } /* FIXME: VLAN */ prs->ip_off[0] = (u8)sizeof(struct ether_header); prs->l4_off = (u8)(sizeof(struct ether_header) + sizeof(struct ip)); fd->cmd |= FM_FD_CMD_RPD | FM_FD_CMD_DTC; } static void fman_mac_txstart_locked(struct ifnet *ifp, struct fman_mac_softc *sc) { FMAN_MAC_ASSERT_LOCKED(sc); for (;;) { struct fman_mac_sgt *sgt; struct mbuf *m; struct mbuf *n; struct qm_fd fd; struct dpaa_priv *priv; struct qman_fq *egress_fq; int queue = 0; size_t i; int err; IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) { break; } sgt = uma_zalloc(fman_mac_sgt_zone, M_NOWAIT); if (sgt == NULL) { if_inc_counter(ifp, IFCOUNTER_OQDROPS, 1); m_freem(m); continue; } qm_fd_clear_fd(&fd); qm_fd_set_sg(&fd, offsetof(struct fman_mac_sgt, sg), m->m_pkthdr.len); fd.bpid = FSL_DPAA_BPID_INV; fd.cmd |= cpu_to_be32(FM_FD_CMD_FCO); qm_fd_addr_set64(&fd, (uintptr_t)sgt); fman_mac_enable_tx_csum(m, &fd, &sgt->prs); repeat_with_collapsed_mbuf_chain: i = 0; n = m; while (n != NULL && i < DPAA_SGT_MAX_ENTRIES) { int len = n->m_len; if (len > 0) { qm_sg_entry_set_len(&sgt->sg[i], len); sgt->sg[i].bpid = FSL_DPAA_BPID_INV; sgt->sg[i].offset = 0; qm_sg_entry_set64(&sgt->sg[i], mtod(n, uintptr_t)); ++i; } n = n->m_next; } if (n != NULL && i == DPAA_SGT_MAX_ENTRIES) { struct mbuf *c; c = m_collapse(m, M_NOWAIT, DPAA_SGT_MAX_ENTRIES); if (c == NULL) { if_inc_counter(ifp, IFCOUNTER_OQDROPS, 1); m_freem(m); uma_zfree(fman_mac_sgt_zone, sgt); continue; } m = c; goto repeat_with_collapsed_mbuf_chain; } sgt->sg[i - 1].cfg |= cpu_to_be32(QM_SG_FIN); sgt->m = m; priv = netdev_priv(&sc->mac_dev.net_dev); egress_fq = priv->egress_fqs[queue]; fd.cmd |= cpu_to_be32(qman_fq_fqid(priv->conf_fqs[queue])); for (i = 0; i < DPAA_ENQUEUE_RETRIES; ++i) { err = qman_enqueue(egress_fq, &fd); if (err != -EBUSY) { break; } } if (unlikely(err < 0)) { if_inc_counter(ifp, IFCOUNTER_OQDROPS, 1); m_freem(m); continue; } } } static void fman_mac_txstart(struct ifnet *ifp) { struct fman_mac_softc *sc; sc = ifp->if_softc; FMAN_MAC_LOCK(sc); fman_mac_txstart_locked(ifp, sc); FMAN_MAC_UNLOCK(sc); } static void fman_mac_tick(void *arg) { struct fman_mac_softc *sc; struct ifnet *ifp; sc = arg; ifp = sc->ifp; FMAN_MAC_ASSERT_LOCKED(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { return; } mii_tick(sc->mii_softc); callout_reset(&sc->fman_mac_callout, hz, fman_mac_tick, sc); } static void fman_mac_set_multi(struct fman_mac_softc *sc) { struct mac_device *mac_dev; FMAN_MAC_ASSERT_LOCKED(sc); mac_dev = &sc->mac_dev; (*mac_dev->set_multi)(&mac_dev->net_dev, mac_dev); } static void fman_mac_set_promisc(struct fman_mac_softc *sc, int if_flags) { struct mac_device *mac_dev; FMAN_MAC_ASSERT_LOCKED(sc); mac_dev = &sc->mac_dev; (*mac_dev->set_promisc)(mac_dev->fman_mac, (if_flags & IFF_PROMISC) != 0); } static int fman_mac_set_mtu(struct fman_mac_softc *sc, int mtu) { struct ifnet *ifp; int real_mtu; ifp = sc->ifp; real_mtu = mtu + ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN; if (real_mtu > fman_get_max_frm() || real_mtu < ETHER_MIN_LEN) { return (EINVAL); } ifp->if_mtu = mtu; return (0); } static void fman_mac_init_locked(struct fman_mac_softc *sc) { struct ifnet *ifp; int error; FMAN_MAC_ASSERT_LOCKED(sc); ifp = sc->ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { return; } ifp->if_drv_flags |= IFF_DRV_RUNNING; error = dpa_eth_priv_start(&sc->mac_dev.net_dev); BSD_ASSERT(error == 0); if (sc->mii_softc != NULL) { mii_mediachg(sc->mii_softc); callout_reset(&sc->fman_mac_callout, hz, fman_mac_tick, sc); } fman_mac_set_multi(sc); } static void fman_mac_stop_locked(struct fman_mac_softc *sc) { struct ifnet *ifp; int error; FMAN_MAC_ASSERT_LOCKED(sc); ifp = sc->ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); error = dpa_eth_priv_stop(&sc->mac_dev.net_dev); BSD_ASSERT(error == 0); } static void fman_mac_init(void *if_softc) { struct fman_mac_softc *sc; sc = if_softc; FMAN_MAC_LOCK(sc); fman_mac_init_locked(sc); FMAN_MAC_UNLOCK(sc); } static int fman_mac_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct fman_mac_softc *sc; struct mii_data *mii; struct ifreq *ifr; int error; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFFLAGS: FMAN_MAC_LOCK(sc); if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { if ((ifp->if_flags ^ sc->if_flags) & IFF_PROMISC) fman_mac_set_promisc(sc, ifp->if_flags); } else { fman_mac_init_locked(sc); } } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { fman_mac_stop_locked(sc); } } sc->if_flags = ifp->if_flags; FMAN_MAC_UNLOCK(sc); break; case SIOCSIFMTU: error = fman_mac_set_mtu(sc, ifr->ifr_mtu); break; case SIOCADDMULTI: case SIOCDELMULTI: if (ifp->if_drv_flags & IFF_DRV_RUNNING) { FMAN_MAC_LOCK(sc); fman_mac_set_multi(sc); FMAN_MAC_UNLOCK(sc); } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = sc->mii_softc; if (mii != NULL) { error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); } else { error = EINVAL; } break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static int fman_mac_media_change(struct ifnet *ifp) { struct fman_mac_softc *sc; int error; sc = ifp->if_softc; FMAN_MAC_LOCK(sc); error = mii_mediachg(sc->mii_softc); FMAN_MAC_UNLOCK(sc); return (error); } static void fman_mac_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) { struct fman_mac_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = sc->mii_softc; FMAN_MAC_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; FMAN_MAC_UNLOCK(sc); } int fman_mac_dev_attach(device_t dev) { struct fman_mac_softc *sc; struct ifnet *ifp; struct phy_device *phy_dev; int error; sc = device_get_softc(dev); mtx_init(&sc->mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->fman_mac_callout, &sc->mtx, 0); /* Set up the Ethernet interface */ sc->ifp = ifp = if_alloc(IFT_ETHER); if (sc->ifp == NULL) { goto error; } snprintf(&sc->name[0], sizeof(sc->name), "fm%im", device_get_unit(device_get_parent(dev))); ifp->if_softc = sc; if_initname(ifp, &sc->name[0], sc->mac_dev.data.mac_hw_id); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_HWCSUM_IPV6 | IFCAP_VLAN_MTU | IFCAP_JUMBO_MTU; ifp->if_capenable = ifp->if_capabilities; ifp->if_hwassist = FMAN_MAC_CSUM; ifp->if_start = fman_mac_txstart; ifp->if_ioctl = fman_mac_ioctl; ifp->if_init = fman_mac_init; IFQ_SET_MAXLEN(&ifp->if_snd, 128); ifp->if_snd.ifq_drv_maxlen = 128; IFQ_SET_READY(&ifp->if_snd); ifp->if_hdrlen = sizeof(struct ether_vlan_header); /* Attach the MII driver if necessary */ phy_dev = sc->mac_dev.phy_dev; if (phy_dev != NULL) { error = mii_attach(dev, &sc->miibus, ifp, fman_mac_media_change, fman_mac_media_status, BMSR_DEFCAPMASK, phy_dev->mdio.addr, MII_OFFSET_ANY, 0); if (error == 0) { sc->mii_softc = device_get_softc(sc->miibus); } } sc->mac_dev.net_dev.ifp = ifp; ether_ifattach(ifp, &sc->mac_dev.addr[0]); #if 0 fman_mac_set_mtu(sc, ETHERMTU_JUMBO); #endif return (0); if_free(ifp); error: mtx_destroy(&sc->mtx); return (ENXIO); } int fman_mac_dev_detach(device_t _dev) { struct fman_mac_softc *sc = device_get_softc(_dev); ether_ifdetach(sc->ifp); FMAN_MAC_LOCK(sc); fman_mac_stop_locked(sc); FMAN_MAC_UNLOCK(sc); if_free(sc->ifp); mtx_destroy(&sc->mtx); return (bus_generic_detach(_dev)); } int fman_mac_miibus_read_reg(device_t dev, int phy, int reg) { struct fman_mac_softc *sc; struct phy_device *phy_dev; sc = device_get_softc(dev); phy_dev = sc->mac_dev.phy_dev; BSD_ASSERT(phy == phy_dev->mdio.addr); return (phy_read(phy_dev, reg)); } int fman_mac_miibus_write_reg(device_t dev, int phy, int reg, int val) { struct fman_mac_softc *sc; struct phy_device *phy_dev; sc = device_get_softc(dev); phy_dev = sc->mac_dev.phy_dev; BSD_ASSERT(phy == phy_dev->mdio.addr); return (phy_write(phy_dev, reg, val)); } void fman_mac_miibus_statchg(device_t dev) { struct fman_mac_softc *sc; struct mac_device *mac_dev; struct mii_data *mii; u16 speed; sc = device_get_softc(dev); mac_dev = &sc->mac_dev; mii = sc->mii_softc; FMAN_MAC_ASSERT_LOCKED(sc); switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_10_2: case IFM_10_5: case IFM_10_STP: case IFM_10_FL: speed = SPEED_10; break; case IFM_100_TX: case IFM_100_FX: case IFM_100_T4: case IFM_100_VG: case IFM_100_T2: speed = SPEED_100; break; case IFM_1000_SX: case IFM_1000_LX: case IFM_1000_CX: case IFM_1000_T: speed = SPEED_1000; break; case IFM_10G_LR: case IFM_10G_SR: case IFM_10G_CX4: case IFM_10G_TWINAX: case IFM_10G_TWINAX_LONG: case IFM_10G_LRM: speed = SPEED_10000; break; default: speed = 0; break; } (*mac_dev->adjust_link)(mac_dev, speed); } void dpaa_cleanup_tx_fd(struct ifnet *ifp, const struct qm_fd *fd) { struct fman_mac_sgt *sgt; BSD_ASSERT(qm_fd_get_format(fd) == qm_fd_sg); sgt = (struct fman_mac_sgt *)qm_fd_addr(fd); m_freem(sgt->m); uma_zfree(fman_mac_sgt_zone, sgt); } struct dpaa_priv * dpaa_get_priv_of_ifp(struct ifnet *ifp) { struct fman_mac_softc *sc; sc = ifp->if_softc; return (netdev_priv(&sc->mac_dev.net_dev)); } int dpaa_get_qman_portal_irq_of_ifp(struct ifnet *ifp) { struct fman_mac_softc *sc; sc = ifp->if_softc; return (qman_portal_get_irq(sc->mac_dev.portal)); }