From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
To: "Andrew Lunn" <andrew@lunn.ch>,
"Vladimir Oltean" <olteanv@gmail.com>,
"David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@google.com>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Simon Horman" <horms@kernel.org>,
"Linus Walleij" <linusw@kernel.org>,
"Alvin Šipraga" <alsi@bang-olufsen.dk>,
"Yury Norov" <yury.norov@gmail.com>,
"Rasmus Villemoes" <linux@rasmusvillemoes.dk>,
"Russell King" <linux@armlinux.org.uk>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
Mieczyslaw Nalewaj <namiltd@yahoo.com>,
Luiz Angelo Daros de Luca <luizluca@gmail.com>
Subject: [net-next PATCH v13 8/9] net: dsa: realtek: rtl8365mb: add port_bridge_{join,leave}
Date: Sat, 06 Jun 2026 05:29:32 -0300 [thread overview]
Message-ID: <20260606-realtek_forward-v13-8-b9e409687cbe@gmail.com> (raw)
In-Reply-To: <20260606-realtek_forward-v13-0-b9e409687cbe@gmail.com>
From: Alvin Šipraga <alsi@bang-olufsen.dk>
Implement hardware offloading of bridge functionality. This is achieved
by using the per-port isolation registers, which contain a forwarding
port mask. The switch will refuse to forward packets ingressed on a
given port to a port which is not in its forwarding mask.
For each bridge that is offloaded, use the DSA-provided bridge number
for the Extended Filtering ID (EFID). When using Independent VLAN
Learning (IVL), the forwarding database is keyed with the tuple
{VID, MAC, EFID}. There are 8 EFIDs available (0~7), but we reserve the
default EFID 0 for standalone ports where learning is disabled. This
fits nicely because DSA indexes the bridge number starting from 1.
Because of the limited number of EFIDs, we have to set the
max_num_bridges property of our switch to 7: we can't offload more than
that or we will fail to offer IVL as at least two bridges would end up
having to share an EFID.
All ports start isolated, forwarding exclusively to CPU ports, and
with VLAN transparent, ignoring VLAN membership. Once a member in a
bridge, the port isolation is expanded to include the bridge members.
When that bridge enables VLAN filtering, the VLAN transparent feature is
disabled, letting the switch filter based on VLAN setup.
Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Co-developed-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
---
drivers/net/dsa/realtek/realtek.h | 7 ++
drivers/net/dsa/realtek/rtl8365mb_main.c | 47 ++++++++-
drivers/net/dsa/realtek/rtl83xx.c | 169 +++++++++++++++++++++++++++++++
drivers/net/dsa/realtek/rtl83xx.h | 7 ++
4 files changed, 229 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h
index 0f70ce185174..a6863f757c50 100644
--- a/drivers/net/dsa/realtek/realtek.h
+++ b/drivers/net/dsa/realtek/realtek.h
@@ -127,6 +127,13 @@ struct realtek_ops {
int (*enable_vlan)(struct realtek_priv *priv, bool enable);
int (*enable_vlan4k)(struct realtek_priv *priv, bool enable);
int (*enable_port)(struct realtek_priv *priv, int port, bool enable);
+ int (*port_add_isolation)(struct realtek_priv *priv, int port,
+ u32 mask);
+ int (*port_remove_isolation)(struct realtek_priv *priv, int port,
+ u32 mask);
+ int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid);
+ int (*port_set_learning)(struct realtek_priv *priv, int port,
+ bool enable);
int (*l2_add_uc)(struct realtek_priv *priv, int port,
const unsigned char addr[ETH_ALEN],
u16 efid, u16 vid);
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index dd8c0c2f12ba..683836328899 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -1568,10 +1568,44 @@ static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port,
enable ? RTL8365MB_LEARN_LIMIT_MAX : 0);
}
+static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port,
+ u32 efid)
+{
+ return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port),
+ RTL8365MB_PORT_EFID_MASK(port),
+ efid << RTL8365MB_PORT_EFID_OFFSET(port));
+}
+
+/* Port isolation manipulation functions.
+ *
+ * The port isolation register controls the forwarding mask of a given
+ * port. The switch will not forward packets ingressed on a given port
+ * to ports which are not enabled in its forwarding mask.
+ *
+ * The port forwarding mask has the highest priority in forwarding
+ * decisions. The only exception to this rule is when the switch
+ * receives a packet on its CPU port with ALLOW=0. In that case the TX
+ * field of the CPU tag will override the forwarding port mask.
+ */
static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port,
u32 mask)
{
- return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask);
+ return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port),
+ mask);
+}
+
+static int rtl8365mb_port_add_isolation(struct realtek_priv *priv, int port,
+ u32 mask)
+{
+ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port),
+ mask, mask);
+}
+
+static int rtl8365mb_port_remove_isolation(struct realtek_priv *priv, int port,
+ u32 mask)
+{
+ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port),
+ mask, 0);
}
static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port,
@@ -2378,6 +2412,11 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
if (ret)
goto out_teardown_irq;
+ /* Set the default EFID 0 for standalone mode */
+ ret = rtl8365mb_port_set_efid(priv, dp->index, 0);
+ if (ret)
+ goto out_teardown_irq;
+
/* Disable learning */
ret = rtl8365mb_port_set_learning(priv, dp->index, false);
if (ret)
@@ -2567,6 +2606,8 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = {
.setup = rtl8365mb_setup,
.teardown = rtl8365mb_teardown,
.phylink_get_caps = rtl8365mb_phylink_get_caps,
+ .port_bridge_join = rtl83xx_port_bridge_join,
+ .port_bridge_leave = rtl83xx_port_bridge_leave,
.port_stp_state_set = rtl8365mb_port_stp_state_set,
.port_fast_age = rtl83xx_port_fast_age,
.port_fdb_add = rtl83xx_port_fdb_add,
@@ -2592,6 +2633,10 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = {
static const struct realtek_ops rtl8365mb_ops = {
.detect = rtl8365mb_detect,
+ .port_add_isolation = rtl8365mb_port_add_isolation,
+ .port_remove_isolation = rtl8365mb_port_remove_isolation,
+ .port_set_efid = rtl8365mb_port_set_efid,
+ .port_set_learning = rtl8365mb_port_set_learning,
.l2_add_uc = rtl8365mb_l2_add_uc,
.l2_del_uc = rtl8365mb_l2_del_uc,
.l2_get_next_uc = rtl8365mb_l2_get_next_uc,
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index 7a9a2363d81f..61921f914a57 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -328,6 +328,175 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv)
gpiod_set_value(priv->reset, false);
}
+/**
+ * rtl83xx_port_bridge_join() - join a port to a bridge
+ * @ds: DSA switch instance
+ * @port: port index
+ * @bridge: bridge being joined
+ * @tx_forward_offload: if the switch can offload TX forwarding
+ * @extack: netlink extended ack for reporting errors
+ *
+ * This function handles joining a port to a bridge. It updates the port
+ * isolation masks and EFID.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_forward_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct dsa_port *dp;
+ u32 mask = 0;
+ int ret;
+
+ if (!priv->ops->port_add_isolation)
+ return -EOPNOTSUPP;
+
+ if (!priv->ops->port_set_learning)
+ return -EOPNOTSUPP;
+
+ dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port);
+
+ /* Add this port to the isolation group of every other port
+ * offloading this bridge.
+ */
+ dsa_switch_for_each_user_port(dp, ds) {
+ /* Handle this port after */
+ if (dp->index == port)
+ continue;
+
+ /* Skip ports that are not in this bridge */
+ if (!dsa_port_offloads_bridge(dp, &bridge))
+ continue;
+
+ ret = priv->ops->port_add_isolation(priv, dp->index, BIT(port));
+ if (ret)
+ goto undo_isolation;
+
+ mask |= BIT(dp->index);
+ }
+
+ /* If we support cascade switches, it should also include the
+ * downstream DSA ports to the isolation group.
+ */
+
+ /* Add those ports to the isolation group of this port */
+ ret = priv->ops->port_add_isolation(priv, port, mask);
+ if (ret)
+ goto undo_isolation;
+
+ /* Use the bridge number as the EFID for this port */
+ if (priv->ops->port_set_efid) {
+ ret = priv->ops->port_set_efid(priv, port, bridge.num);
+ if (ret)
+ goto undo_self_isolation;
+ }
+
+ ret = priv->ops->port_set_learning(priv, port, true);
+ if (ret)
+ goto undo_efid;
+
+ return 0;
+
+undo_efid:
+ if (priv->ops->port_set_efid)
+ priv->ops->port_set_efid(priv, port, 0);
+
+undo_self_isolation:
+ priv->ops->port_remove_isolation(priv, port, mask);
+
+undo_isolation:
+ dsa_switch_for_each_port(dp, ds) {
+ if (mask & BIT(dp->index))
+ priv->ops->port_remove_isolation(priv, dp->index,
+ BIT(port));
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_bridge_leave() - leave a bridge
+ * @ds: DSA switch instance
+ * @port: port index
+ * @bridge: bridge being left
+ *
+ * This function handles removing a port from a bridge. It updates the port
+ * isolation masks and EFID.
+ *
+ * Context: Can sleep.
+ * Return: nothing
+ */
+void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct dsa_port *dp;
+ u32 mask = 0;
+ int ret;
+
+ if (!priv->ops->port_remove_isolation)
+ return;
+
+ if (!priv->ops->port_set_learning)
+ return;
+
+ dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port);
+
+ /* Remove this port from the isolation group of every other
+ * port offloading this bridge.
+ */
+ dsa_switch_for_each_user_port(dp, ds) {
+ /* Handle this port after */
+ if (dp->index == port)
+ continue;
+
+ /* Skip ports that are not in this bridge */
+ if (!dsa_port_offloads_bridge(dp, &bridge))
+ continue;
+
+ ret = priv->ops->port_remove_isolation(priv, dp->index,
+ BIT(port));
+ if (ret)
+ dev_err(priv->dev,
+ "failed to isolate port %d from port %d: %pe\n",
+ port, dp->index, ERR_PTR(ret));
+
+ mask |= BIT(dp->index);
+ }
+
+ /* If we support cascade switches, it should also exclude the
+ * downstream DSA ports from the isolation group.
+ */
+
+ ret = priv->ops->port_set_learning(priv, port, false);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to disable learning on port %d: %pe\n",
+ port, ERR_PTR(ret));
+
+ /* Remove those ports from the isolation group of this port */
+ ret = priv->ops->port_remove_isolation(priv, port, mask);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to remove isolation mask from port %d: %pe\n",
+ port, ERR_PTR(ret));
+
+ /* Revert to the default EFID 0 for standalone mode */
+ if (priv->ops->port_set_efid) {
+ ret = priv->ops->port_set_efid(priv, port, 0);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to clear EFID on port %d: %pe\n",
+ port, ERR_PTR(ret));
+ }
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA");
+
/**
* rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port
* @ds: DSA switch instance
diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rtl83xx.h
index 6c1cfeea4b6b..dcb819fe567f 100644
--- a/drivers/net/dsa/realtek/rtl83xx.h
+++ b/drivers/net/dsa/realtek/rtl83xx.h
@@ -21,6 +21,13 @@ void rtl83xx_remove(struct realtek_priv *priv);
void rtl83xx_reset_assert(struct realtek_priv *priv);
void rtl83xx_reset_deassert(struct realtek_priv *priv);
+int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_forward_offload,
+ struct netlink_ext_ack *extack);
+void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge);
+
void rtl83xx_port_fast_age(struct dsa_switch *ds, int port);
int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
--
2.54.0
next prev parent reply other threads:[~2026-06-06 8:30 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-06 8:29 [net-next PATCH v13 0/9] net: dsa: realtek: rtl8365mb: bridge offloading and VLAN support Luiz Angelo Daros de Luca
2026-06-06 8:29 ` [net-next PATCH v13 1/9] net: dsa: realtek: rtl8365mb: use ERR_PTR Luiz Angelo Daros de Luca
2026-06-06 8:29 ` [net-next PATCH v13 2/9] net: dsa: realtek: rtl8365mb: reject unsupported topologies Luiz Angelo Daros de Luca
2026-06-07 12:55 ` Mieczyslaw Nalewaj
2026-06-07 17:28 ` Andrew Lunn
2026-06-08 2:41 ` Luiz Angelo Daros de Luca
2026-06-10 2:35 ` Jakub Kicinski
2026-06-06 8:29 ` [net-next PATCH v13 3/9] net: dsa: realtek: rtl8365mb: use dsa helpers for port iteration Luiz Angelo Daros de Luca
2026-06-06 8:29 ` [net-next PATCH v13 4/9] net: dsa: realtek: rtl8365mb: prepare for multiple source files Luiz Angelo Daros de Luca
2026-06-06 8:29 ` [net-next PATCH v13 5/9] net: dsa: realtek: rtl8365mb: add table lookup interface Luiz Angelo Daros de Luca
2026-06-06 8:29 ` [net-next PATCH v13 6/9] net: dsa: realtek: rtl8365mb: add VLAN support Luiz Angelo Daros de Luca
2026-06-06 8:29 ` [net-next PATCH v13 7/9] net: dsa: realtek: rtl8365mb: add FDB support Luiz Angelo Daros de Luca
2026-06-06 8:29 ` Luiz Angelo Daros de Luca [this message]
2026-06-06 8:29 ` [net-next PATCH v13 9/9] net: dsa: realtek: rtl8365mb: add bridge port flags Luiz Angelo Daros de Luca
2026-06-08 3:43 ` [net-next PATCH v13 0/9] net: dsa: realtek: rtl8365mb: bridge offloading and VLAN support Luiz Angelo Daros de Luca
2026-06-08 7:41 ` Linus Walleij
2026-06-10 2:40 ` patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260606-realtek_forward-v13-8-b9e409687cbe@gmail.com \
--to=luizluca@gmail.com \
--cc=alsi@bang-olufsen.dk \
--cc=andrew@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=linux@rasmusvillemoes.dk \
--cc=namiltd@yahoo.com \
--cc=netdev@vger.kernel.org \
--cc=olteanv@gmail.com \
--cc=pabeni@redhat.com \
--cc=yury.norov@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox