/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "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(struct ifnet *); static void felix_ifmedia_sts(struct ifnet *, 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 (sc->ports[port].ifp == NULL) return (ENOMEM); sc->ports[port].ifp->if_softc = sc; sc->ports[port].ifp->if_flags = 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_generic_probe(dev); bus_generic_attach(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; error = 0; sc = device_get_softc(dev); bus_generic_detach(dev); 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].miibus != NULL) device_delete_child(dev, sc->ports[i].miibus); 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(struct ifnet *ifp) { struct mii_data *mii; felix_softc_t sc; sc = ifp->if_softc; mii = felix_miiforport(sc, ifp->if_dunit); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void felix_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { felix_softc_t sc; struct mii_data *mii; sc = ifp->if_softc; mii = felix_miiforport(sc, ifp->if_dunit); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static 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)); }