net: dsa: mv88e6xxx: restore VLANTable map control
authorVivien Didelot <vivien.didelot@savoirfairelinux.com>
Fri, 26 Feb 2016 18:16:06 +0000 (13:16 -0500)
committerDavid S. Miller <davem@davemloft.net>
Tue, 1 Mar 2016 21:24:52 +0000 (16:24 -0500)
The In Chip Port Based VLAN Table contains bits used to restrict which
output ports this input port can send frames to.

With the VLAN filtering enabled, these tables work in conjunction with
the VLAN Table Unit to allow egressing frames.

In order to remove the current dependency to BRIDGE_VLAN_FILTERING for
basic hardware bridging to work, it is necessary to restore a fine
control of each port's VLANTable, on setup and when a port joins or
leaves a bridge.

Signed-off-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/mv88e6xxx.c

index 0f16911..7f3036b 100644 (file)
@@ -1087,12 +1087,32 @@ abort:
        return ret;
 }
 
-static int _mv88e6xxx_port_vlan_map_set(struct dsa_switch *ds, int port,
-                                       u16 output_ports)
+static int _mv88e6xxx_port_based_vlan_map(struct dsa_switch *ds, int port)
 {
        struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
+       struct net_device *bridge = ps->ports[port].bridge_dev;
        const u16 mask = (1 << ps->num_ports) - 1;
+       u16 output_ports = 0;
        int reg;
+       int i;
+
+       /* allow CPU port or DSA link(s) to send frames to every port */
+       if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)) {
+               output_ports = mask;
+       } else {
+               for (i = 0; i < ps->num_ports; ++i) {
+                       /* allow sending frames to every group member */
+                       if (bridge && ps->ports[i].bridge_dev == bridge)
+                               output_ports |= BIT(i);
+
+                       /* allow sending frames to CPU port and DSA link(s) */
+                       if (dsa_is_cpu_port(ds, i) || dsa_is_dsa_port(ds, i))
+                               output_ports |= BIT(i);
+               }
+       }
+
+       /* prevent frames from going back out of the port they came in on */
+       output_ports &= ~BIT(port);
 
        reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_BASE_VLAN);
        if (reg < 0)
@@ -2114,7 +2134,17 @@ int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
        if (err)
                goto unlock;
 
+       /* Assign the bridge and remap each port's VLANTable */
        ps->ports[port].bridge_dev = bridge;
+
+       for (i = 0; i < ps->num_ports; ++i) {
+               if (ps->ports[i].bridge_dev == bridge) {
+                       err = _mv88e6xxx_port_based_vlan_map(ds, i);
+                       if (err)
+                               break;
+               }
+       }
+
 unlock:
        mutex_unlock(&ps->smi_mutex);
 
@@ -2124,8 +2154,9 @@ unlock:
 int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port)
 {
        struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
+       struct net_device *bridge = ps->ports[port].bridge_dev;
        u16 fid;
-       int err;
+       int i, err;
 
        mutex_lock(&ps->smi_mutex);
 
@@ -2138,7 +2169,17 @@ int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port)
        if (err)
                goto unlock;
 
+       /* Unassign the bridge and remap each port's VLANTable */
        ps->ports[port].bridge_dev = NULL;
+
+       for (i = 0; i < ps->num_ports; ++i) {
+               if (i == port || ps->ports[i].bridge_dev == bridge) {
+                       err = _mv88e6xxx_port_based_vlan_map(ds, i);
+                       if (err)
+                               break;
+               }
+       }
+
 unlock:
        mutex_unlock(&ps->smi_mutex);
 
@@ -2402,15 +2443,14 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port)
                goto abort;
 
        /* Port based VLAN map: give each port its own address
-        * database, and allow every port to egress frames on all other ports.
+        * database, and allow bidirectional communication between the
+        * CPU and DSA port(s), and the other ports.
         */
        ret = _mv88e6xxx_port_fid_set(ds, port, port + 1);
        if (ret)
                goto abort;
 
-       reg = BIT(ps->num_ports) - 1; /* all ports */
-       reg &= ~BIT(port); /* except itself */
-       ret = _mv88e6xxx_port_vlan_map_set(ds, port, reg);
+       ret = _mv88e6xxx_port_based_vlan_map(ds, port);
        if (ret)
                goto abort;