Commit b6ad86e6 authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller

net: dsa: sja1105: add bridge TX data plane offload based on tag_8021q

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: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 884be12f
...@@ -2367,6 +2367,8 @@ static int sja1105_setup(struct dsa_switch *ds) ...@@ -2367,6 +2367,8 @@ static int sja1105_setup(struct dsa_switch *ds)
*/ */
ds->vlan_filtering_is_global = true; ds->vlan_filtering_is_global = true;
ds->untag_bridge_pvid = 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 */ /* Advertise the 8 egress queues */
ds->num_tx_queues = SJA1105_NUM_TC; ds->num_tx_queues = SJA1105_NUM_TC;
...@@ -2880,6 +2882,8 @@ static const struct dsa_switch_ops sja1105_switch_ops = { ...@@ -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_add = sja1105_dsa_8021q_vlan_add,
.tag_8021q_vlan_del = sja1105_dsa_8021q_vlan_del, .tag_8021q_vlan_del = sja1105_dsa_8021q_vlan_del,
.port_prechangeupper = sja1105_prechangeupper, .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[]; static const struct of_device_id sja1105_dt_ids[];
......
...@@ -35,6 +35,16 @@ struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev, ...@@ -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); 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_tx_vid(struct dsa_switch *ds, int port);
u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port); u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port);
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
* *
* | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * | 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]: * DIR - VID[11:10]:
...@@ -30,9 +30,10 @@ ...@@ -30,9 +30,10 @@
* SWITCH_ID - VID[8:6]: * SWITCH_ID - VID[8:6]:
* Index of switch within DSA tree. Must be between 0 and 7. * Index of switch within DSA tree. Must be between 0 and 7.
* *
* RSV - VID[5:4]: * VBID - { VID[9], VID[5:4] }:
* To be used for further expansion of PORT or for other purposes. * Virtual bridge ID. If between 1 and 7, packet targets the broadcast
* Must be transmitted as zero and ignored on receive. * 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]: * PORT - VID[3:0]:
* Index of switch port. Must be between 0 and 15. * Index of switch port. Must be between 0 and 15.
...@@ -50,11 +51,30 @@ ...@@ -50,11 +51,30 @@
#define DSA_8021Q_SWITCH_ID(x) (((x) << DSA_8021Q_SWITCH_ID_SHIFT) & \ #define DSA_8021Q_SWITCH_ID(x) (((x) << DSA_8021Q_SWITCH_ID_SHIFT) & \
DSA_8021Q_SWITCH_ID_MASK) 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_SHIFT 0
#define DSA_8021Q_PORT_MASK GENMASK(3, 0) #define DSA_8021Q_PORT_MASK GENMASK(3, 0)
#define DSA_8021Q_PORT(x) (((x) << DSA_8021Q_PORT_SHIFT) & \ #define DSA_8021Q_PORT(x) (((x) << DSA_8021Q_PORT_SHIFT) & \
DSA_8021Q_PORT_MASK) 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 /* Returns the VID to be inserted into the frame from xmit for switch steering
* instructions on egress. Encodes switch ID and port ID. * instructions on egress. Encodes switch ID and port ID.
*/ */
...@@ -387,6 +407,26 @@ int dsa_tag_8021q_bridge_leave(struct dsa_switch *ds, ...@@ -387,6 +407,26 @@ int dsa_tag_8021q_bridge_leave(struct dsa_switch *ds,
return 0; 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 */ /* 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) static int dsa_tag_8021q_port_setup(struct dsa_switch *ds, int port)
{ {
......
...@@ -133,6 +133,31 @@ static u16 sja1105_xmit_tpid(struct sja1105_port *sp) ...@@ -133,6 +133,31 @@ static u16 sja1105_xmit_tpid(struct sja1105_port *sp)
return sp->xmit_tpid; 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, static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
struct net_device *netdev) struct net_device *netdev)
{ {
...@@ -141,6 +166,9 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb, ...@@ -141,6 +166,9 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
u16 queue_mapping = skb_get_queue_mapping(skb); u16 queue_mapping = skb_get_queue_mapping(skb);
u8 pcp = netdev_txq_to_tc(netdev, queue_mapping); 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, /* Transmitting management traffic does not rely upon switch tagging,
* but instead SPI-installed management routes. Part 2 of this * but instead SPI-installed management routes. Part 2 of this
* is the .port_deferred_xmit driver callback. * is the .port_deferred_xmit driver callback.
...@@ -165,6 +193,9 @@ static struct sk_buff *sja1110_xmit(struct sk_buff *skb, ...@@ -165,6 +193,9 @@ static struct sk_buff *sja1110_xmit(struct sk_buff *skb,
__be16 *tx_header; __be16 *tx_header;
int trailer_pos; int trailer_pos;
if (skb->offload_fwd_mark)
return sja1105_imprecise_xmit(skb, netdev);
/* Transmitting control packets is done using in-band control /* Transmitting control packets is done using in-band control
* extensions, while data packets are transmitted using * extensions, while data packets are transmitted using
* tag_8021q TX VLANs. * tag_8021q TX VLANs.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment