net: dsa: sja1105: add bridge TX data plane offload based on tag_8021q
authorVladimir Oltean <vladimir.oltean@nxp.com>
Mon, 26 Jul 2021 16:55:35 +0000 (19:55 +0300)
committerDavid S. Miller <davem@davemloft.net>
Mon, 26 Jul 2021 21:35:22 +0000 (22:35 +0100)
The main desire for having this feature in sja1105 is to support network
stack termination for traffic coming from a VLAN-aware bridge.

For sja1105, offloading the bridge data plane means sending packets
as-is, with the proper VLAN tag, to the chip. The chip will look up its
FDB and forward them to the correct destination port.

But we support bridge data plane offload even for VLAN-unaware bridges,
and the implementation there is different. In fact, VLAN-unaware
bridging is governed by tag_8021q, so it makes sense to have the
.bridge_fwd_offload_add() implementation fully within tag_8021q.
The key difference is that we only support 1 VLAN-aware bridge, but we
support multiple VLAN-unaware bridges. So we need to make sure that the
forwarding domain is not crossed by packets injected from the stack.

For this, we introduce the concept of a tag_8021q TX VLAN for bridge
forwarding offload. As opposed to the regular TX VLANs which contain
only 2 ports (the user port and the CPU port), a bridge data plane TX
VLAN is "multicast" (or "imprecise"): it contains all the ports that are
part of a certain bridge, and the hardware will select where the packet
goes within this "imprecise" forwarding domain.

Each VLAN-unaware bridge has its own "imprecise" TX VLAN, so we make use
of the unique "bridge_num" provided by DSA for the data plane offload.
We use the same 3 bits from the tag_8021q VLAN ID format to encode this
bridge number.

Note that these 3 bit positions have been used before for sub-VLANs in
best-effort VLAN filtering mode. The difference is that for best-effort,
the sub-VLANs were only valid on RX (and it was documented that the
sub-VLAN field needed to be transmitted as zero). Whereas for the bridge
data plane offload, these 3 bits are only valid on TX.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/sja1105/sja1105_main.c
include/linux/dsa/8021q.h
net/dsa/tag_8021q.c
net/dsa/tag_sja1105.c

index a6a671f..da042e2 100644 (file)
@@ -2367,6 +2367,8 @@ static int sja1105_setup(struct dsa_switch *ds)
         */
        ds->vlan_filtering_is_global = true;
        ds->untag_bridge_pvid = true;
+       /* tag_8021q has 3 bits for the VBID, and the value 0 is reserved */
+       ds->num_fwd_offloading_bridges = 7;
 
        /* Advertise the 8 egress queues */
        ds->num_tx_queues = SJA1105_NUM_TC;
@@ -2880,6 +2882,8 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
        .tag_8021q_vlan_add     = sja1105_dsa_8021q_vlan_add,
        .tag_8021q_vlan_del     = sja1105_dsa_8021q_vlan_del,
        .port_prechangeupper    = sja1105_prechangeupper,
+       .port_bridge_tx_fwd_offload = dsa_tag_8021q_bridge_tx_fwd_offload,
+       .port_bridge_tx_fwd_unoffload = dsa_tag_8021q_bridge_tx_fwd_unoffload,
 };
 
 static const struct of_device_id sja1105_dt_ids[];
index ec5abfc..c7fa4a3 100644 (file)
@@ -35,6 +35,16 @@ struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
 
 void dsa_8021q_rcv(struct sk_buff *skb, int *source_port, int *switch_id);
 
+int dsa_tag_8021q_bridge_tx_fwd_offload(struct dsa_switch *ds, int port,
+                                       struct net_device *br,
+                                       int bridge_num);
+
+void dsa_tag_8021q_bridge_tx_fwd_unoffload(struct dsa_switch *ds, int port,
+                                          struct net_device *br,
+                                          int bridge_num);
+
+u16 dsa_8021q_bridge_tx_fwd_offload_vid(int bridge_num);
+
 u16 dsa_8021q_tx_vid(struct dsa_switch *ds, int port);
 
 u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port);
index 51dcde7..654697e 100644 (file)
@@ -17,7 +17,7 @@
  *
  * | 11  | 10  |  9  |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
  * +-----------+-----+-----------------+-----------+-----------------------+
- * |    DIR    | RSV |    SWITCH_ID    |    RSV    |          PORT         |
+ * |    DIR    | VBID|    SWITCH_ID    |   VBID    |          PORT         |
  * +-----------+-----+-----------------+-----------+-----------------------+
  *
  * DIR - VID[11:10]:
  * SWITCH_ID - VID[8:6]:
  *     Index of switch within DSA tree. Must be between 0 and 7.
  *
- * RSV - VID[5:4]:
- *     To be used for further expansion of PORT or for other purposes.
- *     Must be transmitted as zero and ignored on receive.
+ * VBID - { VID[9], VID[5:4] }:
+ *     Virtual bridge ID. If between 1 and 7, packet targets the broadcast
+ *     domain of a bridge. If transmitted as zero, packet targets a single
+ *     port. Field only valid on transmit, must be ignored on receive.
  *
  * PORT - VID[3:0]:
  *     Index of switch port. Must be between 0 and 15.
 #define DSA_8021Q_SWITCH_ID(x)         (((x) << DSA_8021Q_SWITCH_ID_SHIFT) & \
                                                 DSA_8021Q_SWITCH_ID_MASK)
 
