net: dsa: tag_8021q: replace the SVL bridging with VLAN-unaware IVL bridging

For VLAN-unaware bridging, tag_8021q uses something perhaps a bit too
tied with the sja1105 switch: each port uses the same pvid which is also
used for standalone operation (a unique one from which the source port
and device ID can be retrieved when packets from that port are forwarded
to the CPU). Since each port has a unique pvid when performing
autonomous forwarding, the switch must be configured for Shared VLAN
Learning (SVL) such that the VLAN ID itself is ignored when performing
FDB lookups. Without SVL, packets would always be flooded, since FDB
lookup in the source port's VLAN would never find any entry.

First of all, to make tag_8021q more palatable to switches which might
not support Shared VLAN Learning, let's just use a common VLAN for all
ports that are under the same bridge.

Secondly, using Shared VLAN Learning means that FDB isolation can never
be enforced. But if all ports under the same VLAN-unaware bridge share
the same VLAN ID, it can.

The disadvantage is that the CPU port can no longer perform precise
source port identification for these packets. But at least we have a
mechanism which has proven to be adequate for that situation: imprecise
RX (dsa_find_designated_bridge_port_by_vid), which is what we use for
termination on VLAN-aware bridges.

The VLAN ID that VLAN-unaware bridges will use with tag_8021q is the
same one as we were previously using for imprecise TX (bridge TX
forwarding offload). It is already allocated, it is just a matter of
using it.

Note that because now all ports under the same bridge share the same
VLAN, the complexity of performing a tag_8021q bridge join decreases
dramatically. We no longer have to install the RX VLAN of a newly
joining port into the port membership of the existing bridge ports.
The newly joining port just becomes a member of the VLAN corresponding
to that bridge, and the other ports are already members of it from when
they joined the bridge themselves. So forwarding works properly.

This means that we can unhook dsa_tag_8021q_bridge_{join,leave} from the
cross-chip notifier level dsa_switch_bridge_{join,leave}. We can put
these calls directly into the sja1105 driver.

With this new mode of operation, a port controlled by tag_8021q can have
two pvids whereas before it could only have one. The pvid for standalone
operation is different from the pvid used for VLAN-unaware bridging.
This is done, again, so that FDB isolation can be enforced.
Let tag_8021q manage this by deleting the standalone pvid when a port
joins a bridge, and restoring it when it leaves it.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Vladimir Oltean 2022-02-25 11:22:16 +02:00 committed by David S. Miller
parent 1bb1c5bc54
commit 91495f21fc
6 changed files with 70 additions and 101 deletions

View File

