summaryrefslogtreecommitdiff
path: root/net/dsa
diff options
context:
space:
mode:
authorVladimir Oltean <vladimir.oltean@nxp.com>2020-05-10 19:37:43 +0300
committerJakub Kicinski <kuba@kernel.org>2020-05-11 05:52:33 +0300
commitac02a451a6148bb9c395b39783ce7299eddf4f31 (patch)
treed2c3d327c9f38243eb277e6ffb798dade9e0fa4c /net/dsa
parent3b7bc1f09101ccace330d105c13c2946bf3be6d5 (diff)
downloadlinux-ac02a451a6148bb9c395b39783ce7299eddf4f31.tar.xz
net: dsa: sja1105: implement cross-chip bridging operations
sja1105 uses dsa_8021q for DSA tagging, a format which is VLAN at heart and which is compatible with cascading. A complete description of this tagging format is in net/dsa/tag_8021q.c, but a quick summary is that each external-facing port tags incoming frames with a unique pvid, and this special VLAN is transmitted as tagged towards the inside of the system, and as untagged towards the exterior. The tag encodes the switch id and the source port index. This means that cross-chip bridging for dsa_8021q only entails adding the dsa_8021q pvids of one switch to the RX filter of the other switches. Everything else falls naturally into place, as long as the bottom-end of ports (the leaves in the tree) is comprised exclusively of dsa_8021q-compatible (i.e. sja1105 switches). Otherwise, there would be a chance that a front-panel switch transmits a packet tagged with a dsa_8021q header, header which it wouldn't be able to remove, and which would hence "leak" out. The only use case I tested (due to lack of board availability) was when the sja1105 switches are part of disjoint trees (however, this doesn't change the fact that multiple sja1105 switches still need unique switch identifiers in such a system). But in principle, even "true" single-tree setups (with DSA links) should work just as fine, except for a small change which I can't test: dsa_towards_port should be used instead of dsa_upstream_port (I made the assumption that the routing port that any sja1105 should use towards its neighbours is the CPU port. That might not hold true in other setups). Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'net/dsa')
-rw-r--r--net/dsa/tag_8021q.c151
1 files changed, 151 insertions, 0 deletions
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)
{