In order to support cross-chip operations, we need to inform each switch
driver when a port operation occurs in a DSA tree.

This allows drivers to configure cross-chip port-based VLAN table, VTU
or FDB entries on DSA links, in order to implement a correct hardware
switching of frames.

Add a new tree.c file to implement tree-wide operations, propagating a
port-based operation on each switch of a tree.

Implement tree-wide bridge operations.

Signed-off-by: Vivien Didelot <vivien.dide...@savoirfairelinux.com>
---
 drivers/net/dsa/bcm_sf2.c   |  6 +++++
 drivers/net/dsa/mv88e6xxx.c |  6 +++++
 include/net/dsa.h           |  6 +++++
 net/dsa/Makefile            |  2 +-
 net/dsa/dsa_priv.h          |  6 +++++
 net/dsa/slave.c             | 46 ++++---------------------------
 net/dsa/tree.c              | 66 +++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 96 insertions(+), 42 deletions(-)
 create mode 100644 net/dsa/tree.c

diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
index 6e3b844..0a91ea9 100644
--- a/drivers/net/dsa/bcm_sf2.c
+++ b/drivers/net/dsa/bcm_sf2.c
@@ -498,6 +498,9 @@ static int bcm_sf2_sw_br_join(struct dsa_switch *ds, struct 
dsa_port *dp,
        struct dsa_port *intp;
        u32 reg, p_ctl;
 
+       if (dsa_port_is_external(dp, ds))
+               return -EOPNOTSUPP;
+
        p_ctl = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(dp->port));
 
        dsa_switch_for_each_port(ds, intp, priv->hw_params.num_ports) {
@@ -531,6 +534,9 @@ static void bcm_sf2_sw_br_leave(struct dsa_switch *ds, 
struct dsa_port *dp,
        struct dsa_port *intp;
        u32 reg, p_ctl;
 
+       if (dsa_port_is_external(dp, ds))
+               return;
+
        p_ctl = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(dp->port));
 
        dsa_switch_for_each_port(ds, intp, priv->hw_params.num_ports) {
diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c
index 89d0206..6fef29b 100644
--- a/drivers/net/dsa/mv88e6xxx.c
+++ b/drivers/net/dsa/mv88e6xxx.c
@@ -2212,6 +2212,9 @@ int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, 
struct dsa_port *dp,
        struct dsa_port *intp;
        int err;
 
+       if (dsa_port_is_external(dp, ds))
+               return -EOPNOTSUPP;
+
        mutex_lock(&ps->smi_mutex);
 
        /* Remap each port's VLANTable */
@@ -2234,6 +2237,9 @@ void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, 
struct dsa_port *dp,
        struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
        struct dsa_port *intp;
 
+       if (dsa_port_is_external(dp, ds))
+               return;
+
        mutex_lock(&ps->smi_mutex);
 
        /* Remap each port's VLANTable */
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 85fac8a..33172c9 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -193,6 +193,12 @@ struct dsa_switch {
        struct list_head        dp;
 };
 
+static inline bool dsa_port_is_external(struct dsa_port *dp,
+                                       struct dsa_switch *ds)
+{
+       return dp->ds != ds;
+}
+
 static inline bool dsa_is_cpu_port(struct dsa_switch *ds, int p)
 {
        return !!(ds->index == ds->dst->cpu_switch && p == ds->dst->cpu_port);
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index da06ed1..bf8d12c 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -1,6 +1,6 @@
 # the core
 obj-$(CONFIG_NET_DSA) += dsa_core.o
-dsa_core-y += dsa.o slave.o
+dsa_core-y += dsa.o tree.o slave.o
 
 # tagging formats
 dsa_core-$(CONFIG_NET_DSA_TAG_BRCM) += tag_brcm.o
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index c5afddd..6e08b3d 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -46,6 +46,12 @@ struct dsa_slave_priv {
 /* dsa.c */
 extern char dsa_driver_version[];
 
+/* tree.c */
+int dsa_tree_bridge_port_join(struct dsa_switch_tree *dst, struct dsa_port *dp,
+                             struct net_device *br);
+void dsa_tree_bridge_port_leave(struct dsa_switch_tree *dst,
+                               struct dsa_port *dp, struct net_device *br);
+
 /* slave.c */
 extern const struct dsa_device_ops notag_netdev_ops;
 void dsa_slave_mii_bus_init(struct dsa_switch *ds);
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index b90caf8..7123ae2 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -425,45 +425,6 @@ static int dsa_slave_port_obj_dump(struct net_device *dev,
        return err;
 }
 
-static int dsa_slave_bridge_port_join(struct net_device *dev,
-                                     struct net_device *br)
-{
-       struct dsa_slave_priv *p = netdev_priv(dev);
-       struct dsa_switch *ds = p->dp->ds;
-       int ret = -EOPNOTSUPP;
-
-       p->dp->br = br;
-
-       if (ds->drv->port_bridge_join)
-               ret = ds->drv->port_bridge_join(ds, p->dp, br);
-
-       if (ret && ret != -EOPNOTSUPP) {
-               p->dp->br = NULL;
-               return ret;
-       }
-
-       return 0;
-}
-
-static void dsa_slave_bridge_port_leave(struct net_device *dev)
-{
-       struct dsa_slave_priv *p = netdev_priv(dev);
-       struct dsa_switch *ds = p->dp->ds;
-       struct net_device *br = p->dp->br;
-
-       p->dp->br = NULL;
-
-       if (ds->drv->port_bridge_leave)
-               ds->drv->port_bridge_leave(ds, p->dp, br);
-
-       /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
-        * so allow it to be in BR_STATE_FORWARDING to be kept functional
-        */
-       if (ds->drv->port_stp_state_set)
-               ds->drv->port_stp_state_set(ds, p->dp->port,
-                                           BR_STATE_FORWARDING);
-}
-
 static int dsa_slave_port_attr_get(struct net_device *dev,
                                   struct switchdev_attr *attr)
 {
@@ -1140,6 +1101,9 @@ static bool dsa_slave_dev_check(struct net_device *dev)
 static int dsa_slave_port_upper_event(struct net_device *dev,
                                      unsigned long event, void *ptr)
 {
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_port *dp = p->dp;
+       struct dsa_switch_tree *dst = dp->ds->dst;
        struct netdev_notifier_changeupper_info *info = ptr;
        struct net_device *upper = info->upper_dev;
        int err = 0;
@@ -1148,9 +1112,9 @@ static int dsa_slave_port_upper_event(struct net_device 
*dev,
        case NETDEV_CHANGEUPPER:
                if (netif_is_bridge_master(upper)) {
                        if (info->linking)
-                               err = dsa_slave_bridge_port_join(dev, upper);
+                               err = dsa_tree_bridge_port_join(dst, dp, upper);
                        else
-                               dsa_slave_bridge_port_leave(dev);
+                               dsa_tree_bridge_port_leave(dst, dp, upper);
                }
 
                break;
diff --git a/net/dsa/tree.c b/net/dsa/tree.c
new file mode 100644
index 0000000..d3f5aea
--- /dev/null
+++ b/net/dsa/tree.c
@@ -0,0 +1,66 @@
+/*
+ * net/dsa/tree.c - DSA switch tree handling
+ * Copyright (c) 2016 Vivien Didelot <vivien.dide...@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/if_bridge.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+
+#include "dsa_priv.h"
+
+int dsa_tree_bridge_port_join(struct dsa_switch_tree *dst, struct dsa_port *dp,
+                             struct net_device *br)
+{
+       struct dsa_switch *ds;
+       int err = 0;
+
+       /* on NETDEV_CHANGEUPPER, the port is already bridged */
+       dp->br = br;
+
+       dsa_tree_for_each_switch(dst, ds) {
+               if (ds->drv->port_bridge_join) {
+                       err = ds->drv->port_bridge_join(ds, dp, br);
+                       if (err) {
+                               if (err != -EOPNOTSUPP)
+                                       break;
+                               err = 0;
+                       }
+               }
+       }
+
+       /* if an error is reported, bridge rolls back the operation */
+       if (err)
+               dp->br = NULL;
+
+       return err;
+}
+
+void dsa_tree_bridge_port_leave(struct dsa_switch_tree *dst,
+                               struct dsa_port *dp, struct net_device *br)
+{
+       struct dsa_switch *ds;
+
+       /* on NETDEV_CHANGEUPPER, the port is already unbridged */
+       dp->br = NULL;
+
+       dsa_tree_for_each_switch(dst, ds) {
+               if (ds->drv->port_bridge_leave)
+                       ds->drv->port_bridge_leave(ds, dp, br);
+
+               if (dsa_port_is_external(dp, ds))
+                       continue;
+
+               /* The bridge layer put the port in BR_STATE_DISABLED,
+                * restore BR_STATE_FORWARDING to keep it functional.
+                */
+               if (ds->drv->port_stp_state_set)
+                       ds->drv->port_stp_state_set(ds, dp->port,
+                                                   BR_STATE_FORWARDING);
+       }
+}
-- 
2.8.0

Reply via email to