diff mbox series

[RFC,3/6] net: dsa: tag_8021q: allow DSA tags and VLAN filtering simultaneously

Message ID 20200504124325.26758-4-olteanv@gmail.com
State New
Headers show
Series None | expand

Commit Message

Vladimir Oltean May 4, 2020, 12:43 p.m. UTC
From: Vladimir Oltean <vladimir.oltean@nxp.com>

There are very good reasons to want this, but there are also very good
reasons for not enabling it by default. So a devlink param named
best_effort_vlan_filtering, currently driver-specific and exported only
by sja1105, is used to configure this.

In practice, this is perhaps the way that most users are going to use
the switch in. Best-effort untagged traffic can be bridged with any net
device in the system or terminated locally, and VLAN-tagged streams are
forwarded autonomously in a time-sensitive manner according to their
PCP (they need not transit the CPU). For those cases where the CPU needs
to terminate some VLAN-tagged traffic, the next patch will also address
that, via dsa_8021q sub-VLANs.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
 Documentation/networking/dsa/sja1105.rst | 21 +++---
 drivers/net/dsa/sja1105/sja1105.h        |  1 +
 drivers/net/dsa/sja1105/sja1105_main.c   | 81 ++++++++++++++++++++++--
 include/linux/dsa/8021q.h                |  7 ++
 include/linux/dsa/sja1105.h              |  2 +
 net/dsa/tag_8021q.c                      | 62 ++++++++++++++++++
 net/dsa/tag_sja1105.c                    | 16 ++---
 7 files changed, 167 insertions(+), 23 deletions(-)
diff mbox series

Patch

diff --git a/Documentation/networking/dsa/sja1105.rst b/Documentation/networking/dsa/sja1105.rst
index 35d0643f1377..4a8639cba1f3 100644
--- a/Documentation/networking/dsa/sja1105.rst
+++ b/Documentation/networking/dsa/sja1105.rst
@@ -85,15 +85,18 @@  functionality.
 
 The following traffic modes are supported over the switch netdevices:
 
-+--------------------+------------+------------------+------------------+
-|                    | Standalone | Bridged with     | Bridged with     |
-|                    | ports      | vlan_filtering 0 | vlan_filtering 1 |
-+====================+============+==================+==================+
-| Regular traffic    |     Yes    |       Yes        |  No (use master) |
-+--------------------+------------+------------------+------------------+
-| Management traffic |     Yes    |       Yes        |       Yes        |
-| (BPDU, PTP)        |            |                  |                  |
-+--------------------+------------+------------------+------------------+
++-------------+------------+----------------+----------------+----------------------------+
+|             | Standalone |  Bridged with  |  Bridged with  |        Bridged with        |
+|             |    ports   | vlan_filtering | vlan_filtering | best_effort_vlan_filtering |
+|             |            |        0       |        1       |              1             |
++=============+============+================+================+============================+
+|   Regular   |     Yes    |       Yes      |       No       |     Partial (untagged),    |
+|   traffic   |            |                |  (use master)  |    use master for tagged   |
++-------------+------------+----------------+----------------+----------------------------+
+| Management  |     Yes    |       Yes      |      Yes       |             Yes            |
+|  traffic    |            |                |                |                            |
+| (BPDU, PTP) |            |                |                |                            |
++-------------+------------+----------------+----------------+----------------------------+
 
 Switching features
 ==================
diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index 2a21cab0888c..8fedcaa99f3b 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -132,6 +132,7 @@  struct sja1105_private {
 	struct sja1105_static_config static_config;
 	bool rgmii_rx_delay[SJA1105_NUM_PORTS];
 	bool rgmii_tx_delay[SJA1105_NUM_PORTS];
+	bool best_effort_vlan_filtering;
 	const struct sja1105_info *info;
 	struct gpio_desc *reset_gpio;
 	struct spi_device *spidev;
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 8a444e6949fd..edbe5dd4af37 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -1901,10 +1901,27 @@  sja1105_get_tag_protocol(struct dsa_switch *ds, int port,
 	return DSA_TAG_PROTO_SJA1105;
 }
 
-/* This callback needs to be present */
 static int sja1105_vlan_prepare(struct dsa_switch *ds, int port,
 				const struct switchdev_obj_port_vlan *vlan)
 {
+	struct sja1105_private *priv = ds->priv;
+	u16 vid;
+	int rc;
+
+	if (!dsa_port_is_vlan_filtering(dsa_to_port(ds, port)) ||
+	    !priv->best_effort_vlan_filtering)
+		return 0;
+
+	/* If the user wants best-effort VLAN filtering (aka vlan_filtering
+	 * bridge plus tagging), be sure to at least deny alterations to the
+	 * configuration done by dsa_8021q.
+	 */
+	for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+		rc = dsa_8021q_vid_validate(ds, port, vid, vlan->flags);
+		if (rc < 0)
+			return rc;
+	}
+
 	return 0;
 }
 
