diff --git a/sys/dev/etherswitch/ar40xx/ar40xx_main.c b/sys/dev/etherswitch/ar40xx/ar40xx_main.c index 41e6813bc840..d5636d26120b 100644 --- a/sys/dev/etherswitch/ar40xx/ar40xx_main.c +++ b/sys/dev/etherswitch/ar40xx/ar40xx_main.c @@ -1,967 +1,968 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 Adrian Chadd . * * 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 #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 #include #include #include #include "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" static struct ofw_compat_data compat_data[] = { { "qcom,ess-switch", 1 }, { NULL, 0 }, }; static int ar40xx_probe(device_t dev) { if (! ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "IPQ4018 ESS Switch fabric / PSGMII PHY"); return (BUS_PROBE_DEFAULT); } static void ar40xx_tick(void *arg) { struct ar40xx_softc *sc = arg; (void) ar40xx_phy_tick(sc); callout_reset(&sc->sc_phy_callout, hz, ar40xx_tick, sc); } static void ar40xx_statchg(device_t dev) { struct ar40xx_softc *sc = device_get_softc(dev); AR40XX_DPRINTF(sc, AR40XX_DBG_PORT_STATUS, "%s\n", __func__); } static int ar40xx_readphy(device_t dev, int phy, int reg) { struct ar40xx_softc *sc = device_get_softc(dev); return MDIO_READREG(sc->sc_mdio_dev, phy, reg); } static int ar40xx_writephy(device_t dev, int phy, int reg, int val) { struct ar40xx_softc *sc = device_get_softc(dev); return MDIO_WRITEREG(sc->sc_mdio_dev, phy, reg, val); } /* * Do the initial switch configuration. */ static int ar40xx_reset_switch(struct ar40xx_softc *sc) { int ret, i; AR40XX_DPRINTF(sc, AR40XX_DBG_HW_INIT, "%s: called\n", __func__); /* blank the VLAN config */ memset(&sc->sc_vlan, 0, sizeof(sc->sc_vlan)); /* initial vlan port mapping */ for (i = 0; i < AR40XX_NUM_VTU_ENTRIES; i++) sc->sc_vlan.vlan_id[i] = 0; /* init vlan config */ ret = ar40xx_hw_vlan_init(sc); /* init monitor config */ sc->sc_monitor.mirror_tx = false; sc->sc_monitor.mirror_rx = false; sc->sc_monitor.source_port = 0; sc->sc_monitor.monitor_port = 0; /* apply switch config */ ret = ar40xx_hw_sw_hw_apply(sc); return (ret); } static int ar40xx_sysctl_dump_port_state(SYSCTL_HANDLER_ARGS) { struct ar40xx_softc *sc = arg1; int val = 0; int error; int i; (void) i; (void) sc; error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr) return (error); if (val < 0 || val > 5) { return (EINVAL); } AR40XX_LOCK(sc); device_printf(sc->sc_dev, "port %d: PORT_STATUS=0x%08x\n", val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_STATUS(val))); device_printf(sc->sc_dev, "port %d: PORT_HEADER=0x%08x\n", val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_HEADER(val))); device_printf(sc->sc_dev, "port %d: PORT_VLAN0=0x%08x\n", val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_VLAN0(val))); device_printf(sc->sc_dev, "port %d: PORT_VLAN1=0x%08x\n", val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_VLAN1(val))); device_printf(sc->sc_dev, "port %d: PORT_LOOKUP=0x%08x\n", val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_LOOKUP(val))); device_printf(sc->sc_dev, "port %d: PORT_HOL_CTRL1=0x%08x\n", val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_HOL_CTRL1(val))); device_printf(sc->sc_dev, "port %d: PORT_FLOWCTRL_THRESH=0x%08x\n", val, AR40XX_REG_READ(sc, AR40XX_REG_PORT_FLOWCTRL_THRESH(val))); AR40XX_UNLOCK(sc); return (0); } static int ar40xx_sysctl_dump_port_mibstats(SYSCTL_HANDLER_ARGS) { struct ar40xx_softc *sc = arg1; int val = 0; int error; int i; (void) i; (void) sc; error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr) return (error); if (val < 0 || val > 5) { return (EINVAL); } AR40XX_LOCK(sc); /* Yes, this snapshots all ports */ (void) ar40xx_hw_mib_capture(sc); (void) ar40xx_hw_mib_fetch(sc, val); AR40XX_UNLOCK(sc); return (0); } static int ar40xx_sysctl_attach(struct ar40xx_softc *sc) { struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, "debugging flags"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "port_state", CTLTYPE_INT | CTLFLAG_RW, sc, 0, ar40xx_sysctl_dump_port_state, "I", ""); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "port_mibstats", CTLTYPE_INT | CTLFLAG_RW, sc, 0, ar40xx_sysctl_dump_port_mibstats, "I", ""); return (0); } static int ar40xx_detach(device_t dev) { struct ar40xx_softc *sc = device_get_softc(dev); - int i; + int error, i; device_printf(sc->sc_dev, "%s: called\n", __func__); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + callout_drain(&sc->sc_phy_callout); /* Free PHYs */ for (i = 0; i < AR40XX_NUM_PHYS; i++) { - if (sc->sc_phys.miibus[i] != NULL) - device_delete_child(dev, sc->sc_phys.miibus[i]); if (sc->sc_phys.ifp[i] != NULL) if_free(sc->sc_phys.ifp[i]); free(sc->sc_phys.ifname[i], M_DEVBUF); } - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } static int ar40xx_attach(device_t dev) { struct ar40xx_softc *sc = device_get_softc(dev); phandle_t psgmii_p, root_p, mdio_p; int ret, i; sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "ar40xx_switch", NULL, MTX_DEF); psgmii_p = OF_finddevice("/soc/ess-psgmii"); if (psgmii_p == -1) { device_printf(dev, "%s: couldn't find /soc/ess-psgmii DT node\n", __func__); goto error; } /* * Get the ipq4019-mdio node here, to talk to our local PHYs * if needed */ root_p = OF_finddevice("/soc"); mdio_p = ofw_bus_find_compatible(root_p, "qcom,ipq4019-mdio"); if (mdio_p == -1) { device_printf(dev, "%s: couldn't find ipq4019-mdio DT node\n", __func__); goto error; } sc->sc_mdio_phandle = mdio_p; sc->sc_mdio_dev = OF_device_from_xref(OF_xref_from_node(mdio_p)); if (sc->sc_mdio_dev == NULL) { device_printf(dev, "%s: couldn't get mdio device (mdio_p=%u)\n", __func__, mdio_p); goto error; } /* get psgmii base address from psgmii node */ ret = OF_decode_addr(psgmii_p, 0, &sc->sc_psgmii_mem_tag, &sc->sc_psgmii_mem_handle, &sc->sc_psgmii_mem_size); if (ret != 0) { device_printf(dev, "%s: couldn't map psgmii mem (%d)\n", __func__, ret); goto error; } /* get switch base address */ sc->sc_ess_mem_rid = 0; sc->sc_ess_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_ess_mem_rid, RF_ACTIVE); if (sc->sc_ess_mem_res == NULL) { device_printf(dev, "%s: failed to find memory resource\n", __func__); goto error; } sc->sc_ess_mem_size = (size_t) bus_get_resource_count(dev, SYS_RES_MEMORY, sc->sc_ess_mem_rid); if (sc->sc_ess_mem_size == 0) { device_printf(dev, "%s: failed to get device memory size\n", __func__); goto error; } ret = OF_getencprop(ofw_bus_get_node(dev), "switch_mac_mode", &sc->sc_config.switch_mac_mode, sizeof(sc->sc_config.switch_mac_mode)); if (ret < 0) { device_printf(dev, "%s: missing switch_mac_mode property\n", __func__); goto error; } ret = OF_getencprop(ofw_bus_get_node(dev), "switch_cpu_bmp", &sc->sc_config.switch_cpu_bmp, sizeof(sc->sc_config.switch_cpu_bmp)); if (ret < 0) { device_printf(dev, "%s: missing switch_cpu_bmp property\n", __func__); goto error; } ret = OF_getencprop(ofw_bus_get_node(dev), "switch_lan_bmp", &sc->sc_config.switch_lan_bmp, sizeof(sc->sc_config.switch_lan_bmp)); if (ret < 0) { device_printf(dev, "%s: missing switch_lan_bmp property\n", __func__); goto error; } ret = OF_getencprop(ofw_bus_get_node(dev), "switch_wan_bmp", &sc->sc_config.switch_wan_bmp, sizeof(sc->sc_config.switch_wan_bmp)); if (ret < 0) { device_printf(dev, "%s: missing switch_wan_bmp property\n", __func__); goto error; } ret = clk_get_by_ofw_name(dev, 0, "ess_clk", &sc->sc_ess_clk); if (ret != 0) { device_printf(dev, "%s: failed to find ess_clk (%d)\n", __func__, ret); goto error; } ret = clk_enable(sc->sc_ess_clk); if (ret != 0) { device_printf(dev, "%s: failed to enable clock (%d)\n", __func__, ret); goto error; } ret = hwreset_get_by_ofw_name(dev, 0, "ess_rst", &sc->sc_ess_rst); if (ret != 0) { device_printf(dev, "%s: failed to find ess_rst (%d)\n", __func__, ret); goto error; } /* * Ok, at this point we have enough resources to do an initial * reset and configuration. */ AR40XX_LOCK(sc); /* Initial PSGMII/RGMII port configuration */ ret = ar40xx_hw_psgmii_init_config(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to init PSGMII (%d)\n", ret); goto error_locked; } /* * ESS reset - this resets both the ethernet switch * AND the ethernet block. */ ret = ar40xx_hw_ess_reset(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to reset ESS block (%d)\n", ret); goto error_locked; } /* * Check the PHY IDs for each of the PHYs from 0..4; * this is useful to make sure that we can SEE the external * PHY(s). */ if (bootverbose) { ret = ar40xx_hw_phy_get_ids(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to check PHY IDs (%d)\n", ret); goto error_locked; } } /* * Do PSGMII PHY self-test; work-around issues. */ ret = ar40xx_hw_psgmii_self_test(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to do PSGMII self-test (%d)\n", ret); goto error_locked; } /* Return port config to runtime state */ ret = ar40xx_hw_psgmii_self_test_clean(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to do PSGMII runtime config (%d)\n", ret); goto error_locked; } /* mac_mode_init */ ret = ar40xx_hw_psgmii_set_mac_mode(sc, sc->sc_config.switch_mac_mode); /* Initialise each hardware port */ for (i = 0; i < AR40XX_NUM_PORTS; i++) { ret = ar40xx_hw_port_init(sc, i); } /* initialise the global switch configuration */ ret = ar40xx_hw_init_globals(sc); /* reset the switch vlan/port learning config */ ret = ar40xx_reset_switch(sc); /* cpuport setup */ ret = ar40xx_hw_port_cpuport_setup(sc); AR40XX_UNLOCK(sc); #if 0 /* We may end up needing the QM workaround code here.. */ device_printf(dev, "%s: TODO: QM error check\n", __func__); #endif /* Attach PHYs */ ret = ar40xx_attach_phys(sc); bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); /* Start timer */ callout_init_mtx(&sc->sc_phy_callout, &sc->sc_mtx, 0); /* * Setup the etherswitch info block. */ strlcpy(sc->sc_info.es_name, device_get_desc(dev), sizeof(sc->sc_info.es_name)); sc->sc_info.es_nports = AR40XX_NUM_PORTS; sc->sc_info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q; /* XXX TODO: double-tag / 802.1ad */ sc->sc_info.es_nvlangroups = AR40XX_NUM_VTU_ENTRIES; /* * Fetch the initial port configuration. */ AR40XX_LOCK(sc); ar40xx_tick(sc); AR40XX_UNLOCK(sc); ar40xx_sysctl_attach(sc); return (0); error_locked: AR40XX_UNLOCK(sc); error: ar40xx_detach(dev); return (ENXIO); } static void ar40xx_lock(device_t dev) { struct ar40xx_softc *sc = device_get_softc(dev); AR40XX_LOCK(sc); } static void ar40xx_unlock(device_t dev) { struct ar40xx_softc *sc = device_get_softc(dev); AR40XX_LOCK_ASSERT(sc); AR40XX_UNLOCK(sc); } static etherswitch_info_t * ar40xx_getinfo(device_t dev) { struct ar40xx_softc *sc = device_get_softc(dev); return (&sc->sc_info); } static int ar40xx_readreg(device_t dev, int addr) { struct ar40xx_softc *sc = device_get_softc(dev); if (addr >= sc->sc_ess_mem_size - 1) return (-1); AR40XX_REG_BARRIER_READ(sc); return AR40XX_REG_READ(sc, addr); } static int ar40xx_writereg(device_t dev, int addr, int value) { struct ar40xx_softc *sc = device_get_softc(dev); if (addr >= sc->sc_ess_mem_size - 1) return (-1); AR40XX_REG_WRITE(sc, addr, value); AR40XX_REG_BARRIER_WRITE(sc); return (0); } /* * Get the port configuration and status. */ static int ar40xx_getport(device_t dev, etherswitch_port_t *p) { struct ar40xx_softc *sc = device_get_softc(dev); struct mii_data *mii = NULL; struct ifmediareq *ifmr; int err; if (p->es_port < 0 || p->es_port > sc->sc_info.es_nports) return (ENXIO); AR40XX_LOCK(sc); /* Fetch the current VLAN configuration for this port */ /* PVID */ ar40xx_hw_get_port_pvid(sc, p->es_port, &p->es_pvid); /* * The VLAN egress aren't appropriate to the ports; * instead it's part of the VLAN group config. */ /* Get MII config */ mii = ar40xx_phy_miiforport(sc, p->es_port); AR40XX_UNLOCK(sc); if (p->es_port == 0) { /* CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr = &p->es_ifmr; ifmr->ifm_count = 0; ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else if (mii != NULL) { /* non-CPU port */ err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } else { return (ENXIO); } return (0); } /* * Set the port configuration and status. */ static int ar40xx_setport(device_t dev, etherswitch_port_t *p) { struct ar40xx_softc *sc = device_get_softc(dev); struct ifmedia *ifm; struct mii_data *mii; if_t ifp; int ret; if (p->es_port < 0 || p->es_port > sc->sc_info.es_nports) return (EINVAL); /* Port flags */ AR40XX_LOCK(sc); ret = ar40xx_hw_set_port_pvid(sc, p->es_port, p->es_pvid); if (ret != 0) { AR40XX_UNLOCK(sc); return (ret); } /* XXX TODO: tag strip/unstrip, double-tag, etc */ AR40XX_UNLOCK(sc); /* Don't change media config on CPU port */ if (p->es_port == 0) return (0); mii = ar40xx_phy_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); ifp = ar40xx_phy_ifpforport(sc, p->es_port); ifm = &mii->mii_media; return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); return (0); } /* * Get the current VLAN group (per-port, ISL, dot1q) configuration. * * For now the only supported operating mode is dot1q. */ static int ar40xx_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct ar40xx_softc *sc = device_get_softc(dev); int vid, ret; if (vg->es_vlangroup > sc->sc_info.es_nvlangroups) return (EINVAL); vg->es_untagged_ports = 0; vg->es_member_ports = 0; vg->es_fid = 0; AR40XX_LOCK(sc); /* Note: only supporting 802.1q VLAN config for now */ if (sc->sc_vlan.vlan != 1) { vg->es_member_ports = 0; vg->es_untagged_ports = 0; AR40XX_UNLOCK(sc); return (-1); } /* Get vlangroup mapping to VLAN id */ vid = sc->sc_vlan.vlan_id[vg->es_vlangroup]; if ((vid & ETHERSWITCH_VID_VALID) == 0) { /* Not an active vgroup; bail */ AR40XX_UNLOCK(sc); return (0); } vg->es_vid = vid; ret = ar40xx_hw_vtu_get_vlan(sc, vid, &vg->es_member_ports, &vg->es_untagged_ports); AR40XX_UNLOCK(sc); if (ret == 0) { vg->es_vid |= ETHERSWITCH_VID_VALID; } return (ret); } /* * Set the current VLAN group (per-port, ISL, dot1q) configuration. * * For now the only supported operating mode is dot1q. */ static int ar40xx_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct ar40xx_softc *sc = device_get_softc(dev); int err, vid; /* For now we only support 802.1q mode */ if (sc->sc_vlan.vlan == 0) return (EINVAL); AR40XX_LOCK(sc); vid = sc->sc_vlan.vlan_id[vg->es_vlangroup]; /* * If we have an 802.1q VID and it's different to the current one, * purge the current VTU entry. */ if ((vid != 0) && ((vid & ETHERSWITCH_VID_VALID) != 0) && ((vid & ETHERSWITCH_VID_MASK) != (vg->es_vid & ETHERSWITCH_VID_MASK))) { AR40XX_DPRINTF(sc, AR40XX_DBG_VTU_OP, "%s: purging VID %d first\n", __func__, vid); err = ar40xx_hw_vtu_flush(sc); if (err != 0) { AR40XX_UNLOCK(sc); return (err); } } /* Update VLAN ID */ vid = vg->es_vid & ETHERSWITCH_VID_MASK; sc->sc_vlan.vlan_id[vg->es_vlangroup] = vid; if (vid == 0) { /* Setting it to 0 disables the group */ AR40XX_UNLOCK(sc); return (0); } /* Add valid bit for this entry */ sc->sc_vlan.vlan_id[vg->es_vlangroup] = vid | ETHERSWITCH_VID_VALID; /* Update hardware */ err = ar40xx_hw_vtu_load_vlan(sc, vid, vg->es_member_ports, vg->es_untagged_ports); if (err != 0) { AR40XX_UNLOCK(sc); return (err); } /* Update the config for the given entry */ sc->sc_vlan.vlan_ports[vg->es_vlangroup] = vg->es_member_ports; sc->sc_vlan.vlan_untagged[vg->es_vlangroup] = vg->es_untagged_ports; AR40XX_UNLOCK(sc); return (0); } /* * Get the current configuration mode. */ static int ar40xx_getconf(device_t dev, etherswitch_conf_t *conf) { struct ar40xx_softc *sc = device_get_softc(dev); int ret; AR40XX_LOCK(sc); /* Only support dot1q VLAN for now */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; /* Switch MAC address */ ret = ar40xx_hw_read_switch_mac_address(sc, &conf->switch_macaddr); if (ret == 0) conf->cmd |= ETHERSWITCH_CONF_SWITCH_MACADDR; AR40XX_UNLOCK(sc); return (0); } /* * Set the current configuration and do a switch reset. * * For now the only supported operating mode is dot1q, don't * allow it to be set to non-dot1q. */ static int ar40xx_setconf(device_t dev, etherswitch_conf_t *conf) { struct ar40xx_softc *sc = device_get_softc(dev); int ret = 0; if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) { /* Only support dot1q VLAN for now */ if (conf->vlan_mode != ETHERSWITCH_VLAN_DOT1Q) return (EINVAL); } if (conf->cmd & ETHERSWITCH_CONF_SWITCH_MACADDR) { AR40XX_LOCK(sc); ret = ar40xx_hw_read_switch_mac_address(sc, &conf->switch_macaddr); AR40XX_UNLOCK(sc); } return (ret); } /* * Flush all ATU entries. */ static int ar40xx_atu_flush_all(device_t dev) { struct ar40xx_softc *sc = device_get_softc(dev); int ret; AR40XX_LOCK(sc); ret = ar40xx_hw_atu_flush_all(sc); AR40XX_UNLOCK(sc); return (ret); } /* * Flush all ATU entries for the given port. */ static int ar40xx_atu_flush_port(device_t dev, int port) { struct ar40xx_softc *sc = device_get_softc(dev); int ret; AR40XX_LOCK(sc); ret = ar40xx_hw_atu_flush_port(sc, port); AR40XX_UNLOCK(sc); return (ret); } /* * Load the ATU table into local storage so it can be iterated * over. */ static int ar40xx_atu_fetch_table(device_t dev, etherswitch_atu_table_t *table) { struct ar40xx_softc *sc = device_get_softc(dev); int err, nitems; memset(&sc->atu.entries, 0, sizeof(sc->atu.entries)); table->es_nitems = 0; nitems = 0; AR40XX_LOCK(sc); sc->atu.count = 0; err = ar40xx_hw_atu_fetch_entry(sc, NULL, 0); if (err != 0) goto done; while (nitems < AR40XX_NUM_ATU_ENTRIES) { err = ar40xx_hw_atu_fetch_entry(sc, &sc->atu.entries[nitems], 1); if (err != 0) goto done; sc->atu.entries[nitems].id = nitems; nitems++; } done: sc->atu.count = nitems; table->es_nitems = nitems; AR40XX_UNLOCK(sc); return (0); } /* * Iterate over the ATU table entries that have been previously * fetched. */ static int ar40xx_atu_fetch_table_entry(device_t dev, etherswitch_atu_entry_t *e) { struct ar40xx_softc *sc = device_get_softc(dev); int id, err = 0; id = e->id; AR40XX_LOCK(sc); if (id > sc->atu.count) { err = ENOENT; goto done; } memcpy(e, &sc->atu.entries[id], sizeof(*e)); done: AR40XX_UNLOCK(sc); return (err); } static device_method_t ar40xx_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ar40xx_probe), DEVMETHOD(device_attach, ar40xx_attach), DEVMETHOD(device_detach, ar40xx_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, ar40xx_readphy), DEVMETHOD(miibus_writereg, ar40xx_writephy), DEVMETHOD(miibus_statchg, ar40xx_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, ar40xx_readphy), DEVMETHOD(mdio_writereg, ar40xx_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, ar40xx_lock), DEVMETHOD(etherswitch_unlock, ar40xx_unlock), DEVMETHOD(etherswitch_getinfo, ar40xx_getinfo), DEVMETHOD(etherswitch_readreg, ar40xx_readreg), DEVMETHOD(etherswitch_writereg, ar40xx_writereg), DEVMETHOD(etherswitch_readphyreg, ar40xx_readphy), DEVMETHOD(etherswitch_writephyreg, ar40xx_writephy), DEVMETHOD(etherswitch_getport, ar40xx_getport), DEVMETHOD(etherswitch_setport, ar40xx_setport), DEVMETHOD(etherswitch_getvgroup, ar40xx_getvgroup), DEVMETHOD(etherswitch_setvgroup, ar40xx_setvgroup), DEVMETHOD(etherswitch_getconf, ar40xx_getconf), DEVMETHOD(etherswitch_setconf, ar40xx_setconf), DEVMETHOD(etherswitch_flush_all, ar40xx_atu_flush_all), DEVMETHOD(etherswitch_flush_port, ar40xx_atu_flush_port), DEVMETHOD(etherswitch_fetch_table, ar40xx_atu_fetch_table), DEVMETHOD(etherswitch_fetch_table_entry, ar40xx_atu_fetch_table_entry), DEVMETHOD_END }; DEFINE_CLASS_0(ar40xx, ar40xx_driver, ar40xx_methods, sizeof(struct ar40xx_softc)); DRIVER_MODULE(ar40xx, simplebus, ar40xx_driver, 0, 0); DRIVER_MODULE(ar40xx, ofwbus, ar40xx_driver, 0, 0); DRIVER_MODULE(miibus, ar40xx, miibus_driver, 0, 0); DRIVER_MODULE(mdio, ar40xx, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, ar40xx, etherswitch_driver, 0, 0); MODULE_DEPEND(ar40xx, mdio, 1, 1, 1); MODULE_DEPEND(ar40xx, miibus, 1, 1, 1); MODULE_DEPEND(ar40xx, etherswitch, 1, 1, 1); MODULE_VERSION(ar40xx, 1); diff --git a/sys/dev/etherswitch/arswitch/arswitch.c b/sys/dev/etherswitch/arswitch/arswitch.c index 92c3460e5f78..924793a0488f 100644 --- a/sys/dev/etherswitch/arswitch/arswitch.c +++ b/sys/dev/etherswitch/arswitch/arswitch.c @@ -1,1312 +1,1313 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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 #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 "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" /* Map ETHERSWITCH_PORT_LED_* to Atheros pattern codes */ static int led_pattern_table[] = { [ETHERSWITCH_PORT_LED_DEFAULT] = 0x3, [ETHERSWITCH_PORT_LED_ON] = 0x2, [ETHERSWITCH_PORT_LED_OFF] = 0x0, [ETHERSWITCH_PORT_LED_BLINK] = 0x1 }; static inline int arswitch_portforphy(int phy); static void arswitch_tick(void *arg); static int arswitch_ifmedia_upd(if_t); static void arswitch_ifmedia_sts(if_t, struct ifmediareq *); static int ar8xxx_port_vlan_setup(struct arswitch_softc *sc, etherswitch_port_t *p); static int ar8xxx_port_vlan_get(struct arswitch_softc *sc, etherswitch_port_t *p); static int arswitch_setled(struct arswitch_softc *sc, int phy, int led, int style); static int arswitch_probe(device_t dev) { struct arswitch_softc *sc; uint32_t id; char *chipname; sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); sc->page = -1; /* AR8xxx probe */ id = arswitch_readreg(dev, AR8X16_REG_MASK_CTRL); sc->chip_rev = (id & AR8X16_MASK_CTRL_REV_MASK); sc->chip_ver = (id & AR8X16_MASK_CTRL_VER_MASK) >> AR8X16_MASK_CTRL_VER_SHIFT; switch (id & (AR8X16_MASK_CTRL_VER_MASK | AR8X16_MASK_CTRL_REV_MASK)) { case 0x0101: chipname = "AR8216"; sc->sc_switchtype = AR8X16_SWITCH_AR8216; break; case 0x0201: chipname = "AR8226"; sc->sc_switchtype = AR8X16_SWITCH_AR8226; break; /* 0x0301 - AR8236 */ case 0x1000: case 0x1001: chipname = "AR8316"; sc->sc_switchtype = AR8X16_SWITCH_AR8316; break; case 0x1202: case 0x1204: chipname = "AR8327"; sc->sc_switchtype = AR8X16_SWITCH_AR8327; sc->mii_lo_first = 1; break; default: chipname = NULL; } DPRINTF(sc, ARSWITCH_DBG_ANY, "chipname=%s, id=%08x\n", chipname, id); if (chipname != NULL) { device_set_descf(dev, "Atheros %s Ethernet Switch (ver %d rev %d)", chipname, sc->chip_ver, sc->chip_rev); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int arswitch_attach_phys(struct arswitch_softc *sc) { int phy, err = 0; char name[IFNAMSIZ]; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < sc->numphys; phy++) { sc->ifp[phy] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[phy], sc); if_setflagbits(sc->ifp[phy], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX, 0); sc->ifname[phy] = malloc(strlen(name)+1, M_DEVBUF, M_WAITOK); bcopy(name, sc->ifname[phy], strlen(name)+1); if_initname(sc->ifp[phy], sc->ifname[phy], arswitch_portforphy(phy)); err = mii_attach(sc->sc_dev, &sc->miibus[phy], sc->ifp[phy], arswitch_ifmedia_upd, arswitch_ifmedia_sts, \ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); #if 0 DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", device_get_nameunit(sc->miibus[phy]), sc->ifp[phy]->if_xname); #endif if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); return (err); } if (AR8X16_IS_SWITCH(sc, AR8327)) { int led; char ledname[IFNAMSIZ+4]; for (led = 0; led < 3; led++) { sprintf(ledname, "%s%dled%d", name, arswitch_portforphy(phy), led+1); sc->dev_led[phy][led].sc = sc; sc->dev_led[phy][led].phy = phy; sc->dev_led[phy][led].lednum = led; } } } return (0); } static int arswitch_reset(device_t dev) { arswitch_writereg(dev, AR8X16_REG_MASK_CTRL, AR8X16_MASK_CTRL_SOFT_RESET); DELAY(1000); if (arswitch_readreg(dev, AR8X16_REG_MASK_CTRL) & AR8X16_MASK_CTRL_SOFT_RESET) { device_printf(dev, "unable to reset switch\n"); return (-1); } return (0); } static int arswitch_set_vlan_mode(struct arswitch_softc *sc, uint32_t mode) { /* Check for invalid modes. */ if ((mode & sc->info.es_vlan_caps) != mode) return (EINVAL); switch (mode) { case ETHERSWITCH_VLAN_DOT1Q: sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; break; case ETHERSWITCH_VLAN_PORT: sc->vlan_mode = ETHERSWITCH_VLAN_PORT; break; default: sc->vlan_mode = 0; } /* Reset VLANs. */ sc->hal.arswitch_vlan_init_hw(sc); return (0); } static void ar8xxx_port_init(struct arswitch_softc *sc, int port) { /* Port0 - CPU */ if (port == AR8X16_PORT_CPU) { arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_STS(0), (AR8X16_IS_SWITCH(sc, AR8216) ? AR8X16_PORT_STS_SPEED_100 : AR8X16_PORT_STS_SPEED_1000) | (AR8X16_IS_SWITCH(sc, AR8216) ? 0 : AR8X16_PORT_STS_RXFLOW) | (AR8X16_IS_SWITCH(sc, AR8216) ? 0 : AR8X16_PORT_STS_TXFLOW) | AR8X16_PORT_STS_RXMAC | AR8X16_PORT_STS_TXMAC | AR8X16_PORT_STS_DUPLEX); arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_CTRL(0), arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(0)) & ~AR8X16_PORT_CTRL_HEADER); } else { /* Set ports to auto negotiation. */ arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_STS(port), AR8X16_PORT_STS_LINK_AUTO); arswitch_writereg(sc->sc_dev, AR8X16_REG_PORT_CTRL(port), arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(port)) & ~AR8X16_PORT_CTRL_HEADER); } } static int ar8xxx_atu_wait_ready(struct arswitch_softc *sc) { int ret; ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); ret = arswitch_waitreg(sc->sc_dev, AR8216_REG_ATU, AR8216_ATU_ACTIVE, 0, 1000); return (ret); } /* * Flush all ATU entries. */ static int ar8xxx_atu_flush(struct arswitch_softc *sc) { int ret; ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: flushing all ports\n", __func__); ret = ar8xxx_atu_wait_ready(sc); if (ret) device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__); if (!ret) arswitch_writereg(sc->sc_dev, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH | AR8216_ATU_ACTIVE); return (ret); } /* * Flush ATU entries for a single port. */ static int ar8xxx_atu_flush_port(struct arswitch_softc *sc, int port) { int ret, val; DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: flushing port %d\n", __func__, port); ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); /* Flush unicast entries on port */ val = AR8216_ATU_OP_FLUSH_UNICAST; /* TODO: bit 4 indicates whether to flush dynamic (0) or static (1) */ /* Which port */ val |= SM(port, AR8216_ATU_PORT_NUM); ret = ar8xxx_atu_wait_ready(sc); if (ret) device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__); if (!ret) arswitch_writereg(sc->sc_dev, AR8216_REG_ATU, val | AR8216_ATU_ACTIVE); return (ret); } /* * XXX TODO: flush a single MAC address. */ /* * Fetch a single entry from the ATU. */ static int ar8xxx_atu_fetch_table(struct arswitch_softc *sc, etherswitch_atu_entry_t *e, int atu_fetch_op) { uint32_t ret0, ret1, ret2, val; ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); switch (atu_fetch_op) { case 0: /* Initialise things for the first fetch */ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: initializing\n", __func__); (void) ar8xxx_atu_wait_ready(sc); arswitch_writereg(sc->sc_dev, AR8216_REG_ATU, AR8216_ATU_OP_GET_NEXT); arswitch_writereg(sc->sc_dev, AR8216_REG_ATU_DATA, 0); arswitch_writereg(sc->sc_dev, AR8216_REG_ATU_CTRL2, 0); return (0); case 1: DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: reading next\n", __func__); /* * Attempt to read the next address entry; don't modify what * is there in AT_ADDR{4,5} as its used for the next fetch */ (void) ar8xxx_atu_wait_ready(sc); /* Begin the next read event; not modifying anything */ val = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU); val |= AR8216_ATU_ACTIVE; arswitch_writereg(sc->sc_dev, AR8216_REG_ATU, val); /* Wait for it to complete */ (void) ar8xxx_atu_wait_ready(sc); /* Fetch the ethernet address and ATU status */ ret0 = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU); ret1 = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU_DATA); ret2 = arswitch_readreg(sc->sc_dev, AR8216_REG_ATU_CTRL2); /* If the status is zero, then we're done */ if (MS(ret2, AR8216_ATU_CTRL2_AT_STATUS) == 0) return (-1); /* MAC address */ e->es_macaddr[5] = MS(ret0, AR8216_ATU_ADDR5); e->es_macaddr[4] = MS(ret0, AR8216_ATU_ADDR4); e->es_macaddr[3] = MS(ret1, AR8216_ATU_ADDR3); e->es_macaddr[2] = MS(ret1, AR8216_ATU_ADDR2); e->es_macaddr[1] = MS(ret1, AR8216_ATU_ADDR1); e->es_macaddr[0] = MS(ret1, AR8216_ATU_ADDR0); /* Bitmask of ports this entry is for */ e->es_portmask = MS(ret2, AR8216_ATU_CTRL2_DESPORT); /* TODO: other flags that are interesting */ DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: MAC %6D portmask 0x%08x\n", __func__, e->es_macaddr, ":", e->es_portmask); return (0); default: return (-1); } return (-1); } /* * Configure aging register defaults. */ static int ar8xxx_atu_learn_default(struct arswitch_softc *sc) { int ret; uint32_t val; DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: resetting learning\n", __func__); /* * For now, configure the aging defaults: * * + ARP_EN - enable "acknowledgement" of ARP frames - they are * forwarded to the CPU port * + LEARN_CHANGE_EN - hash table violations when learning MAC addresses * will force an entry to be expired/updated and a new one to be * programmed in. * + AGE_EN - enable address table aging * + AGE_TIME - set to 5 minutes */ val = 0; val |= AR8216_ATU_CTRL_ARP_EN; val |= AR8216_ATU_CTRL_LEARN_CHANGE; val |= AR8216_ATU_CTRL_AGE_EN; val |= 0x2b; /* 5 minutes; bits 15:0 */ ret = arswitch_writereg(sc->sc_dev, AR8216_REG_ATU_CTRL, val); if (ret) device_printf(sc->sc_dev, "%s: writereg failed\n", __func__); return (ret); } /* * XXX TODO: add another routine to configure the leaky behaviour * when unknown frames are received. These must be consistent * between ethernet switches. */ /* * Fetch the configured switch MAC address. */ static int ar8xxx_hw_get_switch_macaddr(struct arswitch_softc *sc, struct ether_addr *ea) { uint32_t ret0, ret1; char *s; s = (void *) ea; ret0 = arswitch_readreg(sc->sc_dev, AR8X16_REG_SW_MAC_ADDR0); ret1 = arswitch_readreg(sc->sc_dev, AR8X16_REG_SW_MAC_ADDR1); s[5] = MS(ret0, AR8X16_REG_SW_MAC_ADDR0_BYTE5); s[4] = MS(ret0, AR8X16_REG_SW_MAC_ADDR0_BYTE4); s[3] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE3); s[2] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE2); s[1] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE1); s[0] = MS(ret1, AR8X16_REG_SW_MAC_ADDR1_BYTE0); return (0); } /* * Set the switch mac address. */ static int ar8xxx_hw_set_switch_macaddr(struct arswitch_softc *sc, const struct ether_addr *ea) { return (ENXIO); } /* * XXX TODO: this attach routine does NOT free all memory, resources * upon failure! */ static int arswitch_attach(device_t dev) { struct arswitch_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; int err = 0; int port; /* sc->sc_switchtype is already decided in arswitch_probe() */ sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "arswitch", NULL, MTX_DEF); sc->page = -1; strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name)); /* Debugging */ ctx = device_get_sysctl_ctx(sc->sc_dev); tree = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, "control debugging printfs"); /* Allocate a 128 entry ATU table; hopefully its big enough! */ /* XXX TODO: make this per chip */ sc->atu.entries = malloc(sizeof(etherswitch_atu_entry_t) * 128, M_DEVBUF, M_NOWAIT); if (sc->atu.entries == NULL) { device_printf(sc->sc_dev, "%s: failed to allocate ATU table\n", __func__); return (ENXIO); } sc->atu.count = 0; sc->atu.size = 128; /* Default HAL methods */ sc->hal.arswitch_port_init = ar8xxx_port_init; sc->hal.arswitch_port_vlan_setup = ar8xxx_port_vlan_setup; sc->hal.arswitch_port_vlan_get = ar8xxx_port_vlan_get; sc->hal.arswitch_vlan_init_hw = ar8xxx_reset_vlans; sc->hal.arswitch_hw_get_switch_macaddr = ar8xxx_hw_get_switch_macaddr; sc->hal.arswitch_hw_set_switch_macaddr = ar8xxx_hw_set_switch_macaddr; sc->hal.arswitch_vlan_getvgroup = ar8xxx_getvgroup; sc->hal.arswitch_vlan_setvgroup = ar8xxx_setvgroup; sc->hal.arswitch_vlan_get_pvid = ar8xxx_get_pvid; sc->hal.arswitch_vlan_set_pvid = ar8xxx_set_pvid; sc->hal.arswitch_get_dot1q_vlan = ar8xxx_get_dot1q_vlan; sc->hal.arswitch_set_dot1q_vlan = ar8xxx_set_dot1q_vlan; sc->hal.arswitch_flush_dot1q_vlan = ar8xxx_flush_dot1q_vlan; sc->hal.arswitch_purge_dot1q_vlan = ar8xxx_purge_dot1q_vlan; sc->hal.arswitch_get_port_vlan = ar8xxx_get_port_vlan; sc->hal.arswitch_set_port_vlan = ar8xxx_set_port_vlan; sc->hal.arswitch_atu_flush = ar8xxx_atu_flush; sc->hal.arswitch_atu_flush_port = ar8xxx_atu_flush_port; sc->hal.arswitch_atu_learn_default = ar8xxx_atu_learn_default; sc->hal.arswitch_atu_fetch_table = ar8xxx_atu_fetch_table; sc->hal.arswitch_phy_read = arswitch_readphy_internal; sc->hal.arswitch_phy_write = arswitch_writephy_internal; /* * Attach switch related functions */ if (AR8X16_IS_SWITCH(sc, AR8216)) ar8216_attach(sc); else if (AR8X16_IS_SWITCH(sc, AR8226)) ar8226_attach(sc); else if (AR8X16_IS_SWITCH(sc, AR8316)) ar8316_attach(sc); else if (AR8X16_IS_SWITCH(sc, AR8327)) ar8327_attach(sc); else { DPRINTF(sc, ARSWITCH_DBG_ANY, "%s: unknown switch (%d)?\n", __func__, sc->sc_switchtype); return (ENXIO); } /* Common defaults. */ sc->info.es_nports = 5; /* XXX technically 6, but 6th not used */ /* XXX Defaults for externally connected AR8316 */ sc->numphys = 4; sc->phy4cpu = 1; sc->is_rgmii = 1; sc->is_gmii = 0; sc->is_mii = 0; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "numphys", &sc->numphys); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phy4cpu", &sc->phy4cpu); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "is_rgmii", &sc->is_rgmii); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "is_gmii", &sc->is_gmii); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "is_mii", &sc->is_mii); if (sc->numphys > AR8X16_NUM_PHYS) sc->numphys = AR8X16_NUM_PHYS; /* Reset the switch. */ if (arswitch_reset(dev)) { DPRINTF(sc, ARSWITCH_DBG_ANY, "%s: arswitch_reset: failed\n", __func__); return (ENXIO); } err = sc->hal.arswitch_hw_setup(sc); if (err != 0) { DPRINTF(sc, ARSWITCH_DBG_ANY, "%s: hw_setup: err=%d\n", __func__, err); return (err); } err = sc->hal.arswitch_hw_global_setup(sc); if (err != 0) { DPRINTF(sc, ARSWITCH_DBG_ANY, "%s: hw_global_setup: err=%d\n", __func__, err); return (err); } /* * Configure the default address table learning parameters for this * switch. */ err = sc->hal.arswitch_atu_learn_default(sc); if (err != 0) { DPRINTF(sc, ARSWITCH_DBG_ANY, "%s: atu_learn_default: err=%d\n", __func__, err); return (err); } /* Initialize the switch ports. */ for (port = 0; port <= sc->numphys; port++) { sc->hal.arswitch_port_init(sc, port); } /* * Attach the PHYs and complete the bus enumeration. */ err = arswitch_attach_phys(sc); if (err != 0) { DPRINTF(sc, ARSWITCH_DBG_ANY, "%s: attach_phys: err=%d\n", __func__, err); return (err); } /* Default to ingress filters off. */ err = arswitch_set_vlan_mode(sc, 0); if (err != 0) { DPRINTF(sc, ARSWITCH_DBG_ANY, "%s: set_vlan_mode: err=%d\n", __func__, err); return (err); } bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init_mtx(&sc->callout_tick, &sc->sc_mtx, 0); ARSWITCH_LOCK(sc); arswitch_tick(sc); ARSWITCH_UNLOCK(sc); return (err); } static int arswitch_detach(device_t dev) { struct arswitch_softc *sc = device_get_softc(dev); - int i; + int error, i; callout_drain(&sc->callout_tick); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + for (i=0; i < sc->numphys; i++) { - if (sc->miibus[i] != NULL) - device_delete_child(dev, sc->miibus[i]); if (sc->ifp[i] != NULL) if_free(sc->ifp[i]); free(sc->ifname[i], M_DEVBUF); } free(sc->atu.entries, M_DEVBUF); - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } /* * Convert PHY number to port number. PHY0 is connected to port 1, PHY1 to * port 2, etc. */ static inline int arswitch_portforphy(int phy) { return (phy+1); } static inline struct mii_data * arswitch_miiforport(struct arswitch_softc *sc, int port) { int phy = port-1; if (phy < 0 || phy >= sc->numphys) return (NULL); return (device_get_softc(sc->miibus[phy])); } static inline if_t arswitch_ifpforport(struct arswitch_softc *sc, int port) { int phy = port-1; if (phy < 0 || phy >= sc->numphys) return (NULL); return (sc->ifp[phy]); } /* * Convert port status to ifmedia. */ static void arswitch_update_ifmedia(int portstatus, u_int *media_status, u_int *media_active) { *media_active = IFM_ETHER; *media_status = IFM_AVALID; if ((portstatus & AR8X16_PORT_STS_LINK_UP) != 0) *media_status |= IFM_ACTIVE; else { *media_active |= IFM_NONE; return; } switch (portstatus & AR8X16_PORT_STS_SPEED_MASK) { case AR8X16_PORT_STS_SPEED_10: *media_active |= IFM_10_T; break; case AR8X16_PORT_STS_SPEED_100: *media_active |= IFM_100_TX; break; case AR8X16_PORT_STS_SPEED_1000: *media_active |= IFM_1000_T; break; } if ((portstatus & AR8X16_PORT_STS_DUPLEX) == 0) *media_active |= IFM_FDX; else *media_active |= IFM_HDX; if ((portstatus & AR8X16_PORT_STS_TXFLOW) != 0) *media_active |= IFM_ETH_TXPAUSE; if ((portstatus & AR8X16_PORT_STS_RXFLOW) != 0) *media_active |= IFM_ETH_RXPAUSE; } /* * Poll the status for all PHYs. We're using the switch port status because * thats a lot quicker to read than talking to all the PHYs. Care must be * taken that the resulting ifmedia_active is identical to what the PHY will * compute, or gratuitous link status changes will occur whenever the PHYs * update function is called. */ static void arswitch_miipollstat(struct arswitch_softc *sc) { int i; struct mii_data *mii; struct mii_softc *miisc; int portstatus; int port_flap = 0; ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); for (i = 0; i < sc->numphys; i++) { if (sc->miibus[i] == NULL) continue; mii = device_get_softc(sc->miibus[i]); /* XXX This would be nice to have abstracted out to be per-chip */ /* AR8327/AR8337 has a different register base */ if (AR8X16_IS_SWITCH(sc, AR8327)) portstatus = arswitch_readreg(sc->sc_dev, AR8327_REG_PORT_STATUS(arswitch_portforphy(i))); else portstatus = arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_STS(arswitch_portforphy(i))); #if 1 DPRINTF(sc, ARSWITCH_DBG_POLL, "p[%d]=0x%08x (%b)\n", i, portstatus, portstatus, "\20\3TXMAC\4RXMAC\5TXFLOW\6RXFLOW\7" "DUPLEX\11LINK_UP\12LINK_AUTO\13LINK_PAUSE"); #endif /* * If the current status is down, but we have a link * status showing up, we need to do an ATU flush. */ if ((mii->mii_media_status & IFM_ACTIVE) == 0 && (portstatus & AR8X16_PORT_STS_LINK_UP) != 0) { device_printf(sc->sc_dev, "%s: port %d: port -> UP\n", __func__, i); port_flap = 1; } /* * and maybe if a port goes up->down? */ if ((mii->mii_media_status & IFM_ACTIVE) != 0 && (portstatus & AR8X16_PORT_STS_LINK_UP) == 0) { device_printf(sc->sc_dev, "%s: port %d: port -> DOWN\n", __func__, i); port_flap = 1; } arswitch_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; mii_phy_update(miisc, MII_POLLSTAT); } } /* If a port went from down->up, flush the ATU */ if (port_flap) sc->hal.arswitch_atu_flush(sc); } static void arswitch_tick(void *arg) { struct arswitch_softc *sc = arg; arswitch_miipollstat(sc); callout_reset(&sc->callout_tick, hz, arswitch_tick, sc); } static void arswitch_lock(device_t dev) { struct arswitch_softc *sc = device_get_softc(dev); ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); ARSWITCH_LOCK(sc); } static void arswitch_unlock(device_t dev) { struct arswitch_softc *sc = device_get_softc(dev); ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); ARSWITCH_UNLOCK(sc); } static etherswitch_info_t * arswitch_getinfo(device_t dev) { struct arswitch_softc *sc = device_get_softc(dev); return (&sc->info); } static int ar8xxx_port_vlan_get(struct arswitch_softc *sc, etherswitch_port_t *p) { uint32_t reg; ARSWITCH_LOCK(sc); /* Retrieve the PVID. */ sc->hal.arswitch_vlan_get_pvid(sc, p->es_port, &p->es_pvid); /* Port flags. */ reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(p->es_port)); if (reg & AR8X16_PORT_CTRL_DOUBLE_TAG) p->es_flags |= ETHERSWITCH_PORT_DOUBLE_TAG; reg >>= AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT; if ((reg & 0x3) == AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_ADD) p->es_flags |= ETHERSWITCH_PORT_ADDTAG; if ((reg & 0x3) == AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_STRIP) p->es_flags |= ETHERSWITCH_PORT_STRIPTAG; ARSWITCH_UNLOCK(sc); return (0); } static int arswitch_is_cpuport(struct arswitch_softc *sc, int port) { return ((port == AR8X16_PORT_CPU) || ((AR8X16_IS_SWITCH(sc, AR8327) && port == AR8327_PORT_GMAC6))); } static int arswitch_getport(device_t dev, etherswitch_port_t *p) { struct arswitch_softc *sc; struct mii_data *mii; struct ifmediareq *ifmr; int err; sc = device_get_softc(dev); /* XXX +1 is for AR8327; should make this configurable! */ if (p->es_port < 0 || p->es_port > sc->info.es_nports) return (ENXIO); err = sc->hal.arswitch_port_vlan_get(sc, p); if (err != 0) return (err); mii = arswitch_miiforport(sc, p->es_port); if (arswitch_is_cpuport(sc, p->es_port)) { /* fill in fixed values for CPU port */ /* XXX is this valid in all cases? */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr = &p->es_ifmr; ifmr->ifm_count = 0; ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else if (mii != NULL) { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } else { return (ENXIO); } if (!arswitch_is_cpuport(sc, p->es_port) && AR8X16_IS_SWITCH(sc, AR8327)) { int led; p->es_nleds = 3; for (led = 0; led < p->es_nleds; led++) { int style; uint32_t val; /* Find the right style enum for our pattern */ val = arswitch_readreg(dev, ar8327_led_mapping[p->es_port-1][led].reg); val = (val>>ar8327_led_mapping[p->es_port-1][led].shift)&0x03; for (style = 0; style < ETHERSWITCH_PORT_LED_MAX; style++) { if (led_pattern_table[style] == val) break; } /* can't happen */ if (style == ETHERSWITCH_PORT_LED_MAX) style = ETHERSWITCH_PORT_LED_DEFAULT; p->es_led[led] = style; } } else { p->es_nleds = 0; } return (0); } static int ar8xxx_port_vlan_setup(struct arswitch_softc *sc, etherswitch_port_t *p) { uint32_t reg; int err; ARSWITCH_LOCK(sc); /* Set the PVID. */ if (p->es_pvid != 0) sc->hal.arswitch_vlan_set_pvid(sc, p->es_port, p->es_pvid); /* Mutually exclusive. */ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG && p->es_flags & ETHERSWITCH_PORT_STRIPTAG) { ARSWITCH_UNLOCK(sc); return (EINVAL); } reg = 0; if (p->es_flags & ETHERSWITCH_PORT_DOUBLE_TAG) reg |= AR8X16_PORT_CTRL_DOUBLE_TAG; if (p->es_flags & ETHERSWITCH_PORT_ADDTAG) reg |= AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_ADD << AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT; if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG) reg |= AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_STRIP << AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT; err = arswitch_modifyreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(p->es_port), 0x3 << AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT | AR8X16_PORT_CTRL_DOUBLE_TAG, reg); ARSWITCH_UNLOCK(sc); return (err); } static int arswitch_setport(device_t dev, etherswitch_port_t *p) { int err, i; struct arswitch_softc *sc; struct ifmedia *ifm; struct mii_data *mii; if_t ifp; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port > sc->info.es_nports) return (ENXIO); /* Port flags. */ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { err = sc->hal.arswitch_port_vlan_setup(sc, p); if (err) return (err); } /* Do not allow media or led changes on CPU port. */ if (arswitch_is_cpuport(sc, p->es_port)) return (0); if (AR8X16_IS_SWITCH(sc, AR8327)) { for (i = 0; i < 3; i++) { int err; err = arswitch_setled(sc, p->es_port-1, i, p->es_led[i]); if (err) return (err); } } mii = arswitch_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); ifp = arswitch_ifpforport(sc, p->es_port); ifm = &mii->mii_media; return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); } static int arswitch_setled(struct arswitch_softc *sc, int phy, int led, int style) { int shift; int err; if (phy < 0 || phy > sc->numphys) return EINVAL; if (style < 0 || style > ETHERSWITCH_PORT_LED_MAX) return (EINVAL); ARSWITCH_LOCK(sc); shift = ar8327_led_mapping[phy][led].shift; err = (arswitch_modifyreg(sc->sc_dev, ar8327_led_mapping[phy][led].reg, 0x03 << shift, led_pattern_table[style] << shift)); ARSWITCH_UNLOCK(sc); return (err); } static void arswitch_statchg(device_t dev) { struct arswitch_softc *sc = device_get_softc(dev); DPRINTF(sc, ARSWITCH_DBG_POLL, "%s\n", __func__); } static int arswitch_ifmedia_upd(if_t ifp) { struct arswitch_softc *sc = if_getsoftc(ifp); struct mii_data *mii = arswitch_miiforport(sc, if_getdunit(ifp)); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void arswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct arswitch_softc *sc = if_getsoftc(ifp); struct mii_data *mii = arswitch_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc, ARSWITCH_DBG_POLL, "%s\n", __func__); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int arswitch_getconf(device_t dev, etherswitch_conf_t *conf) { struct arswitch_softc *sc; int ret; sc = device_get_softc(dev); /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; /* Return the switch ethernet address. */ ret = sc->hal.arswitch_hw_get_switch_macaddr(sc, &conf->switch_macaddr); if (ret == 0) { conf->cmd |= ETHERSWITCH_CONF_SWITCH_MACADDR; } return (0); } static int arswitch_setconf(device_t dev, etherswitch_conf_t *conf) { struct arswitch_softc *sc; int err; sc = device_get_softc(dev); /* Set the VLAN mode. */ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) { err = arswitch_set_vlan_mode(sc, conf->vlan_mode); if (err != 0) return (err); } /* TODO: Set the switch ethernet address. */ return (0); } static int arswitch_atu_flush_all(device_t dev) { struct arswitch_softc *sc; int err; sc = device_get_softc(dev); ARSWITCH_LOCK(sc); err = sc->hal.arswitch_atu_flush(sc); /* Invalidate cached ATU */ sc->atu.count = 0; ARSWITCH_UNLOCK(sc); return (err); } static int arswitch_atu_flush_port(device_t dev, int port) { struct arswitch_softc *sc; int err; sc = device_get_softc(dev); ARSWITCH_LOCK(sc); err = sc->hal.arswitch_atu_flush_port(sc, port); /* Invalidate cached ATU */ sc->atu.count = 0; ARSWITCH_UNLOCK(sc); return (err); } static int arswitch_atu_fetch_table(device_t dev, etherswitch_atu_table_t *table) { struct arswitch_softc *sc; int err, nitems; sc = device_get_softc(dev); ARSWITCH_LOCK(sc); /* Initial setup */ nitems = 0; err = sc->hal.arswitch_atu_fetch_table(sc, NULL, 0); /* fetch - ideally yes we'd fetch into a separate table then switch */ while (err == 0 && nitems < sc->atu.size) { err = sc->hal.arswitch_atu_fetch_table(sc, &sc->atu.entries[nitems], 1); if (err == 0) { sc->atu.entries[nitems].id = nitems; nitems++; } } sc->atu.count = nitems; ARSWITCH_UNLOCK(sc); table->es_nitems = nitems; return (0); } static int arswitch_atu_fetch_table_entry(device_t dev, etherswitch_atu_entry_t *e) { struct arswitch_softc *sc; int id; sc = device_get_softc(dev); id = e->id; ARSWITCH_LOCK(sc); if (id > sc->atu.count) { ARSWITCH_UNLOCK(sc); return (ENOENT); } memcpy(e, &sc->atu.entries[id], sizeof(*e)); ARSWITCH_UNLOCK(sc); return (0); } static int arswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *e) { struct arswitch_softc *sc = device_get_softc(dev); return (sc->hal.arswitch_vlan_getvgroup(sc, e)); } static int arswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *e) { struct arswitch_softc *sc = device_get_softc(dev); return (sc->hal.arswitch_vlan_setvgroup(sc, e)); } static int arswitch_readphy(device_t dev, int phy, int reg) { struct arswitch_softc *sc = device_get_softc(dev); return (sc->hal.arswitch_phy_read(dev, phy, reg)); } static int arswitch_writephy(device_t dev, int phy, int reg, int val) { struct arswitch_softc *sc = device_get_softc(dev); return (sc->hal.arswitch_phy_write(dev, phy, reg, val)); } static device_method_t arswitch_methods[] = { /* Device interface */ DEVMETHOD(device_probe, arswitch_probe), DEVMETHOD(device_attach, arswitch_attach), DEVMETHOD(device_detach, arswitch_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, arswitch_readphy), DEVMETHOD(miibus_writereg, arswitch_writephy), DEVMETHOD(miibus_statchg, arswitch_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, arswitch_readphy), DEVMETHOD(mdio_writereg, arswitch_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, arswitch_lock), DEVMETHOD(etherswitch_unlock, arswitch_unlock), DEVMETHOD(etherswitch_getinfo, arswitch_getinfo), DEVMETHOD(etherswitch_readreg, arswitch_readreg), DEVMETHOD(etherswitch_writereg, arswitch_writereg), DEVMETHOD(etherswitch_readphyreg, arswitch_readphy), DEVMETHOD(etherswitch_writephyreg, arswitch_writephy), DEVMETHOD(etherswitch_getport, arswitch_getport), DEVMETHOD(etherswitch_setport, arswitch_setport), DEVMETHOD(etherswitch_getvgroup, arswitch_getvgroup), DEVMETHOD(etherswitch_setvgroup, arswitch_setvgroup), DEVMETHOD(etherswitch_getconf, arswitch_getconf), DEVMETHOD(etherswitch_setconf, arswitch_setconf), DEVMETHOD(etherswitch_flush_all, arswitch_atu_flush_all), DEVMETHOD(etherswitch_flush_port, arswitch_atu_flush_port), DEVMETHOD(etherswitch_fetch_table, arswitch_atu_fetch_table), DEVMETHOD(etherswitch_fetch_table_entry, arswitch_atu_fetch_table_entry), DEVMETHOD_END }; DEFINE_CLASS_0(arswitch, arswitch_driver, arswitch_methods, sizeof(struct arswitch_softc)); DRIVER_MODULE(arswitch, mdio, arswitch_driver, 0, 0); DRIVER_MODULE(miibus, arswitch, miibus_driver, 0, 0); DRIVER_MODULE(mdio, arswitch, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, arswitch, etherswitch_driver, 0, 0); MODULE_VERSION(arswitch, 1); MODULE_DEPEND(arswitch, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(arswitch, etherswitch, 1, 1, 1); /* XXX which versions? */ diff --git a/sys/dev/etherswitch/e6000sw/e6060sw.c b/sys/dev/etherswitch/e6000sw/e6060sw.c index 374eb8c5316b..901f887ffdc6 100644 --- a/sys/dev/etherswitch/e6000sw/e6060sw.c +++ b/sys/dev/etherswitch/e6000sw/e6060sw.c @@ -1,1020 +1,1021 @@ /*- * Copyright (c) 2016-2017 Hiroki Mori * Copyright (c) 2013 Luiz Otavio O Souza. * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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. */ /* * This code is Marvell 88E6060 ethernet switch support code on etherswitch * framework. * 88E6060 support is only port vlan support. Not support ingress/egress * trailer. * 88E6065 support is port and dot1q vlan. Also group base tag support. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" #define CORE_REGISTER 0x8 #define SWITCH_ID 3 #define PORT_CONTROL 4 #define ENGRESSFSHIFT 2 #define ENGRESSFMASK 3 #define ENGRESSTAGSHIFT 12 #define ENGRESSTAGMASK 3 #define PORT_VLAN_MAP 6 #define FORCEMAPSHIFT 8 #define FORCEMAPMASK 1 #define PORT_DEFVLAN 7 #define DEFVIDMASK 0xfff #define DEFPRIMASK 7 #define PORT_CONTROL2 8 #define DOT1QMODESHIFT 10 #define DOT1QMODEMASK 3 #define DOT1QNONE 0 #define DOT1QFALLBACK 1 #define DOT1QCHECK 2 #define DOT1QSECURE 3 #define GLOBAL_REGISTER 0xf #define VTU_OPERATION 5 #define VTU_VID_REG 6 #define VTU_DATA1_REG 7 #define VTU_DATA2_REG 8 #define VTU_DATA3_REG 9 #define VTU_BUSY 0x8000 #define VTU_FLASH 1 #define VTU_LOAD_PURGE 3 #define VTU_GET_NEXT 4 #define VTU_VIOLATION 7 MALLOC_DECLARE(M_E6060SW); MALLOC_DEFINE(M_E6060SW, "e6060sw", "e6060sw data structures"); struct e6060sw_softc { struct mtx sc_mtx; /* serialize access to softc */ device_t sc_dev; int vlan_mode; int media; /* cpu port media */ int cpuport; /* which PHY is connected to the CPU */ int phymask; /* PHYs we manage */ int numports; /* number of ports */ int ifpport[MII_NPHY]; int *portphy; char **ifname; device_t **miibus; if_t *ifp; struct callout callout_tick; etherswitch_info_t info; int smi_offset; int sw_model; }; /* Switch Identifier DeviceID */ #define E6060 0x60 #define E6063 0x63 #define E6065 0x65 #define E6060SW_LOCK(_sc) \ mtx_lock(&(_sc)->sc_mtx) #define E6060SW_UNLOCK(_sc) \ mtx_unlock(&(_sc)->sc_mtx) #define E6060SW_LOCK_ASSERT(_sc, _what) \ mtx_assert(&(_sc)->sc_mtx, (_what)) #define E6060SW_TRYLOCK(_sc) \ mtx_trylock(&(_sc)->sc_mtx) #if defined(DEBUG) #define DPRINTF(dev, args...) device_printf(dev, args) #else #define DPRINTF(dev, args...) #endif static inline int e6060sw_portforphy(struct e6060sw_softc *, int); static void e6060sw_tick(void *); static int e6060sw_ifmedia_upd(if_t); static void e6060sw_ifmedia_sts(if_t, struct ifmediareq *); static void e6060sw_setup(device_t dev); static int e6060sw_read_vtu(device_t dev, int num, int *data1, int *data2); static void e6060sw_set_vtu(device_t dev, int num, int data1, int data2); static int e6060sw_probe(device_t dev) { int data; struct e6060sw_softc *sc; int devid, i; char *devname; sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); devid = 0; for (i = 0; i < 2; ++i) { data = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + i * 0x10, SWITCH_ID); if (bootverbose) device_printf(dev,"Switch Identifier Register %x\n", data); devid = data >> 4; if (devid == E6060 || devid == E6063 || devid == E6065) { sc->sw_model = devid; sc->smi_offset = i * 0x10; break; } } if (devid == E6060) devname = "88E6060"; else if (devid == E6063) devname = "88E6063"; else if (devid == E6065) devname = "88E6065"; else return (ENXIO); device_set_descf(dev, "Marvell %s MDIO switch driver at 0x%02x", devname, sc->smi_offset); return (BUS_PROBE_DEFAULT); } static int e6060sw_attach_phys(struct e6060sw_softc *sc) { int phy, port, err; char name[IFNAMSIZ]; port = 0; err = 0; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < sc->numports; phy++) { if (((1 << phy) & sc->phymask) == 0) continue; sc->ifpport[phy] = port; sc->portphy[port] = phy; sc->ifp[port] = if_alloc(IFT_ETHER); sc->ifp[port]->if_softc = sc; sc->ifp[port]->if_flags |= IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX; if_initname(sc->ifp[port], name, port); sc->miibus[port] = malloc(sizeof(device_t), M_E6060SW, M_WAITOK | M_ZERO); err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port], e6060sw_ifmedia_upd, e6060sw_ifmedia_sts, \ BMSR_DEFCAPMASK, phy + sc->smi_offset, MII_OFFSET_ANY, 0); DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", device_get_nameunit(*sc->miibus[port]), sc->ifp[port]->if_xname); if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); break; } ++port; } sc->info.es_nports = port; if (sc->cpuport != -1) { /* assume cpuport is last one */ sc->ifpport[sc->cpuport] = port; sc->portphy[port] = sc->cpuport; ++sc->info.es_nports; } return (err); } static int e6060sw_attach(device_t dev) { struct e6060sw_softc *sc; int err; sc = device_get_softc(dev); err = 0; sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "e6060sw", NULL, MTX_DEF); strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name)); /* XXX Defaults */ if (sc->sw_model == E6063) { sc->numports = 3; sc->phymask = 0x07; sc->cpuport = 2; } else { sc->numports = 6; sc->phymask = 0x1f; sc->cpuport = 5; } sc->media = 100; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "numports", &sc->numports); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phymask", &sc->phymask); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "cpuport", &sc->cpuport); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "media", &sc->media); if (sc->sw_model == E6060) { sc->info.es_nvlangroups = sc->numports; sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT; } else { sc->info.es_nvlangroups = 64; sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q; } e6060sw_setup(dev); sc->ifp = malloc(sizeof(if_t) * sc->numports, M_E6060SW, M_WAITOK | M_ZERO); sc->ifname = malloc(sizeof(char *) * sc->numports, M_E6060SW, M_WAITOK | M_ZERO); sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_E6060SW, M_WAITOK | M_ZERO); sc->portphy = malloc(sizeof(int) * sc->numports, M_E6060SW, M_WAITOK | M_ZERO); /* * Attach the PHYs and complete the bus enumeration. */ err = e6060sw_attach_phys(sc); if (err != 0) return (err); bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init(&sc->callout_tick, 0); e6060sw_tick(sc); return (err); } static int e6060sw_detach(device_t dev) { struct e6060sw_softc *sc; - int i, port; + int error, i, port; sc = device_get_softc(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + callout_drain(&sc->callout_tick); for (i = 0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = e6060sw_portforphy(sc, i); - if (sc->miibus[port] != NULL) - device_delete_child(dev, (*sc->miibus[port])); if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); free(sc->ifname[port], M_E6060SW); free(sc->miibus[port], M_E6060SW); } free(sc->portphy, M_E6060SW); free(sc->miibus, M_E6060SW); free(sc->ifname, M_E6060SW); free(sc->ifp, M_E6060SW); - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } /* * Convert PHY number to port number. */ static inline int e6060sw_portforphy(struct e6060sw_softc *sc, int phy) { return (sc->ifpport[phy]); } static inline struct mii_data * e6060sw_miiforport(struct e6060sw_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); if (port == sc->cpuport) return (NULL); return (device_get_softc(*sc->miibus[port])); } static inline if_t e6060sw_ifpforport(struct e6060sw_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (sc->ifp[port]); } /* * Poll the status for all PHYs. */ static void e6060sw_miipollstat(struct e6060sw_softc *sc) { int i, port; struct mii_data *mii; struct mii_softc *miisc; E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED); for (i = 0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = e6060sw_portforphy(sc, i); if ((*sc->miibus[port]) == NULL) continue; mii = device_get_softc(*sc->miibus[port]); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; ukphy_status(miisc); mii_phy_update(miisc, MII_POLLSTAT); } } } static void e6060sw_tick(void *arg) { struct e6060sw_softc *sc; sc = arg; e6060sw_miipollstat(sc); callout_reset(&sc->callout_tick, hz, e6060sw_tick, sc); } static void e6060sw_lock(device_t dev) { struct e6060sw_softc *sc; sc = device_get_softc(dev); E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED); E6060SW_LOCK(sc); } static void e6060sw_unlock(device_t dev) { struct e6060sw_softc *sc; sc = device_get_softc(dev); E6060SW_LOCK_ASSERT(sc, MA_OWNED); E6060SW_UNLOCK(sc); } static etherswitch_info_t * e6060sw_getinfo(device_t dev) { struct e6060sw_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static int e6060sw_getport(device_t dev, etherswitch_port_t *p) { struct e6060sw_softc *sc; struct mii_data *mii; struct ifmediareq *ifmr; int err, phy; sc = device_get_softc(dev); ifmr = &p->es_ifmr; if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); p->es_pvid = 0; if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { p->es_pvid = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + p->es_port, PORT_DEFVLAN) & 0xfff; } phy = sc->portphy[p->es_port]; mii = e6060sw_miiforport(sc, p->es_port); if (sc->cpuport != -1 && phy == sc->cpuport) { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr->ifm_count = 0; if (sc->media == 100) ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_100_TX | IFM_FDX; else ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else if (mii != NULL) { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } else { return (ENXIO); } return (0); } static int e6060sw_setport(device_t dev, etherswitch_port_t *p) { struct e6060sw_softc *sc; struct ifmedia *ifm; struct mii_data *mii; if_t ifp; int err; int data; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { data = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + p->es_port, PORT_DEFVLAN); data &= ~0xfff; data |= p->es_pvid; data |= 1 << 12; MDIO_WRITEREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + p->es_port, PORT_DEFVLAN, data); } if (sc->portphy[p->es_port] == sc->cpuport) return(0); mii = e6060sw_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); ifp = e6060sw_ifpforport(sc, p->es_port); ifm = &mii->mii_media; err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA); return (err); } static int e6060sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct e6060sw_softc *sc; int data1, data2; int vid; int i, tag; sc = device_get_softc(dev); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { vg->es_vid = ETHERSWITCH_VID_VALID; vg->es_vid |= vg->es_vlangroup; data1 = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + vg->es_vlangroup, PORT_VLAN_MAP); vg->es_member_ports = data1 & 0x3f; vg->es_untagged_ports = vg->es_member_ports; vg->es_fid = 0; } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { if (vg->es_vlangroup == 0) return (0); vid = e6060sw_read_vtu(dev, vg->es_vlangroup, &data1, &data2); if (vid > 0) { vg->es_vid = ETHERSWITCH_VID_VALID; vg->es_vid |= vid; vg->es_member_ports = 0; vg->es_untagged_ports = 0; for (i = 0; i < 4; ++i) { tag = data1 >> (i * 4) & 3; if (tag == 0 || tag == 1) { vg->es_member_ports |= 1 << i; vg->es_untagged_ports |= 1 << i; } else if (tag == 2) { vg->es_member_ports |= 1 << i; } } for (i = 0; i < 2; ++i) { tag = data2 >> (i * 4) & 3; if (tag == 0 || tag == 1) { vg->es_member_ports |= 1 << (i + 4); vg->es_untagged_ports |= 1 << (i + 4); } else if (tag == 2) { vg->es_member_ports |= 1 << (i + 4); } } } } else { vg->es_vid = 0; } return (0); } static int e6060sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct e6060sw_softc *sc; int data1, data2; int i; sc = device_get_softc(dev); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { data1 = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + vg->es_vlangroup, PORT_VLAN_MAP); data1 &= ~0x3f; data1 |= vg->es_member_ports; MDIO_WRITEREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + vg->es_vlangroup, PORT_VLAN_MAP, data1); } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { if (vg->es_vlangroup == 0) return (0); data1 = 0; data2 = 0; for (i = 0; i < 6; ++i) { if (vg->es_member_ports & vg->es_untagged_ports & (1 << i)) { if (i < 4) { data1 |= (0xd << i * 4); } else { data2 |= (0xd << (i - 4) * 4); } } else if (vg->es_member_ports & (1 << i)) { if (i < 4) { data1 |= (0xe << i * 4); } else { data2 |= (0xe << (i - 4) * 4); } } else { if (i < 4) { data1 |= (0x3 << i * 4); } else { data2 |= (0x3 << (i - 4) * 4); } } } e6060sw_set_vtu(dev, vg->es_vlangroup, data1, data2); } return (0); } static void e6060sw_reset_vlans(device_t dev) { struct e6060sw_softc *sc; uint32_t ports; int i; int data; sc = device_get_softc(dev); for (i = 0; i <= sc->numports; i++) { ports = (1 << (sc->numports + 1)) - 1; ports &= ~(1 << i); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { data = i << 12; } else if (sc->vlan_mode == 0) { data = 1 << 8; } else { data = 0; } data |= ports; MDIO_WRITEREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_VLAN_MAP, data); } } static void e6060sw_setup(device_t dev) { struct e6060sw_softc *sc; int i; int data; sc = device_get_softc(dev); for (i = 0; i <= sc->numports; i++) { if (sc->sw_model == E6063 || sc->sw_model == E6065) { data = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_VLAN_MAP); data &= ~(FORCEMAPMASK << FORCEMAPSHIFT); MDIO_WRITEREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_VLAN_MAP, data); data = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_CONTROL); data |= 3 << ENGRESSFSHIFT; MDIO_WRITEREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_CONTROL, data); } } } static void e6060sw_dot1q_mode(device_t dev, int mode) { struct e6060sw_softc *sc; int i; int data; sc = device_get_softc(dev); for (i = 0; i <= sc->numports; i++) { data = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_CONTROL2); data &= ~(DOT1QMODEMASK << DOT1QMODESHIFT); data |= mode << DOT1QMODESHIFT; MDIO_WRITEREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_CONTROL2, data); data = MDIO_READREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_DEFVLAN); data &= ~0xfff; data |= 1; MDIO_WRITEREG(device_get_parent(dev), CORE_REGISTER + sc->smi_offset + i, PORT_DEFVLAN, data); } } static int e6060sw_getconf(device_t dev, etherswitch_conf_t *conf) { struct e6060sw_softc *sc; sc = device_get_softc(dev); /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; return (0); } static void e6060sw_init_vtu(device_t dev) { struct e6060sw_softc *sc; int busy; sc = device_get_softc(dev); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION, VTU_BUSY | (VTU_FLASH << 12)); while (1) { busy = MDIO_READREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION); if ((busy & VTU_BUSY) == 0) break; } /* initial member set at vlan 1*/ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_DATA1_REG, 0xcccc); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_DATA2_REG, 0x00cc); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_VID_REG, 0x1000 | 1); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION, VTU_BUSY | (VTU_LOAD_PURGE << 12) | 1); while (1) { busy = MDIO_READREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION); if ((busy & VTU_BUSY) == 0) break; } } static void e6060sw_set_vtu(device_t dev, int num, int data1, int data2) { struct e6060sw_softc *sc; int busy; sc = device_get_softc(dev); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_DATA1_REG, data1); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_DATA2_REG, data2); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_VID_REG, 0x1000 | num); MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION, VTU_BUSY | (VTU_LOAD_PURGE << 12) | num); while (1) { busy = MDIO_READREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION); if ((busy & VTU_BUSY) == 0) break; } } static int e6060sw_read_vtu(device_t dev, int num, int *data1, int *data2) { struct e6060sw_softc *sc; int busy; sc = device_get_softc(dev); num = num - 1; MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_VID_REG, num & 0xfff); /* Get Next */ MDIO_WRITEREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION, VTU_BUSY | (VTU_GET_NEXT << 12)); while (1) { busy = MDIO_READREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_OPERATION); if ((busy & VTU_BUSY) == 0) break; } int vid = MDIO_READREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_VID_REG); if (vid & 0x1000) { *data1 = MDIO_READREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_DATA1_REG); *data2 = MDIO_READREG(device_get_parent(dev), GLOBAL_REGISTER + sc->smi_offset, VTU_DATA2_REG); return (vid & 0xfff); } return (-1); } static int e6060sw_setconf(device_t dev, etherswitch_conf_t *conf) { struct e6060sw_softc *sc; sc = device_get_softc(dev); /* Set the VLAN mode. */ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) { if (conf->vlan_mode == ETHERSWITCH_VLAN_PORT) { sc->vlan_mode = ETHERSWITCH_VLAN_PORT; e6060sw_dot1q_mode(dev, DOT1QNONE); e6060sw_reset_vlans(dev); } else if ((sc->sw_model == E6063 || sc->sw_model == E6065) && conf->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; e6060sw_dot1q_mode(dev, DOT1QSECURE); e6060sw_init_vtu(dev); } else { sc->vlan_mode = 0; /* Reset VLANs. */ e6060sw_dot1q_mode(dev, DOT1QNONE); e6060sw_reset_vlans(dev); } } return (0); } static void e6060sw_statchg(device_t dev) { DPRINTF(dev, "%s\n", __func__); } static int e6060sw_ifmedia_upd(if_t ifp) { struct e6060sw_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = e6060sw_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void e6060sw_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct e6060sw_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = e6060sw_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int e6060sw_readphy(device_t dev, int phy, int reg) { struct e6060sw_softc *sc; int data; sc = device_get_softc(dev); E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED); if (phy < 0 || phy >= 32) return (ENXIO); if (reg < 0 || reg >= 32) return (ENXIO); E6060SW_LOCK(sc); data = MDIO_READREG(device_get_parent(dev), phy, reg); E6060SW_UNLOCK(sc); return (data); } static int e6060sw_writephy(device_t dev, int phy, int reg, int data) { struct e6060sw_softc *sc; int err; sc = device_get_softc(dev); E6060SW_LOCK_ASSERT(sc, MA_NOTOWNED); if (phy < 0 || phy >= 32) return (ENXIO); if (reg < 0 || reg >= 32) return (ENXIO); E6060SW_LOCK(sc); err = MDIO_WRITEREG(device_get_parent(dev), phy, reg, data); E6060SW_UNLOCK(sc); return (err); } /* addr is 5-8 bit is SMI Device Addres, 0-4 bit is SMI Register Address */ static int e6060sw_readreg(device_t dev, int addr) { int devaddr, regaddr; devaddr = (addr >> 5) & 0x1f; regaddr = addr & 0x1f; return MDIO_READREG(device_get_parent(dev), devaddr, regaddr); } /* addr is 5-8 bit is SMI Device Addres, 0-4 bit is SMI Register Address */ static int e6060sw_writereg(device_t dev, int addr, int value) { int devaddr, regaddr; devaddr = (addr >> 5) & 0x1f; regaddr = addr & 0x1f; return (MDIO_WRITEREG(device_get_parent(dev), devaddr, regaddr, value)); } static device_method_t e6060sw_methods[] = { /* Device interface */ DEVMETHOD(device_probe, e6060sw_probe), DEVMETHOD(device_attach, e6060sw_attach), DEVMETHOD(device_detach, e6060sw_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, e6060sw_readphy), DEVMETHOD(miibus_writereg, e6060sw_writephy), DEVMETHOD(miibus_statchg, e6060sw_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, e6060sw_readphy), DEVMETHOD(mdio_writereg, e6060sw_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, e6060sw_lock), DEVMETHOD(etherswitch_unlock, e6060sw_unlock), DEVMETHOD(etherswitch_getinfo, e6060sw_getinfo), DEVMETHOD(etherswitch_readreg, e6060sw_readreg), DEVMETHOD(etherswitch_writereg, e6060sw_writereg), DEVMETHOD(etherswitch_readphyreg, e6060sw_readphy), DEVMETHOD(etherswitch_writephyreg, e6060sw_writephy), DEVMETHOD(etherswitch_getport, e6060sw_getport), DEVMETHOD(etherswitch_setport, e6060sw_setport), DEVMETHOD(etherswitch_getvgroup, e6060sw_getvgroup), DEVMETHOD(etherswitch_setvgroup, e6060sw_setvgroup), DEVMETHOD(etherswitch_setconf, e6060sw_setconf), DEVMETHOD(etherswitch_getconf, e6060sw_getconf), DEVMETHOD_END }; DEFINE_CLASS_0(e6060sw, e6060sw_driver, e6060sw_methods, sizeof(struct e6060sw_softc)); DRIVER_MODULE(e6060sw, mdio, e6060sw_driver, 0, 0); DRIVER_MODULE(miibus, e6060sw, miibus_driver, 0, 0); DRIVER_MODULE(mdio, e6060sw, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, e6060sw, etherswitch_driver, 0, 0); MODULE_VERSION(e6060sw, 1); MODULE_DEPEND(e6060sw, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(e6060sw, etherswitch, 1, 1, 1); /* XXX which versions? */ diff --git a/sys/dev/etherswitch/infineon/adm6996fc.c b/sys/dev/etherswitch/infineon/adm6996fc.c index 2a8ee58107ee..58a3f9625d4a 100644 --- a/sys/dev/etherswitch/infineon/adm6996fc.c +++ b/sys/dev/etherswitch/infineon/adm6996fc.c @@ -1,838 +1,839 @@ /*- * Copyright (c) 2016 Hiroki Mori * Copyright (c) 2013 Luiz Otavio O Souza. * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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. */ /* * This is Infineon ADM6996FC/M/MX driver code on etherswitch framework. * Support PORT and DOT1Q VLAN. * This code suppose ADM6996FC SDC/SDIO connect to SOC network interface * MDC/MDIO. * This code development on Netgear WGR614Cv7. * etherswitchcfg command port option support addtag. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" #define ADM6996FC_PRODUCT_CODE 0x7102 #define ADM6996FC_SC3 0x11 #define ADM6996FC_VF0L 0x40 #define ADM6996FC_VF0H 0x41 #define ADM6996FC_CI0 0xa0 #define ADM6996FC_CI1 0xa1 #define ADM6996FC_PHY_C0 0x200 #define ADM6996FC_PC_SHIFT 4 #define ADM6996FC_TBV_SHIFT 5 #define ADM6996FC_PVID_SHIFT 10 #define ADM6996FC_OPTE_SHIFT 4 #define ADM6996FC_VV_SHIFT 15 #define ADM6996FC_PHY_SIZE 0x20 MALLOC_DECLARE(M_ADM6996FC); MALLOC_DEFINE(M_ADM6996FC, "adm6996fc", "adm6996fc data structures"); struct adm6996fc_softc { struct mtx sc_mtx; /* serialize access to softc */ device_t sc_dev; int vlan_mode; int media; /* cpu port media */ int cpuport; /* which PHY is connected to the CPU */ int phymask; /* PHYs we manage */ int numports; /* number of ports */ int ifpport[MII_NPHY]; int *portphy; char **ifname; device_t **miibus; if_t *ifp; struct callout callout_tick; etherswitch_info_t info; }; #define ADM6996FC_LOCK(_sc) \ mtx_lock(&(_sc)->sc_mtx) #define ADM6996FC_UNLOCK(_sc) \ mtx_unlock(&(_sc)->sc_mtx) #define ADM6996FC_LOCK_ASSERT(_sc, _what) \ mtx_assert(&(_sc)->sc_mtx, (_what)) #define ADM6996FC_TRYLOCK(_sc) \ mtx_trylock(&(_sc)->sc_mtx) #if defined(DEBUG) #define DPRINTF(dev, args...) device_printf(dev, args) #else #define DPRINTF(dev, args...) #endif static inline int adm6996fc_portforphy(struct adm6996fc_softc *, int); static void adm6996fc_tick(void *); static int adm6996fc_ifmedia_upd(if_t); static void adm6996fc_ifmedia_sts(if_t, struct ifmediareq *); #define ADM6996FC_READREG(dev, x) \ MDIO_READREG(dev, ((x) >> 5), ((x) & 0x1f)); #define ADM6996FC_WRITEREG(dev, x, v) \ MDIO_WRITEREG(dev, ((x) >> 5), ((x) & 0x1f), v); #define ADM6996FC_PVIDBYDATA(data1, data2) \ ((((data1) >> ADM6996FC_PVID_SHIFT) & 0x0f) | ((data2) << 4)) static int adm6996fc_probe(device_t dev) { int data1, data2; int pc; struct adm6996fc_softc *sc; sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); data1 = ADM6996FC_READREG(device_get_parent(dev), ADM6996FC_CI0); data2 = ADM6996FC_READREG(device_get_parent(dev), ADM6996FC_CI1); pc = ((data2 << 16) | data1) >> ADM6996FC_PC_SHIFT; if (bootverbose) device_printf(dev,"Chip Identifier Register %x %x\n", data1, data2); /* check Product Code */ if (pc != ADM6996FC_PRODUCT_CODE) { return (ENXIO); } device_set_desc(dev, "Infineon ADM6996FC/M/MX MDIO switch driver"); return (BUS_PROBE_DEFAULT); } static int adm6996fc_attach_phys(struct adm6996fc_softc *sc) { int phy, port, err; char name[IFNAMSIZ]; port = 0; err = 0; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < sc->numports; phy++) { if (((1 << phy) & sc->phymask) == 0) continue; sc->ifpport[phy] = port; sc->portphy[port] = phy; sc->ifp[port] = if_alloc(IFT_ETHER); sc->ifp[port]->if_softc = sc; sc->ifp[port]->if_flags |= IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX; if_initname(sc->ifp[port], name, port); sc->miibus[port] = malloc(sizeof(device_t), M_ADM6996FC, M_WAITOK | M_ZERO); err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port], adm6996fc_ifmedia_upd, adm6996fc_ifmedia_sts, \ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", device_get_nameunit(*sc->miibus[port]), sc->ifp[port]->if_xname); if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); goto failed; } ++port; } sc->info.es_nports = port; if (sc->cpuport != -1) { /* assume cpuport is last one */ sc->ifpport[sc->cpuport] = port; sc->portphy[port] = sc->cpuport; ++sc->info.es_nports; } return (0); failed: for (phy = 0; phy < sc->numports; phy++) { if (((1 << phy) & sc->phymask) == 0) continue; port = adm6996fc_portforphy(sc, phy); if (sc->miibus[port] != NULL) device_delete_child(sc->sc_dev, (*sc->miibus[port])); if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); if (sc->ifname[port] != NULL) free(sc->ifname[port], M_ADM6996FC); if (sc->miibus[port] != NULL) free(sc->miibus[port], M_ADM6996FC); } return (err); } static int adm6996fc_attach(device_t dev) { struct adm6996fc_softc *sc; int err; err = 0; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "adm6996fc", NULL, MTX_DEF); strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name)); /* ADM6996FC Defaults */ sc->numports = 6; sc->phymask = 0x1f; sc->cpuport = 5; sc->media = 100; sc->info.es_nvlangroups = 16; sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q; sc->ifp = malloc(sizeof(if_t) * sc->numports, M_ADM6996FC, M_WAITOK | M_ZERO); sc->ifname = malloc(sizeof(char *) * sc->numports, M_ADM6996FC, M_WAITOK | M_ZERO); sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_ADM6996FC, M_WAITOK | M_ZERO); sc->portphy = malloc(sizeof(int) * sc->numports, M_ADM6996FC, M_WAITOK | M_ZERO); /* * Attach the PHYs and complete the bus enumeration. */ err = adm6996fc_attach_phys(sc); if (err != 0) goto failed; bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init(&sc->callout_tick, 0); adm6996fc_tick(sc); return (0); failed: free(sc->portphy, M_ADM6996FC); free(sc->miibus, M_ADM6996FC); free(sc->ifname, M_ADM6996FC); free(sc->ifp, M_ADM6996FC); return (err); } static int adm6996fc_detach(device_t dev) { struct adm6996fc_softc *sc; - int i, port; + int error, i, port; sc = device_get_softc(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + callout_drain(&sc->callout_tick); for (i = 0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = adm6996fc_portforphy(sc, i); - if (sc->miibus[port] != NULL) - device_delete_child(dev, (*sc->miibus[port])); if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); free(sc->ifname[port], M_ADM6996FC); free(sc->miibus[port], M_ADM6996FC); } free(sc->portphy, M_ADM6996FC); free(sc->miibus, M_ADM6996FC); free(sc->ifname, M_ADM6996FC); free(sc->ifp, M_ADM6996FC); - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } /* * Convert PHY number to port number. */ static inline int adm6996fc_portforphy(struct adm6996fc_softc *sc, int phy) { return (sc->ifpport[phy]); } static inline struct mii_data * adm6996fc_miiforport(struct adm6996fc_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); if (port == sc->cpuport) return (NULL); return (device_get_softc(*sc->miibus[port])); } static inline if_t adm6996fc_ifpforport(struct adm6996fc_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (sc->ifp[port]); } /* * Poll the status for all PHYs. */ static void adm6996fc_miipollstat(struct adm6996fc_softc *sc) { int i, port; struct mii_data *mii; struct mii_softc *miisc; ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED); for (i = 0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = adm6996fc_portforphy(sc, i); if ((*sc->miibus[port]) == NULL) continue; mii = device_get_softc(*sc->miibus[port]); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; ukphy_status(miisc); mii_phy_update(miisc, MII_POLLSTAT); } } } static void adm6996fc_tick(void *arg) { struct adm6996fc_softc *sc; sc = arg; adm6996fc_miipollstat(sc); callout_reset(&sc->callout_tick, hz, adm6996fc_tick, sc); } static void adm6996fc_lock(device_t dev) { struct adm6996fc_softc *sc; sc = device_get_softc(dev); ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED); ADM6996FC_LOCK(sc); } static void adm6996fc_unlock(device_t dev) { struct adm6996fc_softc *sc; sc = device_get_softc(dev); ADM6996FC_LOCK_ASSERT(sc, MA_OWNED); ADM6996FC_UNLOCK(sc); } static etherswitch_info_t * adm6996fc_getinfo(device_t dev) { struct adm6996fc_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static int adm6996fc_getport(device_t dev, etherswitch_port_t *p) { struct adm6996fc_softc *sc; struct mii_data *mii; struct ifmediareq *ifmr; device_t parent; int err, phy; int data1, data2; int bcaddr[6] = {0x01, 0x03, 0x05, 0x07, 0x08, 0x09}; int vidaddr[6] = {0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c}; sc = device_get_softc(dev); ifmr = &p->es_ifmr; if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); parent = device_get_parent(dev); if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { data1 = ADM6996FC_READREG(parent, bcaddr[p->es_port]); data2 = ADM6996FC_READREG(parent, vidaddr[p->es_port]); /* only port 4 is hi bit */ if (p->es_port == 4) data2 = (data2 >> 8) & 0xff; else data2 = data2 & 0xff; p->es_pvid = ADM6996FC_PVIDBYDATA(data1, data2); if (((data1 >> ADM6996FC_OPTE_SHIFT) & 0x01) == 1) p->es_flags |= ETHERSWITCH_PORT_ADDTAG; } else { p->es_pvid = 0; } phy = sc->portphy[p->es_port]; mii = adm6996fc_miiforport(sc, p->es_port); if (sc->cpuport != -1 && phy == sc->cpuport) { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr->ifm_count = 0; if (sc->media == 100) ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_100_TX | IFM_FDX; else ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else if (mii != NULL) { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } else { return (ENXIO); } return (0); } static int adm6996fc_setport(device_t dev, etherswitch_port_t *p) { struct adm6996fc_softc *sc; struct ifmedia *ifm; struct mii_data *mii; if_t ifp; device_t parent; int err; int data; int bcaddr[6] = {0x01, 0x03, 0x05, 0x07, 0x08, 0x09}; int vidaddr[6] = {0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c}; sc = device_get_softc(dev); parent = device_get_parent(dev); if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { data = ADM6996FC_READREG(parent, bcaddr[p->es_port]); data &= ~(0xf << 10); data |= (p->es_pvid & 0xf) << ADM6996FC_PVID_SHIFT; if (p->es_flags & ETHERSWITCH_PORT_ADDTAG) data |= 1 << ADM6996FC_OPTE_SHIFT; else data &= ~(1 << ADM6996FC_OPTE_SHIFT); ADM6996FC_WRITEREG(parent, bcaddr[p->es_port], data); data = ADM6996FC_READREG(parent, vidaddr[p->es_port]); /* only port 4 is hi bit */ if (p->es_port == 4) { data &= ~(0xff << 8); data = data | (((p->es_pvid >> 4) & 0xff) << 8); } else { data &= ~0xff; data = data | ((p->es_pvid >> 4) & 0xff); } ADM6996FC_WRITEREG(parent, vidaddr[p->es_port], data); err = 0; } else { if (sc->portphy[p->es_port] == sc->cpuport) return (ENXIO); } if (sc->portphy[p->es_port] != sc->cpuport) { mii = adm6996fc_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); ifp = adm6996fc_ifpforport(sc, p->es_port); ifm = &mii->mii_media; err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA); } return (err); } static int adm6996fc_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct adm6996fc_softc *sc; device_t parent; int datahi, datalo; sc = device_get_softc(dev); parent = device_get_parent(dev); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { if (vg->es_vlangroup <= 5) { vg->es_vid = ETHERSWITCH_VID_VALID; vg->es_vid |= vg->es_vlangroup; datalo = ADM6996FC_READREG(parent, ADM6996FC_VF0L + 2 * vg->es_vlangroup); datahi = ADM6996FC_READREG(parent, ADM6996FC_VF0H + 2 * vg->es_vlangroup); vg->es_member_ports = datalo & 0x3f; vg->es_untagged_ports = vg->es_member_ports; vg->es_fid = 0; } else { vg->es_vid = 0; } } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { datalo = ADM6996FC_READREG(parent, ADM6996FC_VF0L + 2 * vg->es_vlangroup); datahi = ADM6996FC_READREG(parent, ADM6996FC_VF0H + 2 * vg->es_vlangroup); if (datahi & (1 << ADM6996FC_VV_SHIFT)) { vg->es_vid = ETHERSWITCH_VID_VALID; vg->es_vid |= datahi & 0xfff; vg->es_member_ports = datalo & 0x3f; vg->es_untagged_ports = (~datalo >> 6) & 0x3f; vg->es_fid = 0; } else { vg->es_fid = 0; } } else { vg->es_fid = 0; } return (0); } static int adm6996fc_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct adm6996fc_softc *sc; device_t parent; sc = device_get_softc(dev); parent = device_get_parent(dev); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2 * vg->es_vlangroup, vg->es_member_ports); } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2 * vg->es_vlangroup, vg->es_member_ports | ((~vg->es_untagged_ports & 0x3f)<< 6)); ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2 * vg->es_vlangroup, (1 << ADM6996FC_VV_SHIFT) | vg->es_vid); } return (0); } static int adm6996fc_getconf(device_t dev, etherswitch_conf_t *conf) { struct adm6996fc_softc *sc; sc = device_get_softc(dev); /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; return (0); } static int adm6996fc_setconf(device_t dev, etherswitch_conf_t *conf) { struct adm6996fc_softc *sc; device_t parent; int i; int data; int bcaddr[6] = {0x01, 0x03, 0x05, 0x07, 0x08, 0x09}; sc = device_get_softc(dev); parent = device_get_parent(dev); if ((conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) == 0) return (0); if (conf->vlan_mode == ETHERSWITCH_VLAN_PORT) { sc->vlan_mode = ETHERSWITCH_VLAN_PORT; data = ADM6996FC_READREG(parent, ADM6996FC_SC3); data &= ~(1 << ADM6996FC_TBV_SHIFT); ADM6996FC_WRITEREG(parent, ADM6996FC_SC3, data); for (i = 0;i <= 5; ++i) { data = ADM6996FC_READREG(parent, bcaddr[i]); data &= ~(0xf << 10); data |= (i << 10); ADM6996FC_WRITEREG(parent, bcaddr[i], data); ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2 * i, 0x003f); ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2 * i, (1 << ADM6996FC_VV_SHIFT) | 1); } } else if (conf->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; data = ADM6996FC_READREG(parent, ADM6996FC_SC3); data |= (1 << ADM6996FC_TBV_SHIFT); ADM6996FC_WRITEREG(parent, ADM6996FC_SC3, data); for (i = 0;i <= 5; ++i) { data = ADM6996FC_READREG(parent, bcaddr[i]); /* Private VID set 1 */ data &= ~(0xf << 10); data |= (1 << 10); ADM6996FC_WRITEREG(parent, bcaddr[i], data); } for (i = 2;i <= 15; ++i) { ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2 * i, 0x0000); } } else { /* ADM6996FC have no VLAN off. Then set Port base and add all port to member. Use VLAN Filter 1 is reset default. */ sc->vlan_mode = 0; data = ADM6996FC_READREG(parent, ADM6996FC_SC3); data &= ~(1 << ADM6996FC_TBV_SHIFT); ADM6996FC_WRITEREG(parent, ADM6996FC_SC3, data); for (i = 0;i <= 5; ++i) { data = ADM6996FC_READREG(parent, bcaddr[i]); data &= ~(0xf << 10); data |= (1 << 10); if (i == 5) data &= ~(1 << 4); ADM6996FC_WRITEREG(parent, bcaddr[i], data); } /* default setting */ ADM6996FC_WRITEREG(parent, ADM6996FC_VF0L + 2, 0x003f); ADM6996FC_WRITEREG(parent, ADM6996FC_VF0H + 2, (1 << ADM6996FC_VV_SHIFT) | 1); } return (0); } static void adm6996fc_statchg(device_t dev) { DPRINTF(dev, "%s\n", __func__); } static int adm6996fc_ifmedia_upd(if_t ifp) { struct adm6996fc_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = adm6996fc_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void adm6996fc_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct adm6996fc_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = adm6996fc_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int adm6996fc_readphy(device_t dev, int phy, int reg) { struct adm6996fc_softc *sc; int data; sc = device_get_softc(dev); ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED); if (phy < 0 || phy >= 32) return (ENXIO); if (reg < 0 || reg >= 32) return (ENXIO); ADM6996FC_LOCK(sc); data = ADM6996FC_READREG(device_get_parent(dev), (ADM6996FC_PHY_C0 + ADM6996FC_PHY_SIZE * phy) + reg); ADM6996FC_UNLOCK(sc); return (data); } static int adm6996fc_writephy(device_t dev, int phy, int reg, int data) { struct adm6996fc_softc *sc; int err; sc = device_get_softc(dev); ADM6996FC_LOCK_ASSERT(sc, MA_NOTOWNED); if (phy < 0 || phy >= 32) return (ENXIO); if (reg < 0 || reg >= 32) return (ENXIO); ADM6996FC_LOCK(sc); err = ADM6996FC_WRITEREG(device_get_parent(dev), (ADM6996FC_PHY_C0 + ADM6996FC_PHY_SIZE * phy) + reg, data); ADM6996FC_UNLOCK(sc); return (err); } static int adm6996fc_readreg(device_t dev, int addr) { return ADM6996FC_READREG(device_get_parent(dev), addr); } static int adm6996fc_writereg(device_t dev, int addr, int value) { int err; err = ADM6996FC_WRITEREG(device_get_parent(dev), addr, value); return (err); } static device_method_t adm6996fc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, adm6996fc_probe), DEVMETHOD(device_attach, adm6996fc_attach), DEVMETHOD(device_detach, adm6996fc_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, adm6996fc_readphy), DEVMETHOD(miibus_writereg, adm6996fc_writephy), DEVMETHOD(miibus_statchg, adm6996fc_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, adm6996fc_readphy), DEVMETHOD(mdio_writereg, adm6996fc_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, adm6996fc_lock), DEVMETHOD(etherswitch_unlock, adm6996fc_unlock), DEVMETHOD(etherswitch_getinfo, adm6996fc_getinfo), DEVMETHOD(etherswitch_readreg, adm6996fc_readreg), DEVMETHOD(etherswitch_writereg, adm6996fc_writereg), DEVMETHOD(etherswitch_readphyreg, adm6996fc_readphy), DEVMETHOD(etherswitch_writephyreg, adm6996fc_writephy), DEVMETHOD(etherswitch_getport, adm6996fc_getport), DEVMETHOD(etherswitch_setport, adm6996fc_setport), DEVMETHOD(etherswitch_getvgroup, adm6996fc_getvgroup), DEVMETHOD(etherswitch_setvgroup, adm6996fc_setvgroup), DEVMETHOD(etherswitch_setconf, adm6996fc_setconf), DEVMETHOD(etherswitch_getconf, adm6996fc_getconf), DEVMETHOD_END }; DEFINE_CLASS_0(adm6996fc, adm6996fc_driver, adm6996fc_methods, sizeof(struct adm6996fc_softc)); DRIVER_MODULE(adm6996fc, mdio, adm6996fc_driver, 0, 0); DRIVER_MODULE(miibus, adm6996fc, miibus_driver, 0, 0); DRIVER_MODULE(mdio, adm6996fc, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, adm6996fc, etherswitch_driver, 0, 0); MODULE_VERSION(adm6996fc, 1); MODULE_DEPEND(adm6996fc, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(adm6996fc, etherswitch, 1, 1, 1); /* XXX which versions? */ diff --git a/sys/dev/etherswitch/ip17x/ip17x.c b/sys/dev/etherswitch/ip17x/ip17x.c index 1eee45148040..c90d46c49857 100644 --- a/sys/dev/etherswitch/ip17x/ip17x.c +++ b/sys/dev/etherswitch/ip17x/ip17x.c @@ -1,651 +1,652 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Luiz Otavio O Souza. * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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 "opt_platform.h" #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 #ifdef FDT #include #include #include #endif #include "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" MALLOC_DECLARE(M_IP17X); MALLOC_DEFINE(M_IP17X, "ip17x", "ip17x data structures"); static void ip17x_tick(void *); static int ip17x_ifmedia_upd(if_t); static void ip17x_ifmedia_sts(if_t, struct ifmediareq *); static void ip17x_identify(driver_t *driver, device_t parent) { if (device_find_child(parent, "ip17x", -1) == NULL) BUS_ADD_CHILD(parent, 0, "ip17x", DEVICE_UNIT_ANY); } static int ip17x_probe(device_t dev) { struct ip17x_softc *sc; uint32_t oui, model, phy_id1, phy_id2; #ifdef FDT phandle_t ip17x_node; pcell_t cell; ip17x_node = fdt_find_compatible(OF_finddevice("/"), "icplus,ip17x", 0); if (ip17x_node == 0) return (ENXIO); #endif sc = device_get_softc(dev); /* Read ID from PHY 0. */ phy_id1 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR1); phy_id2 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR2); oui = MII_OUI(phy_id1, phy_id2); model = MII_MODEL(phy_id2); /* We only care about IC+ devices. */ if (oui != IP17X_OUI) { device_printf(dev, "Unsupported IC+ switch. Unknown OUI: %#x\n", oui); return (ENXIO); } switch (model) { case IP17X_IP175A: sc->sc_switchtype = IP17X_SWITCH_IP175A; break; case IP17X_IP175C: sc->sc_switchtype = IP17X_SWITCH_IP175C; break; default: device_printf(dev, "Unsupported IC+ switch model: %#x\n", model); return (ENXIO); } /* IP175D has a specific ID register. */ model = MDIO_READREG(device_get_parent(dev), IP175D_ID_PHY, IP175D_ID_REG); if (model == 0x175d) sc->sc_switchtype = IP17X_SWITCH_IP175D; else { /* IP178 has more PHYs. Try it. */ model = MDIO_READREG(device_get_parent(dev), 5, MII_PHYIDR1); if (phy_id1 == model) sc->sc_switchtype = IP17X_SWITCH_IP178C; } sc->miipoll = 1; #ifdef FDT if ((OF_getencprop(ip17x_node, "mii-poll", &cell, sizeof(cell))) > 0) sc->miipoll = cell ? 1 : 0; #else (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "mii-poll", &sc->miipoll); #endif device_set_desc(dev, "IC+ IP17x switch driver"); return (BUS_PROBE_DEFAULT); } static int ip17x_attach_phys(struct ip17x_softc *sc) { int err, phy, port; char name[IFNAMSIZ]; port = err = 0; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < MII_NPHY; phy++) { if (((1 << phy) & sc->phymask) == 0) continue; sc->phyport[phy] = port; sc->portphy[port] = phy; sc->ifp[port] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[port], sc); if_setflags(sc->ifp[port], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX); if_initname(sc->ifp[port], name, port); sc->miibus[port] = malloc(sizeof(device_t), M_IP17X, M_WAITOK | M_ZERO); err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port], ip17x_ifmedia_upd, ip17x_ifmedia_sts, \ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", device_get_nameunit(*sc->miibus[port]), if_name(sc->ifp[port])); if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); break; } sc->info.es_nports = port + 1; if (++port >= sc->numports) break; } return (err); } static int ip17x_attach(device_t dev) { struct ip17x_softc *sc; int err; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "ip17x", NULL, MTX_DEF); strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name)); /* XXX Defaults */ sc->phymask = 0x0f; sc->media = 100; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phymask", &sc->phymask); /* Number of vlans supported by the switch. */ sc->info.es_nvlangroups = IP17X_MAX_VLANS; /* Attach the switch related functions. */ if (IP17X_IS_SWITCH(sc, IP175C)) ip175c_attach(sc); else if (IP17X_IS_SWITCH(sc, IP175D)) ip175d_attach(sc); else /* We don't have support to all the models yet :-/ */ return (ENXIO); /* Always attach the cpu port. */ sc->phymask |= (1 << sc->cpuport); sc->ifp = malloc(sizeof(if_t) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); sc->pvid = malloc(sizeof(uint32_t) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); sc->portphy = malloc(sizeof(int) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); /* Initialize the switch. */ sc->hal.ip17x_reset(sc); /* * Attach the PHYs and complete the bus enumeration. */ err = ip17x_attach_phys(sc); if (err != 0) return (err); /* * Set the switch to port based vlans or disabled (if not supported * on this model). */ sc->hal.ip17x_set_vlan_mode(sc, ETHERSWITCH_VLAN_PORT); bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); if (sc->miipoll) { callout_init(&sc->callout_tick, 0); ip17x_tick(sc); } return (0); } static int ip17x_detach(device_t dev) { struct ip17x_softc *sc; - int i, port; + int error, i, port; + + error = bus_generic_detach(dev); + if (error != 0) + return (error); sc = device_get_softc(dev); if (sc->miipoll) callout_drain(&sc->callout_tick); for (i=0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = sc->phyport[i]; - if (sc->miibus[port] != NULL) - device_delete_child(dev, (*sc->miibus[port])); if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); free(sc->miibus[port], M_IP17X); } free(sc->portphy, M_IP17X); free(sc->miibus, M_IP17X); free(sc->pvid, M_IP17X); free(sc->ifp, M_IP17X); /* Reset the switch. */ sc->hal.ip17x_reset(sc); - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } static inline struct mii_data * ip17x_miiforport(struct ip17x_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (device_get_softc(*sc->miibus[port])); } static inline if_t ip17x_ifpforport(struct ip17x_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (sc->ifp[port]); } /* * Poll the status for all PHYs. */ static void ip17x_miipollstat(struct ip17x_softc *sc) { struct mii_softc *miisc; struct mii_data *mii; int i, port; IP17X_LOCK_ASSERT(sc, MA_NOTOWNED); for (i = 0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = sc->phyport[i]; if ((*sc->miibus[port]) == NULL) continue; mii = device_get_softc(*sc->miibus[port]); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; ukphy_status(miisc); mii_phy_update(miisc, MII_POLLSTAT); } } } static void ip17x_tick(void *arg) { struct ip17x_softc *sc; sc = arg; ip17x_miipollstat(sc); callout_reset(&sc->callout_tick, hz, ip17x_tick, sc); } static void ip17x_lock(device_t dev) { struct ip17x_softc *sc; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_NOTOWNED); IP17X_LOCK(sc); } static void ip17x_unlock(device_t dev) { struct ip17x_softc *sc; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_OWNED); IP17X_UNLOCK(sc); } static etherswitch_info_t * ip17x_getinfo(device_t dev) { struct ip17x_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static int ip17x_getport(device_t dev, etherswitch_port_t *p) { struct ip17x_softc *sc; struct ifmediareq *ifmr; struct mii_data *mii; int err, phy; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); phy = sc->portphy[p->es_port]; /* Retrieve the PVID. */ p->es_pvid = sc->pvid[phy]; /* Port flags. */ if (sc->addtag & (1 << phy)) p->es_flags |= ETHERSWITCH_PORT_ADDTAG; if (sc->striptag & (1 << phy)) p->es_flags |= ETHERSWITCH_PORT_STRIPTAG; ifmr = &p->es_ifmr; /* No media settings ? */ if (p->es_ifmr.ifm_count == 0) return (0); mii = ip17x_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); if (phy == sc->cpuport) { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr->ifm_count = 0; if (sc->media == 100) ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_100_TX | IFM_FDX; else ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } return (0); } static int ip17x_setport(device_t dev, etherswitch_port_t *p) { struct ip17x_softc *sc; struct ifmedia *ifm; if_t ifp; struct mii_data *mii; int phy; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); phy = sc->portphy[p->es_port]; ifp = ip17x_ifpforport(sc, p->es_port); mii = ip17x_miiforport(sc, p->es_port); if (ifp == NULL || mii == NULL) return (ENXIO); /* Port flags. */ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { /* Set the PVID. */ if (p->es_pvid != 0) { if (IP17X_IS_SWITCH(sc, IP175C) && p->es_pvid > IP175C_LAST_VLAN) return (ENXIO); sc->pvid[phy] = p->es_pvid; } /* Mutually exclusive. */ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG && p->es_flags & ETHERSWITCH_PORT_STRIPTAG) return (EINVAL); /* Reset the settings for this port. */ sc->addtag &= ~(1 << phy); sc->striptag &= ~(1 << phy); /* And then set it to the new value. */ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG) sc->addtag |= (1 << phy); if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG) sc->striptag |= (1 << phy); } /* Update the switch configuration. */ if (sc->hal.ip17x_hw_setup(sc)) return (ENXIO); /* Do not allow media changes on CPU port. */ if (phy == sc->cpuport) return (0); /* No media settings ? */ if (p->es_ifmr.ifm_count == 0) return (0); ifm = &mii->mii_media; return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); } static void ip17x_statchg(device_t dev) { DPRINTF(dev, "%s\n", __func__); } static int ip17x_ifmedia_upd(if_t ifp) { struct ip17x_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); DPRINTF(sc->sc_dev, "%s\n", __func__); mii = ip17x_miiforport(sc, if_getdunit(ifp)); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void ip17x_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct ip17x_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); DPRINTF(sc->sc_dev, "%s\n", __func__); mii = ip17x_miiforport(sc, if_getdunit(ifp)); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int ip17x_readreg(device_t dev, int addr) { struct ip17x_softc *sc __diagused; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_OWNED); /* Not supported. */ return (0); } static int ip17x_writereg(device_t dev, int addr, int value) { struct ip17x_softc *sc __diagused; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_OWNED); /* Not supported. */ return (0); } static int ip17x_getconf(device_t dev, etherswitch_conf_t *conf) { struct ip17x_softc *sc; sc = device_get_softc(dev); /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->hal.ip17x_get_vlan_mode(sc); return (0); } static int ip17x_setconf(device_t dev, etherswitch_conf_t *conf) { struct ip17x_softc *sc; sc = device_get_softc(dev); /* Set the VLAN mode. */ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) sc->hal.ip17x_set_vlan_mode(sc, conf->vlan_mode); return (0); } static device_method_t ip17x_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ip17x_identify), DEVMETHOD(device_probe, ip17x_probe), DEVMETHOD(device_attach, ip17x_attach), DEVMETHOD(device_detach, ip17x_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, ip17x_readphy), DEVMETHOD(miibus_writereg, ip17x_writephy), DEVMETHOD(miibus_statchg, ip17x_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, ip17x_readphy), DEVMETHOD(mdio_writereg, ip17x_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, ip17x_lock), DEVMETHOD(etherswitch_unlock, ip17x_unlock), DEVMETHOD(etherswitch_getinfo, ip17x_getinfo), DEVMETHOD(etherswitch_readreg, ip17x_readreg), DEVMETHOD(etherswitch_writereg, ip17x_writereg), DEVMETHOD(etherswitch_readphyreg, ip17x_readphy), DEVMETHOD(etherswitch_writephyreg, ip17x_writephy), DEVMETHOD(etherswitch_getport, ip17x_getport), DEVMETHOD(etherswitch_setport, ip17x_setport), DEVMETHOD(etherswitch_getvgroup, ip17x_getvgroup), DEVMETHOD(etherswitch_setvgroup, ip17x_setvgroup), DEVMETHOD(etherswitch_getconf, ip17x_getconf), DEVMETHOD(etherswitch_setconf, ip17x_setconf), DEVMETHOD_END }; DEFINE_CLASS_0(ip17x, ip17x_driver, ip17x_methods, sizeof(struct ip17x_softc)); DRIVER_MODULE(ip17x, mdio, ip17x_driver, 0, 0); DRIVER_MODULE(miibus, ip17x, miibus_driver, 0, 0); DRIVER_MODULE(etherswitch, ip17x, etherswitch_driver, 0, 0); MODULE_VERSION(ip17x, 1); #ifdef FDT MODULE_DEPEND(ip17x, mdio, 1, 1, 1); /* XXX which versions? */ #else DRIVER_MODULE(mdio, ip17x, mdio_driver, 0, 0); MODULE_DEPEND(ip17x, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(ip17x, etherswitch, 1, 1, 1); /* XXX which versions? */ #endif diff --git a/sys/dev/etherswitch/micrel/ksz8995ma.c b/sys/dev/etherswitch/micrel/ksz8995ma.c index a51bdb50fdfc..c2ac994fe882 100644 --- a/sys/dev/etherswitch/micrel/ksz8995ma.c +++ b/sys/dev/etherswitch/micrel/ksz8995ma.c @@ -1,940 +1,941 @@ /*- * Copyright (c) 2016 Hiroki Mori * Copyright (c) 2013 Luiz Otavio O Souza. * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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. */ /* * This is Micrel KSZ8995MA driver code. KSZ8995MA use SPI bus on control. * This code development on @SRCHACK's ksz8995ma board and FON2100 with * gpiospi. * etherswitchcfg command port option support addtag, ingress, striptag, * dropuntagged. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" #include "miibus_if.h" #include "etherswitch_if.h" #define KSZ8995MA_SPI_READ 0x03 #define KSZ8995MA_SPI_WRITE 0x02 #define KSZ8995MA_CID0 0x00 #define KSZ8995MA_CID1 0x01 #define KSZ8995MA_GC0 0x02 #define KSZ8995MA_GC1 0x03 #define KSZ8995MA_GC2 0x04 #define KSZ8995MA_GC3 0x05 #define KSZ8995MA_PORT_SIZE 0x10 #define KSZ8995MA_PC0_BASE 0x10 #define KSZ8995MA_PC1_BASE 0x11 #define KSZ8995MA_PC2_BASE 0x12 #define KSZ8995MA_PC3_BASE 0x13 #define KSZ8995MA_PC4_BASE 0x14 #define KSZ8995MA_PC5_BASE 0x15 #define KSZ8995MA_PC6_BASE 0x16 #define KSZ8995MA_PC7_BASE 0x17 #define KSZ8995MA_PC8_BASE 0x18 #define KSZ8995MA_PC9_BASE 0x19 #define KSZ8995MA_PC10_BASE 0x1a #define KSZ8995MA_PC11_BASE 0x1b #define KSZ8995MA_PC12_BASE 0x1c #define KSZ8995MA_PC13_BASE 0x1d #define KSZ8995MA_PS0_BASE 0x1e #define KSZ8995MA_PC14_BASE 0x1f #define KSZ8995MA_IAC0 0x6e #define KSZ8995MA_IAC1 0x6f #define KSZ8995MA_IDR8 0x70 #define KSZ8995MA_IDR7 0x71 #define KSZ8995MA_IDR6 0x72 #define KSZ8995MA_IDR5 0x73 #define KSZ8995MA_IDR4 0x74 #define KSZ8995MA_IDR3 0x75 #define KSZ8995MA_IDR2 0x76 #define KSZ8995MA_IDR1 0x77 #define KSZ8995MA_IDR0 0x78 #define KSZ8995MA_FAMILI_ID 0x95 #define KSZ8995MA_CHIP_ID 0x00 #define KSZ8995MA_CHIP_ID_MASK 0xf0 #define KSZ8995MA_START 0x01 #define KSZ8995MA_VLAN_ENABLE 0x80 #define KSZ8995MA_TAG_INS 0x04 #define KSZ8995MA_TAG_RM 0x02 #define KSZ8995MA_INGR_FILT 0x40 #define KSZ8995MA_DROP_NONPVID 0x20 #define KSZ8995MA_PDOWN 0x08 #define KSZ8995MA_STARTNEG 0x20 #define KSZ8995MA_MII_STAT 0x7808 #define KSZ8995MA_MII_PHYID_H 0x0022 #define KSZ8995MA_MII_PHYID_L 0x1450 #define KSZ8995MA_MII_AA 0x0401 #define KSZ8995MA_VLAN_TABLE_VALID 0x20 #define KSZ8995MA_VLAN_TABLE_READ 0x14 #define KSZ8995MA_VLAN_TABLE_WRITE 0x04 #define KSZ8995MA_MAX_PORT 5 MALLOC_DECLARE(M_KSZ8995MA); MALLOC_DEFINE(M_KSZ8995MA, "ksz8995ma", "ksz8995ma data structures"); struct ksz8995ma_softc { struct mtx sc_mtx; /* serialize access to softc */ device_t sc_dev; int vlan_mode; int media; /* cpu port media */ int cpuport; /* which PHY is connected to the CPU */ int phymask; /* PHYs we manage */ int numports; /* number of ports */ int ifpport[KSZ8995MA_MAX_PORT]; int *portphy; char **ifname; device_t **miibus; if_t *ifp; struct callout callout_tick; etherswitch_info_t info; }; #define KSZ8995MA_LOCK(_sc) \ mtx_lock(&(_sc)->sc_mtx) #define KSZ8995MA_UNLOCK(_sc) \ mtx_unlock(&(_sc)->sc_mtx) #define KSZ8995MA_LOCK_ASSERT(_sc, _what) \ mtx_assert(&(_sc)->sc_mtx, (_what)) #define KSZ8995MA_TRYLOCK(_sc) \ mtx_trylock(&(_sc)->sc_mtx) #if defined(DEBUG) #define DPRINTF(dev, args...) device_printf(dev, args) #else #define DPRINTF(dev, args...) #endif static inline int ksz8995ma_portforphy(struct ksz8995ma_softc *, int); static void ksz8995ma_tick(void *); static int ksz8995ma_ifmedia_upd(if_t); static void ksz8995ma_ifmedia_sts(if_t, struct ifmediareq *); static int ksz8995ma_readreg(device_t dev, int addr); static int ksz8995ma_writereg(device_t dev, int addr, int value); static void ksz8995ma_portvlanreset(device_t dev); static int ksz8995ma_probe(device_t dev) { int id0, id1; struct ksz8995ma_softc *sc; sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); id0 = ksz8995ma_readreg(dev, KSZ8995MA_CID0); id1 = ksz8995ma_readreg(dev, KSZ8995MA_CID1); if (bootverbose) device_printf(dev,"Chip Identifier Register %x %x\n", id0, id1); /* check Product Code */ if (id0 != KSZ8995MA_FAMILI_ID || (id1 & KSZ8995MA_CHIP_ID_MASK) != KSZ8995MA_CHIP_ID) { return (ENXIO); } device_set_desc(dev, "Micrel KSZ8995MA SPI switch driver"); return (BUS_PROBE_DEFAULT); } static int ksz8995ma_attach_phys(struct ksz8995ma_softc *sc) { int phy, port, err; char name[IFNAMSIZ]; port = 0; err = 0; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < sc->numports; phy++) { if (phy == sc->cpuport) continue; if (((1 << phy) & sc->phymask) == 0) continue; sc->ifpport[phy] = port; sc->portphy[port] = phy; sc->ifp[port] = if_alloc(IFT_ETHER); sc->ifp[port]->if_softc = sc; sc->ifp[port]->if_flags |= IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX; if_initname(sc->ifp[port], name, port); sc->miibus[port] = malloc(sizeof(device_t), M_KSZ8995MA, M_WAITOK | M_ZERO); err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port], ksz8995ma_ifmedia_upd, ksz8995ma_ifmedia_sts, \ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", device_get_nameunit(*sc->miibus[port]), sc->ifp[port]->if_xname); if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); goto failed; } ++port; } sc->info.es_nports = port; if (sc->cpuport != -1) { /* cpu port is MAC5 on ksz8995ma */ sc->ifpport[sc->cpuport] = port; sc->portphy[port] = sc->cpuport; ++sc->info.es_nports; } return (0); failed: for (phy = 0; phy < sc->numports; phy++) { if (((1 << phy) & sc->phymask) == 0) continue; port = ksz8995ma_portforphy(sc, phy); if (sc->miibus[port] != NULL) device_delete_child(sc->sc_dev, (*sc->miibus[port])); if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); if (sc->ifname[port] != NULL) free(sc->ifname[port], M_KSZ8995MA); if (sc->miibus[port] != NULL) free(sc->miibus[port], M_KSZ8995MA); } return (err); } static int ksz8995ma_attach(device_t dev) { struct ksz8995ma_softc *sc; int err, reg; err = 0; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "ksz8995ma", NULL, MTX_DEF); strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name)); /* KSZ8995MA Defaults */ sc->numports = KSZ8995MA_MAX_PORT; sc->phymask = (1 << (KSZ8995MA_MAX_PORT + 1)) - 1; sc->cpuport = -1; sc->media = 100; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "cpuport", &sc->cpuport); sc->info.es_nvlangroups = 16; sc->info.es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q; sc->ifp = malloc(sizeof(if_t) * sc->numports, M_KSZ8995MA, M_WAITOK | M_ZERO); sc->ifname = malloc(sizeof(char *) * sc->numports, M_KSZ8995MA, M_WAITOK | M_ZERO); sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_KSZ8995MA, M_WAITOK | M_ZERO); sc->portphy = malloc(sizeof(int) * sc->numports, M_KSZ8995MA, M_WAITOK | M_ZERO); /* * Attach the PHYs and complete the bus enumeration. */ err = ksz8995ma_attach_phys(sc); if (err != 0) goto failed; bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init(&sc->callout_tick, 0); ksz8995ma_tick(sc); /* start switch */ sc->vlan_mode = 0; reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3); ksz8995ma_writereg(dev, KSZ8995MA_GC3, reg & ~KSZ8995MA_VLAN_ENABLE); ksz8995ma_portvlanreset(dev); ksz8995ma_writereg(dev, KSZ8995MA_CID1, KSZ8995MA_START); return (0); failed: free(sc->portphy, M_KSZ8995MA); free(sc->miibus, M_KSZ8995MA); free(sc->ifname, M_KSZ8995MA); free(sc->ifp, M_KSZ8995MA); return (err); } static int ksz8995ma_detach(device_t dev) { struct ksz8995ma_softc *sc; - int i, port; + int error, i, port; + + error = bus_generic_detach(dev); + if (error != 0) + return (error); sc = device_get_softc(dev); callout_drain(&sc->callout_tick); for (i = 0; i < KSZ8995MA_MAX_PORT; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = ksz8995ma_portforphy(sc, i); - if (sc->miibus[port] != NULL) - device_delete_child(dev, (*sc->miibus[port])); if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); free(sc->ifname[port], M_KSZ8995MA); free(sc->miibus[port], M_KSZ8995MA); } free(sc->portphy, M_KSZ8995MA); free(sc->miibus, M_KSZ8995MA); free(sc->ifname, M_KSZ8995MA); free(sc->ifp, M_KSZ8995MA); - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } /* * Convert PHY number to port number. */ static inline int ksz8995ma_portforphy(struct ksz8995ma_softc *sc, int phy) { return (sc->ifpport[phy]); } static inline struct mii_data * ksz8995ma_miiforport(struct ksz8995ma_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); if (port == sc->cpuport) return (NULL); return (device_get_softc(*sc->miibus[port])); } static inline if_t ksz8995ma_ifpforport(struct ksz8995ma_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (sc->ifp[port]); } /* * Poll the status for all PHYs. */ static void ksz8995ma_miipollstat(struct ksz8995ma_softc *sc) { int i, port; struct mii_data *mii; struct mii_softc *miisc; KSZ8995MA_LOCK_ASSERT(sc, MA_NOTOWNED); for (i = 0; i < KSZ8995MA_MAX_PORT; i++) { if (i == sc->cpuport) continue; if (((1 << i) & sc->phymask) == 0) continue; port = ksz8995ma_portforphy(sc, i); if ((*sc->miibus[port]) == NULL) continue; mii = device_get_softc(*sc->miibus[port]); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; ukphy_status(miisc); mii_phy_update(miisc, MII_POLLSTAT); } } } static void ksz8995ma_tick(void *arg) { struct ksz8995ma_softc *sc; sc = arg; ksz8995ma_miipollstat(sc); callout_reset(&sc->callout_tick, hz, ksz8995ma_tick, sc); } static void ksz8995ma_lock(device_t dev) { struct ksz8995ma_softc *sc; sc = device_get_softc(dev); KSZ8995MA_LOCK_ASSERT(sc, MA_NOTOWNED); KSZ8995MA_LOCK(sc); } static void ksz8995ma_unlock(device_t dev) { struct ksz8995ma_softc *sc; sc = device_get_softc(dev); KSZ8995MA_LOCK_ASSERT(sc, MA_OWNED); KSZ8995MA_UNLOCK(sc); } static etherswitch_info_t * ksz8995ma_getinfo(device_t dev) { struct ksz8995ma_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static int ksz8995ma_getport(device_t dev, etherswitch_port_t *p) { struct ksz8995ma_softc *sc; struct mii_data *mii; struct ifmediareq *ifmr; int phy, err; int tag1, tag2, portreg; sc = device_get_softc(dev); ifmr = &p->es_ifmr; if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { tag1 = ksz8995ma_readreg(dev, KSZ8995MA_PC3_BASE + KSZ8995MA_PORT_SIZE * p->es_port); tag2 = ksz8995ma_readreg(dev, KSZ8995MA_PC4_BASE + KSZ8995MA_PORT_SIZE * p->es_port); p->es_pvid = (tag1 & 0x0f) << 8 | tag2; portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC0_BASE + KSZ8995MA_PORT_SIZE * p->es_port); if (portreg & KSZ8995MA_TAG_INS) p->es_flags |= ETHERSWITCH_PORT_ADDTAG; if (portreg & KSZ8995MA_TAG_RM) p->es_flags |= ETHERSWITCH_PORT_STRIPTAG; portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC2_BASE + KSZ8995MA_PORT_SIZE * p->es_port); if (portreg & KSZ8995MA_DROP_NONPVID) p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED; if (portreg & KSZ8995MA_INGR_FILT) p->es_flags |= ETHERSWITCH_PORT_INGRESS; } phy = sc->portphy[p->es_port]; mii = ksz8995ma_miiforport(sc, p->es_port); if (sc->cpuport != -1 && phy == sc->cpuport) { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr->ifm_count = 0; if (sc->media == 100) ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_100_TX | IFM_FDX; else ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else if (mii != NULL) { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } else { return (ENXIO); } return (0); } static int ksz8995ma_setport(device_t dev, etherswitch_port_t *p) { struct ksz8995ma_softc *sc; struct mii_data *mii; struct ifmedia *ifm; if_t ifp; int phy, err; int portreg; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { ksz8995ma_writereg(dev, KSZ8995MA_PC4_BASE + KSZ8995MA_PORT_SIZE * p->es_port, p->es_pvid & 0xff); portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC3_BASE + KSZ8995MA_PORT_SIZE * p->es_port); ksz8995ma_writereg(dev, KSZ8995MA_PC3_BASE + KSZ8995MA_PORT_SIZE * p->es_port, (portreg & 0xf0) | ((p->es_pvid >> 8) & 0x0f)); portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC0_BASE + KSZ8995MA_PORT_SIZE * p->es_port); if (p->es_flags & ETHERSWITCH_PORT_ADDTAG) portreg |= KSZ8995MA_TAG_INS; else portreg &= ~KSZ8995MA_TAG_INS; if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG) portreg |= KSZ8995MA_TAG_RM; else portreg &= ~KSZ8995MA_TAG_RM; ksz8995ma_writereg(dev, KSZ8995MA_PC0_BASE + KSZ8995MA_PORT_SIZE * p->es_port, portreg); portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC2_BASE + KSZ8995MA_PORT_SIZE * p->es_port); if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED) portreg |= KSZ8995MA_DROP_NONPVID; else portreg &= ~KSZ8995MA_DROP_NONPVID; if (p->es_flags & ETHERSWITCH_PORT_INGRESS) portreg |= KSZ8995MA_INGR_FILT; else portreg &= ~KSZ8995MA_INGR_FILT; ksz8995ma_writereg(dev, KSZ8995MA_PC2_BASE + KSZ8995MA_PORT_SIZE * p->es_port, portreg); } phy = sc->portphy[p->es_port]; mii = ksz8995ma_miiforport(sc, p->es_port); if (phy != sc->cpuport) { if (mii == NULL) return (ENXIO); ifp = ksz8995ma_ifpforport(sc, p->es_port); ifm = &mii->mii_media; err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA); } return (0); } static int ksz8995ma_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { int data0, data1, data2; int vlantab; struct ksz8995ma_softc *sc; sc = device_get_softc(dev); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { if (vg->es_vlangroup < sc->numports) { vg->es_vid = ETHERSWITCH_VID_VALID; vg->es_vid |= vg->es_vlangroup; data0 = ksz8995ma_readreg(dev, KSZ8995MA_PC1_BASE + KSZ8995MA_PORT_SIZE * vg->es_vlangroup); vg->es_member_ports = data0 & 0x1f; vg->es_untagged_ports = vg->es_member_ports; vg->es_fid = 0; } else { vg->es_vid = 0; } } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { ksz8995ma_writereg(dev, KSZ8995MA_IAC0, KSZ8995MA_VLAN_TABLE_READ); ksz8995ma_writereg(dev, KSZ8995MA_IAC1, vg->es_vlangroup); data2 = ksz8995ma_readreg(dev, KSZ8995MA_IDR2); data1 = ksz8995ma_readreg(dev, KSZ8995MA_IDR1); data0 = ksz8995ma_readreg(dev, KSZ8995MA_IDR0); vlantab = data2 << 16 | data1 << 8 | data0; if (data2 & KSZ8995MA_VLAN_TABLE_VALID) { vg->es_vid = ETHERSWITCH_VID_VALID; vg->es_vid |= vlantab & 0xfff; vg->es_member_ports = (vlantab >> 16) & 0x1f; vg->es_untagged_ports = vg->es_member_ports; vg->es_fid = (vlantab >> 12) & 0x0f; } else { vg->es_fid = 0; } } return (0); } static int ksz8995ma_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct ksz8995ma_softc *sc; int data0; sc = device_get_softc(dev); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { data0 = ksz8995ma_readreg(dev, KSZ8995MA_PC1_BASE + KSZ8995MA_PORT_SIZE * vg->es_vlangroup); ksz8995ma_writereg(dev, KSZ8995MA_PC1_BASE + KSZ8995MA_PORT_SIZE * vg->es_vlangroup, (data0 & 0xe0) | (vg->es_member_ports & 0x1f)); } else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { if (vg->es_member_ports != 0) { ksz8995ma_writereg(dev, KSZ8995MA_IDR2, KSZ8995MA_VLAN_TABLE_VALID | (vg->es_member_ports & 0x1f)); ksz8995ma_writereg(dev, KSZ8995MA_IDR1, vg->es_fid << 4 | vg->es_vid >> 8); ksz8995ma_writereg(dev, KSZ8995MA_IDR0, vg->es_vid & 0xff); } else { ksz8995ma_writereg(dev, KSZ8995MA_IDR2, 0); ksz8995ma_writereg(dev, KSZ8995MA_IDR1, 0); ksz8995ma_writereg(dev, KSZ8995MA_IDR0, 0); } ksz8995ma_writereg(dev, KSZ8995MA_IAC0, KSZ8995MA_VLAN_TABLE_WRITE); ksz8995ma_writereg(dev, KSZ8995MA_IAC1, vg->es_vlangroup); } return (0); } static int ksz8995ma_getconf(device_t dev, etherswitch_conf_t *conf) { struct ksz8995ma_softc *sc; sc = device_get_softc(dev); /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; return (0); } static void ksz8995ma_portvlanreset(device_t dev) { int i, data; struct ksz8995ma_softc *sc; sc = device_get_softc(dev); for (i = 0; i < sc->numports; ++i) { data = ksz8995ma_readreg(dev, KSZ8995MA_PC1_BASE + KSZ8995MA_PORT_SIZE * i); ksz8995ma_writereg(dev, KSZ8995MA_PC1_BASE + KSZ8995MA_PORT_SIZE * i, (data & 0xe0) | 0x1f); } } static int ksz8995ma_setconf(device_t dev, etherswitch_conf_t *conf) { int reg; struct ksz8995ma_softc *sc; sc = device_get_softc(dev); if ((conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) == 0) return (0); if (conf->vlan_mode == ETHERSWITCH_VLAN_PORT) { sc->vlan_mode = ETHERSWITCH_VLAN_PORT; reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3); ksz8995ma_writereg(dev, KSZ8995MA_GC3, reg & ~KSZ8995MA_VLAN_ENABLE); ksz8995ma_portvlanreset(dev); } else if (conf->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3); ksz8995ma_writereg(dev, KSZ8995MA_GC3, reg | KSZ8995MA_VLAN_ENABLE); } else { sc->vlan_mode = 0; reg = ksz8995ma_readreg(dev, KSZ8995MA_GC3); ksz8995ma_writereg(dev, KSZ8995MA_GC3, reg & ~KSZ8995MA_VLAN_ENABLE); ksz8995ma_portvlanreset(dev); } return (0); } static void ksz8995ma_statchg(device_t dev) { DPRINTF(dev, "%s\n", __func__); } static int ksz8995ma_ifmedia_upd(if_t ifp) { struct ksz8995ma_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = ksz8995ma_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void ksz8995ma_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct ksz8995ma_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = ksz8995ma_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int ksz8995ma_readphy(device_t dev, int phy, int reg) { int portreg; /* * This is no mdio/mdc connection code. * simulate MIIM Registers via the SPI interface */ if (reg == MII_BMSR) { portreg = ksz8995ma_readreg(dev, KSZ8995MA_PS0_BASE + KSZ8995MA_PORT_SIZE * phy); return (KSZ8995MA_MII_STAT | (portreg & 0x20 ? BMSR_LINK : 0x00) | (portreg & 0x40 ? BMSR_ACOMP : 0x00)); } else if (reg == MII_PHYIDR1) { return (KSZ8995MA_MII_PHYID_H); } else if (reg == MII_PHYIDR2) { return (KSZ8995MA_MII_PHYID_L); } else if (reg == MII_ANAR) { portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC12_BASE + KSZ8995MA_PORT_SIZE * phy); return (KSZ8995MA_MII_AA | (portreg & 0x0f) << 5); } else if (reg == MII_ANLPAR) { portreg = ksz8995ma_readreg(dev, KSZ8995MA_PS0_BASE + KSZ8995MA_PORT_SIZE * phy); return (((portreg & 0x0f) << 5) | 0x01); } return (0); } static int ksz8995ma_writephy(device_t dev, int phy, int reg, int data) { int portreg; /* * This is no mdio/mdc connection code. * simulate MIIM Registers via the SPI interface */ if (reg == MII_BMCR) { portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC13_BASE + KSZ8995MA_PORT_SIZE * phy); if (data & BMCR_PDOWN) portreg |= KSZ8995MA_PDOWN; else portreg &= ~KSZ8995MA_PDOWN; if (data & BMCR_STARTNEG) portreg |= KSZ8995MA_STARTNEG; else portreg &= ~KSZ8995MA_STARTNEG; ksz8995ma_writereg(dev, KSZ8995MA_PC13_BASE + KSZ8995MA_PORT_SIZE * phy, portreg); } else if (reg == MII_ANAR) { portreg = ksz8995ma_readreg(dev, KSZ8995MA_PC12_BASE + KSZ8995MA_PORT_SIZE * phy); portreg &= 0xf; portreg |= ((data >> 5) & 0x0f); ksz8995ma_writereg(dev, KSZ8995MA_PC12_BASE + KSZ8995MA_PORT_SIZE * phy, portreg); } return (0); } static int ksz8995ma_readreg(device_t dev, int addr) { uint8_t txBuf[8], rxBuf[8]; struct spi_command cmd; int err; memset(&cmd, 0, sizeof(cmd)); memset(txBuf, 0, sizeof(txBuf)); memset(rxBuf, 0, sizeof(rxBuf)); /* read spi */ txBuf[0] = KSZ8995MA_SPI_READ; txBuf[1] = addr; cmd.tx_cmd = &txBuf; cmd.rx_cmd = &rxBuf; cmd.tx_cmd_sz = 3; cmd.rx_cmd_sz = 3; err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); if (err) return(0); return (rxBuf[2]); } static int ksz8995ma_writereg(device_t dev, int addr, int value) { uint8_t txBuf[8], rxBuf[8]; struct spi_command cmd; int err; memset(&cmd, 0, sizeof(cmd)); memset(txBuf, 0, sizeof(txBuf)); memset(rxBuf, 0, sizeof(rxBuf)); /* write spi */ txBuf[0] = KSZ8995MA_SPI_WRITE; txBuf[1] = addr; txBuf[2] = value; cmd.tx_cmd = &txBuf; cmd.rx_cmd = &rxBuf; cmd.tx_cmd_sz = 3; cmd.rx_cmd_sz = 3; err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); if (err) return(0); return (0); } static device_method_t ksz8995ma_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ksz8995ma_probe), DEVMETHOD(device_attach, ksz8995ma_attach), DEVMETHOD(device_detach, ksz8995ma_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, ksz8995ma_readphy), DEVMETHOD(miibus_writereg, ksz8995ma_writephy), DEVMETHOD(miibus_statchg, ksz8995ma_statchg), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, ksz8995ma_lock), DEVMETHOD(etherswitch_unlock, ksz8995ma_unlock), DEVMETHOD(etherswitch_getinfo, ksz8995ma_getinfo), DEVMETHOD(etherswitch_readreg, ksz8995ma_readreg), DEVMETHOD(etherswitch_writereg, ksz8995ma_writereg), DEVMETHOD(etherswitch_readphyreg, ksz8995ma_readphy), DEVMETHOD(etherswitch_writephyreg, ksz8995ma_writephy), DEVMETHOD(etherswitch_getport, ksz8995ma_getport), DEVMETHOD(etherswitch_setport, ksz8995ma_setport), DEVMETHOD(etherswitch_getvgroup, ksz8995ma_getvgroup), DEVMETHOD(etherswitch_setvgroup, ksz8995ma_setvgroup), DEVMETHOD(etherswitch_setconf, ksz8995ma_setconf), DEVMETHOD(etherswitch_getconf, ksz8995ma_getconf), DEVMETHOD_END }; DEFINE_CLASS_0(ksz8995ma, ksz8995ma_driver, ksz8995ma_methods, sizeof(struct ksz8995ma_softc)); DRIVER_MODULE(ksz8995ma, spibus, ksz8995ma_driver, 0, 0); DRIVER_MODULE(miibus, ksz8995ma, miibus_driver, 0, 0); DRIVER_MODULE(etherswitch, ksz8995ma, etherswitch_driver, 0, 0); MODULE_VERSION(ksz8995ma, 1); MODULE_DEPEND(ksz8995ma, spibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(ksz8995ma, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(ksz8995ma, etherswitch, 1, 1, 1); /* XXX which versions? */ diff --git a/sys/dev/etherswitch/mtkswitch/mtkswitch.c b/sys/dev/etherswitch/mtkswitch/mtkswitch.c index 7c4e8ae6f934..89e092d02ac4 100644 --- a/sys/dev/etherswitch/mtkswitch/mtkswitch.c +++ b/sys/dev/etherswitch/mtkswitch/mtkswitch.c @@ -1,662 +1,663 @@ /*- * Copyright (c) 2016 Stanislav Galabov. * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" #define DEBUG #if defined(DEBUG) static SYSCTL_NODE(_debug, OID_AUTO, mtkswitch, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "mtkswitch"); #endif static inline int mtkswitch_portforphy(int phy); static int mtkswitch_ifmedia_upd(if_t ifp); static void mtkswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr); static void mtkswitch_tick(void *arg); static const struct ofw_compat_data compat_data[] = { { "ralink,rt3050-esw", MTK_SWITCH_RT3050 }, { "ralink,rt3352-esw", MTK_SWITCH_RT3352 }, { "ralink,rt5350-esw", MTK_SWITCH_RT5350 }, { "mediatek,mt7620-gsw", MTK_SWITCH_MT7620 }, { "mediatek,mt7621-gsw", MTK_SWITCH_MT7621 }, { "mediatek,mt7628-esw", MTK_SWITCH_MT7628 }, /* Sentinel */ { NULL, MTK_SWITCH_NONE } }; static int mtkswitch_probe(device_t dev) { struct mtkswitch_softc *sc; mtk_switch_type switch_type; if (!ofw_bus_status_okay(dev)) return (ENXIO); switch_type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (switch_type == MTK_SWITCH_NONE) return (ENXIO); sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); sc->sc_switchtype = switch_type; device_set_desc(dev, "MTK Switch Driver"); return (0); } static int mtkswitch_attach_phys(struct mtkswitch_softc *sc) { int phy, err = 0; char name[IFNAMSIZ]; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < sc->numphys; phy++) { if ((sc->phymap & (1u << phy)) == 0) { sc->ifp[phy] = NULL; sc->ifname[phy] = NULL; sc->miibus[phy] = NULL; continue; } sc->ifp[phy] = if_alloc(IFT_ETHER); 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_DEVBUF, M_WAITOK); bcopy(name, sc->ifname[phy], strlen(name) + 1); if_initname(sc->ifp[phy], sc->ifname[phy], mtkswitch_portforphy(phy)); err = mii_attach(sc->sc_dev, &sc->miibus[phy], sc->ifp[phy], mtkswitch_ifmedia_upd, mtkswitch_ifmedia_sts, BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); } else { DPRINTF(sc->sc_dev, "%s attached to pseudo interface " "%s\n", device_get_nameunit(sc->miibus[phy]), sc->ifp[phy]->if_xname); } } return (err); } static int mtkswitch_set_vlan_mode(struct mtkswitch_softc *sc, uint32_t mode) { /* Check for invalid modes. */ if ((mode & sc->info.es_vlan_caps) != mode) return (EINVAL); sc->vlan_mode = mode; /* Reset VLANs. */ sc->hal.mtkswitch_vlan_init_hw(sc); return (0); } static int mtkswitch_attach(device_t dev) { struct mtkswitch_softc *sc; int err = 0; int port, rid; sc = device_get_softc(dev); /* sc->sc_switchtype is already decided in mtkswitch_probe() */ sc->numports = MTKSWITCH_MAX_PORTS; sc->numphys = MTKSWITCH_MAX_PHYS; sc->cpuport = MTKSWITCH_CPU_PORT; sc->sc_dev = dev; /* Attach switch related functions */ if (sc->sc_switchtype == MTK_SWITCH_NONE) { device_printf(dev, "Unknown switch type\n"); return (ENXIO); } if (sc->sc_switchtype == MTK_SWITCH_MT7620 || sc->sc_switchtype == MTK_SWITCH_MT7621) mtk_attach_switch_mt7620(sc); else mtk_attach_switch_rt3050(sc); /* Allocate resources */ rid = 0; sc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_res == NULL) { device_printf(dev, "could not map memory\n"); return (ENXIO); } mtx_init(&sc->sc_mtx, "mtkswitch", NULL, MTX_DEF); /* Reset the switch */ if (sc->hal.mtkswitch_reset(sc)) { DPRINTF(dev, "%s: mtkswitch_reset: failed\n", __func__); return (ENXIO); } err = sc->hal.mtkswitch_hw_setup(sc); DPRINTF(dev, "%s: hw_setup: err=%d\n", __func__, err); if (err != 0) return (err); err = sc->hal.mtkswitch_hw_global_setup(sc); DPRINTF(dev, "%s: hw_global_setup: err=%d\n", __func__, err); if (err != 0) return (err); /* Initialize the switch ports */ for (port = 0; port < sc->numports; port++) { sc->hal.mtkswitch_port_init(sc, port); } /* Attach the PHYs and complete the bus enumeration */ err = mtkswitch_attach_phys(sc); DPRINTF(dev, "%s: attach_phys: err=%d\n", __func__, err); if (err != 0) return (err); /* Default to ingress filters off. */ err = mtkswitch_set_vlan_mode(sc, ETHERSWITCH_VLAN_DOT1Q); DPRINTF(dev, "%s: set_vlan_mode: err=%d\n", __func__, err); if (err != 0) return (err); bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init_mtx(&sc->callout_tick, &sc->sc_mtx, 0); MTKSWITCH_LOCK(sc); mtkswitch_tick(sc); MTKSWITCH_UNLOCK(sc); return (0); } static int mtkswitch_detach(device_t dev) { struct mtkswitch_softc *sc = device_get_softc(dev); - int phy; + int error, phy; + + error = bus_generic_detach(dev); + if (error != 0) + return (error); callout_drain(&sc->callout_tick); for (phy = 0; phy < MTKSWITCH_MAX_PHYS; phy++) { - if (sc->miibus[phy] != NULL) - device_delete_child(dev, sc->miibus[phy]); if (sc->ifp[phy] != NULL) if_free(sc->ifp[phy]); free(sc->ifname[phy], M_DEVBUF); } - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } /* PHY <-> port mapping is currently 1:1 */ static inline int mtkswitch_portforphy(int phy) { return (phy); } static inline int mtkswitch_phyforport(int port) { return (port); } static inline struct mii_data * mtkswitch_miiforport(struct mtkswitch_softc *sc, int port) { int phy = mtkswitch_phyforport(port); if (phy < 0 || phy >= MTKSWITCH_MAX_PHYS || sc->miibus[phy] == NULL) return (NULL); return (device_get_softc(sc->miibus[phy])); } static inline if_t mtkswitch_ifpforport(struct mtkswitch_softc *sc, int port) { int phy = mtkswitch_phyforport(port); if (phy < 0 || phy >= MTKSWITCH_MAX_PHYS) return (NULL); return (sc->ifp[phy]); } /* * Convert port status to ifmedia. */ static void mtkswitch_update_ifmedia(uint32_t portstatus, u_int *media_status, u_int *media_active) { *media_active = IFM_ETHER; *media_status = IFM_AVALID; if ((portstatus & MTKSWITCH_LINK_UP) != 0) *media_status |= IFM_ACTIVE; else { *media_active |= IFM_NONE; return; } switch (portstatus & MTKSWITCH_SPEED_MASK) { case MTKSWITCH_SPEED_10: *media_active |= IFM_10_T; break; case MTKSWITCH_SPEED_100: *media_active |= IFM_100_TX; break; case MTKSWITCH_SPEED_1000: *media_active |= IFM_1000_T; break; } if ((portstatus & MTKSWITCH_DUPLEX) != 0) *media_active |= IFM_FDX; else *media_active |= IFM_HDX; if ((portstatus & MTKSWITCH_TXFLOW) != 0) *media_active |= IFM_ETH_TXPAUSE; if ((portstatus & MTKSWITCH_RXFLOW) != 0) *media_active |= IFM_ETH_RXPAUSE; } static void mtkswitch_miipollstat(struct mtkswitch_softc *sc) { struct mii_data *mii; struct mii_softc *miisc; uint32_t portstatus; int i, port_flap = 0; MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED); for (i = 0; i < sc->numphys; i++) { if (sc->miibus[i] == NULL) continue; mii = device_get_softc(sc->miibus[i]); portstatus = sc->hal.mtkswitch_get_port_status(sc, mtkswitch_portforphy(i)); /* If a port has flapped - mark it so we can flush the ATU */ if (((mii->mii_media_status & IFM_ACTIVE) == 0 && (portstatus & MTKSWITCH_LINK_UP) != 0) || ((mii->mii_media_status & IFM_ACTIVE) != 0 && (portstatus & MTKSWITCH_LINK_UP) == 0)) { port_flap = 1; } mtkswitch_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; mii_phy_update(miisc, MII_POLLSTAT); } } if (port_flap) sc->hal.mtkswitch_atu_flush(sc); } static void mtkswitch_tick(void *arg) { struct mtkswitch_softc *sc = arg; mtkswitch_miipollstat(sc); callout_reset(&sc->callout_tick, hz, mtkswitch_tick, sc); } static void mtkswitch_lock(device_t dev) { struct mtkswitch_softc *sc = device_get_softc(dev); MTKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); MTKSWITCH_LOCK(sc); } static void mtkswitch_unlock(device_t dev) { struct mtkswitch_softc *sc = device_get_softc(dev); MTKSWITCH_LOCK_ASSERT(sc, MA_OWNED); MTKSWITCH_UNLOCK(sc); } static etherswitch_info_t * mtkswitch_getinfo(device_t dev) { struct mtkswitch_softc *sc = device_get_softc(dev); return (&sc->info); } static inline int mtkswitch_is_cpuport(struct mtkswitch_softc *sc, int port) { return (sc->cpuport == port); } static int mtkswitch_getport(device_t dev, etherswitch_port_t *p) { struct mtkswitch_softc *sc; struct mii_data *mii; struct ifmediareq *ifmr; int err; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port > sc->info.es_nports) return (ENXIO); err = sc->hal.mtkswitch_port_vlan_get(sc, p); if (err != 0) return (err); mii = mtkswitch_miiforport(sc, p->es_port); if (mtkswitch_is_cpuport(sc, p->es_port)) { /* fill in fixed values for CPU port */ /* XXX is this valid in all cases? */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr = &p->es_ifmr; ifmr->ifm_count = 0; ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else if (mii != NULL) { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } else { ifmr = &p->es_ifmr; ifmr->ifm_count = 0; ifmr->ifm_current = ifmr->ifm_active = IFM_NONE; ifmr->ifm_mask = 0; ifmr->ifm_status = 0; } return (0); } static int mtkswitch_setport(device_t dev, etherswitch_port_t *p) { int err; struct mtkswitch_softc *sc; struct ifmedia *ifm; struct mii_data *mii; if_t ifp; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port > sc->info.es_nports) return (ENXIO); /* Port flags. */ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { err = sc->hal.mtkswitch_port_vlan_setup(sc, p); if (err) return (err); } /* Do not allow media changes on CPU port. */ if (mtkswitch_is_cpuport(sc, p->es_port)) return (0); mii = mtkswitch_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); ifp = mtkswitch_ifpforport(sc, p->es_port); ifm = &mii->mii_media; return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); } static void mtkswitch_statchg(device_t dev) { DPRINTF(dev, "%s\n", __func__); } static int mtkswitch_ifmedia_upd(if_t ifp) { struct mtkswitch_softc *sc = if_getsoftc(ifp); struct mii_data *mii = mtkswitch_miiforport(sc, if_getdunit(ifp)); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void mtkswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct mtkswitch_softc *sc = if_getsoftc(ifp); struct mii_data *mii = mtkswitch_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int mtkswitch_getconf(device_t dev, etherswitch_conf_t *conf) { struct mtkswitch_softc *sc; sc = device_get_softc(dev); /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; return (0); } static int mtkswitch_setconf(device_t dev, etherswitch_conf_t *conf) { struct mtkswitch_softc *sc; int err; sc = device_get_softc(dev); /* Set the VLAN mode. */ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) { err = mtkswitch_set_vlan_mode(sc, conf->vlan_mode); if (err != 0) return (err); } return (0); } static int mtkswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *e) { struct mtkswitch_softc *sc = device_get_softc(dev); return (sc->hal.mtkswitch_vlan_getvgroup(sc, e)); } static int mtkswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *e) { struct mtkswitch_softc *sc = device_get_softc(dev); return (sc->hal.mtkswitch_vlan_setvgroup(sc, e)); } static int mtkswitch_readphy(device_t dev, int phy, int reg) { struct mtkswitch_softc *sc = device_get_softc(dev); return (sc->hal.mtkswitch_phy_read(dev, phy, reg)); } static int mtkswitch_writephy(device_t dev, int phy, int reg, int val) { struct mtkswitch_softc *sc = device_get_softc(dev); return (sc->hal.mtkswitch_phy_write(dev, phy, reg, val)); } static int mtkswitch_readreg(device_t dev, int addr) { struct mtkswitch_softc *sc = device_get_softc(dev); return (sc->hal.mtkswitch_reg_read(dev, addr)); } static int mtkswitch_writereg(device_t dev, int addr, int value) { struct mtkswitch_softc *sc = device_get_softc(dev); return (sc->hal.mtkswitch_reg_write(dev, addr, value)); } static device_method_t mtkswitch_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mtkswitch_probe), DEVMETHOD(device_attach, mtkswitch_attach), DEVMETHOD(device_detach, mtkswitch_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, mtkswitch_readphy), DEVMETHOD(miibus_writereg, mtkswitch_writephy), DEVMETHOD(miibus_statchg, mtkswitch_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, mtkswitch_readphy), DEVMETHOD(mdio_writereg, mtkswitch_writephy), /* ehterswitch interface */ DEVMETHOD(etherswitch_lock, mtkswitch_lock), DEVMETHOD(etherswitch_unlock, mtkswitch_unlock), DEVMETHOD(etherswitch_getinfo, mtkswitch_getinfo), DEVMETHOD(etherswitch_readreg, mtkswitch_readreg), DEVMETHOD(etherswitch_writereg, mtkswitch_writereg), DEVMETHOD(etherswitch_readphyreg, mtkswitch_readphy), DEVMETHOD(etherswitch_writephyreg, mtkswitch_writephy), DEVMETHOD(etherswitch_getport, mtkswitch_getport), DEVMETHOD(etherswitch_setport, mtkswitch_setport), DEVMETHOD(etherswitch_getvgroup, mtkswitch_getvgroup), DEVMETHOD(etherswitch_setvgroup, mtkswitch_setvgroup), DEVMETHOD(etherswitch_getconf, mtkswitch_getconf), DEVMETHOD(etherswitch_setconf, mtkswitch_setconf), DEVMETHOD_END }; DEFINE_CLASS_0(mtkswitch, mtkswitch_driver, mtkswitch_methods, sizeof(struct mtkswitch_softc)); DRIVER_MODULE(mtkswitch, simplebus, mtkswitch_driver, 0, 0); DRIVER_MODULE(miibus, mtkswitch, miibus_driver, 0, 0); DRIVER_MODULE(mdio, mtkswitch, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, mtkswitch, etherswitch_driver, 0, 0); MODULE_VERSION(mtkswitch, 1); MODULE_DEPEND(mtkswitch, miibus, 1, 1, 1); MODULE_DEPEND(mtkswitch, etherswitch, 1, 1, 1); diff --git a/sys/dev/etherswitch/rtl8366/rtl8366rb.c b/sys/dev/etherswitch/rtl8366/rtl8366rb.c index 304e7f7b8325..079244b2f745 100644 --- a/sys/dev/etherswitch/rtl8366/rtl8366rb.c +++ b/sys/dev/etherswitch/rtl8366/rtl8366rb.c @@ -1,958 +1,959 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015-2016 Hiroki Mori. * Copyright (c) 2011-2012 Stefan Bethke. * 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 "opt_etherswitch.h" #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 "mdio_if.h" #include "iicbus_if.h" #include "miibus_if.h" #include "etherswitch_if.h" struct rtl8366rb_softc { struct mtx sc_mtx; /* serialize access to softc */ int smi_acquired; /* serialize access to SMI/I2C bus */ struct mtx callout_mtx; /* serialize callout */ device_t dev; int vid[RTL8366_NUM_VLANS]; char *ifname[RTL8366_NUM_PHYS]; device_t miibus[RTL8366_NUM_PHYS]; if_t ifp[RTL8366_NUM_PHYS]; struct callout callout_tick; etherswitch_info_t info; int chip_type; int phy4cpu; int numphys; }; #define RTL_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define RTL_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define RTL_LOCK_ASSERT(_sc, _what) mtx_assert(&(_s)c->sc_mtx, (_what)) #define RTL_TRYLOCK(_sc) mtx_trylock(&(_sc)->sc_mtx) #define RTL_WAITOK 0 #define RTL_NOWAIT 1 #define RTL_SMI_ACQUIRED 1 #define RTL_SMI_ACQUIRED_ASSERT(_sc) \ KASSERT((_sc)->smi_acquired == RTL_SMI_ACQUIRED, ("smi must be acquired @%s", __FUNCTION__)) #if defined(DEBUG) #define DPRINTF(dev, args...) device_printf(dev, args) #define DEVERR(dev, err, fmt, args...) do { \ if (err != 0) device_printf(dev, fmt, err, args); \ } while (0) #define DEBUG_INCRVAR(var) do { \ var++; \ } while (0) static int callout_blocked = 0; static int iic_select_retries = 0; static int phy_access_retries = 0; static SYSCTL_NODE(_debug, OID_AUTO, rtl8366rb, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "rtl8366rb"); SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, callout_blocked, CTLFLAG_RW, &callout_blocked, 0, "number of times the callout couldn't acquire the bus"); SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, iic_select_retries, CTLFLAG_RW, &iic_select_retries, 0, "number of times the I2C bus selection had to be retried"); SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, phy_access_retries, CTLFLAG_RW, &phy_access_retries, 0, "number of times PHY register access had to be retried"); #else #define DPRINTF(dev, args...) #define DEVERR(dev, err, fmt, args...) #define DEBUG_INCRVAR(var) #endif static int smi_probe(device_t dev); static int smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep); static int smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep); static int smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep); static void rtl8366rb_tick(void *arg); static int rtl8366rb_ifmedia_upd(if_t); static void rtl8366rb_ifmedia_sts(if_t, struct ifmediareq *); static void rtl8366rb_identify(driver_t *driver, device_t parent) { device_t child; struct iicbus_ivar *devi; if (device_find_child(parent, "rtl8366rb", -1) == NULL) { child = BUS_ADD_CHILD(parent, 0, "rtl8366rb", DEVICE_UNIT_ANY); devi = IICBUS_IVAR(child); devi->addr = RTL8366_IIC_ADDR; } } static int rtl8366rb_probe(device_t dev) { struct rtl8366rb_softc *sc; sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); if (smi_probe(dev) != 0) return (ENXIO); if (sc->chip_type == RTL8366RB) device_set_desc(dev, "RTL8366RB Ethernet Switch Controller"); else device_set_desc(dev, "RTL8366SR Ethernet Switch Controller"); return (BUS_PROBE_DEFAULT); } static void rtl8366rb_init(device_t dev) { struct rtl8366rb_softc *sc; int i; sc = device_get_softc(dev); /* Initialisation for TL-WR1043ND */ #ifdef RTL8366_SOFT_RESET smi_rmw(dev, RTL8366_RCR, RTL8366_RCR_SOFT_RESET, RTL8366_RCR_SOFT_RESET, RTL_WAITOK); #else smi_rmw(dev, RTL8366_RCR, RTL8366_RCR_HARD_RESET, RTL8366_RCR_HARD_RESET, RTL_WAITOK); #endif /* hard reset not return ack */ DELAY(100000); /* Enable 16 VLAN mode */ smi_rmw(dev, RTL8366_SGCR, RTL8366_SGCR_EN_VLAN | RTL8366_SGCR_EN_VLAN_4KTB, RTL8366_SGCR_EN_VLAN, RTL_WAITOK); /* Initialize our vlan table. */ for (i = 0; i <= 1; i++) sc->vid[i] = (i + 1) | ETHERSWITCH_VID_VALID; /* Remove port 0 from VLAN 1. */ smi_rmw(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, 0), (1 << 0), 0, RTL_WAITOK); /* Add port 0 untagged and port 5 tagged to VLAN 2. */ smi_rmw(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, 1), ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_MEMBER_SHIFT) | ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_UNTAG_SHIFT), ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_MEMBER_SHIFT | ((1 << 0) << RTL8366_VMCR_MU_UNTAG_SHIFT)), RTL_WAITOK); /* Set PVID 2 for port 0. */ smi_rmw(dev, RTL8366_PVCR_REG(0), RTL8366_PVCR_VAL(0, RTL8366_PVCR_PORT_MASK), RTL8366_PVCR_VAL(0, 1), RTL_WAITOK); } static int rtl8366rb_attach(device_t dev) { struct rtl8366rb_softc *sc; uint16_t rev = 0; char name[IFNAMSIZ]; int err = 0; int i; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->sc_mtx, "rtl8366rb", NULL, MTX_DEF); sc->smi_acquired = 0; mtx_init(&sc->callout_mtx, "rtl8366rbcallout", NULL, MTX_DEF); rtl8366rb_init(dev); smi_read(dev, RTL8366_CVCR, &rev, RTL_WAITOK); device_printf(dev, "rev. %d\n", rev & 0x000f); sc->phy4cpu = 0; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phy4cpu", &sc->phy4cpu); sc->numphys = sc->phy4cpu ? RTL8366_NUM_PHYS - 1 : RTL8366_NUM_PHYS; sc->info.es_nports = sc->numphys + 1; sc->info.es_nvlangroups = RTL8366_NUM_VLANS; sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q; if (sc->chip_type == RTL8366RB) sprintf(sc->info.es_name, "Realtek RTL8366RB"); else sprintf(sc->info.es_name, "Realtek RTL8366SR"); /* attach miibus and phys */ /* PHYs need an interface, so we generate a dummy one */ for (i = 0; i < sc->numphys; i++) { sc->ifp[i] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[i], sc); if_setflagbits(sc->ifp[i], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX, 0); snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(dev)); sc->ifname[i] = malloc(strlen(name)+1, M_DEVBUF, M_WAITOK); bcopy(name, sc->ifname[i], strlen(name)+1); if_initname(sc->ifp[i], sc->ifname[i], i); err = mii_attach(dev, &sc->miibus[i], sc->ifp[i], rtl8366rb_ifmedia_upd, \ rtl8366rb_ifmedia_sts, BMSR_DEFCAPMASK, \ i, MII_OFFSET_ANY, 0); if (err != 0) { device_printf(dev, "attaching PHY %d failed\n", i); return (err); } } bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init_mtx(&sc->callout_tick, &sc->callout_mtx, 0); rtl8366rb_tick(sc); return (err); } static int rtl8366rb_detach(device_t dev) { struct rtl8366rb_softc *sc; - int i; + int error, i; + + error = bus_generic_detach(dev); + if (error != 0) + return (error); sc = device_get_softc(dev); for (i=0; i < sc->numphys; i++) { - if (sc->miibus[i]) - device_delete_child(dev, sc->miibus[i]); if (sc->ifp[i] != NULL) if_free(sc->ifp[i]); free(sc->ifname[i], M_DEVBUF); } - bus_generic_detach(dev); callout_drain(&sc->callout_tick); mtx_destroy(&sc->callout_mtx); mtx_destroy(&sc->sc_mtx); return (0); } static void rtl8366rb_update_ifmedia(int portstatus, u_int *media_status, u_int *media_active) { *media_active = IFM_ETHER; *media_status = IFM_AVALID; if ((portstatus & RTL8366_PLSR_LINK) != 0) *media_status |= IFM_ACTIVE; else { *media_active |= IFM_NONE; return; } switch (portstatus & RTL8366_PLSR_SPEED_MASK) { case RTL8366_PLSR_SPEED_10: *media_active |= IFM_10_T; break; case RTL8366_PLSR_SPEED_100: *media_active |= IFM_100_TX; break; case RTL8366_PLSR_SPEED_1000: *media_active |= IFM_1000_T; break; } if ((portstatus & RTL8366_PLSR_FULLDUPLEX) != 0) *media_active |= IFM_FDX; else *media_active |= IFM_HDX; if ((portstatus & RTL8366_PLSR_TXPAUSE) != 0) *media_active |= IFM_ETH_TXPAUSE; if ((portstatus & RTL8366_PLSR_RXPAUSE) != 0) *media_active |= IFM_ETH_RXPAUSE; } static void rtl833rb_miipollstat(struct rtl8366rb_softc *sc) { int i; struct mii_data *mii; struct mii_softc *miisc; uint16_t value; int portstatus; for (i = 0; i < sc->numphys; i++) { mii = device_get_softc(sc->miibus[i]); if ((i % 2) == 0) { if (smi_read(sc->dev, RTL8366_PLSR_BASE + i/2, &value, RTL_NOWAIT) != 0) { DEBUG_INCRVAR(callout_blocked); return; } portstatus = value & 0xff; } else { portstatus = (value >> 8) & 0xff; } rtl8366rb_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; mii_phy_update(miisc, MII_POLLSTAT); } } } static void rtl8366rb_tick(void *arg) { struct rtl8366rb_softc *sc; sc = arg; rtl833rb_miipollstat(sc); callout_reset(&sc->callout_tick, hz, rtl8366rb_tick, sc); } static int smi_probe(device_t dev) { struct rtl8366rb_softc *sc; device_t iicbus, iicha; int err, i, j; uint16_t chipid; char bytes[2]; int xferd; sc = device_get_softc(dev); iicbus = device_get_parent(dev); iicha = device_get_parent(iicbus); for (i = 0; i < 2; ++i) { iicbus_reset(iicbus, IIC_FASTEST, RTL8366_IIC_ADDR, NULL); for (j=3; j--; ) { IICBUS_STOP(iicha); /* * we go directly to the host adapter because iicbus.c * only issues a stop on a bus that was successfully started. */ } err = iicbus_request_bus(iicbus, dev, IIC_WAIT); if (err != 0) goto out; err = iicbus_start(iicbus, RTL8366_IIC_ADDR | RTL_IICBUS_READ, RTL_IICBUS_TIMEOUT); if (err != 0) goto out; if (i == 0) { bytes[0] = RTL8366RB_CIR & 0xff; bytes[1] = (RTL8366RB_CIR >> 8) & 0xff; } else { bytes[0] = RTL8366SR_CIR & 0xff; bytes[1] = (RTL8366SR_CIR >> 8) & 0xff; } err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT); if (err != 0) goto out; err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0); if (err != 0) goto out; chipid = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff); if (i == 0 && chipid == RTL8366RB_CIR_ID8366RB) { DPRINTF(dev, "chip id 0x%04x\n", chipid); sc->chip_type = RTL8366RB; err = 0; break; } if (i == 1 && chipid == RTL8366SR_CIR_ID8366SR) { DPRINTF(dev, "chip id 0x%04x\n", chipid); sc->chip_type = RTL8366SR; err = 0; break; } if (i == 0) { iicbus_stop(iicbus); iicbus_release_bus(iicbus, dev); } } if (i == 2) err = ENXIO; out: iicbus_stop(iicbus); iicbus_release_bus(iicbus, dev); return (err == 0 ? 0 : ENXIO); } static int smi_acquire(struct rtl8366rb_softc *sc, int sleep) { int r = 0; if (sleep == RTL_WAITOK) RTL_LOCK(sc); else if (RTL_TRYLOCK(sc) == 0) return (EWOULDBLOCK); if (sc->smi_acquired == RTL_SMI_ACQUIRED) r = EBUSY; else { r = iicbus_request_bus(device_get_parent(sc->dev), sc->dev, \ sleep == RTL_WAITOK ? IIC_WAIT : IIC_DONTWAIT); if (r == 0) sc->smi_acquired = RTL_SMI_ACQUIRED; } RTL_UNLOCK(sc); return (r); } static int smi_release(struct rtl8366rb_softc *sc, int sleep) { if (sleep == RTL_WAITOK) RTL_LOCK(sc); else if (RTL_TRYLOCK(sc) == 0) return (EWOULDBLOCK); RTL_SMI_ACQUIRED_ASSERT(sc); iicbus_release_bus(device_get_parent(sc->dev), sc->dev); sc->smi_acquired = 0; RTL_UNLOCK(sc); return (0); } static int smi_select(device_t dev, int op, int sleep) { struct rtl8366rb_softc *sc; int err, i; device_t iicbus; struct iicbus_ivar *devi; int slave; sc = device_get_softc(dev); iicbus = device_get_parent(dev); devi = IICBUS_IVAR(dev); slave = devi->addr; RTL_SMI_ACQUIRED_ASSERT((struct rtl8366rb_softc *)device_get_softc(dev)); if (sc->chip_type == RTL8366SR) { // RTL8366SR work around // this is same work around at probe for (int i=3; i--; ) IICBUS_STOP(device_get_parent(device_get_parent(dev))); } /* * The chip does not use clock stretching when it is busy, * instead ignoring the command. Retry a few times. */ for (i = RTL_IICBUS_RETRIES; i--; ) { err = iicbus_start(iicbus, slave | op, RTL_IICBUS_TIMEOUT); if (err != IIC_ENOACK) break; if (sleep == RTL_WAITOK) { DEBUG_INCRVAR(iic_select_retries); pause("smi_select", RTL_IICBUS_RETRY_SLEEP); } else break; } return (err); } static int smi_read_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t *data, int sleep) { int err; device_t iicbus; char bytes[2]; int xferd; iicbus = device_get_parent(sc->dev); RTL_SMI_ACQUIRED_ASSERT(sc); bytes[0] = addr & 0xff; bytes[1] = (addr >> 8) & 0xff; err = smi_select(sc->dev, RTL_IICBUS_READ, sleep); if (err != 0) goto out; err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT); if (err != 0) goto out; err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0); if (err != 0) goto out; *data = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff); out: iicbus_stop(iicbus); return (err); } static int smi_write_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t data, int sleep) { int err; device_t iicbus; char bytes[4]; int xferd; iicbus = device_get_parent(sc->dev); RTL_SMI_ACQUIRED_ASSERT(sc); bytes[0] = addr & 0xff; bytes[1] = (addr >> 8) & 0xff; bytes[2] = data & 0xff; bytes[3] = (data >> 8) & 0xff; err = smi_select(sc->dev, RTL_IICBUS_WRITE, sleep); if (err == 0) err = iicbus_write(iicbus, bytes, 4, &xferd, RTL_IICBUS_TIMEOUT); iicbus_stop(iicbus); return (err); } static int smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep) { struct rtl8366rb_softc *sc; int err; sc = device_get_softc(dev); err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); err = smi_read_locked(sc, addr, data, sleep); smi_release(sc, sleep); DEVERR(dev, err, "smi_read()=%d: addr=%04x\n", addr); return (err == 0 ? 0 : EIO); } static int smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep) { struct rtl8366rb_softc *sc; int err; sc = device_get_softc(dev); err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); err = smi_write_locked(sc, addr, data, sleep); smi_release(sc, sleep); DEVERR(dev, err, "smi_write()=%d: addr=%04x\n", addr); return (err == 0 ? 0 : EIO); } static int smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep) { struct rtl8366rb_softc *sc; int err; uint16_t oldv, newv; sc = device_get_softc(dev); err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); if (err == 0) { err = smi_read_locked(sc, addr, &oldv, sleep); if (err == 0) { newv = oldv & ~mask; newv |= data & mask; if (newv != oldv) err = smi_write_locked(sc, addr, newv, sleep); } } smi_release(sc, sleep); DEVERR(dev, err, "smi_rmw()=%d: addr=%04x\n", addr); return (err == 0 ? 0 : EIO); } static etherswitch_info_t * rtl_getinfo(device_t dev) { struct rtl8366rb_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static int rtl_readreg(device_t dev, int reg) { uint16_t data; data = 0; smi_read(dev, reg, &data, RTL_WAITOK); return (data); } static int rtl_writereg(device_t dev, int reg, int value) { return (smi_write(dev, reg, value, RTL_WAITOK)); } static int rtl_getport(device_t dev, etherswitch_port_t *p) { struct rtl8366rb_softc *sc; struct ifmedia *ifm; struct mii_data *mii; struct ifmediareq *ifmr; uint16_t v; int err, vlangroup; sc = device_get_softc(dev); ifmr = &p->es_ifmr; if (p->es_port < 0 || p->es_port >= (sc->numphys + 1)) return (ENXIO); if (sc->phy4cpu && p->es_port == sc->numphys) { vlangroup = RTL8366_PVCR_GET(p->es_port + 1, rtl_readreg(dev, RTL8366_PVCR_REG(p->es_port + 1))); } else { vlangroup = RTL8366_PVCR_GET(p->es_port, rtl_readreg(dev, RTL8366_PVCR_REG(p->es_port))); } p->es_pvid = sc->vid[vlangroup] & ETHERSWITCH_VID_MASK; if (p->es_port < sc->numphys) { mii = device_get_softc(sc->miibus[p->es_port]); ifm = &mii->mii_media; err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCGIFMEDIA); if (err) return (err); } else { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; smi_read(dev, RTL8366_PLSR_BASE + (RTL8366_NUM_PHYS)/2, &v, RTL_WAITOK); v = v >> (8 * ((RTL8366_NUM_PHYS) % 2)); rtl8366rb_update_ifmedia(v, &ifmr->ifm_status, &ifmr->ifm_active); ifmr->ifm_current = ifmr->ifm_active; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; /* Return our static media list. */ if (ifmr->ifm_count > 0) { ifmr->ifm_count = 1; ifmr->ifm_ulist[0] = IFM_MAKEWORD(IFM_ETHER, IFM_1000_T, IFM_FDX, 0); } else ifmr->ifm_count = 0; } return (0); } static int rtl_setport(device_t dev, etherswitch_port_t *p) { struct rtl8366rb_softc *sc; int i, err, vlangroup; struct ifmedia *ifm; struct mii_data *mii; int port; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= (sc->numphys + 1)) return (ENXIO); vlangroup = -1; for (i = 0; i < RTL8366_NUM_VLANS; i++) { if ((sc->vid[i] & ETHERSWITCH_VID_MASK) == p->es_pvid) { vlangroup = i; break; } } if (vlangroup == -1) return (ENXIO); if (sc->phy4cpu && p->es_port == sc->numphys) { port = p->es_port + 1; } else { port = p->es_port; } err = smi_rmw(dev, RTL8366_PVCR_REG(port), RTL8366_PVCR_VAL(port, RTL8366_PVCR_PORT_MASK), RTL8366_PVCR_VAL(port, vlangroup), RTL_WAITOK); if (err) return (err); /* CPU Port */ if (p->es_port == sc->numphys) return (0); mii = device_get_softc(sc->miibus[p->es_port]); ifm = &mii->mii_media; err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCSIFMEDIA); return (err); } static int rtl_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct rtl8366rb_softc *sc; uint16_t vmcr[3]; int i; int member, untagged; sc = device_get_softc(dev); for (i=0; ies_vlangroup)); vg->es_vid = sc->vid[vg->es_vlangroup]; member = RTL8366_VMCR_MEMBER(vmcr); untagged = RTL8366_VMCR_UNTAG(vmcr); if (sc->phy4cpu) { vg->es_member_ports = ((member & 0x20) >> 1) | (member & 0x0f); vg->es_untagged_ports = ((untagged & 0x20) >> 1) | (untagged & 0x0f); } else { vg->es_member_ports = member; vg->es_untagged_ports = untagged; } vg->es_fid = RTL8366_VMCR_FID(vmcr); return (0); } static int rtl_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct rtl8366rb_softc *sc; int g; int member, untagged; sc = device_get_softc(dev); g = vg->es_vlangroup; sc->vid[g] = vg->es_vid; /* VLAN group disabled ? */ if (vg->es_member_ports == 0 && vg->es_untagged_ports == 0 && vg->es_vid == 0) return (0); sc->vid[g] |= ETHERSWITCH_VID_VALID; rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_DOT1Q_REG, g), (vg->es_vid << RTL8366_VMCR_DOT1Q_VID_SHIFT) & RTL8366_VMCR_DOT1Q_VID_MASK); if (sc->phy4cpu) { /* add space at phy4 */ member = (vg->es_member_ports & 0x0f) | ((vg->es_member_ports & 0x10) << 1); untagged = (vg->es_untagged_ports & 0x0f) | ((vg->es_untagged_ports & 0x10) << 1); } else { member = vg->es_member_ports; untagged = vg->es_untagged_ports; } if (sc->chip_type == RTL8366RB) { rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, g), ((member << RTL8366_VMCR_MU_MEMBER_SHIFT) & RTL8366_VMCR_MU_MEMBER_MASK) | ((untagged << RTL8366_VMCR_MU_UNTAG_SHIFT) & RTL8366_VMCR_MU_UNTAG_MASK)); rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_FID_REG, g), vg->es_fid); } else { rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, g), ((member << RTL8366_VMCR_MU_MEMBER_SHIFT) & RTL8366_VMCR_MU_MEMBER_MASK) | ((untagged << RTL8366_VMCR_MU_UNTAG_SHIFT) & RTL8366_VMCR_MU_UNTAG_MASK) | ((vg->es_fid << RTL8366_VMCR_FID_FID_SHIFT) & RTL8366_VMCR_FID_FID_MASK)); } return (0); } static int rtl_getconf(device_t dev, etherswitch_conf_t *conf) { /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; return (0); } static int rtl_readphy(device_t dev, int phy, int reg) { struct rtl8366rb_softc *sc; uint16_t data; int err, i, sleep; sc = device_get_softc(dev); data = 0; if (phy < 0 || phy >= RTL8366_NUM_PHYS) return (ENXIO); if (reg < 0 || reg >= RTL8366_NUM_PHY_REG) return (ENXIO); sleep = RTL_WAITOK; err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); for (i = RTL_IICBUS_RETRIES; i--; ) { err = smi_write_locked(sc, RTL8366_PACR, RTL8366_PACR_READ, sleep); if (err == 0) err = smi_write_locked(sc, RTL8366_PHYREG(phy, 0, reg), 0, sleep); if (err == 0) { err = smi_read_locked(sc, RTL8366_PADR, &data, sleep); break; } DEBUG_INCRVAR(phy_access_retries); DPRINTF(dev, "rtl_readphy(): chip not responsive, retrying %d more times\n", i); pause("rtl_readphy", RTL_IICBUS_RETRY_SLEEP); } smi_release(sc, sleep); DEVERR(dev, err, "rtl_readphy()=%d: phy=%d.%02x\n", phy, reg); return (data); } static int rtl_writephy(device_t dev, int phy, int reg, int data) { struct rtl8366rb_softc *sc; int err, i, sleep; sc = device_get_softc(dev); if (phy < 0 || phy >= RTL8366_NUM_PHYS) return (ENXIO); if (reg < 0 || reg >= RTL8366_NUM_PHY_REG) return (ENXIO); sleep = RTL_WAITOK; err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); for (i = RTL_IICBUS_RETRIES; i--; ) { err = smi_write_locked(sc, RTL8366_PACR, RTL8366_PACR_WRITE, sleep); if (err == 0) err = smi_write_locked(sc, RTL8366_PHYREG(phy, 0, reg), data, sleep); if (err == 0) { break; } DEBUG_INCRVAR(phy_access_retries); DPRINTF(dev, "rtl_writephy(): chip not responsive, retrying %d more tiems\n", i); pause("rtl_writephy", RTL_IICBUS_RETRY_SLEEP); } smi_release(sc, sleep); DEVERR(dev, err, "rtl_writephy()=%d: phy=%d.%02x\n", phy, reg); return (err == 0 ? 0 : EIO); } static int rtl8366rb_ifmedia_upd(if_t ifp) { struct rtl8366rb_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus[if_getdunit(ifp)]); mii_mediachg(mii); return (0); } static void rtl8366rb_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct rtl8366rb_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus[if_getdunit(ifp)]); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static device_method_t rtl8366rb_methods[] = { /* Device interface */ DEVMETHOD(device_identify, rtl8366rb_identify), DEVMETHOD(device_probe, rtl8366rb_probe), DEVMETHOD(device_attach, rtl8366rb_attach), DEVMETHOD(device_detach, rtl8366rb_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, rtl_readphy), DEVMETHOD(miibus_writereg, rtl_writephy), /* MDIO interface */ DEVMETHOD(mdio_readreg, rtl_readphy), DEVMETHOD(mdio_writereg, rtl_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_getconf, rtl_getconf), DEVMETHOD(etherswitch_getinfo, rtl_getinfo), DEVMETHOD(etherswitch_readreg, rtl_readreg), DEVMETHOD(etherswitch_writereg, rtl_writereg), DEVMETHOD(etherswitch_readphyreg, rtl_readphy), DEVMETHOD(etherswitch_writephyreg, rtl_writephy), DEVMETHOD(etherswitch_getport, rtl_getport), DEVMETHOD(etherswitch_setport, rtl_setport), DEVMETHOD(etherswitch_getvgroup, rtl_getvgroup), DEVMETHOD(etherswitch_setvgroup, rtl_setvgroup), DEVMETHOD_END }; DEFINE_CLASS_0(rtl8366rb, rtl8366rb_driver, rtl8366rb_methods, sizeof(struct rtl8366rb_softc)); DRIVER_MODULE(rtl8366rb, iicbus, rtl8366rb_driver, 0, 0); DRIVER_MODULE(miibus, rtl8366rb, miibus_driver, 0, 0); DRIVER_MODULE(mdio, rtl8366rb, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, rtl8366rb, etherswitch_driver, 0, 0); MODULE_VERSION(rtl8366rb, 1); MODULE_DEPEND(rtl8366rb, iicbus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(rtl8366rb, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(rtl8366rb, etherswitch, 1, 1, 1); /* XXX which versions? */ diff --git a/sys/dev/etherswitch/ukswitch/ukswitch.c b/sys/dev/etherswitch/ukswitch/ukswitch.c index ed1a27a19494..a2e30c3af8a1 100644 --- a/sys/dev/etherswitch/ukswitch/ukswitch.c +++ b/sys/dev/etherswitch/ukswitch/ukswitch.c @@ -1,573 +1,574 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Luiz Otavio O Souza. * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" MALLOC_DECLARE(M_UKSWITCH); MALLOC_DEFINE(M_UKSWITCH, "ukswitch", "ukswitch data structures"); struct ukswitch_softc { struct mtx sc_mtx; /* serialize access to softc */ device_t sc_dev; int media; /* cpu port media */ int cpuport; /* which PHY is connected to the CPU */ int phymask; /* PHYs we manage */ int phyoffset; /* PHYs register offset */ int numports; /* number of ports */ int ifpport[MII_NPHY]; int *portphy; char **ifname; device_t **miibus; if_t *ifp; struct callout callout_tick; etherswitch_info_t info; }; #define UKSWITCH_LOCK(_sc) \ mtx_lock(&(_sc)->sc_mtx) #define UKSWITCH_UNLOCK(_sc) \ mtx_unlock(&(_sc)->sc_mtx) #define UKSWITCH_LOCK_ASSERT(_sc, _what) \ mtx_assert(&(_sc)->sc_mtx, (_what)) #define UKSWITCH_TRYLOCK(_sc) \ mtx_trylock(&(_sc)->sc_mtx) #if defined(DEBUG) #define DPRINTF(dev, args...) device_printf(dev, args) #else #define DPRINTF(dev, args...) #endif static inline int ukswitch_portforphy(struct ukswitch_softc *, int); static void ukswitch_tick(void *); static int ukswitch_ifmedia_upd(if_t); static void ukswitch_ifmedia_sts(if_t, struct ifmediareq *); static int ukswitch_probe(device_t dev) { struct ukswitch_softc *sc; sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); device_set_desc(dev, "Generic MDIO switch driver"); return (BUS_PROBE_DEFAULT); } static int ukswitch_attach_phys(struct ukswitch_softc *sc) { int phy, port = 0, err = 0; char name[IFNAMSIZ]; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < MII_NPHY; phy++) { if (((1 << phy) & sc->phymask) == 0) continue; sc->ifpport[phy] = port; sc->portphy[port] = phy; sc->ifp[port] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[port], sc); if_setflags(sc->ifp[port], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX); sc->ifname[port] = malloc(strlen(name)+1, M_UKSWITCH, M_WAITOK); bcopy(name, sc->ifname[port], strlen(name)+1); if_initname(sc->ifp[port], sc->ifname[port], port); sc->miibus[port] = malloc(sizeof(device_t), M_UKSWITCH, M_WAITOK | M_ZERO); err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port], ukswitch_ifmedia_upd, ukswitch_ifmedia_sts, \ BMSR_DEFCAPMASK, phy + sc->phyoffset, MII_OFFSET_ANY, 0); DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", device_get_nameunit(*sc->miibus[port]), if_name(sc->ifp[port])); if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); break; } sc->info.es_nports = port + 1; if (++port >= sc->numports) break; } return (err); } static int ukswitch_attach(device_t dev) { struct ukswitch_softc *sc; int err = 0; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "ukswitch", NULL, MTX_DEF); strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name)); /* XXX Defaults */ sc->numports = 6; sc->phymask = 0x0f; sc->phyoffset = 0; sc->cpuport = -1; sc->media = 100; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "numports", &sc->numports); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phymask", &sc->phymask); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phyoffset", &sc->phyoffset); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "cpuport", &sc->cpuport); (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "media", &sc->media); /* Support only fast and giga ethernet. */ if (sc->media != 100 && sc->media != 1000) sc->media = 100; if (sc->cpuport != -1) /* Always attach the cpu port. */ sc->phymask |= (1 << sc->cpuport); /* We do not support any vlan groups. */ sc->info.es_nvlangroups = 0; sc->ifp = malloc(sizeof(if_t) * sc->numports, M_UKSWITCH, M_WAITOK | M_ZERO); sc->ifname = malloc(sizeof(char *) * sc->numports, M_UKSWITCH, M_WAITOK | M_ZERO); sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_UKSWITCH, M_WAITOK | M_ZERO); sc->portphy = malloc(sizeof(int) * sc->numports, M_UKSWITCH, M_WAITOK | M_ZERO); /* * Attach the PHYs and complete the bus enumeration. */ err = ukswitch_attach_phys(sc); if (err != 0) return (err); bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init(&sc->callout_tick, 0); ukswitch_tick(sc); return (err); } static int ukswitch_detach(device_t dev) { struct ukswitch_softc *sc = device_get_softc(dev); - int i, port; + int error, i, port; + + error = bus_generic_detach(dev); + if (error != 0) + return (error); callout_drain(&sc->callout_tick); for (i=0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = ukswitch_portforphy(sc, i); - if (sc->miibus[port] != NULL) - device_delete_child(dev, (*sc->miibus[port])); if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); free(sc->ifname[port], M_UKSWITCH); free(sc->miibus[port], M_UKSWITCH); } free(sc->portphy, M_UKSWITCH); free(sc->miibus, M_UKSWITCH); free(sc->ifname, M_UKSWITCH); free(sc->ifp, M_UKSWITCH); - bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); return (0); } /* * Convert PHY number to port number. */ static inline int ukswitch_portforphy(struct ukswitch_softc *sc, int phy) { return (sc->ifpport[phy]); } static inline struct mii_data * ukswitch_miiforport(struct ukswitch_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (device_get_softc(*sc->miibus[port])); } static inline if_t ukswitch_ifpforport(struct ukswitch_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (sc->ifp[port]); } /* * Poll the status for all PHYs. */ static void ukswitch_miipollstat(struct ukswitch_softc *sc) { int i, port; struct mii_data *mii; struct mii_softc *miisc; UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); for (i = 0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = ukswitch_portforphy(sc, i); if ((*sc->miibus[port]) == NULL) continue; mii = device_get_softc(*sc->miibus[port]); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; ukphy_status(miisc); mii_phy_update(miisc, MII_POLLSTAT); } } } static void ukswitch_tick(void *arg) { struct ukswitch_softc *sc = arg; ukswitch_miipollstat(sc); callout_reset(&sc->callout_tick, hz, ukswitch_tick, sc); } static void ukswitch_lock(device_t dev) { struct ukswitch_softc *sc = device_get_softc(dev); UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); UKSWITCH_LOCK(sc); } static void ukswitch_unlock(device_t dev) { struct ukswitch_softc *sc = device_get_softc(dev); UKSWITCH_LOCK_ASSERT(sc, MA_OWNED); UKSWITCH_UNLOCK(sc); } static etherswitch_info_t * ukswitch_getinfo(device_t dev) { struct ukswitch_softc *sc = device_get_softc(dev); return (&sc->info); } static int ukswitch_getport(device_t dev, etherswitch_port_t *p) { struct ukswitch_softc *sc = device_get_softc(dev); struct mii_data *mii; struct ifmediareq *ifmr = &p->es_ifmr; int err, phy; if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); p->es_pvid = 0; phy = sc->portphy[p->es_port]; mii = ukswitch_miiforport(sc, p->es_port); if (sc->cpuport != -1 && phy == sc->cpuport) { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr->ifm_count = 0; if (sc->media == 100) ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_100_TX | IFM_FDX; else ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else if (mii != NULL) { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } else { return (ENXIO); } return (0); } static int ukswitch_setport(device_t dev, etherswitch_port_t *p) { struct ukswitch_softc *sc = device_get_softc(dev); struct ifmedia *ifm; struct mii_data *mii; if_t ifp; int err; if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); if (sc->portphy[p->es_port] == sc->cpuport) return (ENXIO); mii = ukswitch_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); ifp = ukswitch_ifpforport(sc, p->es_port); ifm = &mii->mii_media; err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA); return (err); } static int ukswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { /* Not supported. */ vg->es_vid = 0; vg->es_member_ports = 0; vg->es_untagged_ports = 0; vg->es_fid = 0; return (0); } static int ukswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { /* Not supported. */ return (0); } static void ukswitch_statchg(device_t dev) { DPRINTF(dev, "%s\n", __func__); } static int ukswitch_ifmedia_upd(if_t ifp) { struct ukswitch_softc *sc = if_getsoftc(ifp); struct mii_data *mii = ukswitch_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void ukswitch_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct ukswitch_softc *sc = if_getsoftc(ifp); struct mii_data *mii = ukswitch_miiforport(sc, if_getdunit(ifp)); DPRINTF(sc->sc_dev, "%s\n", __func__); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int ukswitch_readphy(device_t dev, int phy, int reg) { struct ukswitch_softc *sc; int data; sc = device_get_softc(dev); UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); if (phy < 0 || phy >= 32) return (ENXIO); if (reg < 0 || reg >= 32) return (ENXIO); UKSWITCH_LOCK(sc); data = MDIO_READREG(device_get_parent(dev), phy, reg); UKSWITCH_UNLOCK(sc); return (data); } static int ukswitch_writephy(device_t dev, int phy, int reg, int data) { struct ukswitch_softc *sc; int err; sc = device_get_softc(dev); UKSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); if (phy < 0 || phy >= 32) return (ENXIO); if (reg < 0 || reg >= 32) return (ENXIO); UKSWITCH_LOCK(sc); err = MDIO_WRITEREG(device_get_parent(dev), phy, reg, data); UKSWITCH_UNLOCK(sc); return (err); } static int ukswitch_readreg(device_t dev, int addr) { struct ukswitch_softc *sc __diagused; sc = device_get_softc(dev); UKSWITCH_LOCK_ASSERT(sc, MA_OWNED); /* Not supported. */ return (0); } static int ukswitch_writereg(device_t dev, int addr, int value) { struct ukswitch_softc *sc __diagused; sc = device_get_softc(dev); UKSWITCH_LOCK_ASSERT(sc, MA_OWNED); /* Not supported. */ return (0); } static device_method_t ukswitch_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ukswitch_probe), DEVMETHOD(device_attach, ukswitch_attach), DEVMETHOD(device_detach, ukswitch_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, ukswitch_readphy), DEVMETHOD(miibus_writereg, ukswitch_writephy), DEVMETHOD(miibus_statchg, ukswitch_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, ukswitch_readphy), DEVMETHOD(mdio_writereg, ukswitch_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, ukswitch_lock), DEVMETHOD(etherswitch_unlock, ukswitch_unlock), DEVMETHOD(etherswitch_getinfo, ukswitch_getinfo), DEVMETHOD(etherswitch_readreg, ukswitch_readreg), DEVMETHOD(etherswitch_writereg, ukswitch_writereg), DEVMETHOD(etherswitch_readphyreg, ukswitch_readphy), DEVMETHOD(etherswitch_writephyreg, ukswitch_writephy), DEVMETHOD(etherswitch_getport, ukswitch_getport), DEVMETHOD(etherswitch_setport, ukswitch_setport), DEVMETHOD(etherswitch_getvgroup, ukswitch_getvgroup), DEVMETHOD(etherswitch_setvgroup, ukswitch_setvgroup), DEVMETHOD_END }; DEFINE_CLASS_0(ukswitch, ukswitch_driver, ukswitch_methods, sizeof(struct ukswitch_softc)); DRIVER_MODULE(ukswitch, mdio, ukswitch_driver, 0, 0); DRIVER_MODULE(miibus, ukswitch, miibus_driver, 0, 0); DRIVER_MODULE(mdio, ukswitch, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, ukswitch, etherswitch_driver, 0, 0); MODULE_VERSION(ukswitch, 1); MODULE_DEPEND(ukswitch, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(ukswitch, etherswitch, 1, 1, 1); /* XXX which versions? */