realtek: 6.6: fix VLAN handling
authorMarkus Stockhausen <markus.stockhausen@gmx.de>
Sat, 7 Sep 2024 07:23:03 +0000 (03:23 -0400)
committerSander Vanheule <sander@svanheule.net>
Sat, 14 Sep 2024 18:14:47 +0000 (20:14 +0200)
The CPU port of realtek switches needs some proper PVID set to handle
untagged packets. Because the ethernet driver does no special VLAN
handling (see CPU tag RVID/RVID_SEL) as of now we can only steer
untagged packets by setting PVID for the CPU port. VLAN handling has
never been perfect but 3 events made things worse.

- Commit a37650821644 ("rtl83xx: dsa: Do nothing when vid 0")
- Commit e691e2b302d9 ("rtl83xx: dsa: reset PVID to 1 instead of 0")
- Upgrade to kernel 6.6

Reasons are:

- Rejecting VID 0 disabled Linux initialization routines
- Initialization for PVID forgot to set priv->ports[port].pvid
- Kernel 6.6 does no longer clarify CPU port as untagged

To fix this prepare the VID 0 setup inside the driver. Join all ports
to VID 0 and let no one from outsinde interfere with this setup.
Especially ignore PVID settings for the CPU port for all further
VLAN commands.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Suggested-by: Bjørn Mork <bjorn@mork.no>
target/linux/realtek/files-6.6/drivers/net/dsa/rtl83xx/dsa.c

index 7ec3f4af1b811b8c68bfa572537d440f183db5b6..2f9b3ba8c1470906884ead52442417393aaf7ae7 100644 (file)
@@ -109,6 +109,20 @@ static enum dsa_tag_protocol rtl83xx_get_tag_protocol(struct dsa_switch *ds,
        return DSA_TAG_PROTO_TRAILER;
 }
 
+static void rtl83xx_vlan_set_pvid(struct rtl838x_switch_priv *priv,
+                                 int port, int pvid)
+{
+       /* Set both inner and outer PVID of the port */
+       priv->r->vlan_port_pvid_set(port, PBVLAN_TYPE_INNER, pvid);
+       priv->r->vlan_port_pvid_set(port, PBVLAN_TYPE_OUTER, pvid);
+       priv->r->vlan_port_pvidmode_set(port, PBVLAN_TYPE_INNER,
+                                       PBVLAN_MODE_UNTAG_AND_PRITAG);
+       priv->r->vlan_port_pvidmode_set(port, PBVLAN_TYPE_OUTER,
+                                       PBVLAN_MODE_UNTAG_AND_PRITAG);
+
+       priv->ports[port].pvid = pvid;
+}
+
 /* Initialize all VLANS */
 static void rtl83xx_vlan_setup(struct rtl838x_switch_priv *priv)
 {
@@ -132,17 +146,22 @@ static void rtl83xx_vlan_setup(struct rtl838x_switch_priv *priv)
                info.l2_tunnel_list_id = -1;
        }
 
-       /* Initialize all vlans 0-4095 */
-       for (int i = 0; i < MAX_VLANS; i ++)
+       /* Initialize normal VLANs 1-4095 */
+       for (int i = 1; i < MAX_VLANS; i ++)
                priv->r->vlan_set_tagged(i, &info);
 
-       /* reset PVIDs; defaults to 1 on reset */
+       /*
+        * Initialize the special VLAN 0 and reset PVIDs. The CPU port PVID
+        * is applied to packets from the CPU for untagged destinations,
+        * regardless if the actual ingress VID. Any port with untagged
+        * egress VLAN(s) must therefore be a member of VLAN 0 to support
+        * CPU port as ingress when VLAN filtering is enabled.
+        */
        for (int i = 0; i <= priv->cpu_port; i++) {
-               priv->r->vlan_port_pvid_set(i, PBVLAN_TYPE_INNER, 1);
-               priv->r->vlan_port_pvid_set(i, PBVLAN_TYPE_OUTER, 1);
-               priv->r->vlan_port_pvidmode_set(i, PBVLAN_TYPE_INNER, PBVLAN_MODE_UNTAG_AND_PRITAG);
-               priv->r->vlan_port_pvidmode_set(i, PBVLAN_TYPE_OUTER, PBVLAN_MODE_UNTAG_AND_PRITAG);
+               rtl83xx_vlan_set_pvid(priv, i, 0);
+               info.tagged_ports |= BIT_ULL(i);
        }
