[PATCH net-next v2 6/8] net: dsa: realtek: rtl8365mb: set/clear pvid for family D
From: Mieczyslaw Nalewaj
Date: Fri Oct 09 2026 - 01:13:01 EST
The RTL8367S-VB (family D) has no working VLAN member-config (MC)
table in hardware. rtl8365mb_vlan_port_get_pvid() was already fixed
to read PVID directly from its dedicated per-port register, but
rtl8365mb_vlan_pvid_port_set() and rtl8365mb_vlan_pvid_port_clear()
still went through the MC-index allocation path meant for family C,
which on family D hardware writes into the wrong register/field
(colliding with the neighbouring port's real PVID register) and reads
back a VLAN MC table that does not exist on this silicon.
Add a direct-VID fast path for both functions, mirroring what was
already done for the getter, and skip the MC table entirely for
family D.
Signed-off-by: Mieczyslaw Nalewaj <namiltd@xxxxxxxxx>
---
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 136 +++++++++++++++++++++++
1 file changed, 136 insertions(+)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 0466e8c..da517b1 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -810,6 +810,64 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
val);
}
+/*
+ * rtl8365mb_vlan_pvid_port_set_direct() - Configure a port's PVID as a raw
+ * VID written to its dedicated register, for chip families without a
+ * working VLAN MC table (RTL8365MB_FAMILY_D)
+ *
+ * Reads back the previous PVID and frame filter first so both can be
+ * restored if enabling the new PVID fails partway through, matching
+ * the rollback behavior of the family-C implementation above.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rtl8365mb_vlan_pvid_port_set_direct(struct realtek_priv *priv,
+ int port, u16 vid)
+{
+ enum rtl8365mb_frame_ingress prev_accepted_frame;
+ u32 prev_pvid;
+ int ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ &prev_pvid);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read current PVID\n");
+ return ret;
+ }
+ prev_pvid &= RTL8365MB_D_VLAN_PVID_CTRL_MASK;
+
+ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, &prev_accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get current framefilter\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_D_VLAN_PVID_CTRL_MASK,
+ vid & RTL8365MB_D_VLAN_PVID_CTRL_MASK);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port PVID\n");
+ return ret;
+ }
+
+ /* Changing accept frame is what enables PVID (if not enabled before) */
+ ret = rtl8365mb_vlan_port_set_framefilter(priv, port,
+ RTL8365MB_FRAME_TYPE_ANY_FRAME);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port frame filter\n");
+ goto undo_pvid_write;
+ }
+
+ return 0;
+
+undo_pvid_write:
+ (void)regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_D_VLAN_PVID_CTRL_MASK, prev_pvid);
+ (void)rtl8365mb_vlan_port_set_framefilter(priv, port, prev_accepted_frame);
+ return ret;
+}
+
/*
* rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated
* VLANMC entry
@@ -839,6 +897,13 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
lockdep_assert_held(&priv->vlan_lock);
+ /* This chip family has no VLAN MC table - PVID is a raw VID in a
+ * dedicated per-port register, and there is no separate membership
+ * table entry to allocate/track.
+ */
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ return rtl8365mb_vlan_pvid_port_set_direct(priv, port, vid);
+
/* Read the old PVID exclusively to undo in case of error */
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx,
&prev_vlanmc);
@@ -918,6 +983,74 @@ undo_vlan_mc_port_set:
return ret;
}
+/*
+ * rtl8365mb_vlan_pvid_port_clear_direct() - Remove a port's raw-VID PVID
+ * configuration, for chip families without a working VLAN MC table
+ * (RTL8365MB_FAMILY_D)
+ *
+ * Reads back the previous frame filter first so it can be restored if
+ * clearing the PVID register fails.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int rtl8365mb_vlan_pvid_port_clear_direct(struct dsa_switch *ds,
+ int port, u16 vid)
+{
+ enum rtl8365mb_frame_ingress prev_accepted_frame;
+ struct realtek_priv *priv = ds->priv;
+ bool filtering;
+ u32 cur_pvid;
+ int ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ &cur_pvid);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read current PVID\n");
+ return ret;
+ }
+
+ /* Port is not using this VID as PVID. Nothing to remove. */
+ if ((cur_pvid & RTL8365MB_D_VLAN_PVID_CTRL_MASK) != vid)
+ return 0;
+
+ filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, port));
+
+ /* Changing accept frame is what really removes PVID. But only do
+ * that if VLAN filtering is enabled.
+ */
+ if (filtering) {
+ ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
+ &prev_accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get current framefilter\n");
+ return ret;
+ }
+
+ ret = rtl8365mb_vlan_port_set_framefilter(
+ priv, port, RTL8365MB_FRAME_TYPE_TAGGED_ONLY);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port frame filter\n");
+ return ret;
+ }
+ }
+
+ ret = regmap_update_bits(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_D_VLAN_PVID_CTRL_MASK, 0);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port PVID to 0\n");
+ goto undo_set_framefilter;
+ }
+
+ return 0;
+
+undo_set_framefilter:
+ if (filtering)
+ (void)rtl8365mb_vlan_port_set_framefilter(priv, port,
+ prev_accepted_frame);
+ return ret;
+}
+
/*
* rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration
* @ds: dsa switch instance
@@ -941,6 +1074,9 @@ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
lockdep_assert_held(&priv->vlan_lock);
+ if (rtl8365mb_get_family(priv) == RTL8365MB_FAMILY_D)
+ return rtl8365mb_vlan_pvid_port_clear_direct(ds, port, vid);
+
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx,
&vlanmc);
if (ret) {
--
2.53.0