@@ -1918,6 +1935,7 @@  static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled)
 	struct sja1105_general_params_entry *general_params;
 	struct sja1105_private *priv = ds->priv;
 	struct sja1105_table *table;
+	bool want_tagging;
 	u16 tpid, tpid2;
 	int rc;
 
@@ -1943,8 +1961,10 @@  static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled)
 	general_params->incl_srcpt1 = enabled;
 	general_params->incl_srcpt0 = enabled;
 
+	want_tagging = priv->best_effort_vlan_filtering || !enabled;
+
 	/* VLAN filtering => independent VLAN learning.
-	 * No VLAN filtering => shared VLAN learning.
+	 * No VLAN filtering (or best effort) => shared VLAN learning.
 	 *
 	 * In shared VLAN learning mode, untagged traffic still gets
 	 * pvid-tagged, and the FDB table gets populated with entries
@@ -1963,7 +1983,7 @@  static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled)
 	 */
 	table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS];
 	l2_lookup_params = table->entries;
-	l2_lookup_params->shared_learn = !enabled;
+	l2_lookup_params->shared_learn = want_tagging;
 
 	rc = sja1105_static_config_reload(priv, SJA1105_VLAN_FILTERING);
 	if (rc)
@@ -1971,11 +1991,24 @@  static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled)
 
 	/* Switch port identification based on 802.1Q is only passable
 	 * if we are not under a vlan_filtering bridge. So make sure
-	 * the two configurations are mutually exclusive.
+	 * the two configurations are mutually exclusive (of course, the
+	 * user may know better, i.e. best_effort_vlan_filtering).
 	 */
-	return sja1105_setup_8021q_tagging(ds, !enabled);
+	return sja1105_setup_8021q_tagging(ds, want_tagging);
 }
 
+bool sja1105_can_use_vlan_as_tags(struct dsa_port *dp)
+{
+	struct dsa_switch *ds = dp->ds;
+	struct sja1105_private *priv = ds->priv;
+
+	if (dsa_port_is_vlan_filtering(dp) && !priv->best_effort_vlan_filtering)
+		return false;
+
+	return true;
+}
+EXPORT_SYMBOL_GPL(sja1105_can_use_vlan_as_tags);
+
 static void sja1105_vlan_add(struct dsa_switch *ds, int port,
 			     const struct switchdev_obj_port_vlan *vlan)
 {
@@ -2048,9 +2081,35 @@  static int sja1105_hostprio_set(struct sja1105_private *priv, u8 hostprio)
 	return sja1105_static_config_reload(priv, SJA1105_HOSTPRIO);
 }
 
+static int sja1105_best_effort_vlan_filtering_get(struct sja1105_private *priv,
+						  bool *be_vlan)
+{
+	*be_vlan = priv->best_effort_vlan_filtering;
+
+	return 0;
+}
+
+static int sja1105_best_effort_vlan_filtering_set(struct sja1105_private *priv,
+						  bool be_vlan)
+{
+	struct dsa_switch *ds = priv->ds;
+	bool vlan_filtering;
+	int rc;
+
+	vlan_filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, 0));
+	priv->best_effort_vlan_filtering = be_vlan;
+
+	rtnl_lock();
+	rc = sja1105_vlan_filtering(ds, 0, vlan_filtering);
+	rtnl_unlock();
+
+	return rc;
+}
+
 enum sja1105_devlink_param_id {
 	SJA1105_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
 	SJA1105_DEVLINK_PARAM_ID_HOSTPRIO,
+	SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING,
 };
 
 static int sja1105_devlink_param_get(struct dsa_switch *ds, u32 id,
@@ -2063,6 +2122,10 @@  static int sja1105_devlink_param_get(struct dsa_switch *ds, u32 id,
 	case SJA1105_DEVLINK_PARAM_ID_HOSTPRIO:
 		err = sja1105_hostprio_get(priv, &ctx->val.vu8);
 		break;
+	case SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING:
+		err = sja1105_best_effort_vlan_filtering_get(priv,
+							     &ctx->val.vbool);
+		break;
 	default:
 		err = -EOPNOTSUPP;
 		break;
@@ -2081,6 +2144,10 @@  static int sja1105_devlink_param_set(struct dsa_switch *ds, u32 id,
 	case SJA1105_DEVLINK_PARAM_ID_HOSTPRIO:
 		err = sja1105_hostprio_set(priv, ctx->val.vu8);
 		break;
+	case SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING:
+		err = sja1105_best_effort_vlan_filtering_set(priv,
+							     ctx->val.vbool);
+		break;
 	default:
 		err = -EOPNOTSUPP;
 		break;
@@ -2093,6 +2160,10 @@  static const struct devlink_param sja1105_devlink_params[] = {
 	DSA_DEVLINK_PARAM_DRIVER(SJA1105_DEVLINK_PARAM_ID_HOSTPRIO,
 				 "hostprio", DEVLINK_PARAM_TYPE_U8,
 				 BIT(DEVLINK_PARAM_CMODE_RUNTIME)),
+	DSA_DEVLINK_PARAM_DRIVER(SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING,
+				 "best_effort_vlan_filtering",
+				 DEVLINK_PARAM_TYPE_BOOL,
+				 BIT(DEVLINK_PARAM_CMODE_RUNTIME)),
 };
 
 static int sja1105_setup_devlink_params(struct dsa_switch *ds)
diff --git a/include/linux/dsa/8021q.h b/include/linux/dsa/8021q.h
index b8daaec0896e..dfbd5b62f67a 100644
--- a/include/linux/dsa/8021q.h
+++ b/include/linux/dsa/8021q.h
@@ -25,6 +25,8 @@  struct dsa_8021q_crosschip_link {
 int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
 				 bool enabled);
 
+int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags);
+
 int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
 				   struct dsa_switch *other_ds,
 				   int other_port, bool enabled);
@@ -58,6 +60,11 @@  int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
 	return 0;
 }
 
+int dsa_8021q_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags)
+{
+	return 0;
+}
+
 int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
 				   struct dsa_switch *other_ds,
 				   int other_port, bool enabled)
diff --git a/include/linux/dsa/sja1105.h b/include/linux/dsa/sja1105.h
index fa5735c353cd..a609fdbe1355 100644
--- a/include/linux/dsa/sja1105.h
+++ b/include/linux/dsa/sja1105.h
@@ -61,4 +61,6 @@  struct sja1105_port {
 	bool hwts_tx_en;
 };
 
+bool sja1105_can_use_vlan_as_tags(struct dsa_port *dp);
+
 #endif /* _NET_DSA_SJA1105_H */