+       priv->r->vlan_set_tagged(0, &info);
 
        /* Set forwarding action based on inner VLAN tag */
        for (int i = 0; i < priv->cpu_port; i++)
@@ -1418,20 +1437,6 @@ static int rtl83xx_vlan_prepare(struct dsa_switch *ds, int port,
        return 0;
 }
 
-static void rtl83xx_vlan_set_pvid(struct rtl838x_switch_priv *priv,
-                                 int port, int pvid)
-{
-       /* Set both inner and outer PVID of the port */
-       priv->r->vlan_port_pvid_set(port, PBVLAN_TYPE_INNER, pvid);
-       priv->r->vlan_port_pvid_set(port, PBVLAN_TYPE_OUTER, pvid);
-       priv->r->vlan_port_pvidmode_set(port, PBVLAN_TYPE_INNER,
-                                       PBVLAN_MODE_UNTAG_AND_PRITAG);
-       priv->r->vlan_port_pvidmode_set(port, PBVLAN_TYPE_OUTER,
-                                       PBVLAN_MODE_UNTAG_AND_PRITAG);
-
-       priv->ports[port].pvid = pvid;
-}
-
 static int rtl83xx_vlan_add(struct dsa_switch *ds, int port,
                            const struct switchdev_obj_port_vlan *vlan,
                            struct netlink_ext_ack *extack)
@@ -1443,7 +1448,8 @@ static int rtl83xx_vlan_add(struct dsa_switch *ds, int port,
        pr_debug("%s port %d, vid %d, flags %x\n",
                __func__, port, vlan->vid, vlan->flags);
 
-       if(!vlan->vid) return 0;
+       /* Let no one mess with our special VLAN 0 */
+       if (!vlan->vid) return 0;
 
        if (vlan->vid > 4095) {
                dev_err(priv->dev, "VLAN out of range: %d", vlan->vid);
@@ -1456,10 +1462,20 @@ static int rtl83xx_vlan_add(struct dsa_switch *ds, int port,
 
        mutex_lock(&priv->reg_mutex);
 
-       if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
-               rtl83xx_vlan_set_pvid(priv, port, vlan->vid);
-       else if (priv->ports[port].pvid == vlan->vid)
-               rtl83xx_vlan_set_pvid(priv, port, 0);
+       /*
+        * Realtek switches copy frames as-is to/from the CPU. For a proper
+        * VLAN handling the 12 bit RVID field (= VLAN id) for incoming traffic
+        * and the 1 bit RVID_SEL field (0 = use inner tag, 1 = use outer tag)
+        * for outgoing traffic of the CPU tag structure need to be handled. As
+        * of now no such logic is in place. So for the CPU port keep the fixed
+        * PVID=0 from initial setup in place and ignore all subsequent settings.
+        */
+       if (port != priv->cpu_port) {
+               if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
+                       rtl83xx_vlan_set_pvid(priv, port, vlan->vid);
+               else if (priv->ports[port].pvid == vlan->vid)
+                       rtl83xx_vlan_set_pvid(priv, port, 0);
+       }
 
        /* Get port memberships of this vlan */
        priv->r->vlan_tables_read(vlan->vid, &info);
@@ -1503,6 +1519,9 @@ static int rtl83xx_vlan_del(struct dsa_switch *ds, int port,
        pr_debug("%s: port %d, vid %d, flags %x\n",
                __func__, port, vlan->vid, vlan->flags);
 
+       /* Let no one mess with our special VLAN 0 */
+       if (!vlan->vid) return 0;
+
        if (vlan->vid > 4095) {
                dev_err(priv->dev, "VLAN out of range: %d", vlan->vid);
                return -ENOTSUPP;