@ -2067,7 +2067,7 @@ static int sja1105_bridge_join(struct dsa_switch *ds, int port,
if (rc) if (rc)
return rc; return rc;
rc = dsa_tag_8021q_bridge_tx_fwd_offload(ds, port, bridge); rc = dsa_tag_8021q_bridge_join(ds, port, bridge);
if (rc) { if (rc) {
sja1105_bridge_member(ds, port, bridge, false); sja1105_bridge_member(ds, port, bridge, false);
return rc; return rc;
@ -2081,7 +2081,7 @@ static int sja1105_bridge_join(struct dsa_switch *ds, int port,
static void sja1105_bridge_leave(struct dsa_switch *ds, int port, static void sja1105_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_bridge bridge) struct dsa_bridge bridge)
{ {
dsa_tag_8021q_bridge_tx_fwd_unoffload(ds, port, bridge); dsa_tag_8021q_bridge_leave(ds, port, bridge);
sja1105_bridge_member(ds, port, bridge, false); sja1105_bridge_member(ds, port, bridge, false);
} }

View File

@ -296,6 +296,19 @@ static bool sja1105_vl_key_lower(struct sja1105_vl_lookup_entry *a,
return false; return false;
} }
/* FIXME: this should change when the bridge upper of the port changes. */
static u16 sja1105_port_get_tag_8021q_vid(struct dsa_port *dp)
{
unsigned long bridge_num;
if (!dp->bridge)
return dsa_tag_8021q_rx_vid(dp);
bridge_num = dsa_port_bridge_num_get(dp);
return dsa_8021q_bridge_tx_fwd_offload_vid(bridge_num);
}
static int sja1105_init_virtual_links(struct sja1105_private *priv, static int sja1105_init_virtual_links(struct sja1105_private *priv,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
@ -395,7 +408,7 @@ static int sja1105_init_virtual_links(struct sja1105_private *priv,
vl_lookup[k].vlanprior = rule->key.vl.pcp; vl_lookup[k].vlanprior = rule->key.vl.pcp;
} else { } else {
struct dsa_port *dp = dsa_to_port(priv->ds, port); struct dsa_port *dp = dsa_to_port(priv->ds, port);
u16 vid = dsa_tag_8021q_rx_vid(dp); u16 vid = sja1105_port_get_tag_8021q_vid(dp);
vl_lookup[k].vlanid = vid; vl_lookup[k].vlanid = vid;
vl_lookup[k].vlanprior = 0; vl_lookup[k].vlanprior = 0;

View File

@ -32,17 +32,17 @@ int dsa_tag_8021q_register(struct dsa_switch *ds, __be16 proto);
void dsa_tag_8021q_unregister(struct dsa_switch *ds); void dsa_tag_8021q_unregister(struct dsa_switch *ds);
int dsa_tag_8021q_bridge_join(struct dsa_switch *ds, int port,
struct dsa_bridge bridge);
void dsa_tag_8021q_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_bridge bridge);
struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev, struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
u16 tpid, u16 tci); u16 tpid, u16 tci);
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 dsa_bridge bridge);
void dsa_tag_8021q_bridge_tx_fwd_unoffload(struct dsa_switch *ds, int port,
struct dsa_bridge bridge);
u16 dsa_8021q_bridge_tx_fwd_offload_vid(unsigned int bridge_num); u16 dsa_8021q_bridge_tx_fwd_offload_vid(unsigned int bridge_num);
u16 dsa_tag_8021q_tx_vid(const struct dsa_port *dp); u16 dsa_tag_8021q_tx_vid(const struct dsa_port *dp);

View File

@ -522,10 +522,6 @@ struct dsa_bridge *dsa_tree_bridge_find(struct dsa_switch_tree *dst,
const struct net_device *br); const struct net_device *br);
/* tag_8021q.c */ /* tag_8021q.c */
int dsa_tag_8021q_bridge_join(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info);
int dsa_tag_8021q_bridge_leave(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info);
int dsa_switch_tag_8021q_vlan_add(struct dsa_switch *ds, int dsa_switch_tag_8021q_vlan_add(struct dsa_switch *ds,
struct dsa_notifier_tag_8021q_vlan_info *info); struct dsa_notifier_tag_8021q_vlan_info *info);
int dsa_switch_tag_8021q_vlan_del(struct dsa_switch *ds, int dsa_switch_tag_8021q_vlan_del(struct dsa_switch *ds,

View File

@ -110,7 +110,7 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds,
return err; return err;
} }
return dsa_tag_8021q_bridge_join(ds, info); return 0;
} }
static int dsa_switch_sync_vlan_filtering(struct dsa_switch *ds, static int dsa_switch_sync_vlan_filtering(struct dsa_switch *ds,
@ -186,7 +186,7 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
return err; return err;
} }
return dsa_tag_8021q_bridge_leave(ds, info); return 0;
} }
/* Matches for all upstream-facing ports (the CPU port and all upstream-facing /* Matches for all upstream-facing ports (the CPU port and all upstream-facing

View File

@ -110,6 +110,15 @@ int dsa_8021q_rx_source_port(u16 vid)
} }
EXPORT_SYMBOL_GPL(dsa_8021q_rx_source_port); EXPORT_SYMBOL_GPL(dsa_8021q_rx_source_port);
/* Returns the decoded VBID from the RX VID. */
static int dsa_tag_8021q_rx_vbid(u16 vid)
{
u16 vbid_hi = (vid & DSA_8021Q_VBID_HI_MASK) >> DSA_8021Q_VBID_HI_SHIFT;
u16 vbid_lo = (vid & DSA_8021Q_VBID_LO_MASK) >> DSA_8021Q_VBID_LO_SHIFT;
return (vbid_hi << 2) | vbid_lo;
}
bool vid_is_dsa_8021q_rxvlan(u16 vid) bool vid_is_dsa_8021q_rxvlan(u16 vid)
{ {
return (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_RX; return (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_RX;
@ -244,11 +253,17 @@ int dsa_switch_tag_8021q_vlan_add(struct dsa_switch *ds,
if (dsa_port_is_user(dp)) if (dsa_port_is_user(dp))
flags |= BRIDGE_VLAN_INFO_UNTAGGED; flags |= BRIDGE_VLAN_INFO_UNTAGGED;
/* Standalone VLANs are PVIDs */
if (vid_is_dsa_8021q_rxvlan(info->vid) && if (vid_is_dsa_8021q_rxvlan(info->vid) &&
dsa_8021q_rx_switch_id(info->vid) == ds->index && dsa_8021q_rx_switch_id(info->vid) == ds->index &&
dsa_8021q_rx_source_port(info->vid) == dp->index) dsa_8021q_rx_source_port(info->vid) == dp->index)
flags |= BRIDGE_VLAN_INFO_PVID; flags |= BRIDGE_VLAN_INFO_PVID;
/* And bridging VLANs are PVIDs too on user ports */
if (dsa_tag_8021q_rx_vbid(info->vid) &&
dsa_port_is_user(dp))
flags |= BRIDGE_VLAN_INFO_PVID;
err = dsa_port_do_tag_8021q_vlan_add(dp, info->vid, err = dsa_port_do_tag_8021q_vlan_add(dp, info->vid,
flags); flags);
if (err) if (err)
@ -326,107 +341,52 @@ int dsa_switch_tag_8021q_vlan_del(struct dsa_switch *ds,
* +-+-----+-+-----+-+-----+-+-----+-+ +-+-----+-+-----+-+-----+-+-----+-+ * +-+-----+-+-----+-+-----+-+-----+-+ +-+-----+-+-----+-+-----+-+-----+-+
* swp0 swp1 swp2 swp3 swp0 swp1 swp2 swp3 * swp0 swp1 swp2 swp3 swp0 swp1 swp2 swp3
*/ */
static bool int dsa_tag_8021q_bridge_join(struct dsa_switch *ds, int port,
dsa_port_tag_8021q_bridge_match(struct dsa_port *dp, struct dsa_bridge bridge)
struct dsa_notifier_bridge_info *info)
{ {
/* Don't match on self */ struct dsa_port *dp = dsa_to_port(ds, port);
if (dp->ds->dst->index == info->tree_index && u16 standalone_vid, bridge_vid;
dp->ds->index == info->sw_index &&
dp->index == info->port)
return false;
if (dsa_port_is_user(dp))
return dsa_port_offloads_bridge(dp, &info->bridge);
return false;
}
int dsa_tag_8021q_bridge_join(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info)
{
struct dsa_switch *targeted_ds;
struct dsa_port *targeted_dp;
struct dsa_port *dp;
u16 targeted_rx_vid;
int err; int err;
if (!ds->tag_8021q_ctx) /* Delete the standalone VLAN of the port and replace it with a
return 0; * bridging VLAN
*/
standalone_vid = dsa_tag_8021q_rx_vid(dp);
bridge_vid = dsa_8021q_bridge_tx_fwd_offload_vid(bridge.num);
targeted_ds = dsa_switch_find(info->tree_index, info->sw_index); err = dsa_port_tag_8021q_vlan_add(dp, bridge_vid, true);
targeted_dp = dsa_to_port(targeted_ds, info->port); if (err)
targeted_rx_vid = dsa_tag_8021q_rx_vid(targeted_dp); return err;
dsa_switch_for_each_port(dp, ds) { dsa_port_tag_8021q_vlan_del(dp, standalone_vid, false);
u16 rx_vid = dsa_tag_8021q_rx_vid(dp);
if (!dsa_port_tag_8021q_bridge_match(dp, info))
continue;
/* Install the RX VID of the targeted port in our VLAN table */
err = dsa_port_tag_8021q_vlan_add(dp, targeted_rx_vid, true);
if (err)
return err;
/* Install our RX VID into the targeted port's VLAN table */
err = dsa_port_tag_8021q_vlan_add(targeted_dp, rx_vid, true);
if (err)
return err;
}
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(dsa_tag_8021q_bridge_join);
int dsa_tag_8021q_bridge_leave(struct dsa_switch *ds, void dsa_tag_8021q_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_notifier_bridge_info *info) struct dsa_bridge bridge)
{ {
struct dsa_switch *targeted_ds; struct dsa_port *dp = dsa_to_port(ds, port);
struct dsa_port *targeted_dp; u16 standalone_vid, bridge_vid;
struct dsa_port *dp; int err;
u16 targeted_rx_vid;
if (!ds->tag_8021q_ctx) /* Delete the bridging VLAN of the port and replace it with a
return 0; * standalone VLAN
*/
standalone_vid = dsa_tag_8021q_rx_vid(dp);
bridge_vid = dsa_8021q_bridge_tx_fwd_offload_vid(bridge.num);
targeted_ds = dsa_switch_find(info->tree_index, info->sw_index); err = dsa_port_tag_8021q_vlan_add(dp, standalone_vid, false);
targeted_dp = dsa_to_port(targeted_ds, info->port); if (err) {
targeted_rx_vid = dsa_tag_8021q_rx_vid(targeted_dp); dev_err(ds->dev,
"Failed to delete tag_8021q standalone VLAN %d from port %d: %pe\n",
dsa_switch_for_each_port(dp, ds) { standalone_vid, port, ERR_PTR(err));
u16 rx_vid = dsa_tag_8021q_rx_vid(dp);
if (!dsa_port_tag_8021q_bridge_match(dp, info))
continue;
/* Remove the RX VID of the targeted port from our VLAN table */
dsa_port_tag_8021q_vlan_del(dp, targeted_rx_vid, true);
/* Remove our RX VID from the targeted port's VLAN table */
dsa_port_tag_8021q_vlan_del(targeted_dp, rx_vid, true);
} }
return 0; dsa_port_tag_8021q_vlan_del(dp, bridge_vid, true);
} }
EXPORT_SYMBOL_GPL(dsa_tag_8021q_bridge_leave);
int dsa_tag_8021q_bridge_tx_fwd_offload(struct dsa_switch *ds, int port,
struct dsa_bridge bridge)
{
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,
true);
}
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 dsa_bridge bridge)
{
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, true);
}
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)