diff --git a/net/dsa/tag_8021q.c b/net/dsa/tag_8021q.c
index ff9c5bf64bda..158584153e15 100644
--- a/net/dsa/tag_8021q.c
+++ b/net/dsa/tag_8021q.c
@@ -289,6 +289,68 @@  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_vid_validate(struct dsa_switch *ds, int port, u16 vid, u16 flags)
+{
+	int upstream = dsa_upstream_port(ds, port);
+	int rx_vid_of = ds->num_ports;
+	int tx_vid_of = ds->num_ports;
+	int other_port;
+
+	/* @vid wants to be a pvid of @port, but is not equal to its rx_vid */
+	if ((flags & BRIDGE_VLAN_INFO_PVID) &&
+	    vid != dsa_8021q_rx_vid(ds, port))
+		return -EPERM;
+
+	for (other_port = 0; other_port < ds->num_ports; other_port++) {
+		if (vid == dsa_8021q_rx_vid(ds, other_port)) {
+			rx_vid_of = other_port;
+			break;
+		}
+		if (vid == dsa_8021q_tx_vid(ds, other_port)) {
+			tx_vid_of = other_port;
+			break;
+		}
+	}
+
+	/* @vid is a TX VLAN of the @tx_vid_of port */
+	if (tx_vid_of != ds->num_ports) {
+		if (tx_vid_of == port) {
+			if (flags != BRIDGE_VLAN_INFO_UNTAGGED)
+				return -EPERM;
+			/* Fall through on proper flags */
+		} else if (port == upstream) {
+			if (flags != 0)
+				return -EPERM;
+			/* Fall through on proper flags */
+		} else {
+			/* Trying to configure on other port */
+			return -EPERM;
+		}
+	}
+
+	/* @vid is an RX VLAN of the @rx_vid_of port */
+	if (rx_vid_of != ds->num_ports) {
+		if (rx_vid_of == port) {
+			if (flags != (BRIDGE_VLAN_INFO_UNTAGGED |
+				      BRIDGE_VLAN_INFO_PVID))
+				return -EPERM;
+			/* Fall through on proper flags */
+		} else if (port == upstream) {
+			if (flags != 0)
+				return -EPERM;
+			/* Fall through on proper flags */
+		} else if (flags != BRIDGE_VLAN_INFO_UNTAGGED) {
+			/* Trying to configure on other port, but with
+			 * invalid flags.
+			 */
+			return -EPERM;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dsa_8021q_vid_validate);
+
 int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
 				   struct dsa_switch *other_ds,
 				   int other_port, bool enabled)
diff --git a/net/dsa/tag_sja1105.c b/net/dsa/tag_sja1105.c
index d553bf36bd41..72d76743c272 100644
--- a/net/dsa/tag_sja1105.c
+++ b/net/dsa/tag_sja1105.c
@@ -74,7 +74,7 @@  static inline bool sja1105_is_meta_frame(const struct sk_buff *skb)
  */
 static bool sja1105_filter(const struct sk_buff *skb, struct net_device *dev)
 {
-	if (!dsa_port_is_vlan_filtering(dev->dsa_ptr))
+	if (sja1105_can_use_vlan_as_tags(skb->dev->dsa_ptr))
 		return true;
 	if (sja1105_is_link_local(skb))
 		return true;
@@ -103,6 +103,7 @@  static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
 	u16 tx_vid = dsa_8021q_tx_vid(dp->ds, dp->index);
 	u16 queue_mapping = skb_get_queue_mapping(skb);
 	u8 pcp = netdev_txq_to_tc(netdev, queue_mapping);
+	u16 tpid;
 
 	/* Transmitting management traffic does not rely upon switch tagging,
 	 * but instead SPI-installed management routes. Part 2 of this
@@ -111,15 +112,12 @@  static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
 	if (unlikely(sja1105_is_link_local(skb)))
 		return sja1105_defer_xmit(dp->priv, skb);
 
-	/* If we are under a vlan_filtering bridge, IP termination on
-	 * switch ports based on 802.1Q tags is simply too brittle to
-	 * be passable. So just defer to the dsa_slave_notag_xmit
-	 * implementation.
-	 */
 	if (dsa_port_is_vlan_filtering(dp))
-		return skb;
+		tpid = ETH_P_8021Q;
+	else
+		tpid = ETH_P_SJA1105;
 
-	return dsa_8021q_xmit(skb, netdev, ETH_P_SJA1105,
+	return dsa_8021q_xmit(skb, netdev, tpid,
 			     ((pcp << VLAN_PRIO_SHIFT) | tx_vid));
 }
 
@@ -258,7 +256,7 @@  static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
 
 	hdr = eth_hdr(skb);
 	tpid = ntohs(hdr->h_proto);
-	is_tagged = (tpid == ETH_P_SJA1105);
+	is_tagged = (tpid == ETH_P_SJA1105 || tpid == ETH_P_8021Q);
 	is_link_local = sja1105_is_link_local(skb);
 	is_meta = sja1105_is_meta_frame(skb);