Commit 6087175b authored by DENG Qingfang's avatar DENG Qingfang Committed by David S. Miller

net: dsa: mt7530: use independent VLAN learning on VLAN-unaware bridges

Consider the following bridge configuration, where bond0 is not
offloaded:

         +-- br0 --+
        / /   |     \
       / /    |      \
      /  |    |     bond0
     /   |    |     /   \
   swp0 swp1 swp2 swp3 swp4
     .        .       .
     .        .       .
     A        B       C

Ideally, when the switch receives a packet from swp3 or swp4, it should
forward the packet to the CPU, according to the port matrix and unknown
unicast flood settings.

But packet loss will happen if the destination address is at one of the
offloaded ports (swp0~2). For example, when client C sends a packet to
A, the FDB lookup will indicate that it should be forwarded to swp0, but
the port matrix of swp3 and swp4 is configured to only allow the CPU to
be its destination, so it is dropped.

However, this issue does not happen if the bridge is VLAN-aware. That is
because VLAN-aware bridges use independent VLAN learning, i.e. use VID
for FDB lookup, on offloaded ports. As swp3 and swp4 are not offloaded,
shared VLAN learning with default filter ID of 0 is used instead. So the
lookup for A with filter ID 0 never hits and the packet can be forwarded
to the CPU.

In the current code, only two combinations were used to toggle user
ports' VLAN awareness: one is PCR.PORT_VLAN set to port matrix mode with
PVC.VLAN_ATTR set to transparent port, the other is PCR.PORT_VLAN set to
security mode with PVC.VLAN_ATTR set to user port.

It turns out that only PVC.VLAN_ATTR contributes to VLAN awareness, and
port matrix mode just skips the VLAN table lookup. The reference manual
is somehow misleading when describing PORT_VLAN modes. It states that
PORT_MEM (VLAN port member) is used for destination if the VLAN table
lookup hits, but actually **PORT_MEM & PORT_MATRIX** (bitwise AND of
VLAN port member and port matrix) is used instead, which means we can
have two or more separate VLAN-aware bridges with the same PVID and
traffic won't leak between them.

Therefore, to solve this, enable independent VLAN learning with PVID 0
on VLAN-unaware bridges, by setting their PCR.PORT_VLAN to fallback
mode, while leaving standalone ports in port matrix mode. The CPU port
is always set to fallback mode to serve those bridges.

