From 6644b9d363df4744ff08866bbbce831ab3ec6bf4 Mon Sep 17 00:00:00 2001 From: zbb Date: Sun, 25 Oct 2015 22:14:04 +0000 Subject: [PATCH] Introduce e6000sw etherswitch support Add e6000sw driver supporting Marvell 88E6352, 88E6172, 88E6176 switches. It needs to be attached to mdio interface, exporting SMI access functionality. e6000sw supports port-based VLAN configuration, per-port media changing, accessing PHY and switch registers. e6000sw attaches miibuses and PHY drivers as children. Instead of typical tick as callout, kthread-based tick is used. This combined with SX locks allows MDIO read/write calls to sleep. It is expected, because this hardware requires long delays in SMI read/write procedures, which can not be handled by busy-waiting. Reviewed by: adrian Obtained from: Semihalf Submitted by: Bartosz Szczepanek Differential revision: https://reviews.freebsd.org/D3902 --- sys/dev/etherswitch/e6000sw/e6000sw.c | 976 +++++++++++++++++++++++ sys/dev/etherswitch/e6000sw/e6000swreg.h | 185 +++++ 2 files changed, 1161 insertions(+) create mode 100644 sys/dev/etherswitch/e6000sw/e6000sw.c create mode 100644 sys/dev/etherswitch/e6000sw/e6000swreg.h diff --git a/sys/dev/etherswitch/e6000sw/e6000sw.c b/sys/dev/etherswitch/e6000sw/e6000sw.c new file mode 100644 index 000000000000..e59860751a0a --- /dev/null +++ b/sys/dev/etherswitch/e6000sw/e6000sw.c @@ -0,0 +1,976 @@ +/*- + * Copyright (c) 2015 Semihalf + * Copyright (c) 2015 Stormshield + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#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 "e6000swreg.h" +#include "etherswitch_if.h" +#include "miibus_if.h" +#include "mdio_if.h" + +MALLOC_DECLARE(M_E6000SW); +MALLOC_DEFINE(M_E6000SW, "e6000sw", "e6000sw switch"); + +#define E6000SW_LOCK(_sc) \ + sx_xlock(&(_sc)->sx) +#define E6000SW_UNLOCK(_sc) \ + sx_unlock(&(_sc)->sx) +#define E6000SW_LOCK_ASSERT(_sc, _what) \ + sx_assert(&(_sc)->sx, (_what)) +#define E6000SW_TRYLOCK(_sc) \ + sx_tryxlock(&(_sc)->sx) + +typedef struct e6000sw_softc { + device_t dev; + + struct sx sx; + struct ifnet *ifp[E6000SW_NUM_PHYS]; + char *ifname[E6000SW_NUM_PHYS]; + device_t miibus[E6000SW_NUM_PHYS]; + struct mii_data *mii[E6000SW_NUM_PHYS]; + struct callout tick_callout; + + uint32_t cpuports_mask; + + int vid[E6000SW_NUM_VGROUPS]; + int members[E6000SW_NUM_VGROUPS]; + int vgroup[E6000SW_NUM_PORTS]; +} e6000sw_softc_t; + +static etherswitch_info_t etherswitch_info = { + .es_nports = E6000SW_NUM_PORTS, + .es_nvlangroups = E6000SW_NUM_VGROUPS, + .es_name = "Marvell 6000 series switch" +}; + +static void e6000sw_identify(driver_t *driver, device_t parent); +static int e6000sw_probe(device_t dev); +static int e6000sw_attach(device_t dev); +static int e6000sw_detach(device_t dev); +static int e6000sw_readphy(device_t dev, int phy, int reg); +static int e6000sw_writephy(device_t dev, int phy, int reg, int data); +static etherswitch_info_t* e6000sw_getinfo(device_t dev); +static void e6000sw_lock(device_t dev); +static void e6000sw_unlock(device_t dev); +static int e6000sw_getport(device_t dev, etherswitch_port_t *p); +static int e6000sw_setport(device_t dev, etherswitch_port_t *p); +static int e6000sw_readreg_wrapper(device_t dev, int addr_reg); +static int e6000sw_writereg_wrapper(device_t dev, int addr_reg, int val); +static int e6000sw_readphy_wrapper(device_t dev, int phy, int reg); +static int e6000sw_writephy_wrapper(device_t dev, int phy, int reg, int data); +static int e6000sw_getvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg); +static int e6000sw_setvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg); +static int e6000sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg); +static int e6000sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg); +static void e6000sw_setup(device_t dev, e6000sw_softc_t *sc); +static void e6000sw_port_vlan_conf(e6000sw_softc_t *sc); +static void e6000sw_tick(void *arg); +static void e6000sw_set_atustat(device_t dev, e6000sw_softc_t *sc, int bin, + int flag); +static int e6000sw_atu_flush(device_t dev, e6000sw_softc_t *sc, int flag); +static __inline void e6000sw_writereg(e6000sw_softc_t *sc, int addr, int reg, + int val); +static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *sc, int addr, + int reg); +static int e6000sw_ifmedia_upd(struct ifnet *ifp); +static void e6000sw_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr); +static int e6000sw_atu_mac_table(device_t dev, e6000sw_softc_t *sc, struct + atu_opt *atu, int flag); +static int e6000sw_get_pvid(e6000sw_softc_t *sc, int port, int *pvid); +static int e6000sw_set_pvid(e6000sw_softc_t *sc, int port, int pvid); +static __inline int e6000sw_cpuport(e6000sw_softc_t *sc, int port); +static __inline struct mii_data *e6000sw_miiforphy(e6000sw_softc_t *sc, + unsigned int phy); + +static struct proc *e6000sw_kproc; + +static device_method_t e6000sw_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, e6000sw_identify), + DEVMETHOD(device_probe, e6000sw_probe), + DEVMETHOD(device_attach, e6000sw_attach), + DEVMETHOD(device_detach, e6000sw_detach), + + /* bus interface */ + DEVMETHOD(bus_add_child, device_add_child_ordered), + + /* mii interface */ + DEVMETHOD(miibus_readreg, e6000sw_readphy), + DEVMETHOD(miibus_writereg, e6000sw_writephy), + + /* etherswitch interface */ + DEVMETHOD(etherswitch_getinfo, e6000sw_getinfo), + DEVMETHOD(etherswitch_lock, e6000sw_lock), + DEVMETHOD(etherswitch_unlock, e6000sw_unlock), + DEVMETHOD(etherswitch_getport, e6000sw_getport), + DEVMETHOD(etherswitch_setport, e6000sw_setport), + DEVMETHOD(etherswitch_readreg, e6000sw_readreg_wrapper), + DEVMETHOD(etherswitch_writereg, e6000sw_writereg_wrapper), + DEVMETHOD(etherswitch_readphyreg, e6000sw_readphy_wrapper), + DEVMETHOD(etherswitch_writephyreg, e6000sw_writephy_wrapper), + DEVMETHOD(etherswitch_setvgroup, e6000sw_setvgroup_wrapper), + DEVMETHOD(etherswitch_getvgroup, e6000sw_getvgroup_wrapper), + + DEVMETHOD_END +}; + +static devclass_t e6000sw_devclass; + +DEFINE_CLASS_0(e6000sw, e6000sw_driver, e6000sw_methods, + sizeof(e6000sw_softc_t)); + +DRIVER_MODULE(e6000sw, mdio, e6000sw_driver, e6000sw_devclass, 0, 0); +DRIVER_MODULE(etherswitch, e6000sw, etherswitch_driver, etherswitch_devclass, 0, + 0); +DRIVER_MODULE(miibus, e6000sw, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(e6000sw, mdio, 1, 1, 1); + +static void +e6000sw_identify(driver_t *driver, device_t parent) +{ + + if (device_find_child(parent, "e6000sw", -1) == NULL) + BUS_ADD_CHILD(parent, 0, "e6000sw", -1); +} + +static int +e6000sw_probe(device_t dev) +{ + e6000sw_softc_t *sc; + const char *description; + unsigned int id; + + sc = device_get_softc(dev); + bzero(sc, sizeof(e6000sw_softc_t)); + sc->dev = dev; + /* Lock is necessary due to assertions. */ + sx_init(&sc->sx, "e6000sw"); + E6000SW_LOCK(sc); + + id = e6000sw_readreg(sc, REG_PORT(0), SWITCH_ID); + + switch (id & 0xfff0) { + case 0x3520: + description = "Marvell 88E6352"; + break; + case 0x1720: + description = "Marvell 88E6172"; + break; + case 0x1760: + description = "Marvell 88E6176"; + break; + default: + E6000SW_UNLOCK(sc); + sx_destroy(&sc->sx); + device_printf(dev, "Unrecognized device.\n"); + return (ENXIO); + } + + device_set_desc(dev, description); + + E6000SW_UNLOCK(sc); + + return (BUS_PROBE_DEFAULT); +} + +static int +e6000sw_attach(device_t dev) +{ + e6000sw_softc_t *sc; + int phy, err, port; + char name[IFNAMSIZ]; + + err = 0; + sc = device_get_softc(dev); + E6000SW_LOCK(sc); + sc->cpuports_mask = E6000SW_CPUPORTS_MASK; + for (port = 0; port < E6000SW_NUM_PORTS; port++) + sc->vgroup[port] = E6000SW_PORT_NO_VGROUP; + e6000sw_setup(dev, sc); + + snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev)); + for (phy = 0; phy < E6000SW_NUM_PHYS; phy++) { + sc->ifp[phy] = if_alloc(IFT_ETHER); + if (sc->ifp[phy] == NULL) + goto out_fail; + sc->ifp[phy]->if_softc = sc; + sc->ifp[phy]->if_flags |= IFF_UP | IFF_BROADCAST | + IFF_DRV_RUNNING | IFF_SIMPLEX; + sc->ifname[phy] = malloc(strlen(name) + 1, M_E6000SW, M_WAITOK); + if (sc->ifname[phy] == NULL) + goto out_fail; + bcopy(name, sc->ifname[phy], strlen(name) + 1); + if_initname(sc->ifp[phy], sc->ifname[phy], phy); + err = mii_attach(sc->dev, &sc->miibus[phy], sc->ifp[phy], + e6000sw_ifmedia_upd, e6000sw_ifmedia_sts, BMSR_DEFCAPMASK, + phy, MII_OFFSET_ANY, 0); + if (err != 0) { + device_printf(sc->dev, + "attaching PHY %d failed\n", + phy); + goto out_fail; + } + sc->mii[phy] = device_get_softc(sc->miibus[phy]); + } + E6000SW_UNLOCK(sc); + + bus_generic_probe(dev); + bus_enumerate_hinted_children(dev); + bus_generic_attach(dev); + + kproc_create(e6000sw_tick, sc, &e6000sw_kproc, 0, 0, + "e6000sw tick kproc"); + + return (0); + +out_fail: + e6000sw_detach(dev); + + return (ENXIO); +} + +static __inline void +e6000sw_poll_done(e6000sw_softc_t *sc) +{ + + while (e6000sw_readreg(sc, REG_GLOBAL2, PHY_CMD) & + (1 << PHY_CMD_SMI_BUSY)) + continue; +} + + +/* + * PHY registers are paged. Put page index in reg 22 (accessible from every + * page), then access specific register. + */ +static int +e6000sw_readphy(device_t dev, int phy, int reg) +{ + e6000sw_softc_t *sc; + uint32_t val; + + sc = device_get_softc(dev); + val = 0; + + if (phy >= E6000SW_NUM_PHYS || reg >= E6000SW_NUM_PHY_REGS) { + device_printf(dev, "Wrong register address.\n"); + return (EINVAL); + } + + E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); + + e6000sw_poll_done(sc); + val |= 1 << PHY_CMD_SMI_BUSY; + val |= PHY_CMD_MODE_MDIO << PHY_CMD_MODE; + val |= PHY_CMD_OPCODE_READ << PHY_CMD_OPCODE; + val |= (reg << PHY_CMD_REG_ADDR) & PHY_CMD_REG_ADDR_MASK; + val |= (phy << PHY_CMD_DEV_ADDR) & PHY_CMD_DEV_ADDR_MASK; + e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, val); + e6000sw_poll_done(sc); + val = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG) + & PHY_DATA_MASK; + + return (val); +} + +static int +e6000sw_writephy(device_t dev, int phy, int reg, int data) +{ + e6000sw_softc_t *sc; + uint32_t val; + + sc = device_get_softc(dev); + val = 0; + + if (phy >= E6000SW_NUM_PHYS || reg >= E6000SW_NUM_PHY_REGS) { + device_printf(dev, "Wrong register address.\n"); + return (EINVAL); + } + + E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); + + e6000sw_poll_done(sc); + val |= PHY_CMD_MODE_MDIO << PHY_CMD_MODE; + val |= 1 << PHY_CMD_SMI_BUSY; + val |= PHY_CMD_OPCODE_WRITE << PHY_CMD_OPCODE; + val |= (reg << PHY_CMD_REG_ADDR) & PHY_CMD_REG_ADDR_MASK; + val |= (phy << PHY_CMD_DEV_ADDR) & PHY_CMD_DEV_ADDR_MASK; + e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, + data & PHY_DATA_MASK); + e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, val); + e6000sw_poll_done(sc); + + return (0); +} + +static int +e6000sw_detach(device_t dev) +{ + int phy; + e6000sw_softc_t *sc; + + sc = device_get_softc(dev); + bus_generic_detach(dev); + sx_destroy(&sc->sx); + for (phy = 0; phy < E6000SW_NUM_PHYS; phy++) { + if (sc->miibus[phy] != NULL) + device_delete_child(dev, sc->miibus[phy]); + if (sc->ifp[phy] != NULL) + if_free(sc->ifp[phy]); + if (sc->ifname[phy] != NULL) + free(sc->ifname[phy], M_E6000SW); + } + + return (0); +} + +static etherswitch_info_t* +e6000sw_getinfo(device_t dev) +{ + + return (ðerswitch_info); +} + +static void +e6000sw_lock(device_t dev) +{ + struct e6000sw_softc *sc; + + sc = device_get_softc(dev); + + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + E6000SW_LOCK(sc); +} + +static void +e6000sw_unlock(device_t dev) +{ + struct e6000sw_softc *sc; + + sc = device_get_softc(dev); + + E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); + E6000SW_UNLOCK(sc); +} + +static int +e6000sw_getport(device_t dev, etherswitch_port_t *p) +{ + struct mii_data *mii; + int err; + struct ifmediareq *ifmr; + + err = 0; + e6000sw_softc_t *sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + + E6000SW_LOCK(sc); + + if (p->es_port >= E6000SW_NUM_PORTS || + p->es_port < 0) { + err = EINVAL; + goto out; + } + + e6000sw_get_pvid(sc, p->es_port, &p->es_pvid); + + if (e6000sw_cpuport(sc, p->es_port)) { + p->es_flags |= ETHERSWITCH_PORT_CPU; + ifmr = &p->es_ifmr; + ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; + ifmr->ifm_count = 0; + ifmr->ifm_current = ifmr->ifm_active = + IFM_ETHER | IFM_1000_T | IFM_FDX; + ifmr->ifm_mask = 0; + } else { + mii = e6000sw_miiforphy(sc, p->es_port); + err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, + &mii->mii_media, SIOCGIFMEDIA); + } + +out: + E6000SW_UNLOCK(sc); + return (err); +} + +static int +e6000sw_setport(device_t dev, etherswitch_port_t *p) +{ + e6000sw_softc_t *sc; + int err; + struct mii_data *mii; + + err = 0; + sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + + E6000SW_LOCK(sc); + + if (p->es_port >= E6000SW_NUM_PORTS || + p->es_port < 0) { + err = EINVAL; + goto out; + } + + if (p->es_pvid != 0) + e6000sw_set_pvid(sc, p->es_port, p->es_pvid); + if (!e6000sw_cpuport(sc, p->es_port)) { + mii = e6000sw_miiforphy(sc, p->es_port); + err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, + SIOCSIFMEDIA); + } + +out: + E6000SW_UNLOCK(sc); + return (err); +} + +/* + * Registers in this switch are divided into sections, specified in + * documentation. So as to access any of them, section index and reg index + * is necessary. etherswitchcfg uses only one variable, so indexes were + * compressed into addr_reg: 32 * section_index + reg_index. + */ +static int +e6000sw_readreg_wrapper(device_t dev, int addr_reg) +{ + + if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || + (addr_reg < (REG_PORT(0) * 32))) { + device_printf(dev, "Wrong register address.\n"); + return (EINVAL); + } + + return (e6000sw_readreg(device_get_softc(dev), addr_reg / 32, + addr_reg % 32)); +} + +static int +e6000sw_writereg_wrapper(device_t dev, int addr_reg, int val) +{ + + if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || + (addr_reg < (REG_PORT(0) * 32))) { + device_printf(dev, "Wrong register address.\n"); + return (EINVAL); + } + e6000sw_writereg(device_get_softc(dev), addr_reg / 5, + addr_reg % 32, val); + + return (0); +} + +/* + * These wrappers are necessary because PHY accesses from etherswitchcfg + * need to be synchronized with locks, while miibus PHY accesses do not. + */ +static int +e6000sw_readphy_wrapper(device_t dev, int phy, int reg) +{ + e6000sw_softc_t *sc; + int ret; + + sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + + E6000SW_LOCK(sc); + ret = e6000sw_readphy(dev, phy, reg); + E6000SW_UNLOCK(sc); + + return (ret); +} + +static int +e6000sw_writephy_wrapper(device_t dev, int phy, int reg, int data) +{ + e6000sw_softc_t *sc; + int ret; + + sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + + E6000SW_LOCK(sc); + ret = e6000sw_writephy(dev, phy, reg, data); + E6000SW_UNLOCK(sc); + + return (ret); +} + +/* + * setvgroup/getvgroup called from etherswitchfcg need to be locked, + * while internal calls do not. + */ +static int +e6000sw_setvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) +{ + e6000sw_softc_t *sc; + int ret; + + sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + + E6000SW_LOCK(sc); + ret = e6000sw_setvgroup(dev, vg); + E6000SW_UNLOCK(sc); + + return (ret); +} + +static int +e6000sw_getvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) +{ + e6000sw_softc_t *sc; + int ret; + + sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + + E6000SW_LOCK(sc); + ret = e6000sw_getvgroup(dev, vg); + E6000SW_UNLOCK(sc); + + return (ret); +} + +static __inline void +e6000sw_flush_port(e6000sw_softc_t *sc, int port) +{ + uint32_t reg; + + reg = e6000sw_readreg(sc, REG_PORT(port), + PORT_VLAN_MAP); + reg &= ~PORT_VLAN_MAP_TABLE_MASK; + reg &= ~PORT_VLAN_MAP_FID_MASK; + e6000sw_writereg(sc, REG_PORT(port), + PORT_VLAN_MAP, reg); + if (sc->vgroup[port] != E6000SW_PORT_NO_VGROUP) { + /* + * If port belonged somewhere, owner-group + * should have its entry removed. + */ + sc->members[sc->vgroup[port]] &= ~(1 << port); + sc->vgroup[port] = E6000SW_PORT_NO_VGROUP; + } +} + +static __inline void +e6000sw_port_assign_vgroup(e6000sw_softc_t *sc, int port, int fid, int vgroup, + int members) +{ + uint32_t reg; + + reg = e6000sw_readreg(sc, REG_PORT(port), + PORT_VLAN_MAP); + reg &= ~PORT_VLAN_MAP_TABLE_MASK; + reg &= ~PORT_VLAN_MAP_FID_MASK; + reg |= members & ~(1 << port); + reg |= (fid << PORT_VLAN_MAP_FID) & PORT_VLAN_MAP_FID_MASK; + e6000sw_writereg(sc, REG_PORT(port), PORT_VLAN_MAP, + reg); + sc->vgroup[port] = vgroup; +} + +static int +e6000sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) +{ + e6000sw_softc_t *sc; + int port, fid; + + sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); + + if (vg->es_vlangroup >= E6000SW_NUM_VGROUPS) + return (EINVAL); + if (vg->es_member_ports != vg->es_untagged_ports) { + device_printf(dev, "Tagged ports not supported.\n"); + return (EINVAL); + } + + vg->es_untagged_ports &= PORT_VLAN_MAP_TABLE_MASK; + fid = vg->es_vlangroup + 1; + for (port = 0; port < E6000SW_NUM_PORTS; port++) { + if ((sc->members[vg->es_vlangroup] & (1 << port)) || + (vg->es_untagged_ports & (1 << port))) + e6000sw_flush_port(sc, port); + if (vg->es_untagged_ports & (1 << port)) + e6000sw_port_assign_vgroup(sc, port, fid, + vg->es_vlangroup, vg->es_untagged_ports); + } + sc->vid[vg->es_vlangroup] = vg->es_vid; + sc->members[vg->es_vlangroup] = vg->es_untagged_ports; + + return (0); +} + +static int +e6000sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) +{ + e6000sw_softc_t *sc; + + sc = device_get_softc(dev); + E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); + + if (vg->es_vlangroup >= E6000SW_NUM_VGROUPS) + return (EINVAL); + vg->es_untagged_ports = vg->es_member_ports = + sc->members[vg->es_vlangroup]; + vg->es_vid = ETHERSWITCH_VID_VALID; + + return (0); +} + +static __inline struct mii_data* +e6000sw_miiforphy(e6000sw_softc_t *sc, unsigned int phy) +{ + + if (phy >= E6000SW_NUM_PHYS) + return (NULL); + + return (device_get_softc(sc->miibus[phy])); +} + +static int +e6000sw_ifmedia_upd(struct ifnet *ifp) +{ + e6000sw_softc_t *sc; + struct mii_data *mii; + + sc = ifp->if_softc; + mii = e6000sw_miiforphy(sc, ifp->if_dunit); + if (mii == NULL) + return (ENXIO); + mii_mediachg(mii); + + return (0); +} + +static void +e6000sw_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + e6000sw_softc_t *sc; + struct mii_data *mii; + + sc = ifp->if_softc; + mii = e6000sw_miiforphy(sc, ifp->if_dunit); + + if (mii == NULL) + return; + + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; +} + +static __inline uint32_t +e6000sw_readreg(e6000sw_softc_t *sc, int addr, int reg) +{ + + E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); + + return (MDIO_READREG(device_get_parent(sc->dev), addr, reg)); +} + +static __inline void +e6000sw_writereg(e6000sw_softc_t *sc, int addr, int reg, int val) +{ + + E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); + + MDIO_WRITEREG(device_get_parent(sc->dev), addr, reg, val); +} + +static __inline int +e6000sw_cpuport(e6000sw_softc_t *sc, int port) +{ + + return (sc->cpuports_mask & (1 << port)); +} + +static __inline int +e6000sw_set_pvid(e6000sw_softc_t *sc, int port, int pvid) +{ + + e6000sw_writereg(sc, REG_PORT(port), PORT_VID, pvid & + PORT_VID_DEF_VID_MASK); + + return (0); +} + +static __inline int +e6000sw_get_pvid(e6000sw_softc_t *sc, int port, int *pvid) +{ + + if (pvid == NULL) + return (ENXIO); + + *pvid = e6000sw_readreg(sc, REG_PORT(port), PORT_VID) & + PORT_VID_DEF_VID_MASK; + + return (0); +} + +static void +e6000sw_tick (void *arg) +{ + e6000sw_softc_t *sc; + struct mii_softc *miisc; + int i; + + sc = arg; + + E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); + for (;;) { + E6000SW_LOCK(sc); + for (i = 0; i < E6000SW_NUM_PHYS; i++) { + mii_tick(sc->mii[i]); + LIST_FOREACH(miisc, &sc->mii[i]->mii_phys, mii_list) { + if (IFM_INST(sc->mii[i]->mii_media.ifm_cur->ifm_media) + != miisc->mii_inst) + continue; + mii_phy_update(miisc, MII_POLLSTAT); + } + } + E6000SW_UNLOCK(sc); + pause("e6000sw tick", 1000); + } +} + +static void +e6000sw_setup(device_t dev, e6000sw_softc_t *sc) +{ + uint16_t atu_ctrl, atu_age; + + /* Set aging time */ + e6000sw_writereg(sc, REG_GLOBAL, ATU_CONTROL, + (E6000SW_DEFAULT_AGETIME << ATU_CONTROL_AGETIME) | + (1 << ATU_CONTROL_LEARN2ALL)); + + /* Send all with specific mac address to cpu port */ + e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_2x, MGMT_EN_ALL); + e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_0x, MGMT_EN_ALL); + + /* Disable Remote Managment */ + e6000sw_writereg(sc, REG_GLOBAL, SWITCH_GLOBAL_CONTROL2, 0); + + /* Disable loopback filter and flow control messages */ + e6000sw_writereg(sc, REG_GLOBAL2, SWITCH_MGMT, + SWITCH_MGMT_PRI_MASK | + (1 << SWITCH_MGMT_RSVD2CPU) | + SWITCH_MGMT_FC_PRI_MASK | + (1 << SWITCH_MGMT_FORCEFLOW)); + + /* Set VLAN configuration */ + e6000sw_port_vlan_conf(sc); + + e6000sw_atu_flush(dev, sc, NO_OPERATION); + e6000sw_atu_mac_table(dev, sc, NULL, NO_OPERATION); + e6000sw_set_atustat(dev, sc, 0, COUNT_ALL); + + /* Set ATU AgeTime to 15 seconds */ + atu_age = 1; + + atu_ctrl = e6000sw_readreg(sc, REG_GLOBAL, ATU_CONTROL); + + /* Set new AgeTime field */ + atu_ctrl &= ~ATU_CONTROL_AGETIME_MASK; + e6000sw_writereg(sc, REG_GLOBAL, ATU_CONTROL, atu_ctrl | + (atu_age << ATU_CONTROL_AGETIME)); +} + +static void +e6000sw_port_vlan_conf(e6000sw_softc_t *sc) +{ + int port, ret; + etherswitch_vlangroup_t vg; + device_t dev; + + dev = sc->dev; + /* Disable all ports */ + for (port = 0; port < E6000SW_NUM_PORTS; port++) { + ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL); + e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL, + (ret & ~PORT_CONTROL_ENABLE)); + } + + /* Set port priority */ + for (port = 0; port < E6000SW_NUM_PORTS; port++) { + ret = e6000sw_readreg(sc, REG_PORT(port), PORT_VID); + ret &= ~PORT_VID_PRIORITY_MASK; + e6000sw_writereg(sc, REG_PORT(port), PORT_VID, ret); + } + + vg.es_vlangroup = 0; + vg.es_vid = 0; + vg.es_member_ports = vg.es_untagged_ports = E6000SW_DEF_VLANGROUP0; + e6000sw_setvgroup(dev, &vg); + vg.es_vlangroup = 1; + vg.es_vid = 1; + vg.es_member_ports = vg.es_untagged_ports = E6000SW_DEF_VLANGROUP1; + e6000sw_setvgroup(dev, &vg); + + device_printf(dev, "Default vlangroups set.\n"); + /* Set VID map */ + for (port = 0; port < E6000SW_NUM_PORTS; port++) { + ret = e6000sw_readreg(sc, REG_PORT(port), PORT_VID); + ret &= ~PORT_VID_DEF_VID_MASK; + ret |= (port + 1); + e6000sw_writereg(sc, REG_PORT(port), PORT_VID, ret); + } + + /* Enable all ports */ + for (port = 0; port < E6000SW_NUM_PORTS; port++) { + ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL); + e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL, (ret | + PORT_CONTROL_ENABLE)); + } +} + +static void +e6000sw_set_atustat(device_t dev, e6000sw_softc_t *sc, int bin, int flag) +{ + uint16_t ret; + + ret = e6000sw_readreg(sc, REG_GLOBAL2, ATU_STATS); + e6000sw_writereg(sc, REG_GLOBAL2, ATU_STATS, (bin << ATU_STATS_BIN ) | + (flag << ATU_STATS_FLAG)); +} + +static int +e6000sw_atu_mac_table(device_t dev, e6000sw_softc_t *sc, struct atu_opt *atu, + int flag) +{ + uint16_t ret_opt; + uint16_t ret_data; + int retries; + + if (flag == NO_OPERATION) + return (0); + else if ((flag & (LOAD_FROM_FIB | PURGE_FROM_FIB | GET_NEXT_IN_FIB | + GET_VIOLATION_DATA | CLEAR_VIOLATION_DATA)) == 0) { + device_printf(dev, "Wrong Opcode for ATU operation\n"); + return (EINVAL); + } + + ret_opt = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); + + if (ret_opt & ATU_UNIT_BUSY) { + device_printf(dev, "ATU unit is busy, cannot access" + "register\n"); + return (EBUSY); + } else { + if(flag & LOAD_FROM_FIB) { + ret_data = e6000sw_readreg(sc, REG_GLOBAL, ATU_DATA); + e6000sw_writereg(sc, REG_GLOBAL2, ATU_DATA, (ret_data & + ~ENTRY_STATE)); + } + e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR01, atu->mac_01); + e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR23, atu->mac_23); + e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR45, atu->mac_45); + e6000sw_writereg(sc, REG_GLOBAL, ATU_FID, atu->fid); + + e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (ret_opt | + ATU_UNIT_BUSY | flag)); + + retries = E6000SW_RETRIES; + while (--retries & (e6000sw_readreg(sc, REG_GLOBAL, + ATU_OPERATION) & ATU_UNIT_BUSY)) + DELAY(1); + + if (retries == 0) + device_printf(dev, "Timeout while flushing\n"); + else if (flag & GET_NEXT_IN_FIB) { + atu->mac_01 = e6000sw_readreg(sc, REG_GLOBAL, + ATU_MAC_ADDR01); + atu->mac_23 = e6000sw_readreg(sc, REG_GLOBAL, + ATU_MAC_ADDR23); + atu->mac_45 = e6000sw_readreg(sc, REG_GLOBAL, + ATU_MAC_ADDR45); + } + } + + return (0); +} + +static int +e6000sw_atu_flush(device_t dev, e6000sw_softc_t *sc, int flag) +{ + uint16_t ret; + int retries; + + if (flag == NO_OPERATION) + return (0); + + ret = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); + if (ret & ATU_UNIT_BUSY) { + device_printf(dev, "Atu unit is busy, cannot flush\n"); + return (EBUSY); + } else { + e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (ret | + ATU_UNIT_BUSY | flag)); + retries = E6000SW_RETRIES; + while (--retries & (e6000sw_readreg(sc, REG_GLOBAL, + ATU_OPERATION) & ATU_UNIT_BUSY)) + DELAY(1); + + if (retries == 0) + device_printf(dev, "Timeout while flushing\n"); + } + + return (0); +} diff --git a/sys/dev/etherswitch/e6000sw/e6000swreg.h b/sys/dev/etherswitch/e6000sw/e6000swreg.h new file mode 100644 index 000000000000..b8cdfe32a6f0 --- /dev/null +++ b/sys/dev/etherswitch/e6000sw/e6000swreg.h @@ -0,0 +1,185 @@ +/*- + * Copyright (c) 2015 Semihalf + * Copyright (c) 2015 Stormshield + * 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$ + * + */ + +#ifndef _E6000SWREG_H_ +#define _E6000SWREG_H_ + +struct atu_opt { + uint16_t mac_01; + uint16_t mac_23; + uint16_t mac_45; + uint16_t fid; +}; + +/* + * Definitions for the Marvell 88E6000 series Ethernet Switch. + */ + +#define CPU_PORT 0x5 + +/* + * Switch Registers + */ +#define REG_GLOBAL 0x1b +#define REG_GLOBAL2 0x1c +#define REG_PORT(p) (0x10 + (p)) + +#define REG_NUM_MAX 31 + +/* + * Per-Port Switch Registers + */ +#define PORT_STATUS 0x0 +#define PSC_CONTROL 0x1 +#define SWITCH_ID 0x3 +#define PORT_CONTROL 0x4 +#define PORT_CONTROL_1 0x5 +#define PORT_VLAN_MAP 0x6 +#define PORT_VID 0x7 +#define PORT_ASSOCIATION_VECTOR 0xb +#define PORT_ATU_CTRL 0xc +#define RX_COUNTER 0x12 +#define TX_COUNTER 0x13 + +#define PORT_VID_DEF_VID 0 +#define PORT_VID_DEF_VID_MASK 0xfff +#define PORT_VID_PRIORITY_MASK 0xc00 + +#define PORT_CONTROL_ENABLE 0x3 + +/* PORT_VLAN fields */ +#define PORT_VLAN_MAP_TABLE_MASK 0x7f +#define PORT_VLAN_MAP_FID 12 +#define PORT_VLAN_MAP_FID_MASK 0xf000 +/* + * Switch Global Register 1 accessed via REG_GLOBAL_ADDR + */ +#define SWITCH_GLOBAL_STATUS 0 +#define SWITCH_GLOBAL_CONTROL 4 +#define SWITCH_GLOBAL_CONTROL2 28 + +#define MONITOR_CONTROL 26 + +/* ATU operation */ +#define ATU_FID 1 +#define ATU_CONTROL 10 +#define ATU_OPERATION 11 +#define ATU_DATA 12 +#define ATU_MAC_ADDR01 13 +#define ATU_MAC_ADDR23 14 +#define ATU_MAC_ADDR45 15 + +#define ATU_UNIT_BUSY (1 << 15) +#define ENTRY_STATE 0xf + +/* ATU_CONTROL fields */ +#define ATU_CONTROL_AGETIME 4 +#define ATU_CONTROL_AGETIME_MASK 0xff0 +#define ATU_CONTROL_LEARN2ALL 3 + +/* ATU opcode */ +#define NO_OPERATION (0 << 0) +#define FLUSH_ALL (1 << 0) +#define FLUSH_NON_STATIC (1 << 1) +#define LOAD_FROM_FIB (3 << 0) +#define PURGE_FROM_FIB (3 << 0) +#define GET_NEXT_IN_FIB (1 << 2) +#define FLUSH_ALL_IN_FIB (5 << 0) +#define FLUSH_NON_STATIC_IN_FIB (3 << 1) +#define GET_VIOLATION_DATA (7 << 0) +#define CLEAR_VIOLATION_DATA (7 << 0) + +/* ATU Stats */ +#define COUNT_ALL (0 << 0) + +/* + * Switch Global Register 2 accessed via REG_GLOBAL2_ADDR + */ +#define MGMT_EN_2x 2 +#define MGMT_EN_0x 3 +#define SWITCH_MGMT 5 +#define ATU_STATS 14 + +#define MGMT_EN_ALL 0xffff + +/* SWITCH_MGMT fields */ + +#define SWITCH_MGMT_PRI 0 +#define SWITCH_MGMT_PRI_MASK 7 +#define SWITCH_MGMT_RSVD2CPU 3 +#define SWITCH_MGMT_FC_PRI 4 +#define SWITCH_MGMT_FC_PRI_MASK (7 << 4) +#define SWITCH_MGMT_FORCEFLOW 7 + +/* ATU_STATS fields */ + +#define ATU_STATS_BIN 14 +#define ATU_STATS_FLAG 12 + +/* + * PHY registers accessed via 'Switch Global Registers' (REG_GLOBAL2). + */ +#define SMI_PHY_CMD_REG 0x18 +#define SMI_PHY_DATA_REG 0x19 + +#define PHY_CMD 0x18 +#define PHY_DATA 0x19 +#define PHY_DATA_MASK 0xffff + +#define PHY_CMD_SMI_BUSY 15 +#define PHY_CMD_MODE 12 +#define PHY_CMD_MODE_MDIO 1 +#define PHY_CMD_MODE_XMDIO 0 +#define PHY_CMD_OPCODE 10 +#define PHY_CMD_OPCODE_WRITE 1 +#define PHY_CMD_OPCODE_READ 2 +#define PHY_CMD_DEV_ADDR 5 +#define PHY_CMD_DEV_ADDR_MASK 0x3e0 +#define PHY_CMD_REG_ADDR 0 +#define PHY_CMD_REG_ADDR_MASK 0x1f + +#define PHY_PAGE_REG 22 + +#define E6000SW_NUM_PHYS 5 +#define E6000SW_NUM_PHY_REGS 29 +#define E6000SW_CPUPORTS_MASK ((1 << 5) | (1 << 6)) +#define E6000SW_NUM_VGROUPS 8 +#define E6000SW_NUM_PORTS 7 +#define E6000SW_PORT_NO_VGROUP -1 +#define E6000SW_DEFAULT_AGETIME 20 +#define E6000SW_RETRIES 100 + + +/* Default vlangroups */ +#define E6000SW_DEF_VLANGROUP0 (1 | (1 << 1) | (1 << 2) | (1 << 3) | \ + (1 << 6)) +#define E6000SW_DEF_VLANGROUP1 ((1 << 4) | (1 << 5)) + +#endif /* _E6000SWREG_H_ */