/*- * Copyright (c) 2010 Juli Mallett * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 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. * * $FreeBSD$ */ /* * Cavium Octeon Ethernet devices. * * XXX This file should be moved to if_octe.c * XXX The driver may have sufficient locking but we need locking to protect * the interfaces presented here, right? */ #include "opt_inet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #include #include #include "wrapper-cvmx-includes.h" #include "cavium-ethernet.h" #include "ethernet-common.h" #include "ethernet-defines.h" #include "ethernet-mdio.h" #include "ethernet-tx.h" #include "miibus_if.h" #define OCTE_TX_LOCK(priv) mtx_lock(&(priv)->tx_mtx) #define OCTE_TX_UNLOCK(priv) mtx_unlock(&(priv)->tx_mtx) static int octe_probe(device_t); static int octe_attach(device_t); static int octe_detach(device_t); static int octe_shutdown(device_t); static int octe_miibus_readreg(device_t, int, int); static int octe_miibus_writereg(device_t, int, int, int); static void octe_init(void *); static void octe_stop(void *); static int octe_transmit(struct ifnet *, struct mbuf *); static int octe_mii_medchange(struct ifnet *); static void octe_mii_medstat(struct ifnet *, struct ifmediareq *); static int octe_medchange(struct ifnet *); static void octe_medstat(struct ifnet *, struct ifmediareq *); static int octe_ioctl(struct ifnet *, u_long, caddr_t); static device_method_t octe_methods[] = { /* Device interface */ DEVMETHOD(device_probe, octe_probe), DEVMETHOD(device_attach, octe_attach), DEVMETHOD(device_detach, octe_detach), DEVMETHOD(device_shutdown, octe_shutdown), /* MII interface */ DEVMETHOD(miibus_readreg, octe_miibus_readreg), DEVMETHOD(miibus_writereg, octe_miibus_writereg), { 0, 0 } }; static driver_t octe_driver = { "octe", octe_methods, sizeof (cvm_oct_private_t), }; static devclass_t octe_devclass; DRIVER_MODULE(octe, octebus, octe_driver, octe_devclass, 0, 0); DRIVER_MODULE(miibus, octe, miibus_driver, miibus_devclass, 0, 0); static int octe_probe(device_t dev) { return (0); } static int octe_attach(device_t dev) { struct ifnet *ifp; cvm_oct_private_t *priv; device_t child; unsigned qos; int error; priv = device_get_softc(dev); ifp = priv->ifp; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); if (priv->phy_id != -1) { if (priv->phy_device == NULL) { error = mii_attach(dev, &priv->miibus, ifp, octe_mii_medchange, octe_mii_medstat, BMSR_DEFCAPMASK, priv->phy_id, MII_OFFSET_ANY, 0); if (error != 0) device_printf(dev, "attaching PHYs failed\n"); } else { child = device_add_child(dev, priv->phy_device, -1); if (child == NULL) device_printf(dev, "missing phy %u device %s\n", priv->phy_id, priv->phy_device); } } if (priv->miibus == NULL) { ifmedia_init(&priv->media, 0, octe_medchange, octe_medstat); ifmedia_add(&priv->media, IFM_ETHER | IFM_AUTO, 0, NULL); ifmedia_set(&priv->media, IFM_ETHER | IFM_AUTO); } /* * XXX * We don't support programming the multicast filter right now, although it * ought to be easy enough. (Presumably it's just a matter of putting * multicast addresses in the CAM?) */ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_ALLMULTI; ifp->if_init = octe_init; ifp->if_ioctl = octe_ioctl; priv->if_flags = ifp->if_flags; mtx_init(&priv->tx_mtx, ifp->if_xname, "octe tx send queue", MTX_DEF); for (qos = 0; qos < 16; qos++) { mtx_init(&priv->tx_free_queue[qos].ifq_mtx, ifp->if_xname, "octe tx free queue", MTX_DEF); IFQ_SET_MAXLEN(&priv->tx_free_queue[qos], MAX_OUT_QUEUE_DEPTH); } ether_ifattach(ifp, priv->mac); ifp->if_transmit = octe_transmit; ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header); ifp->if_capabilities = IFCAP_VLAN_MTU | IFCAP_HWCSUM; ifp->if_capenable = ifp->if_capabilities; ifp->if_hwassist = CSUM_TCP | CSUM_UDP; OCTE_TX_LOCK(priv); IFQ_SET_MAXLEN(&ifp->if_snd, MAX_OUT_QUEUE_DEPTH); ifp->if_snd.ifq_drv_maxlen = MAX_OUT_QUEUE_DEPTH; IFQ_SET_READY(&ifp->if_snd); OCTE_TX_UNLOCK(priv); return (bus_generic_attach(dev)); } static int octe_detach(device_t dev) { return (0); } static int octe_shutdown(device_t dev) { return (octe_detach(dev)); } static int octe_miibus_readreg(device_t dev, int phy, int reg) { cvm_oct_private_t *priv; priv = device_get_softc(dev); /* * Try interface-specific MII routine. */ if (priv->mdio_read != NULL) return (priv->mdio_read(priv->ifp, phy, reg)); /* * Try generic MII routine. */ KASSERT(phy == priv->phy_id, ("read from phy %u but our phy is %u", phy, priv->phy_id)); return (cvm_oct_mdio_read(priv->ifp, phy, reg)); } static int octe_miibus_writereg(device_t dev, int phy, int reg, int val) { cvm_oct_private_t *priv; priv = device_get_softc(dev); /* * Try interface-specific MII routine. */ if (priv->mdio_write != NULL) { priv->mdio_write(priv->ifp, phy, reg, val); return (0); } /* * Try generic MII routine. */ KASSERT(phy == priv->phy_id, ("write to phy %u but our phy is %u", phy, priv->phy_id)); cvm_oct_mdio_write(priv->ifp, phy, reg, val); return (0); } static void octe_init(void *arg) { struct ifnet *ifp; cvm_oct_private_t *priv; priv = arg; ifp = priv->ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) octe_stop(priv); if (priv->open != NULL) priv->open(ifp); if (((ifp->if_flags ^ priv->if_flags) & (IFF_ALLMULTI | IFF_MULTICAST | IFF_PROMISC)) != 0) cvm_oct_common_set_multicast_list(ifp); cvm_oct_common_set_mac_address(ifp, IF_LLADDR(ifp)); cvm_oct_common_poll(ifp); if (priv->miibus != NULL) mii_mediachg(device_get_softc(priv->miibus)); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } static void octe_stop(void *arg) { struct ifnet *ifp; cvm_oct_private_t *priv; priv = arg; ifp = priv->ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; if (priv->stop != NULL) priv->stop(ifp); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; } static int octe_transmit(struct ifnet *ifp, struct mbuf *m) { cvm_oct_private_t *priv; priv = ifp->if_softc; if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) { m_freem(m); return (0); } return (cvm_oct_xmit(m, ifp)); } static int octe_mii_medchange(struct ifnet *ifp) { cvm_oct_private_t *priv; struct mii_data *mii; struct mii_softc *miisc; priv = ifp->if_softc; mii = device_get_softc(priv->miibus); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); mii_mediachg(mii); return (0); } static void octe_mii_medstat(struct ifnet *ifp, struct ifmediareq *ifm) { cvm_oct_private_t *priv; struct mii_data *mii; priv = ifp->if_softc; mii = device_get_softc(priv->miibus); mii_pollstat(mii); ifm->ifm_active = mii->mii_media_active; ifm->ifm_status = mii->mii_media_status; } static int octe_medchange(struct ifnet *ifp) { return (ENOTSUP); } static void octe_medstat(struct ifnet *ifp, struct ifmediareq *ifm) { cvm_oct_private_t *priv; cvmx_helper_link_info_t link_info; priv = ifp->if_softc; ifm->ifm_status = IFM_AVALID; ifm->ifm_active = IFT_ETHER; if (priv->poll == NULL) return; priv->poll(ifp); link_info.u64 = priv->link_info; if (!link_info.s.link_up) return; ifm->ifm_status |= IFM_ACTIVE; switch (link_info.s.speed) { case 10: ifm->ifm_active |= IFM_10_T; break; case 100: ifm->ifm_active |= IFM_100_TX; break; case 1000: ifm->ifm_active |= IFM_1000_T; break; case 10000: ifm->ifm_active |= IFM_10G_T; break; } if (link_info.s.full_duplex) ifm->ifm_active |= IFM_FDX; else ifm->ifm_active |= IFM_HDX; } static int octe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { cvm_oct_private_t *priv; struct mii_data *mii; struct ifreq *ifr; #ifdef INET struct ifaddr *ifa; #endif int error; priv = ifp->if_softc; ifr = (struct ifreq *)data; #ifdef INET ifa = (struct ifaddr *)data; #endif switch (cmd) { case SIOCSIFADDR: #ifdef INET /* * Avoid reinitialization unless it's necessary. */ if (ifa->ifa_addr->sa_family == AF_INET) { ifp->if_flags |= IFF_UP; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) octe_init(priv); arp_ifinit(ifp, ifa); return (0); } #endif error = ether_ioctl(ifp, cmd, data); if (error != 0) return (error); return (0); case SIOCSIFFLAGS: if (ifp->if_flags == priv->if_flags) return (0); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) octe_init(priv); } else { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) octe_stop(priv); } priv->if_flags = ifp->if_flags; return (0); case SIOCSIFCAP: /* * Just change the capabilities in software, currently none * require reprogramming hardware, they just toggle whether we * make use of already-present facilities in software. */ ifp->if_capenable = ifr->ifr_reqcap; return (0); case SIOCSIFMTU: error = cvm_oct_common_change_mtu(ifp, ifr->ifr_mtu); if (error != 0) return (EINVAL); return (0); case SIOCSIFMEDIA: case SIOCGIFMEDIA: if (priv->miibus != NULL) { mii = device_get_softc(priv->miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); if (error != 0) return (error); return (0); } error = ifmedia_ioctl(ifp, ifr, &priv->media, cmd); if (error != 0) return (error); return (0); default: error = ether_ioctl(ifp, cmd, data); if (error != 0) return (error); return (0); } }