diff options
Diffstat (limited to 'sys/dev/etherswitch/felix/felix.c')
-rw-r--r-- | sys/dev/etherswitch/felix/felix.c | 1005 |
1 files changed, 1005 insertions, 0 deletions
diff --git a/sys/dev/etherswitch/felix/felix.c b/sys/dev/etherswitch/felix/felix.c new file mode 100644 index 000000000000..098767ee063e --- /dev/null +++ b/sys/dev/etherswitch/felix/felix.c @@ -0,0 +1,1005 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021 Alstom Group. + * Copyright (c) 2021 Semihalf. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 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 <sys/param.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> +#include <sys/rman.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <dev/enetc/enetc_mdio.h> + +#include <dev/etherswitch/etherswitch.h> +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/etherswitch/felix/felix_var.h> +#include <dev/etherswitch/felix/felix_reg.h> + +#include "etherswitch_if.h" +#include "miibus_if.h" + +MALLOC_DECLARE(M_FELIX); +MALLOC_DEFINE(M_FELIX, "felix", "felix switch"); + +static device_probe_t felix_probe; +static device_attach_t felix_attach; +static device_detach_t felix_detach; + +static etherswitch_info_t* felix_getinfo(device_t); +static int felix_getconf(device_t, etherswitch_conf_t *); +static int felix_setconf(device_t, etherswitch_conf_t *); +static void felix_lock(device_t); +static void felix_unlock(device_t); +static int felix_getport(device_t, etherswitch_port_t *); +static int felix_setport(device_t, etherswitch_port_t *); +static int felix_readreg_wrapper(device_t, int); +static int felix_writereg_wrapper(device_t, int, int); +static int felix_readphy(device_t, int, int); +static int felix_writephy(device_t, int, int, int); +static int felix_setvgroup(device_t, etherswitch_vlangroup_t *); +static int felix_getvgroup(device_t, etherswitch_vlangroup_t *); + +static int felix_parse_port_fdt(felix_softc_t, phandle_t, int *); +static int felix_setup(felix_softc_t); +static void felix_setup_port(felix_softc_t, int); + +static void felix_tick(void *); +static int felix_ifmedia_upd(if_t); +static void felix_ifmedia_sts(if_t, struct ifmediareq *); + +static void felix_get_port_cfg(felix_softc_t, etherswitch_port_t *); +static void felix_set_port_cfg(felix_softc_t, etherswitch_port_t *); + +static bool felix_is_phyport(felix_softc_t, int); +static struct mii_data *felix_miiforport(felix_softc_t, unsigned int); + +static struct felix_pci_id felix_pci_ids[] = { + {PCI_VENDOR_FREESCALE, FELIX_DEV_ID, FELIX_DEV_NAME}, + {0, 0, NULL} +}; + +static device_method_t felix_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, felix_probe), + DEVMETHOD(device_attach, felix_attach), + DEVMETHOD(device_detach, felix_detach), + + /* bus interface */ + DEVMETHOD(bus_add_child, device_add_child_ordered), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + + /* etherswitch interface */ + DEVMETHOD(etherswitch_getinfo, felix_getinfo), + DEVMETHOD(etherswitch_getconf, felix_getconf), + DEVMETHOD(etherswitch_setconf, felix_setconf), + DEVMETHOD(etherswitch_lock, felix_lock), + DEVMETHOD(etherswitch_unlock, felix_unlock), + DEVMETHOD(etherswitch_getport, felix_getport), + DEVMETHOD(etherswitch_setport, felix_setport), + DEVMETHOD(etherswitch_readreg, felix_readreg_wrapper), + DEVMETHOD(etherswitch_writereg, felix_writereg_wrapper), + DEVMETHOD(etherswitch_readphyreg, felix_readphy), + DEVMETHOD(etherswitch_writephyreg, felix_writephy), + DEVMETHOD(etherswitch_setvgroup, felix_setvgroup), + DEVMETHOD(etherswitch_getvgroup, felix_getvgroup), + + /* miibus interface */ + DEVMETHOD(miibus_readreg, felix_readphy), + DEVMETHOD(miibus_writereg, felix_writephy), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(felix, felix_driver, felix_methods, + sizeof(struct felix_softc)); + +DRIVER_MODULE_ORDERED(felix, pci, felix_driver, NULL, NULL, SI_ORDER_ANY); +DRIVER_MODULE(miibus, felix, miibus_fdt_driver, NULL, NULL); +DRIVER_MODULE(etherswitch, felix, etherswitch_driver, NULL, NULL); +MODULE_VERSION(felix, 1); +MODULE_PNP_INFO("U16:vendor;U16:device;D:#", pci, felix, + felix_pci_ids, nitems(felix_pci_ids) - 1); + +static int +felix_probe(device_t dev) +{ + struct felix_pci_id *id; + felix_softc_t sc; + + sc = device_get_softc(dev); + sc->dev = dev; + + for (id = felix_pci_ids; id->vendor != 0; ++id) { + if (pci_get_device(dev) != id->device || + pci_get_vendor(dev) != id->vendor) + continue; + + device_set_desc(dev, id->desc); + + return (BUS_PROBE_DEFAULT); + } + + return (ENXIO); +} + +static int +felix_parse_port_fdt(felix_softc_t sc, phandle_t child, int *pport) +{ + uint32_t port, status; + phandle_t node; + + if (OF_getencprop(child, "reg", (void *)&port, sizeof(port)) < 0) { + device_printf(sc->dev, "Port node doesn't have reg property\n"); + return (ENXIO); + } + + *pport = port; + + node = OF_getproplen(child, "ethernet"); + if (node <= 0) + sc->ports[port].cpu_port = false; + else + sc->ports[port].cpu_port = true; + + node = ofw_bus_find_child(child, "fixed-link"); + if (node <= 0) { + sc->ports[port].fixed_port = false; + return (0); + } + + sc->ports[port].fixed_port = true; + + if (OF_getencprop(node, "speed", &status, sizeof(status)) <= 0) { + device_printf(sc->dev, + "Port has fixed-link node without link speed specified\n"); + return (ENXIO); + } + + switch (status) { + case 2500: + status = IFM_2500_T; + break; + case 1000: + status = IFM_1000_T; + break; + case 100: + status = IFM_100_T; + break; + case 10: + status = IFM_10_T; + break; + default: + device_printf(sc->dev, + "Unsupported link speed value of %d\n", + status); + return (ENXIO); + } + + if (OF_hasprop(node, "full-duplex")) + status |= IFM_FDX; + else + status |= IFM_HDX; + + status |= IFM_ETHER; + sc->ports[port].fixed_link_status = status; + return (0); +} + +static int +felix_init_interface(felix_softc_t sc, int port) +{ + char name[IFNAMSIZ]; + + snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev)); + + sc->ports[port].ifp = if_alloc(IFT_ETHER); + if_setsoftc(sc->ports[port].ifp, sc); + if_setflags(sc->ports[port].ifp, IFF_UP | IFF_BROADCAST | IFF_MULTICAST | + IFF_DRV_RUNNING | IFF_SIMPLEX); + sc->ports[port].ifname = malloc(strlen(name) + 1, M_FELIX, M_NOWAIT); + if (sc->ports[port].ifname == NULL) { + if_free(sc->ports[port].ifp); + return (ENOMEM); + } + + memcpy(sc->ports[port].ifname, name, strlen(name) + 1); + if_initname(sc->ports[port].ifp, sc->ports[port].ifname, port); + return (0); +} + +static void +felix_setup_port(felix_softc_t sc, int port) +{ + + /* Link speed has to be always set to 1000 in the clock register. */ + FELIX_DEVGMII_PORT_WR4(sc, port, FELIX_DEVGMII_CLK_CFG, + FELIX_DEVGMII_CLK_CFG_SPEED_1000); + FELIX_DEVGMII_PORT_WR4(sc, port, FELIX_DEVGMII_MAC_CFG, + FELIX_DEVGMII_MAC_CFG_TX_ENA | FELIX_DEVGMII_MAC_CFG_RX_ENA); + FELIX_WR4(sc, FELIX_QSYS_PORT_MODE(port), + FELIX_QSYS_PORT_MODE_PORT_ENA); + + /* + * Enable "VLANMTU". Each port has a configurable MTU. + * Accept frames that are 8 and 4 bytes longer than it + * for double and single tagged frames respectively. + * Since etherswitch API doesn't provide an option to change + * MTU don't touch it for now. + */ + FELIX_DEVGMII_PORT_WR4(sc, port, FELIX_DEVGMII_VLAN_CFG, + FELIX_DEVGMII_VLAN_CFG_ENA | + FELIX_DEVGMII_VLAN_CFG_LEN_ENA | + FELIX_DEVGMII_VLAN_CFG_DOUBLE_ENA); +} + +static int +felix_setup(felix_softc_t sc) +{ + int timeout, i; + uint32_t reg; + + /* Trigger soft reset, bit is self-clearing, with 5s timeout. */ + FELIX_WR4(sc, FELIX_DEVCPU_GCB_RST, FELIX_DEVCPU_GCB_RST_EN); + timeout = FELIX_INIT_TIMEOUT; + do { + DELAY(1000); + reg = FELIX_RD4(sc, FELIX_DEVCPU_GCB_RST); + if ((reg & FELIX_DEVCPU_GCB_RST_EN) == 0) + break; + } while (timeout-- > 0); + if (timeout == 0) { + device_printf(sc->dev, + "Timeout while waiting for switch to reset\n"); + return (ETIMEDOUT); + } + + FELIX_WR4(sc, FELIX_SYS_RAM_CTRL, FELIX_SYS_RAM_CTRL_INIT); + timeout = FELIX_INIT_TIMEOUT; + do { + DELAY(1000); + reg = FELIX_RD4(sc, FELIX_SYS_RAM_CTRL); + if ((reg & FELIX_SYS_RAM_CTRL_INIT) == 0) + break; + } while (timeout-- > 0); + if (timeout == 0) { + device_printf(sc->dev, + "Timeout while waiting for switch RAM init.\n"); + return (ETIMEDOUT); + } + + FELIX_WR4(sc, FELIX_SYS_CFG, FELIX_SYS_CFG_CORE_EN); + + for (i = 0; i < sc->info.es_nports; i++) + felix_setup_port(sc, i); + + return (0); +} + +static int +felix_timer_rate(SYSCTL_HANDLER_ARGS) +{ + felix_softc_t sc; + int error, value, old; + + sc = arg1; + + old = value = sc->timer_ticks; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + + if (value < 0) + return (EINVAL); + + if (value == old) + return (0); + + FELIX_LOCK(sc); + sc->timer_ticks = value; + callout_reset(&sc->tick_callout, sc->timer_ticks, felix_tick, sc); + FELIX_UNLOCK(sc); + + return (0); +} + +static int +felix_attach(device_t dev) +{ + phandle_t child, ports, node; + int error, port, rid; + felix_softc_t sc; + uint32_t phy_addr; + ssize_t size; + + sc = device_get_softc(dev); + sc->info.es_nports = 0; + sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q; + strlcpy(sc->info.es_name, "Felix TSN Switch", sizeof(sc->info.es_name)); + + rid = PCIR_BAR(FELIX_BAR_MDIO); + sc->mdio = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->mdio == NULL) { + device_printf(dev, "Failed to allocate MDIO registers.\n"); + return (ENXIO); + } + + rid = PCIR_BAR(FELIX_BAR_REGS); + sc->regs = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->regs == NULL) { + device_printf(dev, "Failed to allocate registers BAR.\n"); + error = ENXIO; + goto out_fail; + } + + mtx_init(&sc->mtx, "felix lock", NULL, MTX_DEF); + callout_init_mtx(&sc->tick_callout, &sc->mtx, 0); + + node = ofw_bus_get_node(dev); + if (node <= 0) { + error = ENXIO; + goto out_fail; + } + + ports = ofw_bus_find_child(node, "ports"); + if (ports == 0) { + device_printf(dev, + "Failed to find \"ports\" property in DTS.\n"); + error = ENXIO; + goto out_fail; + } + + for (child = OF_child(ports); child != 0; child = OF_peer(child)) { + /* Do not parse disabled ports. */ + if (ofw_bus_node_status_okay(child) == 0) + continue; + + error = felix_parse_port_fdt(sc, child, &port); + if (error != 0) + goto out_fail; + + error = felix_init_interface(sc, port); + if (error != 0) { + device_printf(sc->dev, + "Failed to initialize interface.\n"); + goto out_fail; + } + + if (sc->ports[port].fixed_port) { + sc->info.es_nports++; + continue; + } + + size = OF_getencprop(child, "phy-handle", &node, sizeof(node)); + if (size <= 0) { + device_printf(sc->dev, + "Failed to acquire PHY handle from FDT.\n"); + error = ENXIO; + goto out_fail; + } + + node = OF_node_from_xref(node); + size = OF_getencprop(node, "reg", &phy_addr, sizeof(phy_addr)); + if (size <= 0) { + device_printf(sc->dev, + "Failed to obtain PHY address.\n"); + error = ENXIO; + goto out_fail; + } + + sc->ports[port].phyaddr = phy_addr; + sc->ports[port].miibus = NULL; + error = mii_attach(dev, &sc->ports[port].miibus, sc->ports[port].ifp, + felix_ifmedia_upd, felix_ifmedia_sts, BMSR_DEFCAPMASK, + phy_addr, MII_OFFSET_ANY, 0); + if (error != 0) + goto out_fail; + + sc->info.es_nports++; + } + + error = felix_setup(sc); + if (error != 0) + goto out_fail; + + sc->timer_ticks = hz; /* Default to 1s. */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "timer_ticks", CTLTYPE_INT | CTLFLAG_RW, + sc, 0, felix_timer_rate, "I", + "Number of ticks between timer invocations"); + + /* The tick routine has to be called with the lock held. */ + FELIX_LOCK(sc); + felix_tick(sc); + FELIX_UNLOCK(sc); + + /* Allow etherswitch to attach as our child. */ + bus_identify_children(dev); + bus_attach_children(dev); + + return (0); + +out_fail: + felix_detach(dev); + return (error); +} + +static int +felix_detach(device_t dev) +{ + felix_softc_t sc; + int error; + int i; + + sc = device_get_softc(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + + mtx_lock(&sc->mtx); + callout_stop(&sc->tick_callout); + mtx_unlock(&sc->mtx); + mtx_destroy(&sc->mtx); + + /* + * If we have been fully attached do a soft reset. + * This way after when driver is unloaded switch is left in unmanaged mode. + */ + if (device_is_attached(dev)) + felix_setup(sc); + + for (i = 0; i < sc->info.es_nports; i++) { + if (sc->ports[i].ifp != NULL) + if_free(sc->ports[i].ifp); + if (sc->ports[i].ifname != NULL) + free(sc->ports[i].ifname, M_FELIX); + } + + if (sc->regs != NULL) + error = bus_release_resource(sc->dev, SYS_RES_MEMORY, + rman_get_rid(sc->regs), sc->regs); + + if (sc->mdio != NULL) + error = bus_release_resource(sc->dev, SYS_RES_MEMORY, + rman_get_rid(sc->mdio), sc->mdio); + + return (error); +} + +static etherswitch_info_t* +felix_getinfo(device_t dev) +{ + felix_softc_t sc; + + sc = device_get_softc(dev); + return (&sc->info); +} + +static int +felix_getconf(device_t dev, etherswitch_conf_t *conf) +{ + felix_softc_t 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 +felix_init_vlan(felix_softc_t sc) +{ + int timeout = FELIX_INIT_TIMEOUT; + uint32_t reg; + int i; + + /* Flush VLAN table in hardware. */ + FELIX_WR4(sc, FELIX_ANA_VT, FELIX_ANA_VT_RESET); + do { + DELAY(1000); + reg = FELIX_RD4(sc, FELIX_ANA_VT); + if ((reg & FELIX_ANA_VT_STS) == FELIX_ANA_VT_IDLE) + break; + } while (timeout-- > 0); + if (timeout == 0) { + device_printf(sc->dev, + "Timeout during VLAN table reset.\n"); + return (ETIMEDOUT); + } + + /* Flush VLAN table in sc. */ + for (i = 0; i < sc->info.es_nvlangroups; i++) + sc->vlans[i] = 0; + + /* + * Make all ports VLAN aware. + * Read VID from incoming frames and use it for port grouping + * purposes. + * Don't set this if pvid is set. + */ + for (i = 0; i < sc->info.es_nports; i++) { + reg = FELIX_ANA_PORT_RD4(sc, i, FELIX_ANA_PORT_VLAN_CFG); + if ((reg & FELIX_ANA_PORT_VLAN_CFG_VID_MASK) != 0) + continue; + + reg |= FELIX_ANA_PORT_VLAN_CFG_VID_AWARE; + FELIX_ANA_PORT_WR4(sc, i, FELIX_ANA_PORT_VLAN_CFG, reg); + } + return (0); +} + +static int +felix_setconf(device_t dev, etherswitch_conf_t *conf) +{ + felix_softc_t sc; + int error; + + error = 0; + /* Set the VLAN mode. */ + sc = device_get_softc(dev); + FELIX_LOCK(sc); + if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) { + switch (conf->vlan_mode) { + case ETHERSWITCH_VLAN_DOT1Q: + sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; + sc->info.es_nvlangroups = FELIX_NUM_VLANS; + error = felix_init_vlan(sc); + break; + default: + error = EINVAL; + } + } + FELIX_UNLOCK(sc); + return (error); +} + +static void +felix_lock(device_t dev) +{ + felix_softc_t sc; + + sc = device_get_softc(dev); + FELIX_LOCK_ASSERT(sc, MA_NOTOWNED); + FELIX_LOCK(sc); +} + +static void +felix_unlock(device_t dev) +{ + felix_softc_t sc; + + sc = device_get_softc(dev); + FELIX_LOCK_ASSERT(sc, MA_OWNED); + FELIX_UNLOCK(sc); +} + +static void +felix_get_port_cfg(felix_softc_t sc, etherswitch_port_t *p) +{ + uint32_t reg; + + p->es_flags = 0; + + reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_DROP_CFG); + if (reg & FELIX_ANA_PORT_DROP_CFG_TAGGED) + p->es_flags |= ETHERSWITCH_PORT_DROPTAGGED; + + if (reg & FELIX_ANA_PORT_DROP_CFG_UNTAGGED) + p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED; + + reg = FELIX_DEVGMII_PORT_RD4(sc, p->es_port, FELIX_DEVGMII_VLAN_CFG); + if (reg & FELIX_DEVGMII_VLAN_CFG_DOUBLE_ENA) + p->es_flags |= ETHERSWITCH_PORT_DOUBLE_TAG; + + reg = FELIX_REW_PORT_RD4(sc, p->es_port, FELIX_REW_PORT_TAG_CFG); + if (reg & FELIX_REW_PORT_TAG_CFG_ALL) + p->es_flags |= ETHERSWITCH_PORT_ADDTAG; + + reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_VLAN_CFG); + if (reg & FELIX_ANA_PORT_VLAN_CFG_POP) + p->es_flags |= ETHERSWITCH_PORT_STRIPTAGINGRESS; + + p->es_pvid = reg & FELIX_ANA_PORT_VLAN_CFG_VID_MASK; +} + +static int +felix_getport(device_t dev, etherswitch_port_t *p) +{ + struct ifmediareq *ifmr; + struct mii_data *mii; + felix_softc_t sc; + int error; + + error = 0; + sc = device_get_softc(dev); + FELIX_LOCK_ASSERT(sc, MA_NOTOWNED); + + if (p->es_port >= sc->info.es_nports || p->es_port < 0) + return (EINVAL); + + FELIX_LOCK(sc); + felix_get_port_cfg(sc, p); + if (sc->ports[p->es_port].fixed_port) { + ifmr = &p->es_ifmr; + ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; + ifmr->ifm_count = 0; + ifmr->ifm_active = sc->ports[p->es_port].fixed_link_status; + ifmr->ifm_current = ifmr->ifm_active; + ifmr->ifm_mask = 0; + } else { + mii = felix_miiforport(sc, p->es_port); + error = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, + &mii->mii_media, SIOCGIFMEDIA); + } + FELIX_UNLOCK(sc); + return (error); +} + +static void +felix_set_port_cfg(felix_softc_t sc, etherswitch_port_t *p) +{ + uint32_t reg; + + reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_DROP_CFG); + if (p->es_flags & ETHERSWITCH_PORT_DROPTAGGED) + reg |= FELIX_ANA_PORT_DROP_CFG_TAGGED; + else + reg &= ~FELIX_ANA_PORT_DROP_CFG_TAGGED; + + if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED) + reg |= FELIX_ANA_PORT_DROP_CFG_UNTAGGED; + else + reg &= ~FELIX_ANA_PORT_DROP_CFG_UNTAGGED; + + FELIX_ANA_PORT_WR4(sc, p->es_port, FELIX_ANA_PORT_DROP_CFG, reg); + + reg = FELIX_REW_PORT_RD4(sc, p->es_port, FELIX_REW_PORT_TAG_CFG); + if (p->es_flags & ETHERSWITCH_PORT_ADDTAG) + reg |= FELIX_REW_PORT_TAG_CFG_ALL; + else + reg &= ~FELIX_REW_PORT_TAG_CFG_ALL; + + FELIX_REW_PORT_WR4(sc, p->es_port, FELIX_REW_PORT_TAG_CFG, reg); + + reg = FELIX_ANA_PORT_RD4(sc, p->es_port, FELIX_ANA_PORT_VLAN_CFG); + if (p->es_flags & ETHERSWITCH_PORT_STRIPTAGINGRESS) + reg |= FELIX_ANA_PORT_VLAN_CFG_POP; + else + reg &= ~FELIX_ANA_PORT_VLAN_CFG_POP; + + reg &= ~FELIX_ANA_PORT_VLAN_CFG_VID_MASK; + reg |= p->es_pvid & FELIX_ANA_PORT_VLAN_CFG_VID_MASK; + /* + * If port VID is set use it for VLAN classification, + * instead of frame VID. + * By default the frame tag takes precedence. + * Force the switch to ignore it. + */ + if (p->es_pvid != 0) + reg &= ~FELIX_ANA_PORT_VLAN_CFG_VID_AWARE; + else + reg |= FELIX_ANA_PORT_VLAN_CFG_VID_AWARE; + + FELIX_ANA_PORT_WR4(sc, p->es_port, FELIX_ANA_PORT_VLAN_CFG, reg); +} + +static int +felix_setport(device_t dev, etherswitch_port_t *p) +{ + felix_softc_t sc; + struct mii_data *mii; + int error; + + error = 0; + sc = device_get_softc(dev); + FELIX_LOCK_ASSERT(sc, MA_NOTOWNED); + + if (p->es_port >= sc->info.es_nports || p->es_port < 0) + return (EINVAL); + + FELIX_LOCK(sc); + felix_set_port_cfg(sc, p); + if (felix_is_phyport(sc, p->es_port)) { + mii = felix_miiforport(sc, p->es_port); + error = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, + SIOCSIFMEDIA); + } + FELIX_UNLOCK(sc); + + return (error); +} + +static int +felix_readreg_wrapper(device_t dev, int addr_reg) +{ + felix_softc_t sc; + + sc = device_get_softc(dev); + if (addr_reg > rman_get_size(sc->regs)) + return (UINT32_MAX); /* Can't return errors here. */ + + return (FELIX_RD4(sc, addr_reg)); +} + +static int +felix_writereg_wrapper(device_t dev, int addr_reg, int val) +{ + felix_softc_t sc; + + sc = device_get_softc(dev); + if (addr_reg > rman_get_size(sc->regs)) + return (EINVAL); + + FELIX_WR4(sc, addr_reg, val); + return (0); +} + +static int +felix_readphy(device_t dev, int phy, int reg) +{ + felix_softc_t sc; + + sc = device_get_softc(dev); + + return (enetc_mdio_read(sc->mdio, FELIX_MDIO_BASE, phy, reg)); +} + +static int +felix_writephy(device_t dev, int phy, int reg, int data) +{ + felix_softc_t sc; + + sc = device_get_softc(dev); + + return (enetc_mdio_write(sc->mdio, FELIX_MDIO_BASE, phy, reg, data)); +} + +static int +felix_set_dot1q_vlan(felix_softc_t sc, etherswitch_vlangroup_t *vg) +{ + uint32_t reg; + int i, vid; + + vid = vg->es_vid & ETHERSWITCH_VID_MASK; + + /* Tagged mode is not supported. */ + if (vg->es_member_ports != vg->es_untagged_ports) + return (EINVAL); + + /* + * Hardware support 4096 groups, but we can't do group_id == vid. + * Note that hw_group_id == vid. + */ + if (vid == 0) { + /* Clear VLAN table entry using old VID. */ + FELIX_WR4(sc, FELIX_ANA_VTIDX, sc->vlans[vg->es_vlangroup]); + FELIX_WR4(sc, FELIX_ANA_VT, FELIX_ANA_VT_WRITE); + sc->vlans[vg->es_vlangroup] = 0; + return (0); + } + + /* The VID is already used in a different group. */ + for (i = 0; i < sc->info.es_nvlangroups; i++) + if (i != vg->es_vlangroup && vid == sc->vlans[i]) + return (EINVAL); + + /* This group already uses a different VID. */ + if (sc->vlans[vg->es_vlangroup] != 0 && + sc->vlans[vg->es_vlangroup] != vid) + return (EINVAL); + + sc->vlans[vg->es_vlangroup] = vid; + + /* Assign members to the given group. */ + reg = vg->es_member_ports & FELIX_ANA_VT_PORTMASK_MASK; + reg <<= FELIX_ANA_VT_PORTMASK_SHIFT; + reg |= FELIX_ANA_VT_WRITE; + + FELIX_WR4(sc, FELIX_ANA_VTIDX, vid); + FELIX_WR4(sc, FELIX_ANA_VT, reg); + + /* + * According to documentation read and write commands + * are instant. + * Add a small delay just to be safe. + */ + mb(); + DELAY(100); + reg = FELIX_RD4(sc, FELIX_ANA_VT); + if ((reg & FELIX_ANA_VT_STS) != FELIX_ANA_VT_IDLE) + return (ENXIO); + + return (0); +} + +static int +felix_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) +{ + felix_softc_t sc; + int error; + + sc = device_get_softc(dev); + + FELIX_LOCK(sc); + if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) + error = felix_set_dot1q_vlan(sc, vg); + else + error = EINVAL; + + FELIX_UNLOCK(sc); + return (error); +} + +static int +felix_get_dot1q_vlan(felix_softc_t sc, etherswitch_vlangroup_t *vg) +{ + uint32_t reg; + int vid; + + vid = sc->vlans[vg->es_vlangroup]; + + if (vid == 0) + return (0); + + FELIX_WR4(sc, FELIX_ANA_VTIDX, vid); + FELIX_WR4(sc, FELIX_ANA_VT, FELIX_ANA_VT_READ); + + /* + * According to documentation read and write commands + * are instant. + * Add a small delay just to be safe. + */ + mb(); + DELAY(100); + reg = FELIX_RD4(sc, FELIX_ANA_VT); + if ((reg & FELIX_ANA_VT_STS) != FELIX_ANA_VT_IDLE) + return (ENXIO); + + reg >>= FELIX_ANA_VT_PORTMASK_SHIFT; + reg &= FELIX_ANA_VT_PORTMASK_MASK; + + vg->es_untagged_ports = vg->es_member_ports = reg; + vg->es_fid = 0; + vg->es_vid = vid | ETHERSWITCH_VID_VALID; + return (0); +} + +static int +felix_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) +{ + felix_softc_t sc; + int error; + + sc = device_get_softc(dev); + + FELIX_LOCK(sc); + if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) + error = felix_get_dot1q_vlan(sc, vg); + else + error = EINVAL; + + FELIX_UNLOCK(sc); + return (error); +} + +static void +felix_tick(void *arg) +{ + struct mii_data *mii; + felix_softc_t sc; + int port; + + sc = arg; + + FELIX_LOCK_ASSERT(sc, MA_OWNED); + + for (port = 0; port < sc->info.es_nports; port++) { + if (!felix_is_phyport(sc, port)) + continue; + + mii = felix_miiforport(sc, port); + MPASS(mii != NULL); + mii_tick(mii); + } + if (sc->timer_ticks != 0) + callout_reset(&sc->tick_callout, sc->timer_ticks, felix_tick, sc); +} + +static int +felix_ifmedia_upd(if_t ifp) +{ + struct mii_data *mii; + felix_softc_t sc; + + sc = if_getsoftc(ifp); + mii = felix_miiforport(sc, if_getdunit(ifp)); + if (mii == NULL) + return (ENXIO); + + mii_mediachg(mii); + return (0); +} + +static void +felix_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) +{ + felix_softc_t sc; + struct mii_data *mii; + + sc = if_getsoftc(ifp); + mii = felix_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 bool +felix_is_phyport(felix_softc_t sc, int port) +{ + + return (!sc->ports[port].fixed_port); +} + +static struct mii_data* +felix_miiforport(felix_softc_t sc, unsigned int port) +{ + + if (!felix_is_phyport(sc, port)) + return (NULL); + + return (device_get_softc(sc->ports[port].miibus)); +} |