From: Vladimir Oltean <[email protected]>

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 <[email protected]>
---
 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 --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);
 
-- 
2.17.1

Reply via email to