Date: Thu, 13 Jun 2013 11:41:10 -0300 From: Luiz Otavio O Souza <lists.br@gmail.com> To: freebsd-embedded@freebsd.org, ray@freebsd.org, Adrian Chadd <adrian@freebsd.org> Subject: arswitch vlan support Message-ID: <CAB=2f8y1tmCKZR=WoBkvdgVeWDaQqRAU9AOrJ5qfb5M189LCPA@mail.gmail.com>
index | next in thread | raw e-mail
[-- Attachment #1 --] Hello, I've been working for a while to support vlans management on arswitch and the result seems to work quite well. I'd to change etherswitchcfg(8) again to better support port based vlans setups. There was a bug in arswitch_writereg() where the lsb/msb parts were written in the wrong order and that was causing all kinds of instability. Fixing that allows me to bring back the page code (which only change the switch register page when needed) and also remove some previous workarounds. Here are the patches (and urls if the list eat them): http://loos.no-ip.org/switch/001-etherswitch-vid-valid.diff Adds a flag to tell to etherswitch consumers when a vlangroup is valid (in use, even when there are no members on the vlangroup). http://loos.no-ip.org/switch/002-ip17x-vid-valid.diff Add the support to report valid vlans and fix a few bugs (don't allow change the vlan when working in port based vlan mode, don't allow setup the same vlan id on different vlangroups). Tested on RB433U (IP175D) and an old RB450 (IP1757C). http://loos.no-ip.org/switch/003-rtl8366rb-vid-valid.diff Report valid vlans for rtl8366 based switches (just a mechanical change, i'll test the changes now that i have the hardware). http://loos.no-ip.org/switch/004-arswitch-vlans.diff Implement port and 802.1q vlan support for ar8x16 (and ar724x) based switches. Fix the instability bug. Tested on RSPRO (standalone ar8316), RB450G (standalone ar8316) and on TPLink MR-3220 (ar7240 integrated switch). Do you guys have suggestions, comments ? Thanks, Luiz [-- Attachment #2 --] Index: sbin/etherswitchcfg/etherswitchcfg.c =================================================================== --- sbin/etherswitchcfg/etherswitchcfg.c (revision 250908) +++ sbin/etherswitchcfg/etherswitchcfg.c (working copy) @@ -471,8 +471,9 @@ vg.es_vlangroup = vlangroup; if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0) err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)"); - if (vg.es_vid == 0 && vg.es_member_ports == 0) + if ((vg.es_vid & ETHERSWITCH_VID_VALID) == 0) return; + vg.es_vid &= ETHERSWITCH_VID_MASK; printf("vlangroup%d:\n", vlangroup); if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_PORT) printf("\tport: %d\n", vg.es_vid); @@ -626,7 +627,9 @@ newmode(&cfg, MODE_PORT); } else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) { if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups) - errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nvlangroups); + errx(EX_USAGE, + "vlangroup unit must be between 0 and %d", + cfg.info.es_nvlangroups - 1); newmode(&cfg, MODE_VLANGROUP); } else if (strcmp(argv[0], "config") == 0) { newmode(&cfg, MODE_CONFIG); Index: sys/dev/etherswitch/etherswitch.h =================================================================== --- sys/dev/etherswitch/etherswitch.h (revision 250908) +++ sys/dev/etherswitch/etherswitch.h (working copy) @@ -26,6 +26,8 @@ typedef struct etherswitch_phyreg etherswitch_phyreg_t; #define ETHERSWITCH_NAMEMAX 64 +#define ETHERSWITCH_VID_MASK 0xfff +#define ETHERSWITCH_VID_VALID (1 << 12) #define ETHERSWITCH_VLAN_ISL (1 << 0) /* ISL */ #define ETHERSWITCH_VLAN_PORT (1 << 1) /* Port based vlan */ #define ETHERSWITCH_VLAN_DOT1Q (1 << 2) /* 802.1q */ [-- Attachment #3 --] Index: dev/etherswitch/ip17x/ip175c.c =================================================================== --- dev/etherswitch/ip17x/ip175c.c (revision 250901) +++ dev/etherswitch/ip17x/ip175c.c (working copy) @@ -147,9 +147,9 @@ memset(vlans, 0, sizeof(vlans)); for (i = 0; i < IP17X_MAX_VLANS; i++) { v = &sc->vlan[i]; - if (v->vlanid == 0) + if ((v->vlanid & ETHERSWITCH_VID_VALID) == 0) continue; - vlans[v->vlanid] = v->ports; + vlans[v->vlanid & ETHERSWITCH_VID_MASK] = v->ports; } for (j = 0, i = 1; i <= IP17X_MAX_VLANS / 2; i++) { Index: dev/etherswitch/ip17x/ip175d.c =================================================================== --- dev/etherswitch/ip17x/ip175d.c (revision 250901) +++ dev/etherswitch/ip17x/ip175d.c (working copy) @@ -94,7 +94,8 @@ striptag[i] = 0; v = &sc->vlan[i]; - if (v->vlanid == 0 || sc->vlan_mode == 0) { + if ((v->vlanid & ETHERSWITCH_VID_VALID) == 0 || + sc->vlan_mode == 0) { /* Vlangroup disabled. Reset the filter. */ ip17x_writephy(sc->sc_dev, 22, 14 + i, i + 1); ports[i] = 0x3f; @@ -105,7 +106,8 @@ ports[i] = v->ports; /* Setup the filter, write the VLAN id. */ - ip17x_writephy(sc->sc_dev, 22, 14 + i, v->vlanid); + ip17x_writephy(sc->sc_dev, 22, 14 + i, + v->vlanid & ETHERSWITCH_VID_MASK); for (j = 0; j < MII_NPHY; j++) { if ((ports[i] & (1 << j)) == 0) Index: dev/etherswitch/ip17x/ip17x_vlans.c =================================================================== --- dev/etherswitch/ip17x/ip17x_vlans.c (revision 250901) +++ dev/etherswitch/ip17x/ip17x_vlans.c (working copy) @@ -74,7 +74,7 @@ if (((1 << phy) & sc->phymask) == 0) continue; v = &sc->vlan[i]; - v->vlanid = i++; + v->vlanid = i++ | ETHERSWITCH_VID_VALID; v->ports = (1 << sc->cpuport); for (j = 0; j < MII_NPHY; j++) { if (((1 << j) & sc->phymask) == 0) @@ -90,10 +90,10 @@ * members of vlan 1. */ v = &sc->vlan[0]; - v->vlanid = 1; - /* Set PVID for everyone. */ + v->vlanid = 1 | ETHERSWITCH_VID_VALID; + /* Set PVID to 1 for everyone. */ for (i = 0; i < sc->numports; i++) - sc->pvid[i] = v->vlanid; + sc->pvid[i] = 1; for (i = 0; i < MII_NPHY; i++) { if ((sc->phymask & (1 << i)) == 0) continue; @@ -148,11 +148,29 @@ return (EINVAL); /* IP175C don't support VLAN IDs > 15. */ - if (IP17X_IS_SWITCH(sc, IP175C) && vg->es_vid > IP175C_LAST_VLAN) + if (IP17X_IS_SWITCH(sc, IP175C) && + (vg->es_vid & ETHERSWITCH_VID_MASK) > IP175C_LAST_VLAN) return (EINVAL); /* Vlan ID. */ - sc->vlan[vg->es_vlangroup].vlanid = vg->es_vid; + if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { + for (i = 0; i < sc->info.es_nvlangroups; i++) { + /* Is this Vlan ID already set in another vlangroup ? */ + if (i != vg->es_vlangroup && + sc->vlan[i].vlanid & ETHERSWITCH_VID_VALID && + (sc->vlan[i].vlanid & ETHERSWITCH_VID_MASK) == + (vg->es_vid & ETHERSWITCH_VID_MASK)) + return (EINVAL); + } + sc->vlan[vg->es_vlangroup].vlanid = vg->es_vid & + ETHERSWITCH_VID_MASK; + /* Setting the vlanid to zero disables the vlangroup. */ + if (sc->vlan[vg->es_vlangroup].vlanid == 0) { + sc->vlan[vg->es_vlangroup].ports = 0; + return (sc->hal.ip17x_hw_setup(sc)); + } + sc->vlan[vg->es_vlangroup].vlanid |= ETHERSWITCH_VID_VALID; + } /* Member Ports. */ sc->vlan[vg->es_vlangroup].ports = 0; [-- Attachment #4 --] Index: dev/etherswitch/rtl8366/rtl8366rb.c =================================================================== --- dev/etherswitch/rtl8366/rtl8366rb.c (revision 250901) +++ dev/etherswitch/rtl8366/rtl8366rb.c (working copy) @@ -619,7 +619,7 @@ for (i=0; i<3; i++) vmcr[i] = rtl_readreg(dev, RTL8366RB_VMCR(i, vg->es_vlangroup)); - vg->es_vid = RTL8366RB_VMCR_VID(vmcr); + vg->es_vid = RTL8366RB_VMCR_VID(vmcr) | ETHERSWITCH_VID_VALID; vg->es_member_ports = RTL8366RB_VMCR_MEMBER(vmcr); vg->es_untagged_ports = RTL8366RB_VMCR_UNTAG(vmcr); vg->es_fid = RTL8366RB_VMCR_FID(vmcr); [-- Attachment #5 --] --- dev/etherswitch/arswitch/arswitch_vlans.h.orig 2013-06-12 10:36:30.000000000 -0300 +++ dev/etherswitch/arswitch/arswitch_vlans.h 2013-05-25 17:34:50.000000000 -0300 @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 2013 Luiz Otavio O Souza. + * 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. + * + * $FreeBSD$ + */ +#ifndef __ARSWITCH_VLANS_H__ +#define __ARSWITCH_VLANS_H__ + +void arswitch_reset_vlans(struct arswitch_softc *); +int arswitch_getvgroup(device_t, etherswitch_vlangroup_t *); +int arswitch_setvgroup(device_t, etherswitch_vlangroup_t *); +int arswitch_get_pvid(struct arswitch_softc *, int, int *); +int arswitch_set_pvid(struct arswitch_softc *, int, int); + +#endif /* __ARSWITCH_VLANS_H__ */ --- dev/etherswitch/arswitch/arswitch_vlans.c.orig 2013-06-12 10:36:30.000000000 -0300 +++ dev/etherswitch/arswitch/arswitch_vlans.c 2013-06-10 12:16:23.823103398 -0300 @@ -0,0 +1,375 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/systm.h> +#include <sys/socket.h> + +#include <net/if.h> + +#include <dev/mii/mii.h> + +#include <dev/etherswitch/etherswitch.h> +#include <dev/etherswitch/arswitch/arswitchreg.h> +#include <dev/etherswitch/arswitch/arswitchvar.h> +#include <dev/etherswitch/arswitch/arswitch_reg.h> +#include <dev/etherswitch/arswitch/arswitch_vlans.h> + +#include "mdio_if.h" +#include "miibus_if.h" +#include "etherswitch_if.h" + +static int +arswitch_vlan_op(struct arswitch_softc *sc, uint32_t op, uint32_t vid, + uint32_t data) +{ + int err; + + if (arswitch_waitreg(sc->sc_dev, AR8X16_REG_VLAN_CTRL, + AR8X16_VLAN_ACTIVE, 0, 5)) + return (EBUSY); + + /* Load the vlan data if needed. */ + if (op == AR8X16_VLAN_OP_LOAD) { + err = arswitch_writereg(sc->sc_dev, AR8X16_REG_VLAN_DATA, + (data & AR8X16_VLAN_MEMBER) | AR8X16_VLAN_VALID); + if (err) + return (err); + } + + if (vid != 0) + op |= ((vid & ETHERSWITCH_VID_MASK) << AR8X16_VLAN_VID_SHIFT); + op |= AR8X16_VLAN_ACTIVE; + arswitch_writereg(sc->sc_dev, AR8X16_REG_VLAN_CTRL, op); + + /* Wait for command processing. */ + if (arswitch_waitreg(sc->sc_dev, AR8X16_REG_VLAN_CTRL, + AR8X16_VLAN_ACTIVE, 0, 5)) + return (EBUSY); + + return (0); +} + +static int +arswitch_flush_dot1q_vlan(struct arswitch_softc *sc) +{ + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + return (arswitch_vlan_op(sc, AR8X16_VLAN_OP_FLUSH, 0, 0)); +} + +static int +arswitch_purge_dot1q_vlan(struct arswitch_softc *sc, int vid) +{ + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + return (arswitch_vlan_op(sc, AR8X16_VLAN_OP_PURGE, vid, 0)); +} + +static int +arswitch_get_dot1q_vlan(struct arswitch_softc *sc, uint32_t *ports, int vid) +{ + uint32_t reg; + int err; + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + err = arswitch_vlan_op(sc, AR8X16_VLAN_OP_GET, vid, 0); + if (err) + return (err); + + reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_VLAN_DATA); + if ((reg & AR8X16_VLAN_VALID) == 0) { + *ports = 0; + return (EINVAL); + } + reg &= ((1 << (sc->numphys + 1)) - 1); + *ports = reg; + return (0); +} + +static int +arswitch_set_dot1q_vlan(struct arswitch_softc *sc, uint32_t ports, int vid) +{ + int err; + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + err = arswitch_vlan_op(sc, AR8X16_VLAN_OP_LOAD, vid, ports); + if (err) + return (err); + return (0); +} + +static int +arswitch_get_port_vlan(struct arswitch_softc *sc, uint32_t *ports, int vid) +{ + int port; + uint32_t reg; + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + /* For port based vlans the vlanid is the same as the port index. */ + port = vid & ETHERSWITCH_VID_MASK; + reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_VLAN(port)); + *ports = (reg >> AR8X16_PORT_VLAN_DEST_PORTS_SHIFT); + *ports &= AR8X16_VLAN_MEMBER; + return (0); +} + +static int +arswitch_set_port_vlan(struct arswitch_softc *sc, uint32_t ports, int vid) +{ + int err, port; + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + /* For port based vlans the vlanid is the same as the port index. */ + port = vid & ETHERSWITCH_VID_MASK; + err = arswitch_modifyreg(sc->sc_dev, AR8X16_REG_PORT_VLAN(port), + AR8X16_VLAN_MEMBER << AR8X16_PORT_VLAN_DEST_PORTS_SHIFT, + (ports & AR8X16_VLAN_MEMBER) << AR8X16_PORT_VLAN_DEST_PORTS_SHIFT); + if (err) + return (err); + return (0); +} + +/* + * Reset vlans to default state. + */ +void +arswitch_reset_vlans(struct arswitch_softc *sc) +{ + uint32_t ports; + int i, j; + + ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); + + ARSWITCH_LOCK(sc); + + /* Reset all vlan data. */ + memset(sc->vid, 0, sizeof(sc->vid)); + + /* Disable the QinQ and egress filters for all ports. */ + for (i = 0; i <= sc->numphys; i++) { + if (arswitch_modifyreg(sc->sc_dev, AR8X16_REG_PORT_CTRL(i), + 0x3 << AR8X16_PORT_CTRL_EGRESS_VLAN_MODE_SHIFT | + AR8X16_PORT_CTRL_DOUBLE_TAG, 0)) { + ARSWITCH_UNLOCK(sc); + return; + } + } + + if (arswitch_flush_dot1q_vlan(sc)) { + ARSWITCH_UNLOCK(sc); + return; + } + + if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { + /* + * Reset the port based vlan settings and turn on the + * ingress filter for all ports. + */ + ports = 0; + for (i = 0; i <= sc->numphys; i++) + arswitch_modifyreg(sc->sc_dev, + AR8X16_REG_PORT_VLAN(i), + AR8X16_PORT_VLAN_MODE_MASK | + AR8X16_VLAN_MEMBER << + AR8X16_PORT_VLAN_DEST_PORTS_SHIFT, + AR8X16_PORT_VLAN_MODE_SECURE << + AR8X16_PORT_VLAN_MODE_SHIFT); + + /* + * Setup vlan 1 as PVID for all switch ports. Add all ports + * as members of vlan 1. + */ + sc->vid[0] = 1; + /* Set PVID for everyone. */ + for (i = 0; i <= sc->numphys; i++) + arswitch_set_pvid(sc, i, sc->vid[0]); + ports = 0; + for (i = 0; i <= sc->numphys; i++) + ports |= (1 << i); + arswitch_set_dot1q_vlan(sc, ports, sc->vid[0]); + sc->vid[0] |= ETHERSWITCH_VID_VALID; + } else if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) { + /* Initialize the port based vlans. */ + for (i = 0; i <= sc->numphys; i++) { + sc->vid[i] = i | ETHERSWITCH_VID_VALID; + ports = 0; + for (j = 0; j <= sc->numphys; j++) + ports |= (1 << j); + arswitch_modifyreg(sc->sc_dev, + AR8X16_REG_PORT_VLAN(i), + AR8X16_PORT_VLAN_MODE_MASK | + AR8X16_VLAN_MEMBER << + AR8X16_PORT_VLAN_DEST_PORTS_SHIFT, + ports << AR8X16_PORT_VLAN_DEST_PORTS_SHIFT | + AR8X16_PORT_VLAN_MODE_SECURE << + AR8X16_PORT_VLAN_MODE_PORT_ONLY); + } + } else { + /* Disable the ingress filter and get everyone on all vlans. */ + for (i = 0; i <= sc->numphys; i++) + arswitch_modifyreg(sc->sc_dev, + AR8X16_REG_PORT_VLAN(i), + AR8X16_PORT_VLAN_MODE_MASK | + AR8X16_VLAN_MEMBER << + AR8X16_PORT_VLAN_DEST_PORTS_SHIFT, + AR8X16_VLAN_MEMBER << + AR8X16_PORT_VLAN_DEST_PORTS_SHIFT | + AR8X16_PORT_VLAN_MODE_SECURE << + AR8X16_PORT_VLAN_MODE_PORT_ONLY); + } + ARSWITCH_UNLOCK(sc); +} + +int +arswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) +{ + struct arswitch_softc *sc; + int err; + + sc = device_get_softc(dev); + ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); + + if (vg->es_vlangroup > sc->info.es_nvlangroups) + return (EINVAL); + + /* Reset the members ports. */ + vg->es_untagged_ports = 0; + vg->es_member_ports = 0; + + /* Not supported. */ + vg->es_fid = 0; + + /* Vlan ID. */ + ARSWITCH_LOCK(sc); + vg->es_vid = sc->vid[vg->es_vlangroup]; + if ((vg->es_vid & ETHERSWITCH_VID_VALID) == 0) { + ARSWITCH_UNLOCK(sc); + return (0); + } + + /* Member Ports. */ + switch (sc->vlan_mode) { + case ETHERSWITCH_VLAN_DOT1Q: + err = arswitch_get_dot1q_vlan(sc, &vg->es_member_ports, + vg->es_vid); + break; + case ETHERSWITCH_VLAN_PORT: + err = arswitch_get_port_vlan(sc, &vg->es_member_ports, + vg->es_vid); + break; + default: + vg->es_member_ports = 0; + err = -1; + } + ARSWITCH_UNLOCK(sc); + vg->es_untagged_ports = vg->es_member_ports; + return (err); +} + +int +arswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) +{ + struct arswitch_softc *sc; + int err, vid; + + sc = device_get_softc(dev); + ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED); + + /* Check VLAN mode. */ + if (sc->vlan_mode == 0) + return (EINVAL); + + /* + * Check if we are changing the vlanid for an already used vtu entry. + * Then purge the entry first. + */ + ARSWITCH_LOCK(sc); + vid = sc->vid[vg->es_vlangroup]; + if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q && + (vid & ETHERSWITCH_VID_VALID) != 0 && + (vid & ETHERSWITCH_VID_MASK) != + (vg->es_vid & ETHERSWITCH_VID_MASK)) { + err = arswitch_purge_dot1q_vlan(sc, vid); + if (err) { + ARSWITCH_UNLOCK(sc); + return (err); + } + } + + /* Vlan ID. */ + if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { + sc->vid[vg->es_vlangroup] = vg->es_vid & ETHERSWITCH_VID_MASK; + /* Setting the vlanid to zero disables the vlangroup. */ + if (sc->vid[vg->es_vlangroup] == 0) { + ARSWITCH_UNLOCK(sc); + return (0); + } + sc->vid[vg->es_vlangroup] |= ETHERSWITCH_VID_VALID; + vid = sc->vid[vg->es_vlangroup]; + } + + /* Member Ports. */ + switch (sc->vlan_mode) { + case ETHERSWITCH_VLAN_DOT1Q: + err = arswitch_set_dot1q_vlan(sc, vg->es_member_ports, vid); + break; + case ETHERSWITCH_VLAN_PORT: + err = arswitch_set_port_vlan(sc, vg->es_member_ports, vid); + break; + default: + err = -1; + } + ARSWITCH_UNLOCK(sc); + return (err); +} + +int +arswitch_get_pvid(struct arswitch_softc *sc, int port, int *pvid) +{ + uint32_t reg; + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + reg = arswitch_readreg(sc->sc_dev, AR8X16_REG_PORT_VLAN(port)); + *pvid = reg & 0xfff; + return (0); +} + +int +arswitch_set_pvid(struct arswitch_softc *sc, int port, int pvid) +{ + + ARSWITCH_LOCK_ASSERT(sc, MA_OWNED); + return (arswitch_modifyreg(sc->sc_dev, + AR8X16_REG_PORT_VLAN(port), 0xfff, pvid)); +} Index: dev/etherswitch/arswitch/arswitch.c =================================================================== --- dev/etherswitch/arswitch/arswitch.c (revision 250901) +++ dev/etherswitch/arswitch/arswitch.c (working copy) @@ -58,6 +58,7 @@ #include <dev/etherswitch/arswitch/arswitchvar.h> #include <dev/etherswitch/arswitch/arswitch_reg.h> #include <dev/etherswitch/arswitch/arswitch_phy.h> +#include <dev/etherswitch/arswitch/arswitch_vlans.h> #include <dev/etherswitch/arswitch/arswitch_7240.h> #include <dev/etherswitch/arswitch/arswitch_8216.h> @@ -162,6 +163,74 @@ } 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. */ + arswitch_reset_vlans(sc); + + return (0); +} + +static void +arswitch_ports_init(struct arswitch_softc *sc) +{ + int port; + + /* Port0 - 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); + + for (port = 1; port <= sc->numphys; port++) { + /* 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 arswitch_attach(device_t dev) { struct arswitch_softc *sc; @@ -190,11 +259,8 @@ else return (ENXIO); - /* - * XXX these two should be part of the switch attach function - */ + /* Common defaults. */ sc->info.es_nports = 5; /* XXX technically 6, but 6th not used */ - sc->info.es_nvlangroups = 16; /* XXX Defaults for externally connected AR8316 */ sc->numphys = 4; @@ -211,34 +277,20 @@ (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "is_gmii", &sc->is_gmii); - /* - * This requires much more setup depending upon each chip, including: - * - * + Proper reinitialisation of the PHYs; - * + Initialising the VLAN table; - * + Initialising the port access table and CPU flood/broadcast - * configuration; - * + Other things I haven't yet thought of. - */ -#ifdef NOTYET - 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"); + if (sc->numphys > AR8X16_NUM_PHYS) + sc->numphys = AR8X16_NUM_PHYS; + + /* Reset the switch. */ + if (arswitch_reset(dev)) return (ENXIO); - } -#endif - err = sc->hal.arswitch_hw_setup(sc); - if (err != 0) - return (err); - err = sc->hal.arswitch_hw_global_setup(sc); if (err != 0) return (err); + /* Initialize the switch ports. */ + arswitch_ports_init(sc); + /* * Attach the PHYs and complete the bus enumeration. */ @@ -246,6 +298,15 @@ if (err != 0) return (err); + /* Default to ingress filters off. */ + err = arswitch_set_vlan_mode(sc, 0); + if (err != 0) + return (err); + + err = sc->hal.arswitch_hw_setup(sc); + if (err != 0) + return (err); + bus_generic_probe(dev); bus_enumerate_hinted_children(dev); err = bus_generic_attach(dev); @@ -428,19 +489,37 @@ static int arswitch_getport(device_t dev, etherswitch_port_t *p) { - struct arswitch_softc *sc = device_get_softc(dev); + struct arswitch_softc *sc; + struct ifmediareq *ifmr; struct mii_data *mii; - struct ifmediareq *ifmr = &p->es_ifmr; + uint32_t reg; int err; - - if (p->es_port < 0 || p->es_port >= AR8X16_NUM_PORTS) + + sc = device_get_softc(dev); + if (p->es_port < 0 || p->es_port > sc->numphys) return (ENXIO); - p->es_pvid = 0; + ARSWITCH_LOCK(sc); + + /* Retrieve the PVID. */ + arswitch_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); + mii = arswitch_miiforport(sc, p->es_port); if (p->es_port == 0) { /* fill in fixed values for 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; @@ -457,30 +536,59 @@ return (0); } -/* - * XXX doesn't yet work? - */ static int arswitch_setport(device_t dev, etherswitch_port_t *p) { int err; + uint32_t reg; struct arswitch_softc *sc; struct ifmedia *ifm; struct mii_data *mii; struct ifnet *ifp; - /* - * XXX check the sc numphys, or the #define ? - */ - if (p->es_port < 0 || p->es_port >= AR8X16_NUM_PHYS) + sc = device_get_softc(dev); + if (p->es_port < 0 || p->es_port > sc->numphys) return (ENXIO); - sc = device_get_softc(dev); + /* Port flags. */ + if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { - /* - * XXX TODO: don't set the CPU port? - */ + ARSWITCH_LOCK(sc); + /* Set the PVID. */ + if (p->es_pvid != 0) + arswitch_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); + if (err) + return (err); + } + + /* Do not allow media changes on CPU port. */ + if (p->es_port == AR8X16_PORT_CPU) + return (0); + mii = arswitch_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); @@ -488,30 +596,9 @@ ifp = arswitch_ifpforport(sc, p->es_port); ifm = &mii->mii_media; - err = ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA); - return (err); + return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); } -static int -arswitch_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) -{ - - /* XXX not implemented yet */ - vg->es_vid = 0; - vg->es_member_ports = 0; - vg->es_untagged_ports = 0; - vg->es_fid = 0; - return (0); -} - -static int -arswitch_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) -{ - - /* XXX not implemented yet */ - return (0); -} - static void arswitch_statchg(device_t dev) { @@ -546,6 +633,38 @@ ifmr->ifm_status = mii->mii_media_status; } +static int +arswitch_getconf(device_t dev, etherswitch_conf_t *conf) +{ + struct arswitch_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 +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); + } + + return (0); +} + static device_method_t arswitch_methods[] = { /* Device interface */ DEVMETHOD(device_probe, arswitch_probe), @@ -576,6 +695,8 @@ 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_END }; Index: dev/etherswitch/arswitch/arswitch_7240.c =================================================================== --- dev/etherswitch/arswitch/arswitch_7240.c (revision 250901) +++ dev/etherswitch/arswitch/arswitch_7240.c (working copy) @@ -71,16 +71,6 @@ ar7240_hw_setup(struct arswitch_softc *sc) { - /* Enable CPU port; disable mirror port */ - arswitch_writereg(sc->sc_dev, AR8X16_REG_CPU_PORT, - AR8X16_CPU_PORT_EN | AR8X16_CPU_MIRROR_DIS); - - /* - * Let things settle; probing PHY4 doesn't seem reliable - * without a litle delay. - */ - DELAY(1000); - return (0); } @@ -91,6 +81,10 @@ ar7240_hw_global_setup(struct arswitch_softc *sc) { + /* Enable CPU port; disable mirror port */ + arswitch_writereg(sc->sc_dev, AR8X16_REG_CPU_PORT, + AR8X16_CPU_PORT_EN | AR8X16_CPU_MIRROR_DIS); + /* Setup TAG priority mapping */ arswitch_writereg(sc->sc_dev, AR8X16_REG_TAG_PRIO, 0xfa50); @@ -139,4 +133,9 @@ sc->hal.arswitch_hw_setup = ar7240_hw_setup; sc->hal.arswitch_hw_global_setup = ar7240_hw_global_setup; + + /* Set the switch vlan capabilities. */ + sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q | + ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOUBLE_TAG; + sc->info.es_nvlangroups = AR8X16_MAX_VLANS; } Index: dev/etherswitch/arswitch/arswitch_8216.c =================================================================== --- dev/etherswitch/arswitch/arswitch_8216.c (revision 250901) +++ dev/etherswitch/arswitch/arswitch_8216.c (working copy) @@ -89,4 +89,6 @@ sc->hal.arswitch_hw_setup = ar8216_hw_setup; sc->hal.arswitch_hw_global_setup = ar8216_hw_global_setup; + + sc->info.es_nvlangroups = 0; } Index: dev/etherswitch/arswitch/arswitch_8226.c =================================================================== --- dev/etherswitch/arswitch/arswitch_8226.c (revision 250901) +++ dev/etherswitch/arswitch/arswitch_8226.c (working copy) @@ -89,4 +89,6 @@ sc->hal.arswitch_hw_setup = ar8226_hw_setup; sc->hal.arswitch_hw_global_setup = ar8226_hw_global_setup; + + sc->info.es_nvlangroups = 0; } Index: dev/etherswitch/arswitch/arswitch_8316.c =================================================================== --- dev/etherswitch/arswitch/arswitch_8316.c (revision 250901) +++ dev/etherswitch/arswitch/arswitch_8316.c (working copy) @@ -127,8 +127,19 @@ ar8316_hw_global_setup(struct arswitch_softc *sc) { - arswitch_writereg(sc->sc_dev, 0x38, 0xc000050e); + arswitch_writereg(sc->sc_dev, 0x38, AR8X16_MAGIC); + /* Enable CPU port and disable mirror port. */ + arswitch_writereg(sc->sc_dev, AR8X16_REG_CPU_PORT, + AR8X16_CPU_PORT_EN | AR8X16_CPU_MIRROR_DIS); + + /* Setup TAG priority mapping. */ + arswitch_writereg(sc->sc_dev, AR8X16_REG_TAG_PRIO, 0xfa50); + + /* Enable ARP frame acknowledge. */ + arswitch_modifyreg(sc->sc_dev, AR8X16_REG_AT_CTRL, 0, + AR8X16_AT_CTRL_ARP_EN); + /* * Flood address table misses to all ports, and enable forwarding of * broadcasts to the cpu port. @@ -136,9 +147,14 @@ arswitch_writereg(sc->sc_dev, AR8X16_REG_FLOOD_MASK, AR8X16_FLOOD_MASK_BCAST_TO_CPU | 0x003f003f); + /* Enable jumbo frames. */ arswitch_modifyreg(sc->sc_dev, AR8X16_REG_GLOBAL_CTRL, AR8316_GLOBAL_CTRL_MTU_MASK, 9018 + 8 + 2); + /* Setup service TAG. */ + arswitch_modifyreg(sc->sc_dev, AR8X16_REG_SERVICE_TAG, + AR8X16_SERVICE_TAG_MASK, 0); + return (0); } @@ -148,4 +164,9 @@ sc->hal.arswitch_hw_setup = ar8316_hw_setup; sc->hal.arswitch_hw_global_setup = ar8316_hw_global_setup; + + /* Set the switch vlan capabilities. */ + sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q | + ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOUBLE_TAG; + sc->info.es_nvlangroups = AR8X16_MAX_VLANS; } Index: dev/etherswitch/arswitch/arswitch_phy.c =================================================================== --- dev/etherswitch/arswitch/arswitch_phy.c (revision 250901) +++ dev/etherswitch/arswitch/arswitch_phy.c (working copy) @@ -127,16 +127,13 @@ return (ENXIO); ARSWITCH_LOCK(sc); - err = arswitch_writereg_lsb(dev, AR8X16_REG_MDIO_CTRL, - (data & AR8X16_MDIO_CTRL_DATA_MASK)); - if (err != 0) - goto out; - err = arswitch_writereg_msb(dev, AR8X16_REG_MDIO_CTRL, + err = arswitch_writereg(dev, AR8X16_REG_MDIO_CTRL, AR8X16_MDIO_CTRL_BUSY | AR8X16_MDIO_CTRL_MASTER_EN | AR8X16_MDIO_CTRL_CMD_WRITE | (phy << AR8X16_MDIO_CTRL_PHY_ADDR_SHIFT) | - (reg << AR8X16_MDIO_CTRL_REG_ADDR_SHIFT)); + (reg << AR8X16_MDIO_CTRL_REG_ADDR_SHIFT) | + (data & AR8X16_MDIO_CTRL_DATA_MASK)); if (err != 0) goto out; for (timeout = 100; timeout--; ) { Index: dev/etherswitch/arswitch/arswitch_reg.c =================================================================== --- dev/etherswitch/arswitch/arswitch_reg.c (revision 250901) +++ dev/etherswitch/arswitch/arswitch_reg.c (working copy) @@ -72,17 +72,10 @@ *phy = (((addr) >> 6) & 0x07) | 0x10; *reg = ((addr) >> 1) & 0x1f; - /* - * The earlier code would only switch the page - * over if the page were different. Experiments have - * shown that this is unstable. - * - * Hence, the page is always set here. - * - * See PR kern/172968 - */ - MDIO_WRITEREG(device_get_parent(dev), 0x18, 0, page); - sc->page = page; + if (sc->page != page) { + MDIO_WRITEREG(device_get_parent(dev), 0x18, 0, page); + sc->page = page; + } } /* @@ -171,8 +164,8 @@ { /* XXX Check the first write too? */ - arswitch_writereg_lsb(dev, addr, value); - return (arswitch_writereg_msb(dev, addr, value)); + arswitch_writereg_msb(dev, addr, value); + return (arswitch_writereg_lsb(dev, addr, value)); } int @@ -185,3 +178,24 @@ value |= set; return (arswitch_writereg(dev, addr, value)); } + +int +arswitch_waitreg(device_t dev, int addr, int mask, int val, int timeout) +{ + int err, v; + + err = -1; + while (1) { + v = arswitch_readreg(dev, addr); + v &= mask; + if (v == val) { + err = 0; + break; + } + if (!timeout) + break; + DELAY(1); + timeout--; + } + return (err); +} Index: dev/etherswitch/arswitch/arswitch_reg.h =================================================================== --- dev/etherswitch/arswitch/arswitch_reg.h (revision 250901) +++ dev/etherswitch/arswitch/arswitch_reg.h (working copy) @@ -34,6 +34,7 @@ extern int arswitch_readreg(device_t dev, int addr); extern int arswitch_writereg(device_t dev, int addr, int value); extern int arswitch_modifyreg(device_t dev, int addr, int mask, int set); +extern int arswitch_waitreg(device_t, int, int, int, int); extern int arswitch_readreg_lsb(device_t dev, int addr); extern int arswitch_readreg_msb(device_t dev, int addr); Index: dev/etherswitch/arswitch/arswitchreg.h =================================================================== --- dev/etherswitch/arswitch/arswitchreg.h (revision 250901) +++ dev/etherswitch/arswitch/arswitchreg.h (working copy) @@ -114,7 +114,7 @@ #define AR8X16_VLAN_PRIO_EN (1 << 31) #define AR8X16_REG_VLAN_DATA 0x0044 -#define AR8X16_VLAN_MEMBER 0x000003ff +#define AR8X16_VLAN_MEMBER 0x0000003f #define AR8X16_VLAN_VALID (1 << 11) #define AR8X16_REG_ARL_CTRL0 0x0050 Index: dev/etherswitch/arswitch/arswitchvar.h =================================================================== --- dev/etherswitch/arswitch/arswitchvar.h (revision 250901) +++ dev/etherswitch/arswitch/arswitchvar.h (working copy) @@ -42,8 +42,6 @@ #define AR8X16_IS_SWITCH(_sc, _type) \ (!!((_sc)->sc_switchtype == AR8X16_SWITCH_ ## _type)) -struct arswitch_softc; - struct arswitch_softc { struct mtx sc_mtx; /* serialize access to softc */ device_t sc_dev; @@ -59,6 +57,10 @@ struct callout callout_tick; etherswitch_info_t info; + /* VLANs support */ + int vid[AR8X16_MAX_VLANS]; + uint32_t vlan_mode; + struct { int (* arswitch_hw_setup) (struct arswitch_softc *); int (* arswitch_hw_global_setup) (struct arswitch_softc *);help
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?CAB=2f8y1tmCKZR=WoBkvdgVeWDaQqRAU9AOrJ5qfb5M189LCPA>
