summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/net/dsa.h18
-rw-r--r--net/dsa/dsa2.c1
-rw-r--r--net/dsa/dsa_priv.h2
-rw-r--r--net/dsa/port.c84
4 files changed, 104 insertions, 1 deletions
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 929bcaec4d41..f8eb2dc3fbef 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -162,6 +162,9 @@ struct dsa_switch_tree {
/* Track the largest switch index within a tree */
unsigned int last_switch;
+
+ /* Track the bridges with forwarding offload enabled */
+ unsigned long fwd_offloading_bridges;
};
#define dsa_lags_foreach_id(_id, _dst) \
@@ -262,6 +265,7 @@ struct dsa_port {
bool vlan_filtering;
u8 stp_state;
struct net_device *bridge_dev;
+ int bridge_num;
struct devlink_port devlink_port;
bool devlink_port_setup;
struct phylink *pl;
@@ -413,6 +417,12 @@ struct dsa_switch {
*/
unsigned int num_lag_ids;
+ /* Drivers that support bridge forwarding offload should set this to
+ * the maximum number of bridges spanning the same switch tree that can
+ * be offloaded.
+ */
+ unsigned int num_fwd_offloading_bridges;
+
size_t num_ports;
};
@@ -696,6 +706,14 @@ struct dsa_switch_ops {
struct net_device *bridge);
void (*port_bridge_leave)(struct dsa_switch *ds, int port,
struct net_device *bridge);
+ /* Called right after .port_bridge_join() */
+ int (*port_bridge_tx_fwd_offload)(struct dsa_switch *ds, int port,
+ struct net_device *bridge,
+ int bridge_num);
+ /* Called right before .port_bridge_leave() */
+ void (*port_bridge_tx_fwd_unoffload)(struct dsa_switch *ds, int port,
+ struct net_device *bridge,
+ int bridge_num);
void (*port_stp_state_set)(struct dsa_switch *ds, int port,
u8 state);
void (*port_fast_age)(struct dsa_switch *ds, int port);
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index de5e93ba2a9d..c7fa85fb3086 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -1044,6 +1044,7 @@ static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
dp->ds = ds;
dp->index = index;
+ dp->bridge_num = -1;
INIT_LIST_HEAD(&dp->list);
list_add_tail(&dp->list, &dst->ports);
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 78c70f5bdab5..b1d9aa4d313c 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -14,6 +14,8 @@
#include <net/dsa.h>
#include <net/gro_cells.h>
+#define DSA_MAX_NUM_OFFLOADING_BRIDGES BITS_PER_LONG
+
enum {
DSA_NOTIFIER_AGEING_TIME,
DSA_NOTIFIER_BRIDGE_JOIN,
diff --git a/net/dsa/port.c b/net/dsa/port.c
index f2704f101ccf..7b9bf45a76b6 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -230,6 +230,83 @@ static void dsa_port_switchdev_unsync_attrs(struct dsa_port *dp)
*/
}
+static int dsa_tree_find_bridge_num(struct dsa_switch_tree *dst,
+ struct net_device *bridge_dev)
+{
+ struct dsa_port *dp;
+
+ /* When preparing the offload for a port, it will have a valid
+ * dp->bridge_dev pointer but a not yet valid dp->bridge_num.
+ * However there might be other ports having the same dp->bridge_dev
+ * and a valid dp->bridge_num, so just ignore this port.
+ */
+ list_for_each_entry(dp, &dst->ports, list)
+ if (dp->bridge_dev == bridge_dev && dp->bridge_num != -1)
+ return dp->bridge_num;
+
+ return -1;
+}
+
+static void dsa_port_bridge_tx_fwd_unoffload(struct dsa_port *dp,
+ struct net_device *bridge_dev)
+{
+ struct dsa_switch_tree *dst = dp->ds->dst;
+ int bridge_num = dp->bridge_num;
+ struct dsa_switch *ds = dp->ds;
+
+ /* No bridge TX forwarding offload => do nothing */
+ if (!ds->ops->port_bridge_tx_fwd_unoffload || dp->bridge_num == -1)
+ return;
+
+ dp->bridge_num = -1;
+
+ /* Check if the bridge is still in use, otherwise it is time
+ * to clean it up so we can reuse this bridge_num later.
+ */
+ if (!dsa_tree_find_bridge_num(dst, bridge_dev))
+ clear_bit(bridge_num, &dst->fwd_offloading_bridges);
+
+ /* Notify the chips only once the offload has been deactivated, so
+ * that they can update their configuration accordingly.
+ */
+ ds->ops->port_bridge_tx_fwd_unoffload(ds, dp->index, bridge_dev,
+ bridge_num);
+}
+
+static bool dsa_port_bridge_tx_fwd_offload(struct dsa_port *dp,
+ struct net_device *bridge_dev)
+{
+ struct dsa_switch_tree *dst = dp->ds->dst;
+ struct dsa_switch *ds = dp->ds;
+ int bridge_num, err;
+
+ if (!ds->ops->port_bridge_tx_fwd_offload)
+ return false;
+
+ bridge_num = dsa_tree_find_bridge_num(dst, bridge_dev);
+ if (bridge_num < 0) {
+ /* First port that offloads TX forwarding for this bridge */
+ bridge_num = find_first_zero_bit(&dst->fwd_offloading_bridges,
+ DSA_MAX_NUM_OFFLOADING_BRIDGES);
+ if (bridge_num >= ds->num_fwd_offloading_bridges)
+ return false;
+
+ set_bit(bridge_num, &dst->fwd_offloading_bridges);
+ }
+
+ dp->bridge_num = bridge_num;
+
+ /* Notify the driver */
+ err = ds->ops->port_bridge_tx_fwd_offload(ds, dp->index, bridge_dev,
+ bridge_num);
+ if (err) {
+ dsa_port_bridge_tx_fwd_unoffload(dp, bridge_dev);
+ return false;
+ }
+
+ return true;
+}
+
int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
struct netlink_ext_ack *extack)
{
@@ -241,6 +318,7 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
};
struct net_device *dev = dp->slave;
struct net_device *brport_dev;
+ bool tx_fwd_offload;
int err;
/* Here the interface is already bridged. Reflect the current
@@ -254,10 +332,12 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
if (err)
goto out_rollback;
+ tx_fwd_offload = dsa_port_bridge_tx_fwd_offload(dp, br);
+
err = switchdev_bridge_port_offload(brport_dev, dev, dp,
&dsa_slave_switchdev_notifier,
&dsa_slave_switchdev_blocking_notifier,
- false, extack);
+ tx_fwd_offload, extack);
if (err)
goto out_rollback_unbridge;
@@ -302,6 +382,8 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
*/
dp->bridge_dev = NULL;
+ dsa_port_bridge_tx_fwd_unoffload(dp, br);
+
err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
if (err)
pr_err("DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");