summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Abeni <pabeni@redhat.com>2026-05-19 12:37:34 +0200
committerPaolo Abeni <pabeni@redhat.com>2026-05-19 12:37:34 +0200
commit2d85ae5d0f39c715277bcf0b4e65d2eed39c34bb (patch)
tree6cab26c4fa51184f3ac2dfa7961778346c66425d
parent2beba18b0160446463bf1dbd749324846db98493 (diff)
parent4cb3cd670b2a29e52dd3cfd6463e44121674c9b8 (diff)
Merge branch 'net-dsa-mt7530-assorted-fixes'
Daniel Golle says: ==================== net: dsa: mt7530: assorted fixes A batch of small, independent fixes for the MediaTek MT7530 family DSA driver, addressing long-standing correctness issues that surface on hardware with bridge VLAN filtering enabled, on link-local frame reception, and during bridge join/leave transitions. ==================== Link: https://patch.msgid.link/cover.1778766629.git.daniel@makrotopia.org Signed-off-by: Paolo Abeni <pabeni@redhat.com>
-rw-r--r--drivers/net/dsa/mt7530.c160
1 files changed, 93 insertions, 67 deletions
diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
index 44d670904ad8..3c2a3029b10c 100644
--- a/drivers/net/dsa/mt7530.c
+++ b/drivers/net/dsa/mt7530.c
@@ -1023,12 +1023,16 @@ mt7530_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
unsigned int age_count;
unsigned int age_unit;
- /* Applied timer is (AGE_CNT + 1) * (AGE_UNIT + 1) seconds */
- if (secs < 1 || secs > (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1))
- return -ERANGE;
-
- /* iterate through all possible age_count to find the closest pair */
- for (tmp_age_count = 0; tmp_age_count <= AGE_CNT_MAX; ++tmp_age_count) {
+ /* Applied timer is (AGE_CNT + 1) * (AGE_UNIT + 1) seconds.
+ * The DSA core has already validated the range using
+ * ds->ageing_time_min and ds->ageing_time_max.
+ *
+ * Iterate through all possible age_count values to find the closest
+ * pair. Start from 1 because the per-entry aging counter is
+ * initialized to AGE_CNT and a value of 0 means the entry will
+ * never be aged out.
+ */
+ for (tmp_age_count = 1; tmp_age_count <= AGE_CNT_MAX; ++tmp_age_count) {
unsigned int tmp_age_unit = secs / (tmp_age_count + 1) - 1;
if (tmp_age_unit <= AGE_UNIT_MAX) {
@@ -1296,37 +1300,40 @@ static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface)
static void
mt753x_trap_frames(struct mt7530_priv *priv)
{
- /* Trap 802.1X PAE frames and BPDUs to the CPU port(s) and egress them
- * VLAN-untagged.
+ /* Trap 802.1X PAE frames and BPDUs to the CPU port(s) and egress
+ * them with the EG_TAG attribute set to disabled (system default)
+ * so that any VLAN tags in the frame are not modified by the
+ * switch egress VLAN tag processing. This preserves VLAN tags
+ * for reception on VLAN sub-interfaces.
*/
mt7530_rmw(priv, MT753X_BPC,
PAE_BPDU_FR | PAE_EG_TAG_MASK | PAE_PORT_FW_MASK |
BPDU_EG_TAG_MASK | BPDU_PORT_FW_MASK,
- PAE_BPDU_FR | PAE_EG_TAG(MT7530_VLAN_EG_UNTAGGED) |
+ PAE_BPDU_FR | PAE_EG_TAG(MT7530_VLAN_EG_DISABLED) |
PAE_PORT_FW(TO_CPU_FW_CPU_ONLY) |
- BPDU_EG_TAG(MT7530_VLAN_EG_UNTAGGED) |
+ BPDU_EG_TAG(MT7530_VLAN_EG_DISABLED) |
TO_CPU_FW_CPU_ONLY);
- /* Trap frames with :01 and :02 MAC DAs to the CPU port(s) and egress
- * them VLAN-untagged.
+ /* Trap frames with :01 and :02 MAC DAs to the CPU port(s) and
+ * egress them with EG_TAG disabled.
*/
mt7530_rmw(priv, MT753X_RGAC1,
R02_BPDU_FR | R02_EG_TAG_MASK | R02_PORT_FW_MASK |
R01_BPDU_FR | R01_EG_TAG_MASK | R01_PORT_FW_MASK,
- R02_BPDU_FR | R02_EG_TAG(MT7530_VLAN_EG_UNTAGGED) |
+ R02_BPDU_FR | R02_EG_TAG(MT7530_VLAN_EG_DISABLED) |
R02_PORT_FW(TO_CPU_FW_CPU_ONLY) | R01_BPDU_FR |
- R01_EG_TAG(MT7530_VLAN_EG_UNTAGGED) |
+ R01_EG_TAG(MT7530_VLAN_EG_DISABLED) |
TO_CPU_FW_CPU_ONLY);
- /* Trap frames with :03 and :0E MAC DAs to the CPU port(s) and egress
- * them VLAN-untagged.
+ /* Trap frames with :03 and :0E MAC DAs to the CPU port(s) and
+ * egress them with EG_TAG disabled.
*/
mt7530_rmw(priv, MT753X_RGAC2,
R0E_BPDU_FR | R0E_EG_TAG_MASK | R0E_PORT_FW_MASK |
R03_BPDU_FR | R03_EG_TAG_MASK | R03_PORT_FW_MASK,
- R0E_BPDU_FR | R0E_EG_TAG(MT7530_VLAN_EG_UNTAGGED) |
+ R0E_BPDU_FR | R0E_EG_TAG(MT7530_VLAN_EG_DISABLED) |
R0E_PORT_FW(TO_CPU_FW_CPU_ONLY) | R03_BPDU_FR |
- R03_EG_TAG(MT7530_VLAN_EG_UNTAGGED) |
+ R03_EG_TAG(MT7530_VLAN_EG_DISABLED) |
TO_CPU_FW_CPU_ONLY);
}
@@ -1616,6 +1623,49 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port,
return 0;
}
+static int
+mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid)
+{
+ struct mt7530_dummy_poll p;
+ u32 val;
+ int ret;
+
+ val = VTCR_BUSY | VTCR_FUNC(cmd) | vid;
+ mt7530_write(priv, MT7530_VTCR, val);
+
+ INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR);
+ ret = readx_poll_timeout(_mt7530_read, &p, val,
+ !(val & VTCR_BUSY), 20, 20000);
+ if (ret < 0) {
+ dev_err(priv->dev, "poll timeout\n");
+ return ret;
+ }
+
+ val = mt7530_read(priv, MT7530_VTCR);
+ if (val & VTCR_INVALID) {
+ dev_err(priv->dev, "read VTCR invalid\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+mt7530_setup_vlan0(struct mt7530_priv *priv)
+{
+ u32 val;
+
+ /* Validate the entry with independent learning, keep the original
+ * ingress tag attribute.
+ */
+ val = IVL_MAC | EG_CON | PORT_MEM(MT7530_ALL_MEMBERS) | FID(FID_BRIDGED) |
+ VLAN_VALID;
+ mt7530_write(priv, MT7530_VAWD1, val);
+ mt7530_write(priv, MT7530_VAWD2, 0);
+
+ return mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, 0);
+}
+
static void
mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port)
{
@@ -1641,6 +1691,8 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port)
G0_PORT_VID_DEF);
for (i = 0; i < priv->ds->num_ports; i++) {
+ if (i == port)
+ continue;
if (dsa_is_user_port(ds, i) &&
dsa_port_is_vlan_filtering(dsa_to_port(ds, i))) {
all_user_ports_removed = false;
@@ -1652,13 +1704,9 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port)
* the CPU port get out of VLAN filtering mode.
*/
if (all_user_ports_removed) {
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct dsa_port *cpu_dp = dp->cpu_dp;
-
- mt7530_write(priv, MT7530_PCR_P(cpu_dp->index),
- PCR_MATRIX(dsa_user_ports(priv->ds)));
- mt7530_write(priv, MT7530_PVC_P(cpu_dp->index), PORT_SPEC_TAG
- | PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
+ mutex_lock(&priv->reg_mutex);
+ mt7530_setup_vlan0(priv);
+ mutex_unlock(&priv->reg_mutex);
}
}
@@ -1847,33 +1895,6 @@ mt7530_port_mdb_del(struct dsa_switch *ds, int port,
}
static int
-mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid)
-{
- struct mt7530_dummy_poll p;
- u32 val;
- int ret;
-
- val = VTCR_BUSY | VTCR_FUNC(cmd) | vid;
- mt7530_write(priv, MT7530_VTCR, val);
-
- INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR);
- ret = readx_poll_timeout(_mt7530_read, &p, val,
- !(val & VTCR_BUSY), 20, 20000);
- if (ret < 0) {
- dev_err(priv->dev, "poll timeout\n");
- return ret;
- }
-
- val = mt7530_read(priv, MT7530_VTCR);
- if (val & VTCR_INVALID) {
- dev_err(priv->dev, "read VTCR invalid\n");
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int
mt7530_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
struct netlink_ext_ack *extack)
{
@@ -1978,21 +1999,6 @@ mt7530_hw_vlan_update(struct mt7530_priv *priv, u16 vid,
}
static int
-mt7530_setup_vlan0(struct mt7530_priv *priv)
-{
- u32 val;
-
- /* Validate the entry with independent learning, keep the original
- * ingress tag attribute.
- */
- val = IVL_MAC | EG_CON | PORT_MEM(MT7530_ALL_MEMBERS) | FID(FID_BRIDGED) |
- VLAN_VALID;
- mt7530_write(priv, MT7530_VAWD1, val);
-
- return mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, 0);
-}
-
-static int
mt7530_port_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
@@ -2004,9 +2010,18 @@ mt7530_port_vlan_add(struct dsa_switch *ds, int port,
mutex_lock(&priv->reg_mutex);
+ /* VID 0 is managed exclusively by mt7530_setup_vlan0() for
+ * VLAN-unaware bridge operation. Don't let the bridge overwrite
+ * its EG_CON flag with VTAG_EN and corrupt PORT_MEM.
+ */
+ if (vlan->vid == 0)
+ goto skip_vlan_table;
+
mt7530_hw_vlan_entry_init(&new_entry, port, untagged);
mt7530_hw_vlan_update(priv, vlan->vid, &new_entry, mt7530_hw_vlan_add);
+skip_vlan_table:
+
if (pvid) {
priv->ports[port].pvid = vlan->vid;
@@ -2046,10 +2061,15 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port,
mutex_lock(&priv->reg_mutex);
+ /* VID 0 is managed exclusively by mt7530_setup_vlan0(). */
+ if (vlan->vid == 0)
+ goto skip_vlan_table;
+
mt7530_hw_vlan_entry_init(&target_entry, port, 0);
mt7530_hw_vlan_update(priv, vlan->vid, &target_entry,
mt7530_hw_vlan_del);
+skip_vlan_table:
/* PVID is being restored to the default whenever the PVID port
* is being removed from the VLAN.
*/
@@ -2427,7 +2447,10 @@ mt7530_setup(struct dsa_switch *ds)
}
ds->assisted_learning_on_cpu_port = true;
+ ds->untag_vlan_aware_bridge_pvid = true;
ds->mtu_enforcement_ingress = true;
+ ds->ageing_time_min = 2 * 1000;
+ ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000;
if (priv->id == ID_MT7530) {
regulator_set_voltage(priv->core_pwr, 1000000, 1000000);
@@ -2616,7 +2639,10 @@ mt7531_setup_common(struct dsa_switch *ds)
int ret, i;
ds->assisted_learning_on_cpu_port = true;
+ ds->untag_vlan_aware_bridge_pvid = true;
ds->mtu_enforcement_ingress = true;
+ ds->ageing_time_min = 2 * 1000;
+ ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000;
mt753x_trap_frames(priv);