During testing, it is found that FDB lookup with filter ID of 0 will
also hit entries with VID 0 even with independent VLAN learning. To
avoid that, install all VLANs with filter ID of 1.
Signed-off-by: default avatarDENG Qingfang <dqfext@gmail.com>
Reviewed-by: default avatarVladimir Oltean <olteanv@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 0b69c54c
...@@ -1021,6 +1021,10 @@ mt753x_cpu_port_enable(struct dsa_switch *ds, int port) ...@@ -1021,6 +1021,10 @@ mt753x_cpu_port_enable(struct dsa_switch *ds, int port)
mt7530_write(priv, MT7530_PCR_P(port), mt7530_write(priv, MT7530_PCR_P(port),
PCR_MATRIX(dsa_user_ports(priv->ds))); PCR_MATRIX(dsa_user_ports(priv->ds)));
/* Set to fallback mode for independent VLAN learning */
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_FALLBACK_MODE);
return 0; return 0;
} }
...@@ -1229,6 +1233,10 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port, ...@@ -1229,6 +1233,10 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port,
PCR_MATRIX_MASK, PCR_MATRIX(port_bitmap)); PCR_MATRIX_MASK, PCR_MATRIX(port_bitmap));
priv->ports[port].pm |= PCR_MATRIX(port_bitmap); priv->ports[port].pm |= PCR_MATRIX(port_bitmap);
/* Set to fallback mode for independent VLAN learning */
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_FALLBACK_MODE);
mutex_unlock(&priv->reg_mutex); mutex_unlock(&priv->reg_mutex);
return 0; return 0;
...@@ -1241,16 +1249,21 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port) ...@@ -1241,16 +1249,21 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port)
bool all_user_ports_removed = true; bool all_user_ports_removed = true;
int i; int i;
/* When a port is removed from the bridge, the port would be set up /* This is called after .port_bridge_leave when leaving a VLAN-aware
* back to the default as is at initial boot which is a VLAN-unaware * bridge. Don't set standalone ports to fallback mode.
* port.
*/ */
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK, if (dsa_to_port(ds, port)->bridge_dev)
MT7530_PORT_MATRIX_MODE); mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_FALLBACK_MODE);
mt7530_rmw(priv, MT7530_PVC_P(port), VLAN_ATTR_MASK | PVC_EG_TAG_MASK, mt7530_rmw(priv, MT7530_PVC_P(port), VLAN_ATTR_MASK | PVC_EG_TAG_MASK,
VLAN_ATTR(MT7530_VLAN_TRANSPARENT) | VLAN_ATTR(MT7530_VLAN_TRANSPARENT) |
PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT)); PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
/* Set PVID to 0 */
mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
G0_PORT_VID_DEF);
for (i = 0; i < MT7530_NUM_PORTS; i++) { for (i = 0; i < MT7530_NUM_PORTS; i++) {
if (dsa_is_user_port(ds, i) && if (dsa_is_user_port(ds, i) &&
dsa_port_is_vlan_filtering(dsa_to_port(ds, i))) { dsa_port_is_vlan_filtering(dsa_to_port(ds, i))) {
...@@ -1276,15 +1289,14 @@ mt7530_port_set_vlan_aware(struct dsa_switch *ds, int port) ...@@ -1276,15 +1289,14 @@ mt7530_port_set_vlan_aware(struct dsa_switch *ds, int port)
struct mt7530_priv *priv = ds->priv; struct mt7530_priv *priv = ds->priv;
/* Trapped into security mode allows packet forwarding through VLAN /* Trapped into security mode allows packet forwarding through VLAN
* table lookup. CPU port is set to fallback mode to let untagged * table lookup.
* frames pass through.
*/ */
if (dsa_is_cpu_port(ds, port)) if (dsa_is_user_port(ds, port)) {
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_FALLBACK_MODE);
else
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK, mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_SECURITY_MODE); MT7530_PORT_SECURITY_MODE);
mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
G0_PORT_VID(priv->ports[port].pvid));
}
/* Set the port as a user port which is to be able to recognize VID /* Set the port as a user port which is to be able to recognize VID
* from incoming packets before fetching entry within the VLAN table. * from incoming packets before fetching entry within the VLAN table.
...@@ -1329,6 +1341,13 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port, ...@@ -1329,6 +1341,13 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port,
PCR_MATRIX(BIT(MT7530_CPU_PORT))); PCR_MATRIX(BIT(MT7530_CPU_PORT)));
priv->ports[port].pm = PCR_MATRIX(BIT(MT7530_CPU_PORT)); priv->ports[port].pm = PCR_MATRIX(BIT(MT7530_CPU_PORT));
/* When a port is removed from the bridge, the port would be set up
* back to the default as is at initial boot which is a VLAN-unaware
* port.
*/
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_MATRIX_MODE);
mutex_unlock(&priv->reg_mutex); mutex_unlock(&priv->reg_mutex);
} }
...@@ -1511,7 +1530,8 @@ mt7530_hw_vlan_add(struct mt7530_priv *priv, ...@@ -1511,7 +1530,8 @@ mt7530_hw_vlan_add(struct mt7530_priv *priv,
/* Validate the entry with independent learning, create egress tag per /* Validate the entry with independent learning, create egress tag per
* VLAN and joining the port as one of the port members. * VLAN and joining the port as one of the port members.
*/ */
val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | VLAN_VALID; val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | FID(FID_BRIDGED) |
VLAN_VALID;
mt7530_write(priv, MT7530_VAWD1, val); mt7530_write(priv, MT7530_VAWD1, val);
/* Decide whether adding tag or not for those outgoing packets from the /* Decide whether adding tag or not for those outgoing packets from the
...@@ -1601,9 +1621,13 @@ mt7530_port_vlan_add(struct dsa_switch *ds, int port, ...@@ -1601,9 +1621,13 @@ mt7530_port_vlan_add(struct dsa_switch *ds, int port,
mt7530_hw_vlan_update(priv, vlan->vid, &new_entry, mt7530_hw_vlan_add); mt7530_hw_vlan_update(priv, vlan->vid, &new_entry, mt7530_hw_vlan_add);
if (pvid) { if (pvid) {
mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
G0_PORT_VID(vlan->vid));
priv->ports[port].pvid = vlan->vid; priv->ports[port].pvid = vlan->vid;
/* Only configure PVID if VLAN filtering is enabled */
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port)))
mt7530_rmw(priv, MT7530_PPBV1_P(port),
G0_PORT_VID_MASK,
G0_PORT_VID(vlan->vid));
} }
mutex_unlock(&priv->reg_mutex); mutex_unlock(&priv->reg_mutex);
...@@ -1617,11 +1641,9 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port, ...@@ -1617,11 +1641,9 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port,
{ {
struct mt7530_hw_vlan_entry target_entry; struct mt7530_hw_vlan_entry target_entry;
struct mt7530_priv *priv = ds->priv; struct mt7530_priv *priv = ds->priv;
u16 pvid;
mutex_lock(&priv->reg_mutex); mutex_lock(&priv->reg_mutex);
pvid = priv->ports[port].pvid;
mt7530_hw_vlan_entry_init(&target_entry, port, 0); mt7530_hw_vlan_entry_init(&target_entry, port, 0);
mt7530_hw_vlan_update(priv, vlan->vid, &target_entry, mt7530_hw_vlan_update(priv, vlan->vid, &target_entry,
mt7530_hw_vlan_del); mt7530_hw_vlan_del);
...@@ -1629,11 +1651,12 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port, ...@@ -1629,11 +1651,12 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port,
/* PVID is being restored to the default whenever the PVID port /* PVID is being restored to the default whenever the PVID port
* is being removed from the VLAN. * is being removed from the VLAN.
*/ */
if (pvid == vlan->vid) if (priv->ports[port].pvid == vlan->vid) {
pvid = G0_PORT_VID_DEF; priv->ports[port].pvid = G0_PORT_VID_DEF;
mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
G0_PORT_VID_DEF);
}
mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK, pvid);
priv->ports[port].pvid = pvid;
mutex_unlock(&priv->reg_mutex); mutex_unlock(&priv->reg_mutex);
...@@ -2126,6 +2149,10 @@ mt7530_setup(struct dsa_switch *ds) ...@@ -2126,6 +2149,10 @@ mt7530_setup(struct dsa_switch *ds)
return ret; return ret;
} else { } else {
mt7530_port_disable(ds, i); mt7530_port_disable(ds, i);
/* Set default PVID to 0 on all user ports */
mt7530_rmw(priv, MT7530_PPBV1_P(i), G0_PORT_VID_MASK,
G0_PORT_VID_DEF);
} }
/* Enable consistent egress tag */ /* Enable consistent egress tag */
mt7530_rmw(priv, MT7530_PVC_P(i), PVC_EG_TAG_MASK, mt7530_rmw(priv, MT7530_PVC_P(i), PVC_EG_TAG_MASK,
...@@ -2293,6 +2320,10 @@ mt7531_setup(struct dsa_switch *ds) ...@@ -2293,6 +2320,10 @@ mt7531_setup(struct dsa_switch *ds)
return ret; return ret;
} else { } else {
mt7530_port_disable(ds, i); mt7530_port_disable(ds, i);
/* Set default PVID to 0 on all user ports */
mt7530_rmw(priv, MT7530_PPBV1_P(i), G0_PORT_VID_MASK,
G0_PORT_VID_DEF);
} }
/* Enable consistent egress tag */ /* Enable consistent egress tag */
......
...@@ -148,11 +148,18 @@ enum mt7530_vlan_cmd { ...@@ -148,11 +148,18 @@ enum mt7530_vlan_cmd {
#define VTAG_EN BIT(28) #define VTAG_EN BIT(28)
/* VLAN Member Control */ /* VLAN Member Control */
#define PORT_MEM(x) (((x) & 0xff) << 16) #define PORT_MEM(x) (((x) & 0xff) << 16)
/* Filter ID */
#define FID(x) (((x) & 0x7) << 1)
/* VLAN Entry Valid */ /* VLAN Entry Valid */
#define VLAN_VALID BIT(0) #define VLAN_VALID BIT(0)
#define PORT_MEM_SHFT 16 #define PORT_MEM_SHFT 16
#define PORT_MEM_MASK 0xff #define PORT_MEM_MASK 0xff
enum mt7530_fid {
FID_STANDALONE = 0,
FID_BRIDGED = 1,
};
#define MT7530_VAWD2 0x98 #define MT7530_VAWD2 0x98
/* Egress Tag Control */ /* Egress Tag Control */
#define ETAG_CTRL_P(p, x) (((x) & 0x3) << ((p) << 1)) #define ETAG_CTRL_P(p, x) (((x) & 0x3) << ((p) << 1))
...@@ -247,7 +254,7 @@ enum mt7530_vlan_port_attr { ...@@ -247,7 +254,7 @@ enum mt7530_vlan_port_attr {
#define MT7530_PPBV1_P(x) (0x2014 + ((x) * 0x100)) #define MT7530_PPBV1_P(x) (0x2014 + ((x) * 0x100))
#define G0_PORT_VID(x) (((x) & 0xfff) << 0) #define G0_PORT_VID(x) (((x) & 0xfff) << 0)
#define G0_PORT_VID_MASK G0_PORT_VID(0xfff) #define G0_PORT_VID_MASK G0_PORT_VID(0xfff)
#define G0_PORT_VID_DEF G0_PORT_VID(1) #define G0_PORT_VID_DEF G0_PORT_VID(0)
/* Register for port MAC control register */ /* Register for port MAC control register */
#define MT7530_PMCR_P(x) (0x3000 + ((x) * 0x100)) #define MT7530_PMCR_P(x) (0x3000 + ((x) * 0x100))
......
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