+#define DSA_8021Q_VBID_HI_SHIFT                9
+#define DSA_8021Q_VBID_HI_MASK         GENMASK(9, 9)
+#define DSA_8021Q_VBID_LO_SHIFT                4
+#define DSA_8021Q_VBID_LO_MASK         GENMASK(5, 4)
+#define DSA_8021Q_VBID_HI(x)           (((x) & GENMASK(2, 2)) >> 2)
+#define DSA_8021Q_VBID_LO(x)           ((x) & GENMASK(1, 0))
+#define DSA_8021Q_VBID(x)              \
+               (((DSA_8021Q_VBID_LO(x) << DSA_8021Q_VBID_LO_SHIFT) & \
+                 DSA_8021Q_VBID_LO_MASK) | \
+                ((DSA_8021Q_VBID_HI(x) << DSA_8021Q_VBID_HI_SHIFT) & \
+                 DSA_8021Q_VBID_HI_MASK))
+
 #define DSA_8021Q_PORT_SHIFT           0
 #define DSA_8021Q_PORT_MASK            GENMASK(3, 0)
 #define DSA_8021Q_PORT(x)              (((x) << DSA_8021Q_PORT_SHIFT) & \
                                                 DSA_8021Q_PORT_MASK)
 
+u16 dsa_8021q_bridge_tx_fwd_offload_vid(int bridge_num)
+{
+       /* The VBID value of 0 is reserved for precise TX */
+       return DSA_8021Q_DIR_TX | DSA_8021Q_VBID(bridge_num + 1);
+}
+EXPORT_SYMBOL_GPL(dsa_8021q_bridge_tx_fwd_offload_vid);
+
 /* Returns the VID to be inserted into the frame from xmit for switch steering
  * instructions on egress. Encodes switch ID and port ID.
  */
@@ -387,6 +407,26 @@ int dsa_tag_8021q_bridge_leave(struct dsa_switch *ds,
        return 0;
 }
 
+int dsa_tag_8021q_bridge_tx_fwd_offload(struct dsa_switch *ds, int port,
+                                       struct net_device *br,
+                                       int bridge_num)
+{
+       u16 tx_vid = dsa_8021q_bridge_tx_fwd_offload_vid(bridge_num);
+
+       return dsa_port_tag_8021q_vlan_add(dsa_to_port(ds, port), tx_vid);
+}
+EXPORT_SYMBOL_GPL(dsa_tag_8021q_bridge_tx_fwd_offload);
+
+void dsa_tag_8021q_bridge_tx_fwd_unoffload(struct dsa_switch *ds, int port,
+                                          struct net_device *br,
+                                          int bridge_num)
+{
+       u16 tx_vid = dsa_8021q_bridge_tx_fwd_offload_vid(bridge_num);
+
+       dsa_port_tag_8021q_vlan_del(dsa_to_port(ds, port), tx_vid);
+}
+EXPORT_SYMBOL_GPL(dsa_tag_8021q_bridge_tx_fwd_unoffload);
+
 /* Set up a port's tag_8021q RX and TX VLAN for standalone mode operation */
 static int dsa_tag_8021q_port_setup(struct dsa_switch *ds, int port)
 {
index f142a93..cddee4b 100644 (file)
@@ -133,6 +133,31 @@ static u16 sja1105_xmit_tpid(struct sja1105_port *sp)
        return sp->xmit_tpid;
 }
 
+static struct sk_buff *sja1105_imprecise_xmit(struct sk_buff *skb,
+                                             struct net_device *netdev)
+{
+       struct dsa_port *dp = dsa_slave_to_port(netdev);
+       struct net_device *br = dp->bridge_dev;
+       u16 tx_vid;
+
+       /* If the port is under a VLAN-aware bridge, just slide the
+        * VLAN-tagged packet into the FDB and hope for the best.
+        * This works because we support a single VLAN-aware bridge
+        * across the entire dst, and its VLANs cannot be shared with
+        * any standalone port.
+        */
+       if (br_vlan_enabled(br))
+               return skb;
+
+       /* If the port is under a VLAN-unaware bridge, use an imprecise
+        * TX VLAN that targets the bridge's entire broadcast domain,
+        * instead of just the specific port.
+        */
+       tx_vid = dsa_8021q_bridge_tx_fwd_offload_vid(dp->bridge_num);
+
+       return dsa_8021q_xmit(skb, netdev, sja1105_xmit_tpid(dp->priv), tx_vid);
+}
+
 static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
                                    struct net_device *netdev)
 {
@@ -141,6 +166,9 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
        u16 queue_mapping = skb_get_queue_mapping(skb);
        u8 pcp = netdev_txq_to_tc(netdev, queue_mapping);
 
+       if (skb->offload_fwd_mark)
+               return sja1105_imprecise_xmit(skb, netdev);
+
        /* Transmitting management traffic does not rely upon switch tagging,
         * but instead SPI-installed management routes. Part 2 of this
         * is the .port_deferred_xmit driver callback.
@@ -165,6 +193,9 @@ static struct sk_buff *sja1110_xmit(struct sk_buff *skb,
        __be16 *tx_header;
        int trailer_pos;
 
+       if (skb->offload_fwd_mark)
+               return sja1105_imprecise_xmit(skb, netdev);
+
        /* Transmitting control packets is done using in-band control
         * extensions, while data packets are transmitted using
         * tag_8021q TX VLANs.