From e4c5f850ee8ca03acbbb903adf2fb0a51d129252 Mon Sep 17 00:00:00 2001 From: Gennaro Cimmino Date: Mon, 19 Oct 2026 17:08:57 +0200 Subject: [PATCH] realtek: dsa: drop untagged frames of ports without PVID Every port is a member of VID 0, so that the frames the CPU sends with its PVID 0 reach the ports with untagged VLANs. A port without PVID keeps PVID 0 too, so in a VLAN-filtering bridge its untagged and priority tagged frames land in VID 0 and are flooded to the other ports of the bridge, tagged VID 0, where the bridge would drop them. The acceptable frame type of a port tells the two apart (one bit each for inner and outer, tagged and untagged frames; vendor SDK). While a port without PVID is in a VLAN-filtering bridge, it accepts only tagged frames, outer tagged ones as an 802.1ad port; standalone ports, which can have VLAN filtering on, and every other state accept all. The driver tracks the bridge membership itself, since DSA rolls back a failed join through port_bridge_leave() before it clears dp->bridge. VID 0 and the CPU path stay as they are. The register has this layout on all four families; only RTL839x sets its address here, the others keep their behaviour until tested. HPE 1920-48G (RTL8393), lan48 a UCI trunk with VLAN 3 and 7 tagged, lan47 an access port in VLAN 1: untagged and priority tagged ARP from the host behind lan48 left lan47 tagged VID 0, 3 of 3 each; now none does. STP, LLDP, LACP and EAPOL frames on lan48 still reach the CPU, 5 of 5 each, and VLAN 3 tagged frames pass. With a PVID on lan48 again, and in a bridge without VLAN filtering, the ARP leaves lan47 untagged; as standalone ports both accept all frames again. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Gennaro Cimmino Link: https://github.com/openwrt/openwrt/pull/25992 Signed-off-by: Markus Stockhausen --- .../files-6.18/drivers/net/dsa/rtl83xx/dsa.c | 6 ++++ .../drivers/net/dsa/rtl83xx/rtl-otto.h | 3 ++ .../drivers/net/dsa/rtl83xx/rtl839x.c | 2 ++ .../files-6.18/drivers/net/dsa/rtl83xx/vlan.c | 29 +++++++++++++++++++ .../files-6.18/drivers/net/dsa/rtl83xx/vlan.h | 1 + 5 files changed, 41 insertions(+) diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c index 07bdcda667d..515fbd7de99 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c @@ -840,6 +840,9 @@ static int rtldsa_port_bridge_join(struct dsa_switch *ds, int port, struct dsa_b priv->ports[port].qinq = true; } + priv->ports[port].bridged = true; + otto_vlan_port_untagged_set(priv, port); + rtldsa_update_port_member(priv, port, bridge.dev, true); if (priv->r->set_static_move_action) @@ -868,6 +871,9 @@ static void rtldsa_port_bridge_leave(struct dsa_switch *ds, int port, struct dsa priv->ports[port].qinq = false; } + priv->ports[port].bridged = false; + otto_vlan_port_untagged_set(priv, port); + if (priv->r->set_static_move_action) priv->r->set_static_move_action(port, true); diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h index 97f825c697b..1724e1cd081 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h @@ -84,6 +84,8 @@ struct rtldsa_port { bool phy:1; bool isolated:1; bool qinq:1; + bool bridged:1; + bool vlan_filtering:1; unsigned long rate_police_egress; unsigned long rate_police_ingress; unsigned long cached_flags; @@ -464,6 +466,7 @@ struct rtldsa_config { u32 (*l2_hash_key)(struct otto_dsa_priv *priv, u64 seed); u64 (*read_mcast_pmask)(int idx); void (*write_mcast_pmask)(int idx, u64 portmask); + u32 vlan_port_aft; void (*vlan_fwd_on_inner)(struct otto_dsa_priv *priv, int port, bool is_set); void (*vlan_qinq_setup)(struct otto_dsa_priv *priv); void (*vlan_port_qinq_set)(struct otto_dsa_priv *priv, int port, bool enable); diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl839x.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl839x.c index 7313bb04890..fca4b6ee03b 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl839x.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl839x.c @@ -51,6 +51,7 @@ #define RTL839X_SPCL_TRAP_EAPOL_CTRL (0x105C) #define RTL839X_SPCL_TRAP_SWITCH_MAC_CTRL (0x1068) +#define RTL839X_VLAN_PORT_AFT (0x2600) #define RTL839X_IMR_GLB (0x0064) #define RTL839X_IMR_PORT_LINK_STS_CHG (0x0068) @@ -239,6 +240,7 @@ const struct rtldsa_config rtldsa_839x_cfg = { .vlan_profile_get = otto_vlan_839x_profile_get, .vlan_profile_dump = otto_vlan_839x_profile_dump, .vlan_profile_setup = otto_vlan_839x_profile_setup, + .vlan_port_aft = RTL839X_VLAN_PORT_AFT, .vlan_fwd_on_inner = otto_vlan_839x_port_forward_on_inner, .vlan_port_keep_tag_set = otto_vlan_839x_port_keep_tag_set, .vlan_port_pvidmode_set = otto_vlan_839x_port_pvid_mode_set, diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.c index ae78fc63a00..7b3a68e3a93 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.c @@ -133,6 +133,31 @@ #define RTL931X_VLAN_PORT_TAG_ITPID_IDX_MASK GENMASK(2, 1) #define RTL931X_VLAN_PORT_TAG_ITPID_KEEP_MASK GENMASK(0, 0) +#define RTLDSA_VLAN_PORT_AFT_ITAG_UNTAG BIT(1) +#define RTLDSA_VLAN_PORT_AFT_OTAG_UNTAG BIT(3) +#define RTLDSA_VLAN_PORT_AFT_ALL GENMASK(3, 0) + +/* + * Every port is a member of VID 0 for the frames the CPU sends, see + * otto_vlan_setup(). A port without PVID in a VLAN-filtering bridge must not + * flood its untagged frames there, so it accepts only tagged ones, outer tagged + * ones as an 802.1ad port. Standalone ports can have VLAN filtering on. + */ +void otto_vlan_port_untagged_set(struct otto_dsa_priv *priv, int port) +{ + struct rtldsa_port *p = &priv->ports[port]; + u32 aft = RTLDSA_VLAN_PORT_AFT_ALL; + + if (!priv->r->vlan_port_aft || port == priv->r->cpu_port) + return; + + if (p->bridged && p->vlan_filtering && !p->pvid) + aft &= ~(p->qinq ? RTLDSA_VLAN_PORT_AFT_OTAG_UNTAG : + RTLDSA_VLAN_PORT_AFT_ITAG_UNTAG); + + regmap_write(priv->map, priv->r->vlan_port_aft + (port << 2), aft); +} + static void otto_vlan_port_pvid_set(struct otto_dsa_priv *priv, int port, int pvid) { @@ -145,6 +170,7 @@ static void otto_vlan_port_pvid_set(struct otto_dsa_priv *priv, PBVLAN_MODE_UNTAG_AND_PRITAG); priv->ports[port].pvid = pvid; + otto_vlan_port_untagged_set(priv, port); } /* Initialize all VLANS */ @@ -905,6 +931,9 @@ int otto_vlan_port_filtering(struct dsa_switch *ds, int port, priv->r->set_vlan_egr_filter(port, EGR_DISABLE); } + priv->ports[port].vlan_filtering = vlan_filtering; + otto_vlan_port_untagged_set(priv, port); + /* Do we need to do something to the CPU-Port, too? */ mutex_unlock(&priv->reg_mutex); diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.h b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.h index 331452ca87a..28f4f4b4101 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.h +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/vlan.h @@ -152,6 +152,7 @@ void otto_vlan_931x_port_pvid_set(struct otto_dsa_priv *priv, int port, enum pbv int pvid); void otto_vlan_setup(struct otto_dsa_priv *priv); +void otto_vlan_port_untagged_set(struct otto_dsa_priv *priv, int port); int otto_vlan_port_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, struct netlink_ext_ack *extack); int otto_vlan_port_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan,