summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/net/dsa/sja1105/sja1105.h2
-rw-r--r--drivers/net/dsa/sja1105/sja1105_main.c90
-rw-r--r--include/linux/dsa/8021q.h45
-rw-r--r--net/dsa/tag_8021q.c151
4 files changed, 288 insertions, 0 deletions
diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index 8df2a5c53b02..a64ace07b89f 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -8,6 +8,7 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/timecounter.h>
#include <linux/dsa/sja1105.h>
+#include <linux/dsa/8021q.h>
#include <net/dsa.h>
#include <linux/mutex.h>
#include "sja1105_static_config.h"
@@ -185,6 +186,7 @@ struct sja1105_private {
struct gpio_desc *reset_gpio;
struct spi_device *spidev;
struct dsa_switch *ds;
+ struct list_head crosschip_links;
struct sja1105_flow_block flow_block;
struct sja1105_port ports[SJA1105_NUM_PORTS];
/* Serializes transmission of management frames so that
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 666e54565df0..d5de9305df25 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -25,6 +25,8 @@
#include "sja1105_sgmii.h"
#include "sja1105_tas.h"
+static const struct dsa_switch_ops sja1105_switch_ops;
+
static void sja1105_hw_reset(struct gpio_desc *gpio, unsigned int pulse_len,
unsigned int startup_delay)
{
@@ -1791,6 +1793,84 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid,
return 0;
}
+static int sja1105_crosschip_bridge_join(struct dsa_switch *ds,
+ int tree_index, int sw_index,
+ int other_port, struct net_device *br)
+{
+ struct dsa_switch *other_ds = dsa_switch_find(tree_index, sw_index);
+ struct sja1105_private *other_priv = other_ds->priv;
+ struct sja1105_private *priv = ds->priv;
+ int port, rc;
+
+ if (other_ds->ops != &sja1105_switch_ops)
+ return 0;
+
+ for (port = 0; port < ds->num_ports; port++) {
+ if (!dsa_is_user_port(ds, port))
+ continue;
+ if (dsa_to_port(ds, port)->bridge_dev != br)
+ continue;
+
+ rc = dsa_8021q_crosschip_bridge_join(ds, port, other_ds,
+ other_port, br,
+ &priv->crosschip_links);
+ if (rc)
+ return rc;
+
+ rc = dsa_8021q_crosschip_bridge_join(other_ds, other_port, ds,
+ port, br,
+ &other_priv->crosschip_links);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static void sja1105_crosschip_bridge_leave(struct dsa_switch *ds,
+ int tree_index, int sw_index,
+ int other_port,
+ struct net_device *br)
+{
+ struct dsa_switch *other_ds = dsa_switch_find(tree_index, sw_index);
+ struct sja1105_private *other_priv = other_ds->priv;
+ struct sja1105_private *priv = ds->priv;
+ int port;
+
+ if (other_ds->ops != &sja1105_switch_ops)
+ return;
+
+ for (port = 0; port < ds->num_ports; port++) {
+ if (!dsa_is_user_port(ds, port))
+ continue;
+ if (dsa_to_port(ds, port)->bridge_dev != br)
+ continue;
+
+ dsa_8021q_crosschip_bridge_leave(ds, port, other_ds, other_port,
+ br, &priv->crosschip_links);
+
+ dsa_8021q_crosschip_bridge_leave(other_ds, other_port, ds,
+ port, br,
+ &other_priv->crosschip_links);
+ }
+}
+
+static int sja1105_replay_crosschip_vlans(struct dsa_switch *ds, bool enabled)
+{
+ struct sja1105_private *priv = ds->priv;
+ struct dsa_8021q_crosschip_link *c;
+ int rc;
+
+ list_for_each_entry(c, &priv->crosschip_links, list) {
+ rc = dsa_8021q_crosschip_link_apply(ds, c->port, c->other_ds,
+ c->other_port, enabled);
+ if (rc)
+ break;
+ }
+
+ return rc;
+}
+
static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled)
{
int rc, i;
@@ -1803,6 +1883,12 @@ static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled)
return rc;
}
}
+ rc = sja1105_replay_crosschip_vlans(ds, enabled);
+ if (rc) {
+ dev_err(ds->dev, "Failed to replay crosschip VLANs: %d\n", rc);
+ return rc;
+ }
+
dev_info(ds->dev, "%s switch tagging\n",
enabled ? "Enabled" : "Disabled");
return 0;
@@ -2370,6 +2456,8 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
.cls_flower_add = sja1105_cls_flower_add,
.cls_flower_del = sja1105_cls_flower_del,
.cls_flower_stats = sja1105_cls_flower_stats,
+ .crosschip_bridge_join = sja1105_crosschip_bridge_join,
+ .crosschip_bridge_leave = sja1105_crosschip_bridge_leave,
};
static int sja1105_check_device_id(struct sja1105_private *priv)
@@ -2472,6 +2560,8 @@ static int sja1105_probe(struct spi_device *spi)
mutex_init(&priv->ptp_data.lock);
mutex_init(&priv->mgmt_lock);
+ INIT_LIST_HEAD(&priv->crosschip_links);
+
sja1105_tas_setup(ds);
sja1105_flower_setup(ds);
diff --git a/include/linux/dsa/8021q.h b/include/linux/dsa/8021q.h
index c620d9139c28..b8daaec0896e 100644
--- a/include/linux/dsa/8021q.h
+++ b/include/linux/dsa/8021q.h
@@ -12,11 +12,33 @@ struct sk_buff;
struct net_device;
struct packet_type;
+struct dsa_8021q_crosschip_link {
+ struct list_head list;
+ int port;
+ struct dsa_switch *other_ds;
+ int other_port;
+ refcount_t refcount;
+};
+
#if IS_ENABLED(CONFIG_NET_DSA_TAG_8021Q)
int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
bool enabled);
+int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, bool enabled);
+
+int dsa_8021q_crosschip_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, struct net_device *br,
+ struct list_head *crosschip_links);
+
+int dsa_8021q_crosschip_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, struct net_device *br,
+ struct list_head *crosschip_links);
+
struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
u16 tpid, u16 tci);
@@ -36,6 +58,29 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
return 0;
}
+int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, bool enabled)
+{
+ return 0;
+}
+
+int dsa_8021q_crosschip_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, struct net_device *br,
+ struct list_head *crosschip_links)
+{
+ return 0;
+}
+
+int dsa_8021q_crosschip_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, struct net_device *br,
+ struct list_head *crosschip_links)
+{
+ return 0;
+}
+
struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
u16 tpid, u16 tci)
{
diff --git a/net/dsa/tag_8021q.c b/net/dsa/tag_8021q.c
index b97ad93d1c1a..ff9c5bf64bda 100644
--- a/net/dsa/tag_8021q.c
+++ b/net/dsa/tag_8021q.c
@@ -8,6 +8,7 @@
*/
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
+#include <linux/dsa/8021q.h>
#include "dsa_priv.h"
@@ -288,6 +289,156 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int port, bool enabled)
}
EXPORT_SYMBOL_GPL(dsa_port_setup_8021q_tagging);
+int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, bool enabled)
+{
+ u16 rx_vid = dsa_8021q_rx_vid(ds, port);
+
+ /* @rx_vid of local @ds port @port goes to @other_port of
+ * @other_ds
+ */
+ return dsa_8021q_vid_apply(other_ds, other_port, rx_vid,
+ BRIDGE_VLAN_INFO_UNTAGGED, enabled);
+}
+EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_link_apply);
+
+static int dsa_8021q_crosschip_link_add(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port,
+ struct list_head *crosschip_links)
+{
+ struct dsa_8021q_crosschip_link *c;
+
+ list_for_each_entry(c, crosschip_links, list) {
+ if (c->port == port && c->other_ds == other_ds &&
+ c->other_port == other_port) {
+ refcount_inc(&c->refcount);
+ return 0;
+ }
+ }
+
+ dev_dbg(ds->dev, "adding crosschip link from port %d to %s port %d\n",
+ port, dev_name(other_ds->dev), other_port);
+
+ c = kzalloc(sizeof(*c), GFP_KERNEL);
+ if (!c)
+ return -ENOMEM;
+
+ c->port = port;
+ c->other_ds = other_ds;
+ c->other_port = other_port;
+ refcount_set(&c->refcount, 1);
+
+ list_add(&c->list, crosschip_links);
+
+ return 0;
+}
+
+static void dsa_8021q_crosschip_link_del(struct dsa_switch *ds,
+ struct dsa_8021q_crosschip_link *c,
+ struct list_head *crosschip_links,
+ bool *keep)
+{
+ *keep = !refcount_dec_and_test(&c->refcount);
+
+ if (*keep)
+ return;
+
+ dev_dbg(ds->dev,
+ "deleting crosschip link from port %d to %s port %d\n",
+ c->port, dev_name(c->other_ds->dev), c->other_port);
+
+ list_del(&c->list);
+ kfree(c);
+}
+
+/* Make traffic from local port @port be received by remote port @other_port.
+ * This means that our @rx_vid needs to be installed on @other_ds's upstream
+ * and user ports. The user ports should be egress-untagged so that they can
+ * pop the dsa_8021q VLAN. But the @other_upstream can be either egress-tagged
+ * or untagged: it doesn't matter, since it should never egress a frame having
+ * our @rx_vid.
+ */
+int dsa_8021q_crosschip_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, struct net_device *br,
+ struct list_head *crosschip_links)
+{
+ /* @other_upstream is how @other_ds reaches us. If we are part
+ * of disjoint trees, then we are probably connected through
+ * our CPU ports. If we're part of the same tree though, we should
+ * probably use dsa_towards_port.
+ */
+ int other_upstream = dsa_upstream_port(other_ds, other_port);
+ int rc;
+
+ rc = dsa_8021q_crosschip_link_add(ds, port, other_ds,
+ other_port, crosschip_links);
+ if (rc)
+ return rc;
+
+ if (!br_vlan_enabled(br)) {
+ rc = dsa_8021q_crosschip_link_apply(ds, port, other_ds,
+ other_port, true);
+ if (rc)
+ return rc;
+ }
+
+ rc = dsa_8021q_crosschip_link_add(ds, port, other_ds,
+ other_upstream,
+ crosschip_links);
+ if (rc)
+ return rc;
+
+ if (!br_vlan_enabled(br)) {
+ rc = dsa_8021q_crosschip_link_apply(ds, port, other_ds,
+ other_upstream, true);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_join);
+
+int dsa_8021q_crosschip_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_switch *other_ds,
+ int other_port, struct net_device *br,
+ struct list_head *crosschip_links)
+{
+ int other_upstream = dsa_upstream_port(other_ds, other_port);
+ struct dsa_8021q_crosschip_link *c, *n;
+
+ list_for_each_entry_safe(c, n, crosschip_links, list) {
+ if (c->port == port && c->other_ds == other_ds &&
+ (c->other_port == other_port ||
+ c->other_port == other_upstream)) {
+ struct dsa_switch *other_ds = c->other_ds;
+ int other_port = c->other_port;
+ bool keep;
+ int rc;
+
+ dsa_8021q_crosschip_link_del(ds, c, crosschip_links,
+ &keep);
+ if (keep)
+ continue;
+
+ if (!br_vlan_enabled(br)) {
+ rc = dsa_8021q_crosschip_link_apply(ds, port,
+ other_ds,
+ other_port,
+ false);
+ if (rc)
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_leave);
+
struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
u16 tpid, u16 tci)
{