kernel: backport rtl8365mb DSA driver improvements
Backport several fixes and enhancements for the rtl8365mb DSA driver from upstream. The backport includes the following changes: - Fix for rtl8_4 tag (from v7.1) - VLAN and Forwarding offload (from v7.2) Before this backport, all bridge forwarding was handled by the CPU, causing a significant performance drop between LAN ports, similar to standard WAN/LAN routing. Enabling hardware forwarding offload alleviates CPU overhead and restores line-rate switching performance. Link: https://patch.msgid.link/20260408-realtek_fixes-v1-0-915ff1404d56@gmail.com Link: https://patch.msgid.link/20260606-realtek_forward-v13-0-b9e409687cbe@gmail.com Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com> Link: https://github.com/openwrt/openwrt/pull/23738 Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
committed by
Robert Marko
parent
4fa4766986
commit
0bba5f31fc
+70
@@ -0,0 +1,70 @@
|
||||
From 297e1f411ed4927a912c7e207ba6f978cb1f9f0e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= <alsi@bang-olufsen.dk>
|
||||
Date: Wed, 8 Apr 2026 17:31:01 -0300
|
||||
Subject: [PATCH 1/2] net: dsa: tag_rtl8_4: update format description
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Document the updated tag layout fields (EFID, VSEL/VIDX) and clarify
|
||||
which bits are set/cleared when emitting tags.
|
||||
|
||||
Co-developed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Link: https://patch.msgid.link/20260408-realtek_fixes-v1-1-915ff1404d56@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
net/dsa/tag_rtl8_4.c | 17 ++++++++++-------
|
||||
1 file changed, 10 insertions(+), 7 deletions(-)
|
||||
|
||||
--- a/net/dsa/tag_rtl8_4.c
|
||||
+++ b/net/dsa/tag_rtl8_4.c
|
||||
@@ -17,8 +17,8 @@
|
||||
* | (8-bit) | (8-bit) |
|
||||
* | Protocol [0x04] | REASON | b
|
||||
* |-----------------------------------+-----------------------------------| y
|
||||
- * | (1) | (1) | (2) | (1) | (3) | (1) | (1) | (1) | (5) | t
|
||||
- * | FID_EN | X | FID | PRI_EN | PRI | KEEP | X | LEARN_DIS | X | e
|
||||
+ * | (1) | (3) | (1) | (3) | (1) | (1) | (1) | (5) | t
|
||||
+ * | EFID_EN | EFID | PRI_EN | PRI | KEEP | VSEL | LEARN_DIS | VIDX | e
|
||||
* |-----------------------------------+-----------------------------------| s
|
||||
* | (1) | (15-bit) | |
|
||||
* | ALLOW | TX/RX | v
|
||||
@@ -32,19 +32,22 @@
|
||||
* EtherType | note that Realtek uses the same EtherType for
|
||||
* | other incompatible tag formats (e.g. tag_rtl4_a.c)
|
||||
* Protocol | 0x04: indicates that this tag conforms to this format
|
||||
- * X | reserved
|
||||
* ------------+-------------
|
||||
* REASON | reason for forwarding packet to CPU
|
||||
* | 0: packet was forwarded or flooded to CPU
|
||||
* | 80: packet was trapped to CPU
|
||||
- * FID_EN | 1: packet has an FID
|
||||
- * | 0: no FID
|
||||
- * FID | FID of packet (if FID_EN=1)
|
||||
+ * EFID_EN | 1: packet has an EFID
|
||||
+ * | 0: no EFID
|
||||
+ * EFID | Extended filter ID (EFID) of packet (if EFID_EN=1)
|
||||
* PRI_EN | 1: force priority of packet
|
||||
* | 0: don't force priority
|
||||
* PRI | priority of packet (if PRI_EN=1)
|
||||
* KEEP | preserve packet VLAN tag format
|
||||
+ * VSEL | 0: switch should classify packet according to VLAN tag
|
||||
+ * | 1: switch should classify packet according to VLAN membership
|
||||
+ * | configuration with index VIDX
|
||||
* LEARN_DIS | don't learn the source MAC address of the packet
|
||||
+ * VIDX | index of a VLAN membership configuration to use with VSEL
|
||||
* ALLOW | 1: treat TX/RX field as an allowance port mask, meaning the
|
||||
* | packet may only be forwarded to ports specified in the
|
||||
* | mask
|
||||
@@ -112,7 +115,7 @@ static void rtl8_4_write_tag(struct sk_b
|
||||
/* Set Protocol; zero REASON */
|
||||
tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB));
|
||||
|
||||
- /* Zero FID_EN, FID, PRI_EN, PRI, KEEP; set LEARN_DIS */
|
||||
+ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */
|
||||
tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1));
|
||||
|
||||
/* Zero ALLOW; set RX (CPU->switch) forwarding port mask */
|
||||
@@ -0,0 +1,48 @@
|
||||
From 82f37bd9a4d779495479c0c13152208d5400c8a4 Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Wed, 8 Apr 2026 17:31:02 -0300
|
||||
Subject: [PATCH 2/2] net: dsa: tag_rtl8_4: set KEEP flag
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
KEEP=1 is needed because we should respect the format of the packet as
|
||||
the kernel sends it to us. Unless tx forward offloading is used, the
|
||||
kernel is giving us the packet exactly as it should leave the specified
|
||||
port on the wire. Until now this was not needed because the ports were
|
||||
always functioning in a standalone mode in a VLAN-unaware way, so the
|
||||
switch would not tag or untag frames anyway. But arguably it should have
|
||||
been KEEP=1 all along.
|
||||
|
||||
Co-developed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Link: https://patch.msgid.link/20260408-realtek_fixes-v1-2-915ff1404d56@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
net/dsa/tag_rtl8_4.c | 6 ++++--
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/net/dsa/tag_rtl8_4.c
|
||||
+++ b/net/dsa/tag_rtl8_4.c
|
||||
@@ -99,6 +99,7 @@
|
||||
#define RTL8_4_REASON_TRAP 80
|
||||
|
||||
#define RTL8_4_LEARN_DIS BIT(5)
|
||||
+#define RTL8_4_KEEP BIT(7)
|
||||
|
||||
#define RTL8_4_TX GENMASK(3, 0)
|
||||
#define RTL8_4_RX GENMASK(10, 0)
|
||||
@@ -115,8 +116,9 @@ static void rtl8_4_write_tag(struct sk_b
|
||||
/* Set Protocol; zero REASON */
|
||||
tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB));
|
||||
|
||||
- /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */
|
||||
- tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1));
|
||||
+ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX; set KEEP, LEARN_DIS */
|
||||
+ tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1) |
|
||||
+ FIELD_PREP(RTL8_4_KEEP, 1));
|
||||
|
||||
/* Zero ALLOW; set RX (CPU->switch) forwarding port mask */
|
||||
tag16[3] = htons(FIELD_PREP(RTL8_4_RX, BIT(dp->index)));
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
From a543687227d86b1f4401f395abaf7f3f710fe964 Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:25 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: use ERR_PTR
|
||||
|
||||
Convert numeric error codes into human-readable strings by using %pe
|
||||
together with ERR_PTR() in dev_err() messages. Also use dev_err_probe()
|
||||
instead of checking for -EPROBE_DEFER.
|
||||
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-1-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/rtl8365mb.c | 51 +++++++++++++++++++++----------------
|
||||
1 file changed, 29 insertions(+), 22 deletions(-)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
@@ -803,8 +803,8 @@ static int rtl8365mb_phy_read(struct rea
|
||||
ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val);
|
||||
if (ret) {
|
||||
dev_err(priv->dev,
|
||||
- "failed to read PHY%d reg %02x @ %04x, ret %d\n", phy,
|
||||
- regnum, ocp_addr, ret);
|
||||
+ "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy,
|
||||
+ regnum, ocp_addr, ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -831,8 +831,8 @@ static int rtl8365mb_phy_write(struct re
|
||||
ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val);
|
||||
if (ret) {
|
||||
dev_err(priv->dev,
|
||||
- "failed to write PHY%d reg %02x @ %04x, ret %d\n", phy,
|
||||
- regnum, ocp_addr, ret);
|
||||
+ "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy,
|
||||
+ regnum, ocp_addr, ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1082,8 +1082,8 @@ static void rtl8365mb_phylink_mac_config
|
||||
ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface);
|
||||
if (ret)
|
||||
dev_err(priv->dev,
|
||||
- "failed to configure RGMII mode on port %d: %d\n",
|
||||
- port, ret);
|
||||
+ "failed to configure RGMII mode on port %d: %pe\n",
|
||||
+ port, ERR_PTR(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1112,8 +1112,8 @@ static void rtl8365mb_phylink_mac_link_d
|
||||
false, false);
|
||||
if (ret)
|
||||
dev_err(priv->dev,
|
||||
- "failed to reset forced mode on port %d: %d\n",
|
||||
- port, ret);
|
||||
+ "failed to reset forced mode on port %d: %pe\n",
|
||||
+ port, ERR_PTR(ret));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1143,8 +1143,8 @@ static void rtl8365mb_phylink_mac_link_u
|
||||
rx_pause);
|
||||
if (ret)
|
||||
dev_err(priv->dev,
|
||||
- "failed to force mode on port %d: %d\n", port,
|
||||
- ret);
|
||||
+ "failed to force mode on port %d: %pe\n", port,
|
||||
+ ERR_PTR(ret));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1299,8 +1299,8 @@ static void rtl8365mb_get_ethtool_stats(
|
||||
mib->length, &data[i]);
|
||||
if (ret) {
|
||||
dev_err(priv->dev,
|
||||
- "failed to read port %d counters: %d\n", port,
|
||||
- ret);
|
||||
+ "failed to read port %d counters: %pe\n", port,
|
||||
+ ERR_PTR(ret));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1652,7 +1652,8 @@ static irqreturn_t rtl8365mb_irq(int irq
|
||||
return IRQ_HANDLED;
|
||||
|
||||
out_error:
|
||||
- dev_err(priv->dev, "failed to read interrupt status: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to read interrupt status: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
|
||||
out_none:
|
||||
return IRQ_NONE;
|
||||
@@ -1725,10 +1726,13 @@ static int rtl8365mb_irq_setup(struct re
|
||||
/* rtl8365mb IRQs cascade off this one */
|
||||
irq = of_irq_get(intc, 0);
|
||||
if (irq <= 0) {
|
||||
- if (irq != -EPROBE_DEFER)
|
||||
- dev_err(priv->dev, "failed to get parent irq: %d\n",
|
||||
- irq);
|
||||
- ret = irq ? irq : -EINVAL;
|
||||
+ if (!irq) {
|
||||
+ dev_err(priv->dev, "failed to map IRQ\n");
|
||||
+ ret = -EINVAL;
|
||||
+ } else {
|
||||
+ ret = dev_err_probe(priv->dev, irq,
|
||||
+ "failed to get parent irq\n");
|
||||
+ }
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
@@ -1790,7 +1794,8 @@ static int rtl8365mb_irq_setup(struct re
|
||||
ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT,
|
||||
"rtl8365mb", priv);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to request irq: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to request irq: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
goto out_remove_irqdomain;
|
||||
}
|
||||
|
||||
@@ -1966,14 +1971,16 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
|
||||
ret = rtl8365mb_reset_chip(priv);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to reset chip: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to reset chip: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
goto out_error;
|
||||
}
|
||||
|
||||
/* Configure switch to vendor-defined initial state */
|
||||
ret = rtl8365mb_switch_init(priv);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to initialize switch: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to initialize switch: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
goto out_error;
|
||||
}
|
||||
|
||||
@@ -2091,8 +2098,8 @@ static int rtl8365mb_detect(struct realt
|
||||
|
||||
ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to read chip id and version: %d\n",
|
||||
- ret);
|
||||
+ dev_err(priv->dev, "failed to read chip id and version: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
From 36c572fd60d6bf2f9631088833c162203907c9ca Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:26 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: reject unsupported topologies
|
||||
|
||||
Explicitly enforce the presence of a CPU port (-EINVAL) and reject DSA
|
||||
cascade links (-EOPNOTSUPP) during setup to prevent silent failures.
|
||||
|
||||
These topologies were already non-functional. Without a CPU port, the
|
||||
driver does not activate CPU tagging. Additionally, the switch hardware
|
||||
was not designed to be cascaded, and DSA links never worked because
|
||||
CPU tagging is not enabled for them.
|
||||
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-2-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/rtl8365mb.c | 21 +++++++++++++++++++++
|
||||
1 file changed, 21 insertions(+)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
@@ -1991,6 +1991,20 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
else if (ret)
|
||||
dev_info(priv->dev, "no interrupt support\n");
|
||||
|
||||
+ for (i = 0; i < priv->num_ports; i++) {
|
||||
+ /* Cascading (DSA links) is not supported yet.
|
||||
+ * Historically, the driver has always been broken
|
||||
+ * without a dedicated CPU port because CPU tagging
|
||||
+ * would be disabled, rendering the switch entirely
|
||||
+ * non-functional for DSA operations.
|
||||
+ */
|
||||
+ if (dsa_is_dsa_port(ds, i)) {
|
||||
+ dev_err(priv->dev, "Cascading (DSA link) not supported\n");
|
||||
+ ret = -EOPNOTSUPP;
|
||||
+ goto out_teardown_irq;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/* Configure CPU tagging */
|
||||
dsa_switch_for_each_cpu_port(cpu_dp, ds) {
|
||||
cpu->mask |= BIT(cpu_dp->index);
|
||||
@@ -1999,6 +2013,13 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
cpu->trap_port = cpu_dp->index;
|
||||
}
|
||||
cpu->enable = cpu->mask > 0;
|
||||
+
|
||||
+ if (!cpu->enable) {
|
||||
+ dev_err(priv->dev, "no CPU port defined\n");
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_teardown_irq;
|
||||
+ }
|
||||
+
|
||||
ret = rtl8365mb_cpu_config(priv);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
From 51a4a22301b023610a1321614e8dc61251ef9064 Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:27 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: use dsa helpers for port iteration
|
||||
|
||||
Convert open-coded port iteration loops to use the DSA helpers and
|
||||
restructure rtl8365mb_setup() into clear blocking, user, and
|
||||
CPU port phases.
|
||||
|
||||
As part of this refactoring, unused ports are explicitly placed into a
|
||||
blocked, isolated state with learning disabled, ensuring safe default
|
||||
hardware behavior. The driver also does not allocate a virtual IRQ
|
||||
mapping for unused ports. To accommodate this, a guard check is added to
|
||||
the interrupt handler (rtl8365mb_irq) to safely skip ports without a
|
||||
valid IRQ mapping. The irq domain teardown, however, does clean all
|
||||
ports as external PHYs may still map the IRQ.
|
||||
|
||||
Furthermore, since the new initialization loop starts with all ports
|
||||
administratively isolated by default, CPU port forwarding and isolation
|
||||
masks are explicitly configured at the end of the setup phase to prevent
|
||||
egress traffic from being blocked.
|
||||
|
||||
Suggested-by: Abdulkader Alrezej <abdulkader.alrezej@gmail.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-3-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/rtl8365mb.c | 166 +++++++++++++++++++++++-------------
|
||||
1 file changed, 105 insertions(+), 61 deletions(-)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
@@ -1554,18 +1554,15 @@ static void rtl8365mb_stats_setup(struct
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
struct dsa_switch *ds = &priv->ds;
|
||||
- int i;
|
||||
+ struct dsa_port *dp;
|
||||
|
||||
/* Per-chip global mutex to protect MIB counter access, since doing
|
||||
* so requires accessing a series of registers in a particular order.
|
||||
*/
|
||||
mutex_init(&mb->mib_lock);
|
||||
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- struct rtl8365mb_port *p = &mb->ports[i];
|
||||
-
|
||||
- if (dsa_is_unused_port(ds, i))
|
||||
- continue;
|
||||
+ dsa_switch_for_each_available_port(dp, ds) {
|
||||
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
|
||||
|
||||
/* Per-port spinlock to protect the stats64 data */
|
||||
spin_lock_init(&p->stats_lock);
|
||||
@@ -1581,13 +1578,10 @@ static void rtl8365mb_stats_teardown(str
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
struct dsa_switch *ds = &priv->ds;
|
||||
- int i;
|
||||
-
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- struct rtl8365mb_port *p = &mb->ports[i];
|
||||
+ struct dsa_port *dp;
|
||||
|
||||
- if (dsa_is_unused_port(ds, i))
|
||||
- continue;
|
||||
+ dsa_switch_for_each_available_port(dp, ds) {
|
||||
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
|
||||
|
||||
cancel_delayed_work_sync(&p->mib_work);
|
||||
}
|
||||
@@ -1646,6 +1640,9 @@ static irqreturn_t rtl8365mb_irq(int irq
|
||||
for_each_set_bit(line, &line_changes, priv->num_ports) {
|
||||
int child_irq = irq_find_mapping(priv->irqdomain, line);
|
||||
|
||||
+ if (!child_irq)
|
||||
+ continue;
|
||||
+
|
||||
handle_nested_irq(child_irq);
|
||||
}
|
||||
|
||||
@@ -1667,10 +1664,14 @@ static struct irq_chip rtl8365mb_irq_chi
|
||||
static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
- irq_set_chip_data(irq, domain->host_data);
|
||||
+ struct realtek_priv *priv = domain->host_data;
|
||||
+ struct rtl8365mb *mb = priv->chip_data;
|
||||
+
|
||||
+ irq_set_chip_data(irq, priv);
|
||||
irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq);
|
||||
irq_set_nested_thread(irq, 1);
|
||||
irq_set_noprobe(irq);
|
||||
+ irq_set_parent(irq, mb->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1709,13 +1710,14 @@ static int rtl8365mb_irq_disable(struct
|
||||
static int rtl8365mb_irq_setup(struct realtek_priv *priv)
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
+ struct dsa_switch *ds = &priv->ds;
|
||||
struct device_node *intc;
|
||||
+ struct dsa_port *dp;
|
||||
u32 irq_trig;
|
||||
int virq;
|
||||
int irq;
|
||||
u32 val;
|
||||
int ret;
|
||||
- int i;
|
||||
|
||||
intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller");
|
||||
if (!intc) {
|
||||
@@ -1736,6 +1738,9 @@ static int rtl8365mb_irq_setup(struct re
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
+ /* Store the irq so that we know to map and free it during teardown */
|
||||
+ mb->irq = irq;
|
||||
+
|
||||
priv->irqdomain = irq_domain_add_linear(intc, priv->num_ports,
|
||||
&rtl8365mb_irqdomain_ops, priv);
|
||||
if (!priv->irqdomain) {
|
||||
@@ -1744,8 +1749,8 @@ static int rtl8365mb_irq_setup(struct re
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- virq = irq_create_mapping(priv->irqdomain, i);
|
||||
+ dsa_switch_for_each_available_port(dp, ds) {
|
||||
+ virq = irq_create_mapping(priv->irqdomain, dp->index);
|
||||
if (!virq) {
|
||||
dev_err(priv->dev,
|
||||
"failed to create irq domain mapping\n");
|
||||
@@ -1799,9 +1804,6 @@ static int rtl8365mb_irq_setup(struct re
|
||||
goto out_remove_irqdomain;
|
||||
}
|
||||
|
||||
- /* Store the irq so that we know to free it during teardown */
|
||||
- mb->irq = irq;
|
||||
-
|
||||
ret = rtl8365mb_irq_enable(priv);
|
||||
if (ret)
|
||||
goto out_free_irq;
|
||||
@@ -1812,18 +1814,20 @@ static int rtl8365mb_irq_setup(struct re
|
||||
|
||||
out_free_irq:
|
||||
free_irq(mb->irq, priv);
|
||||
- mb->irq = 0;
|
||||
|
||||
out_remove_irqdomain:
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- virq = irq_find_mapping(priv->irqdomain, i);
|
||||
- irq_dispose_mapping(virq);
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ virq = irq_find_mapping(priv->irqdomain, dp->index);
|
||||
+
|
||||
+ if (virq)
|
||||
+ irq_dispose_mapping(virq);
|
||||
}
|
||||
|
||||
irq_domain_remove(priv->irqdomain);
|
||||
priv->irqdomain = NULL;
|
||||
|
||||
out_put_node:
|
||||
+ mb->irq = 0;
|
||||
of_node_put(intc);
|
||||
|
||||
return ret;
|
||||
@@ -1832,8 +1836,9 @@ out_put_node:
|
||||
static void rtl8365mb_irq_teardown(struct realtek_priv *priv)
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
+ struct dsa_switch *ds = &priv->ds;
|
||||
+ struct dsa_port *dp;
|
||||
int virq;
|
||||
- int i;
|
||||
|
||||
if (mb->irq) {
|
||||
free_irq(mb->irq, priv);
|
||||
@@ -1841,9 +1846,15 @@ static void rtl8365mb_irq_teardown(struc
|
||||
}
|
||||
|
||||
if (priv->irqdomain) {
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- virq = irq_find_mapping(priv->irqdomain, i);
|
||||
- irq_dispose_mapping(virq);
|
||||
+ /* Unused ports with a linked PHY still have an active IRQ
|
||||
+ * mapping that must be disposed of during teardown. Loop
|
||||
+ * through all ports.
|
||||
+ */
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ virq = irq_find_mapping(priv->irqdomain, dp->index);
|
||||
+
|
||||
+ if (virq)
|
||||
+ irq_dispose_mapping(virq);
|
||||
}
|
||||
|
||||
irq_domain_remove(priv->irqdomain);
|
||||
@@ -1961,10 +1972,11 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
{
|
||||
struct realtek_priv *priv = ds->priv;
|
||||
struct rtl8365mb_cpu *cpu;
|
||||
- struct dsa_port *cpu_dp;
|
||||
+ u32 downports_mask = 0;
|
||||
+ u32 upports_mask = 0;
|
||||
struct rtl8365mb *mb;
|
||||
+ struct dsa_port *dp;
|
||||
int ret;
|
||||
- int i;
|
||||
|
||||
mb = priv->chip_data;
|
||||
cpu = &mb->cpu;
|
||||
@@ -1991,67 +2003,99 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
else if (ret)
|
||||
dev_info(priv->dev, "no interrupt support\n");
|
||||
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
/* Cascading (DSA links) is not supported yet.
|
||||
* Historically, the driver has always been broken
|
||||
* without a dedicated CPU port because CPU tagging
|
||||
* would be disabled, rendering the switch entirely
|
||||
* non-functional for DSA operations.
|
||||
*/
|
||||
- if (dsa_is_dsa_port(ds, i)) {
|
||||
+ if (dsa_port_is_dsa(dp)) {
|
||||
dev_err(priv->dev, "Cascading (DSA link) not supported\n");
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out_teardown_irq;
|
||||
}
|
||||
}
|
||||
|
||||
- /* Configure CPU tagging */
|
||||
- dsa_switch_for_each_cpu_port(cpu_dp, ds) {
|
||||
- cpu->mask |= BIT(cpu_dp->index);
|
||||
+ /* Start with all ports blocked, including unused ports */
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
|
||||
|
||||
- if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS)
|
||||
- cpu->trap_port = cpu_dp->index;
|
||||
- }
|
||||
- cpu->enable = cpu->mask > 0;
|
||||
+ /* Set the initial STP state of all ports to DISABLED, otherwise
|
||||
+ * ports will still forward frames to the CPU despite being
|
||||
+ * administratively down by default.
|
||||
+ */
|
||||
+ rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED);
|
||||
|
||||
- if (!cpu->enable) {
|
||||
- dev_err(priv->dev, "no CPU port defined\n");
|
||||
- ret = -EINVAL;
|
||||
- goto out_teardown_irq;
|
||||
- }
|
||||
+ /* Start with all port completely isolated */
|
||||
+ ret = rtl8365mb_port_set_isolation(priv, dp->index, 0);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
|
||||
- ret = rtl8365mb_cpu_config(priv);
|
||||
- if (ret)
|
||||
- goto out_teardown_irq;
|
||||
+ /* Disable learning */
|
||||
+ ret = rtl8365mb_port_set_learning(priv, dp->index, false);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
+
|
||||
+ /* Set up per-port private data */
|
||||
+ p->priv = priv;
|
||||
+ p->index = dp->index;
|
||||
|
||||
- /* Configure ports */
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- struct rtl8365mb_port *p = &mb->ports[i];
|
||||
+ /* Collect CPU ports. If we support cascade switches, it should
|
||||
+ * also include the upstream DSA ports.
|
||||
+ */
|
||||
+ if (!dsa_port_is_cpu(dp))
|
||||
+ continue;
|
||||
+
|
||||
+ upports_mask |= BIT(dp->index);
|
||||
+ }
|
||||
|
||||
- if (dsa_is_unused_port(ds, i))
|
||||
+ /* Configure user ports */
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ if (!dsa_port_is_user(dp))
|
||||
continue;
|
||||
|
||||
/* Forward only to the CPU */
|
||||
- ret = rtl8365mb_port_set_isolation(priv, i, cpu->mask);
|
||||
+ ret = rtl8365mb_port_set_isolation(priv, dp->index,
|
||||
+ upports_mask);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
|
||||
- /* Disable learning */
|
||||
- ret = rtl8365mb_port_set_learning(priv, i, false);
|
||||
+ /* If we support cascade switches, it should also include the
|
||||
+ * downstream DSA ports.
|
||||
+ */
|
||||
+ downports_mask |= BIT(dp->index);
|
||||
+ }
|
||||
+
|
||||
+ /* Configure CPU tagging */
|
||||
+ /* If we support cascade switches, it should also include the upstream
|
||||
+ * DSA ports.
|
||||
+ */
|
||||
+ dsa_switch_for_each_cpu_port(dp, ds) {
|
||||
+ /* Use the first CPU port as trap_port */
|
||||
+ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS)
|
||||
+ cpu->trap_port = dp->index;
|
||||
+
|
||||
+ /* Forward to all user ports */
|
||||
+ ret = rtl8365mb_port_set_isolation(priv, dp->index,
|
||||
+ downports_mask);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
+ }
|
||||
|
||||
- /* Set the initial STP state of all ports to DISABLED, otherwise
|
||||
- * ports will still forward frames to the CPU despite being
|
||||
- * administratively down by default.
|
||||
- */
|
||||
- rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED);
|
||||
+ cpu->mask = upports_mask;
|
||||
+ cpu->enable = cpu->mask > 0;
|
||||
|
||||
- /* Set up per-port private data */
|
||||
- p->priv = priv;
|
||||
- p->index = i;
|
||||
+ if (!cpu->enable) {
|
||||
+ dev_err(priv->dev, "no CPU port defined\n");
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_teardown_irq;
|
||||
}
|
||||
|
||||
+ ret = rtl8365mb_cpu_config(priv);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
+
|
||||
ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
+4631
File diff suppressed because it is too large
Load Diff
+394
@@ -0,0 +1,394 @@
|
||||
From fbafdd3b224a03b7b335de144f44a600de937586 Mon Sep 17 00:00:00 2001
|
||||
From: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Date: Sat, 6 Jun 2026 05:29:29 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: add table lookup interface
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Add a generic table lookup interface to centralize access to
|
||||
the RTL8365MB internal tables.
|
||||
|
||||
This interface abstracts the low-level table access logic and
|
||||
will be used by subsequent commits to implement FDB and VLAN
|
||||
operations.
|
||||
|
||||
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>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-5-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/Makefile | 1 +
|
||||
drivers/net/dsa/realtek/rtl8365mb_table.c | 214 ++++++++++++++++++++++++++++++
|
||||
drivers/net/dsa/realtek/rtl8365mb_table.h | 138 +++++++++++++++++++
|
||||
3 files changed, 353 insertions(+)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/Makefile
|
||||
+++ b/drivers/net/dsa/realtek/Makefile
|
||||
@@ -17,4 +17,5 @@ rtl8366-objs += rtl8366rb-leds.o
|
||||
endif
|
||||
obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
|
||||
rtl8365mb-objs := rtl8365mb_main.o \
|
||||
+ rtl8365mb_table.o \
|
||||
# end of rtl8365mb-objs
|
||||
--- /dev/null
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.c
|
||||
@@ -0,0 +1,214 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0
|
||||
+/* Look-up table query interface for the rtl8365mb switch family
|
||||
+ *
|
||||
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
+ */
|
||||
+
|
||||
+#include "rtl8365mb_table.h"
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+/* Table access control register */
|
||||
+#define RTL8365MB_TABLE_CTRL_REG 0x0500
|
||||
+/* Should be one of rtl8365mb_table enum members */
|
||||
+#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0)
|
||||
+/* Should be one of rtl8365mb_table_op enum members */
|
||||
+#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3)
|
||||
+/* Should be one of rtl8365mb_table_l2_method enum members */
|
||||
+#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4)
|
||||
+#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8)
|
||||
+
|
||||
+/* Table access address register */
|
||||
+#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501
|
||||
+#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0)
|
||||
+
|
||||
+/* Table status register */
|
||||
+#define RTL8365MB_TABLE_STATUS_REG 0x0502
|
||||
+#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0)
|
||||
+/* set for L3, unset for L2 */
|
||||
+#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11)
|
||||
+#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12)
|
||||
+#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13)
|
||||
+#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14)
|
||||
+
|
||||
+/* Table read/write registers */
|
||||
+#define RTL8365MB_TABLE_WRITE_BASE 0x0510
|
||||
+#define RTL8365MB_TABLE_WRITE_REG(_x) \
|
||||
+ (RTL8365MB_TABLE_WRITE_BASE + (_x))
|
||||
+#define RTL8365MB_TABLE_READ_BASE 0x0520
|
||||
+#define RTL8365MB_TABLE_READ_REG(_x) \
|
||||
+ (RTL8365MB_TABLE_READ_BASE + (_x))
|
||||
+#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0)
|
||||
+#define RTL8365MB_TABLE_WRITE_10TH_REG \
|
||||
+ RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1)
|
||||
+
|
||||
+static int rtl8365mb_table_poll_busy(struct realtek_priv *priv)
|
||||
+{
|
||||
+ u32 val;
|
||||
+
|
||||
+ return regmap_read_poll_timeout(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_STATUS_REG, val,
|
||||
+ !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val),
|
||||
+ 10, 10000);
|
||||
+}
|
||||
+
|
||||
+int rtl8365mb_table_query(struct realtek_priv *priv,
|
||||
+ enum rtl8365mb_table table,
|
||||
+ enum rtl8365mb_table_op op, u16 *addr,
|
||||
+ enum rtl8365mb_table_l2_method method,
|
||||
+ u16 port, u16 *data, size_t size)
|
||||
+{
|
||||
+ bool addr_as_input = true;
|
||||
+ bool write_data = false;
|
||||
+ int ret = 0;
|
||||
+ u32 cmd;
|
||||
+ u32 val;
|
||||
+ u32 hit;
|
||||
+
|
||||
+ /* Prepare target table and operation (read or write) */
|
||||
+ cmd = 0;
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table);
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op);
|
||||
+ if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) {
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method);
|
||||
+ switch (method) {
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_MAC:
|
||||
+ /*
|
||||
+ * Method MAC requires as input the same L2 table format
|
||||
+ * you'll get as result. However, it might only use mac
|
||||
+ * address and FID/VID fields.
|
||||
+ */
|
||||
+ write_data = true;
|
||||
+
|
||||
+ /* METHOD_MAC does not use addr as input, but may return
|
||||
+ * the matched index.
|
||||
+ */
|
||||
+ addr_as_input = false;
|
||||
+
|
||||
+ break;
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR:
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT:
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC:
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC:
|
||||
+ break;
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT:
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port);
|
||||
+ break;
|
||||
+ default:
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+ } else if (op == RTL8365MB_TABLE_OP_WRITE) {
|
||||
+ write_data = true;
|
||||
+
|
||||
+ /* Writing to L2 does not use addr as input, as the table index
|
||||
+ * is derived from key fields.
|
||||
+ */
|
||||
+ if (table == RTL8365MB_TABLE_L2)
|
||||
+ addr_as_input = false;
|
||||
+ }
|
||||
+
|
||||
+ /* To prevent concurrent access to the look-up tables, take the regmap
|
||||
+ * lock manually and access via the map_nolock regmap.
|
||||
+ */
|
||||
+ mutex_lock(&priv->map_lock);
|
||||
+
|
||||
+ /* Protect from a busy table access (i.e. previous access timeouts) */
|
||||
+ ret = rtl8365mb_table_poll_busy(priv);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* Write entry data if writing to the table (or L2_METHOD_MAC) */
|
||||
+ if (write_data) {
|
||||
+ /* bulk write data up to 9th word */
|
||||
+ ret = regmap_bulk_write(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_WRITE_BASE,
|
||||
+ data,
|
||||
+ min_t(size_t, size,
|
||||
+ RTL8365MB_TABLE_ENTRY_MAX_SIZE -
|
||||
+ 1));
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* 10th register uses only 4 least significant bits */
|
||||
+ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
|
||||
+ val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK,
|
||||
+ data[size - 1]);
|
||||
+ ret = regmap_update_bits(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_WRITE_10TH_REG,
|
||||
+ RTL8365MB_TABLE_10TH_DATA_MASK,
|
||||
+ val);
|
||||
+ }
|
||||
+
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ /* Write address (if needed) */
|
||||
+ if (addr_as_input) {
|
||||
+ ret = regmap_write(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_ACCESS_ADDR_REG,
|
||||
+ FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK,
|
||||
+ *addr));
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ /* Execute */
|
||||
+ ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* Poll for completion */
|
||||
+ ret = rtl8365mb_table_poll_busy(priv);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* For both reads and writes to the L2 table, check status */
|
||||
+ if (table == RTL8365MB_TABLE_L2) {
|
||||
+ ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG,
|
||||
+ &val);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* Did the query find an entry? */
|
||||
+ hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val);
|
||||
+ if (!hit) {
|
||||
+ ret = -ENOENT;
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ /* If so, extract the address */
|
||||
+ *addr = 0;
|
||||
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val);
|
||||
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val)
|
||||
+ << 11;
|
||||
+ /* only set if it is a L3 address */
|
||||
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val)
|
||||
+ << 12;
|
||||
+ }
|
||||
+
|
||||
+ /* Finally, get the table entry if we were reading */
|
||||
+ if (op == RTL8365MB_TABLE_OP_READ) {
|
||||
+ ret = regmap_bulk_read(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_READ_BASE,
|
||||
+ data, size);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* For the biggest table entries, the uppermost table
|
||||
+ * entry register has space for only one nibble. Mask
|
||||
+ * out the remainder bits. Empirically I saw nothing
|
||||
+ * wrong with omitting this mask, but it may prevent
|
||||
+ * unwanted behaviour. FYI.
|
||||
+ */
|
||||
+ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
|
||||
+ val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK,
|
||||
+ data[size - 1]);
|
||||
+ data[size - 1] = val;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+out:
|
||||
+ mutex_unlock(&priv->map_lock);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
--- /dev/null
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.h
|
||||
@@ -0,0 +1,138 @@
|
||||
+/* SPDX-License-Identifier: GPL-2.0 */
|
||||
+/* Look-up table query interface for the rtl8365mb switch family
|
||||
+ *
|
||||
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
+ */
|
||||
+
|
||||
+#ifndef _REALTEK_RTL8365MB_TABLE_H
|
||||
+#define _REALTEK_RTL8365MB_TABLE_H
|
||||
+
|
||||
+#include <linux/if_ether.h>
|
||||
+#include <linux/types.h>
|
||||
+
|
||||
+#include "realtek.h"
|
||||
+
|
||||
+#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10
|
||||
+
|
||||
+/*
|
||||
+ * enum rtl8365mb_table - available switch tables
|
||||
+ * @RTL8365MB_TABLE_ACL_RULE: ACL rules
|
||||
+ * @RTL8365MB_TABLE_ACL_ACTION: ACL actions
|
||||
+ * @RTL8365MB_TABLE_CVLAN: VLAN4k configurations
|
||||
+ * @RTL8365MB_TABLE_L2: filtering database (2K hash table)
|
||||
+ * @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly)
|
||||
+ *
|
||||
+ * NOTE: Don't change the enum values. They must concur with the field
|
||||
+ * described by @RTL8365MB_TABLE_CTRL_TABLE_MASK.
|
||||
+ */
|
||||
+enum rtl8365mb_table {
|
||||
+ RTL8365MB_TABLE_ACL_RULE = 1,
|
||||
+ RTL8365MB_TABLE_ACL_ACTION = 2,
|
||||
+ RTL8365MB_TABLE_CVLAN = 3,
|
||||
+ RTL8365MB_TABLE_L2 = 4,
|
||||
+ RTL8365MB_TABLE_IGMP_GROUP = 5,
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * enum rtl8365mb_table_op - table query operation
|
||||
+ * @RTL8365MB_TABLE_OP_READ: read an entry from the target table
|
||||
+ * @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table
|
||||
+ *
|
||||
+ * NOTE: Don't change the enum values. They must concur with the field
|
||||
+ * described by @RTL8365MB_TABLE_CTRL_OP_MASK.
|
||||
+ */
|
||||
+enum rtl8365mb_table_op {
|
||||
+ RTL8365MB_TABLE_OP_READ = 0,
|
||||
+ RTL8365MB_TABLE_OP_WRITE = 1,
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * enum rtl8365mb_table_l2_method - look-up method for read queries of L2 table
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (or
|
||||
+ * VID)
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from the
|
||||
+ * supplied address
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search only
|
||||
+ * unicast addresses
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search only
|
||||
+ * multicast addresses
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but
|
||||
+ * search only entries with matching source port
|
||||
+ *
|
||||
+ * NOTE: Don't change the enum values. They must concur with the field
|
||||
+ * described by @RTL8365MB_TABLE_CTRL_METHOD_MASK
|
||||
+ */
|
||||
+enum rtl8365mb_table_l2_method {
|
||||
+ RTL8365MB_TABLE_L2_METHOD_MAC = 0,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR = 1,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT = 2,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC = 3,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC = 4,
|
||||
+ /*
|
||||
+ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 = 5,
|
||||
+ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 = 6,
|
||||
+ */
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT = 7,
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * rtl8365mb_table_query() - read from or write to a switch table
|
||||
+ * @priv: driver context
|
||||
+ * @table: target table, see &enum rtl8365mb_table
|
||||
+ * @op: read or write operation, see &enum rtl8365mb_table_op
|
||||
+ * @addr: table address. For indexed tables, this selects the entry to access.
|
||||
+ * For L2 read queries, it is ignored as input for MAC-based lookup
|
||||
+ * methods and used as input for address-based lookup methods. On
|
||||
+ * successful L2 queries, it is updated with the matched entry address.
|
||||
+ * @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method.
|
||||
+ * Ignored for non-L2 tables.
|
||||
+ * @port: for L2 read queries using method
|
||||
+ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search
|
||||
+ * to entries associated with this source port. Ignored otherwise.
|
||||
+ * @data: data buffer used to read from or write to the table. For L2 MAC
|
||||
+ * lookups, this buffer provides the lookup key and receives the
|
||||
+ * matched entry contents on success.
|
||||
+ * @size: size of @data in 16-bit words. The caller must ensure that @size
|
||||
+ * matches the target table's entry size and does not exceed
|
||||
+ * RTL8365MB_TABLE_ENTRY_MAX_SIZE.
|
||||
+ *
|
||||
+ * This function provides unified access to the internal tables of the switch.
|
||||
+ * All tables except the L2 table are simple indexed tables, where @addr
|
||||
+ * selects the entry and @op determines whether the access is a read or a
|
||||
+ * write operation.
|
||||
+ *
|
||||
+ * The content of @data is used as input when writing to tables or when
|
||||
+ * specifying the lookup key for L2 MAC searches, and as output for all
|
||||
+ * successful read operations. It remains unchanged during write operations or
|
||||
+ * failed read operations that return %-ENOENT. For other errors during read
|
||||
+ * operations, it is undefined.
|
||||
+ *
|
||||
+ * The L2 table is a hash table and supports multiple lookup methods. For
|
||||
+ * %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC
|
||||
+ * address and FID/VID fields provided in @data, using the same format as
|
||||
+ * an L2 table entry. Address-based methods either read a specific entry
|
||||
+ * (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting
|
||||
+ * from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When using
|
||||
+ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated with
|
||||
+ * the specified @port are considered.
|
||||
+ *
|
||||
+ * On successful L2 operations, @addr is updated with the matched table address
|
||||
+ * or allocated entry address. If no matching entry is found, or if an L2 write
|
||||
+ * operation fails (e.g., due to a full table during addition or a missing entry
|
||||
+ * during deletion), %-ENOENT is returned and @addr remains unchanged. It is the
|
||||
+ * caller's responsibility to map the returned error to the appropriate
|
||||
+ * semantic error.
|
||||
+ *
|
||||
+ * @size must match the size of the target table entry, expressed in 16-bit
|
||||
+ * words.
|
||||
+ *
|
||||
+ * Return: 0 on success, or a negative error code on failure.
|
||||
+ */
|
||||
+int rtl8365mb_table_query(struct realtek_priv *priv,
|
||||
+ enum rtl8365mb_table table,
|
||||
+ enum rtl8365mb_table_op op, u16 *addr,
|
||||
+ enum rtl8365mb_table_l2_method method,
|
||||
+ u16 port, u16 *data, size_t size);
|
||||
+
|
||||
+#endif /* _REALTEK_RTL8365MB_TABLE_H */
|
||||
+1461
File diff suppressed because it is too large
Load Diff
+1131
File diff suppressed because it is too large
Load Diff
+335
@@ -0,0 +1,335 @@
|
||||
From 183bd68b1fe1d5ad584355a7449eea32da79334a Mon Sep 17 00:00:00 2001
|
||||
From: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Date: Sat, 6 Jun 2026 05:29:32 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: add port_bridge_{join,leave}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
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>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-8-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
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(-)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/realtek.h
|
||||
+++ b/drivers/net/dsa/realtek/realtek.h
|
||||
@@ -130,6 +130,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);
|
||||
--- 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(s
|
||||
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_sw
|
||||
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 rtl83
|
||||
.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,
|
||||
@@ -2590,6 +2631,10 @@ static const struct dsa_switch_ops rtl83
|
||||
|
||||
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,
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.c
|
||||
@@ -329,6 +329,175 @@ void rtl83xx_reset_deassert(struct realt
|
||||
}
|
||||
|
||||
/**
|
||||
+ * 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
|
||||
* @port: port index
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.h
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.h
|
||||
@@ -21,6 +21,13 @@ void rtl83xx_remove(struct realtek_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,
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
From 660a9e399ab02c0cb86d277ed6b0c9d10c350fdd Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:33 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: add bridge port flags
|
||||
|
||||
Implement support for bridge port flags to control learning and flooding
|
||||
behavior. This patch maps hardware functionalities to the following
|
||||
bridge flags:
|
||||
|
||||
- BR_LEARNING
|
||||
- BR_FLOOD
|
||||
- BR_MCAST_FLOOD
|
||||
- BR_BCAST_FLOOD
|
||||
|
||||
By default, all flooding types are enabled during port setup to ensure
|
||||
standard bridge behavior.
|
||||
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-9-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/realtek.h | 6 ++
|
||||
drivers/net/dsa/realtek/rtl8365mb_main.c | 68 +++++++++++++++++++++
|
||||
drivers/net/dsa/realtek/rtl83xx.c | 101 +++++++++++++++++++++++++++++++
|
||||
drivers/net/dsa/realtek/rtl83xx.h | 4 ++
|
||||
4 files changed, 179 insertions(+)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/realtek.h
|
||||
+++ b/drivers/net/dsa/realtek/realtek.h
|
||||
@@ -137,6 +137,12 @@ struct realtek_ops {
|
||||
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 (*port_set_ucast_flood)(struct realtek_priv *priv, int port,
|
||||
+ bool enable);
|
||||
+ int (*port_set_mcast_flood)(struct realtek_priv *priv, int port,
|
||||
+ bool enable);
|
||||
+ int (*port_set_bcast_flood)(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);
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
|
||||
@@ -307,6 +307,21 @@
|
||||
#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \
|
||||
(0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport)))
|
||||
|
||||
+/* Unknown unicast DA flooding port mask */
|
||||
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890
|
||||
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF
|
||||
+
|
||||
+/* Unknown multicast DA flooding port mask */
|
||||
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891
|
||||
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF
|
||||
+
|
||||
+/* Broadcast flooding port mask */
|
||||
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892
|
||||
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF
|
||||
+
|
||||
+#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \
|
||||
+ (BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD)
|
||||
+
|
||||
/* Miscellaneous port configuration register, incl. VLAN egress mode */
|
||||
#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E
|
||||
#define RTL8365MB_PORT_MISC_CFG_REG(_p) \
|
||||
@@ -1568,6 +1583,49 @@ static int rtl8365mb_port_set_learning(s
|
||||
enable ? RTL8365MB_LEARN_LIMIT_MAX : 0);
|
||||
}
|
||||
|
||||
+static int rtl8365mb_port_set_ucast_flood(struct realtek_priv *priv, int port,
|
||||
+ bool enable)
|
||||
+{
|
||||
+ /* Frames with unknown unicast DA will be flooded to a programmable
|
||||
+ * port mask that by default includes all ports. Add or remove
|
||||
+ * the specified port from this port mask accordingly.
|
||||
+ */
|
||||
+ return regmap_update_bits(priv->map,
|
||||
+ RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG,
|
||||
+ BIT(port), enable ? BIT(port) : 0);
|
||||
+}
|
||||
+
|
||||
+static int rtl8365mb_port_set_mcast_flood(struct realtek_priv *priv, int port,
|
||||
+ bool enable)
|
||||
+{
|
||||
+ return regmap_update_bits(priv->map,
|
||||
+ RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG,
|
||||
+ BIT(port), enable ? BIT(port) : 0);
|
||||
+}
|
||||
+
|
||||
+static int rtl8365mb_port_set_bcast_flood(struct realtek_priv *priv, int port,
|
||||
+ bool enable)
|
||||
+{
|
||||
+ return regmap_update_bits(priv->map,
|
||||
+ RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG,
|
||||
+ BIT(port), enable ? BIT(port) : 0);
|
||||
+}
|
||||
+
|
||||
+static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
|
||||
+ struct switchdev_brport_flags flags,
|
||||
+ struct netlink_ext_ack *extack)
|
||||
+{
|
||||
+ struct realtek_priv *priv = ds->priv;
|
||||
+
|
||||
+ dev_dbg(priv->dev, "pre_bridge_flags port:%d flags:%lx supported:%lx\n",
|
||||
+ port, flags.mask, RTL8365MB_SUPPORTED_BRIDGE_FLAGS);
|
||||
+
|
||||
+ if (flags.mask & ~RTL8365MB_SUPPORTED_BRIDGE_FLAGS)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port,
|
||||
u32 efid)
|
||||
{
|
||||
@@ -2422,6 +2480,11 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
|
||||
+ /* Enable all types of flooding */
|
||||
+ ret = rtl83xx_setup_port_flood_control(priv, dp->index);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
+
|
||||
/* Set up per-port private data */
|
||||
p->priv = priv;
|
||||
p->index = dp->index;
|
||||
@@ -2608,6 +2671,8 @@ static const struct dsa_switch_ops rtl83
|
||||
.phylink_get_caps = rtl8365mb_phylink_get_caps,
|
||||
.port_bridge_join = rtl83xx_port_bridge_join,
|
||||
.port_bridge_leave = rtl83xx_port_bridge_leave,
|
||||
+ .port_pre_bridge_flags = rtl8365mb_port_pre_bridge_flags,
|
||||
+ .port_bridge_flags = rtl83xx_port_bridge_flags,
|
||||
.port_stp_state_set = rtl8365mb_port_stp_state_set,
|
||||
.port_fast_age = rtl83xx_port_fast_age,
|
||||
.port_fdb_add = rtl83xx_port_fdb_add,
|
||||
@@ -2635,6 +2700,9 @@ static const struct realtek_ops rtl8365m
|
||||
.port_remove_isolation = rtl8365mb_port_remove_isolation,
|
||||
.port_set_efid = rtl8365mb_port_set_efid,
|
||||
.port_set_learning = rtl8365mb_port_set_learning,
|
||||
+ .port_set_ucast_flood = rtl8365mb_port_set_ucast_flood,
|
||||
+ .port_set_mcast_flood = rtl8365mb_port_set_mcast_flood,
|
||||
+ .port_set_bcast_flood = rtl8365mb_port_set_bcast_flood,
|
||||
.l2_add_uc = rtl8365mb_l2_add_uc,
|
||||
.l2_del_uc = rtl8365mb_l2_del_uc,
|
||||
.l2_get_next_uc = rtl8365mb_l2_get_next_uc,
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.c
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/of_mdio.h>
|
||||
+#include <linux/if_bridge.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "realtek.h"
|
||||
@@ -787,6 +788,106 @@ int rtl83xx_port_mdb_del(struct dsa_swit
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, REALTEK_DSA);
|
||||
|
||||
+/**
|
||||
+ * rtl83xx_port_bridge_flags() - set port bridge flags
|
||||
+ * @ds: DSA switch instance
|
||||
+ * @port: port index
|
||||
+ * @flags: bridge port flags
|
||||
+ * @extack: netlink extended ack for reporting errors
|
||||
+ *
|
||||
+ * This function handles setting bridge port flags like learning and flooding.
|
||||
+ *
|
||||
+ * Context: Can sleep.
|
||||
+ * Return: 0 on success, negative value for failure.
|
||||
+ */
|
||||
+int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
|
||||
+ struct switchdev_brport_flags flags,
|
||||
+ struct netlink_ext_ack *extack)
|
||||
+{
|
||||
+ struct realtek_priv *priv = ds->priv;
|
||||
+ bool enable;
|
||||
+ int ret;
|
||||
+
|
||||
+ if (flags.mask & BR_LEARNING) {
|
||||
+ if (!priv->ops->port_set_learning)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_LEARNING);
|
||||
+ ret = priv->ops->port_set_learning(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (flags.mask & BR_FLOOD) {
|
||||
+ if (!priv->ops->port_set_ucast_flood)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_FLOOD);
|
||||
+ ret = priv->ops->port_set_ucast_flood(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (flags.mask & BR_MCAST_FLOOD) {
|
||||
+ if (!priv->ops->port_set_mcast_flood)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_MCAST_FLOOD);
|
||||
+ ret = priv->ops->port_set_mcast_flood(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (flags.mask & BR_BCAST_FLOOD) {
|
||||
+ if (!priv->ops->port_set_bcast_flood)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_BCAST_FLOOD);
|
||||
+ ret = priv->ops->port_set_bcast_flood(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, REALTEK_DSA);
|
||||
+
|
||||
+/**
|
||||
+ * rtl83xx_setup_port_flood_control() - setup default flood control for a port
|
||||
+ * @priv: realtek_priv pointer
|
||||
+ * @port: port index
|
||||
+ *
|
||||
+ * This function enables flooding for a given port.
|
||||
+ *
|
||||
+ * Context: Can sleep.
|
||||
+ * Return: 0 on success, negative value for failure.
|
||||
+ */
|
||||
+int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ if (priv->ops->port_set_ucast_flood) {
|
||||
+ ret = priv->ops->port_set_ucast_flood(priv, port, true);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (priv->ops->port_set_mcast_flood) {
|
||||
+ ret = priv->ops->port_set_mcast_flood(priv, port, true);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (priv->ops->port_set_bcast_flood) {
|
||||
+ ret = priv->ops->port_set_bcast_flood(priv, port, true);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, REALTEK_DSA);
|
||||
+
|
||||
MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
|
||||
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
|
||||
MODULE_DESCRIPTION("Realtek DSA switches common module");
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.h
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.h
|
||||
@@ -27,6 +27,10 @@ int rtl83xx_port_bridge_join(struct dsa_
|
||||
struct netlink_ext_ack *extack);
|
||||
void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
|
||||
struct dsa_bridge bridge);
|
||||
+int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
|
||||
+ struct switchdev_brport_flags flags,
|
||||
+ struct netlink_ext_ack *extack);
|
||||
+int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port);
|
||||
|
||||
void rtl83xx_port_fast_age(struct dsa_switch *ds, int port);
|
||||
int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
From 297e1f411ed4927a912c7e207ba6f978cb1f9f0e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= <alsi@bang-olufsen.dk>
|
||||
Date: Wed, 8 Apr 2026 17:31:01 -0300
|
||||
Subject: [PATCH 1/2] net: dsa: tag_rtl8_4: update format description
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Document the updated tag layout fields (EFID, VSEL/VIDX) and clarify
|
||||
which bits are set/cleared when emitting tags.
|
||||
|
||||
Co-developed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Link: https://patch.msgid.link/20260408-realtek_fixes-v1-1-915ff1404d56@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
net/dsa/tag_rtl8_4.c | 17 ++++++++++-------
|
||||
1 file changed, 10 insertions(+), 7 deletions(-)
|
||||
|
||||
--- a/net/dsa/tag_rtl8_4.c
|
||||
+++ b/net/dsa/tag_rtl8_4.c
|
||||
@@ -17,8 +17,8 @@
|
||||
* | (8-bit) | (8-bit) |
|
||||
* | Protocol [0x04] | REASON | b
|
||||
* |-----------------------------------+-----------------------------------| y
|
||||
- * | (1) | (1) | (2) | (1) | (3) | (1) | (1) | (1) | (5) | t
|
||||
- * | FID_EN | X | FID | PRI_EN | PRI | KEEP | X | LEARN_DIS | X | e
|
||||
+ * | (1) | (3) | (1) | (3) | (1) | (1) | (1) | (5) | t
|
||||
+ * | EFID_EN | EFID | PRI_EN | PRI | KEEP | VSEL | LEARN_DIS | VIDX | e
|
||||
* |-----------------------------------+-----------------------------------| s
|
||||
* | (1) | (15-bit) | |
|
||||
* | ALLOW | TX/RX | v
|
||||
@@ -32,19 +32,22 @@
|
||||
* EtherType | note that Realtek uses the same EtherType for
|
||||
* | other incompatible tag formats (e.g. tag_rtl4_a.c)
|
||||
* Protocol | 0x04: indicates that this tag conforms to this format
|
||||
- * X | reserved
|
||||
* ------------+-------------
|
||||
* REASON | reason for forwarding packet to CPU
|
||||
* | 0: packet was forwarded or flooded to CPU
|
||||
* | 80: packet was trapped to CPU
|
||||
- * FID_EN | 1: packet has an FID
|
||||
- * | 0: no FID
|
||||
- * FID | FID of packet (if FID_EN=1)
|
||||
+ * EFID_EN | 1: packet has an EFID
|
||||
+ * | 0: no EFID
|
||||
+ * EFID | Extended filter ID (EFID) of packet (if EFID_EN=1)
|
||||
* PRI_EN | 1: force priority of packet
|
||||
* | 0: don't force priority
|
||||
* PRI | priority of packet (if PRI_EN=1)
|
||||
* KEEP | preserve packet VLAN tag format
|
||||
+ * VSEL | 0: switch should classify packet according to VLAN tag
|
||||
+ * | 1: switch should classify packet according to VLAN membership
|
||||
+ * | configuration with index VIDX
|
||||
* LEARN_DIS | don't learn the source MAC address of the packet
|
||||
+ * VIDX | index of a VLAN membership configuration to use with VSEL
|
||||
* ALLOW | 1: treat TX/RX field as an allowance port mask, meaning the
|
||||
* | packet may only be forwarded to ports specified in the
|
||||
* | mask
|
||||
@@ -112,7 +115,7 @@ static void rtl8_4_write_tag(struct sk_b
|
||||
/* Set Protocol; zero REASON */
|
||||
tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB));
|
||||
|
||||
- /* Zero FID_EN, FID, PRI_EN, PRI, KEEP; set LEARN_DIS */
|
||||
+ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */
|
||||
tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1));
|
||||
|
||||
/* Zero ALLOW; set RX (CPU->switch) forwarding port mask */
|
||||
@@ -0,0 +1,48 @@
|
||||
From 82f37bd9a4d779495479c0c13152208d5400c8a4 Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Wed, 8 Apr 2026 17:31:02 -0300
|
||||
Subject: [PATCH 2/2] net: dsa: tag_rtl8_4: set KEEP flag
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
KEEP=1 is needed because we should respect the format of the packet as
|
||||
the kernel sends it to us. Unless tx forward offloading is used, the
|
||||
kernel is giving us the packet exactly as it should leave the specified
|
||||
port on the wire. Until now this was not needed because the ports were
|
||||
always functioning in a standalone mode in a VLAN-unaware way, so the
|
||||
switch would not tag or untag frames anyway. But arguably it should have
|
||||
been KEEP=1 all along.
|
||||
|
||||
Co-developed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Link: https://patch.msgid.link/20260408-realtek_fixes-v1-2-915ff1404d56@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
net/dsa/tag_rtl8_4.c | 6 ++++--
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/net/dsa/tag_rtl8_4.c
|
||||
+++ b/net/dsa/tag_rtl8_4.c
|
||||
@@ -99,6 +99,7 @@
|
||||
#define RTL8_4_REASON_TRAP 80
|
||||
|
||||
#define RTL8_4_LEARN_DIS BIT(5)
|
||||
+#define RTL8_4_KEEP BIT(7)
|
||||
|
||||
#define RTL8_4_TX GENMASK(3, 0)
|
||||
#define RTL8_4_RX GENMASK(10, 0)
|
||||
@@ -115,8 +116,9 @@ static void rtl8_4_write_tag(struct sk_b
|
||||
/* Set Protocol; zero REASON */
|
||||
tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB));
|
||||
|
||||
- /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */
|
||||
- tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1));
|
||||
+ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX; set KEEP, LEARN_DIS */
|
||||
+ tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1) |
|
||||
+ FIELD_PREP(RTL8_4_KEEP, 1));
|
||||
|
||||
/* Zero ALLOW; set RX (CPU->switch) forwarding port mask */
|
||||
tag16[3] = htons(FIELD_PREP(RTL8_4_RX, BIT(dp->index)));
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
From a543687227d86b1f4401f395abaf7f3f710fe964 Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:25 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: use ERR_PTR
|
||||
|
||||
Convert numeric error codes into human-readable strings by using %pe
|
||||
together with ERR_PTR() in dev_err() messages. Also use dev_err_probe()
|
||||
instead of checking for -EPROBE_DEFER.
|
||||
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-1-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/rtl8365mb.c | 51 +++++++++++++++++++++----------------
|
||||
1 file changed, 29 insertions(+), 22 deletions(-)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
@@ -803,8 +803,8 @@ static int rtl8365mb_phy_read(struct rea
|
||||
ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val);
|
||||
if (ret) {
|
||||
dev_err(priv->dev,
|
||||
- "failed to read PHY%d reg %02x @ %04x, ret %d\n", phy,
|
||||
- regnum, ocp_addr, ret);
|
||||
+ "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy,
|
||||
+ regnum, ocp_addr, ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -831,8 +831,8 @@ static int rtl8365mb_phy_write(struct re
|
||||
ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val);
|
||||
if (ret) {
|
||||
dev_err(priv->dev,
|
||||
- "failed to write PHY%d reg %02x @ %04x, ret %d\n", phy,
|
||||
- regnum, ocp_addr, ret);
|
||||
+ "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy,
|
||||
+ regnum, ocp_addr, ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1082,8 +1082,8 @@ static void rtl8365mb_phylink_mac_config
|
||||
ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface);
|
||||
if (ret)
|
||||
dev_err(priv->dev,
|
||||
- "failed to configure RGMII mode on port %d: %d\n",
|
||||
- port, ret);
|
||||
+ "failed to configure RGMII mode on port %d: %pe\n",
|
||||
+ port, ERR_PTR(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1112,8 +1112,8 @@ static void rtl8365mb_phylink_mac_link_d
|
||||
false, false);
|
||||
if (ret)
|
||||
dev_err(priv->dev,
|
||||
- "failed to reset forced mode on port %d: %d\n",
|
||||
- port, ret);
|
||||
+ "failed to reset forced mode on port %d: %pe\n",
|
||||
+ port, ERR_PTR(ret));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1143,8 +1143,8 @@ static void rtl8365mb_phylink_mac_link_u
|
||||
rx_pause);
|
||||
if (ret)
|
||||
dev_err(priv->dev,
|
||||
- "failed to force mode on port %d: %d\n", port,
|
||||
- ret);
|
||||
+ "failed to force mode on port %d: %pe\n", port,
|
||||
+ ERR_PTR(ret));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1299,8 +1299,8 @@ static void rtl8365mb_get_ethtool_stats(
|
||||
mib->length, &data[i]);
|
||||
if (ret) {
|
||||
dev_err(priv->dev,
|
||||
- "failed to read port %d counters: %d\n", port,
|
||||
- ret);
|
||||
+ "failed to read port %d counters: %pe\n", port,
|
||||
+ ERR_PTR(ret));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1652,7 +1652,8 @@ static irqreturn_t rtl8365mb_irq(int irq
|
||||
return IRQ_HANDLED;
|
||||
|
||||
out_error:
|
||||
- dev_err(priv->dev, "failed to read interrupt status: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to read interrupt status: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
|
||||
out_none:
|
||||
return IRQ_NONE;
|
||||
@@ -1725,10 +1726,13 @@ static int rtl8365mb_irq_setup(struct re
|
||||
/* rtl8365mb IRQs cascade off this one */
|
||||
irq = of_irq_get(intc, 0);
|
||||
if (irq <= 0) {
|
||||
- if (irq != -EPROBE_DEFER)
|
||||
- dev_err(priv->dev, "failed to get parent irq: %d\n",
|
||||
- irq);
|
||||
- ret = irq ? irq : -EINVAL;
|
||||
+ if (!irq) {
|
||||
+ dev_err(priv->dev, "failed to map IRQ\n");
|
||||
+ ret = -EINVAL;
|
||||
+ } else {
|
||||
+ ret = dev_err_probe(priv->dev, irq,
|
||||
+ "failed to get parent irq\n");
|
||||
+ }
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
@@ -1790,7 +1794,8 @@ static int rtl8365mb_irq_setup(struct re
|
||||
ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT,
|
||||
"rtl8365mb", priv);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to request irq: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to request irq: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
goto out_remove_irqdomain;
|
||||
}
|
||||
|
||||
@@ -1966,14 +1971,16 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
|
||||
ret = rtl8365mb_reset_chip(priv);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to reset chip: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to reset chip: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
goto out_error;
|
||||
}
|
||||
|
||||
/* Configure switch to vendor-defined initial state */
|
||||
ret = rtl8365mb_switch_init(priv);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to initialize switch: %d\n", ret);
|
||||
+ dev_err(priv->dev, "failed to initialize switch: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
goto out_error;
|
||||
}
|
||||
|
||||
@@ -2091,8 +2098,8 @@ static int rtl8365mb_detect(struct realt
|
||||
|
||||
ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver);
|
||||
if (ret) {
|
||||
- dev_err(priv->dev, "failed to read chip id and version: %d\n",
|
||||
- ret);
|
||||
+ dev_err(priv->dev, "failed to read chip id and version: %pe\n",
|
||||
+ ERR_PTR(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
From 36c572fd60d6bf2f9631088833c162203907c9ca Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:26 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: reject unsupported topologies
|
||||
|
||||
Explicitly enforce the presence of a CPU port (-EINVAL) and reject DSA
|
||||
cascade links (-EOPNOTSUPP) during setup to prevent silent failures.
|
||||
|
||||
These topologies were already non-functional. Without a CPU port, the
|
||||
driver does not activate CPU tagging. Additionally, the switch hardware
|
||||
was not designed to be cascaded, and DSA links never worked because
|
||||
CPU tagging is not enabled for them.
|
||||
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-2-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/rtl8365mb.c | 21 +++++++++++++++++++++
|
||||
1 file changed, 21 insertions(+)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
@@ -1991,6 +1991,20 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
else if (ret)
|
||||
dev_info(priv->dev, "no interrupt support\n");
|
||||
|
||||
+ for (i = 0; i < priv->num_ports; i++) {
|
||||
+ /* Cascading (DSA links) is not supported yet.
|
||||
+ * Historically, the driver has always been broken
|
||||
+ * without a dedicated CPU port because CPU tagging
|
||||
+ * would be disabled, rendering the switch entirely
|
||||
+ * non-functional for DSA operations.
|
||||
+ */
|
||||
+ if (dsa_is_dsa_port(ds, i)) {
|
||||
+ dev_err(priv->dev, "Cascading (DSA link) not supported\n");
|
||||
+ ret = -EOPNOTSUPP;
|
||||
+ goto out_teardown_irq;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/* Configure CPU tagging */
|
||||
dsa_switch_for_each_cpu_port(cpu_dp, ds) {
|
||||
cpu->mask |= BIT(cpu_dp->index);
|
||||
@@ -1999,6 +2013,13 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
cpu->trap_port = cpu_dp->index;
|
||||
}
|
||||
cpu->enable = cpu->mask > 0;
|
||||
+
|
||||
+ if (!cpu->enable) {
|
||||
+ dev_err(priv->dev, "no CPU port defined\n");
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_teardown_irq;
|
||||
+ }
|
||||
+
|
||||
ret = rtl8365mb_cpu_config(priv);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
From 51a4a22301b023610a1321614e8dc61251ef9064 Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:27 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: use dsa helpers for port iteration
|
||||
|
||||
Convert open-coded port iteration loops to use the DSA helpers and
|
||||
restructure rtl8365mb_setup() into clear blocking, user, and
|
||||
CPU port phases.
|
||||
|
||||
As part of this refactoring, unused ports are explicitly placed into a
|
||||
blocked, isolated state with learning disabled, ensuring safe default
|
||||
hardware behavior. The driver also does not allocate a virtual IRQ
|
||||
mapping for unused ports. To accommodate this, a guard check is added to
|
||||
the interrupt handler (rtl8365mb_irq) to safely skip ports without a
|
||||
valid IRQ mapping. The irq domain teardown, however, does clean all
|
||||
ports as external PHYs may still map the IRQ.
|
||||
|
||||
Furthermore, since the new initialization loop starts with all ports
|
||||
administratively isolated by default, CPU port forwarding and isolation
|
||||
masks are explicitly configured at the end of the setup phase to prevent
|
||||
egress traffic from being blocked.
|
||||
|
||||
Suggested-by: Abdulkader Alrezej <abdulkader.alrezej@gmail.com>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-3-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/rtl8365mb.c | 166 +++++++++++++++++++++++-------------
|
||||
1 file changed, 105 insertions(+), 61 deletions(-)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb.c
|
||||
@@ -1554,18 +1554,15 @@ static void rtl8365mb_stats_setup(struct
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
struct dsa_switch *ds = &priv->ds;
|
||||
- int i;
|
||||
+ struct dsa_port *dp;
|
||||
|
||||
/* Per-chip global mutex to protect MIB counter access, since doing
|
||||
* so requires accessing a series of registers in a particular order.
|
||||
*/
|
||||
mutex_init(&mb->mib_lock);
|
||||
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- struct rtl8365mb_port *p = &mb->ports[i];
|
||||
-
|
||||
- if (dsa_is_unused_port(ds, i))
|
||||
- continue;
|
||||
+ dsa_switch_for_each_available_port(dp, ds) {
|
||||
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
|
||||
|
||||
/* Per-port spinlock to protect the stats64 data */
|
||||
spin_lock_init(&p->stats_lock);
|
||||
@@ -1581,13 +1578,10 @@ static void rtl8365mb_stats_teardown(str
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
struct dsa_switch *ds = &priv->ds;
|
||||
- int i;
|
||||
-
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- struct rtl8365mb_port *p = &mb->ports[i];
|
||||
+ struct dsa_port *dp;
|
||||
|
||||
- if (dsa_is_unused_port(ds, i))
|
||||
- continue;
|
||||
+ dsa_switch_for_each_available_port(dp, ds) {
|
||||
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
|
||||
|
||||
cancel_delayed_work_sync(&p->mib_work);
|
||||
}
|
||||
@@ -1646,6 +1640,9 @@ static irqreturn_t rtl8365mb_irq(int irq
|
||||
for_each_set_bit(line, &line_changes, priv->num_ports) {
|
||||
int child_irq = irq_find_mapping(priv->irqdomain, line);
|
||||
|
||||
+ if (!child_irq)
|
||||
+ continue;
|
||||
+
|
||||
handle_nested_irq(child_irq);
|
||||
}
|
||||
|
||||
@@ -1667,10 +1664,14 @@ static struct irq_chip rtl8365mb_irq_chi
|
||||
static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
- irq_set_chip_data(irq, domain->host_data);
|
||||
+ struct realtek_priv *priv = domain->host_data;
|
||||
+ struct rtl8365mb *mb = priv->chip_data;
|
||||
+
|
||||
+ irq_set_chip_data(irq, priv);
|
||||
irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq);
|
||||
irq_set_nested_thread(irq, 1);
|
||||
irq_set_noprobe(irq);
|
||||
+ irq_set_parent(irq, mb->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1709,13 +1710,14 @@ static int rtl8365mb_irq_disable(struct
|
||||
static int rtl8365mb_irq_setup(struct realtek_priv *priv)
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
+ struct dsa_switch *ds = &priv->ds;
|
||||
struct device_node *intc;
|
||||
+ struct dsa_port *dp;
|
||||
u32 irq_trig;
|
||||
int virq;
|
||||
int irq;
|
||||
u32 val;
|
||||
int ret;
|
||||
- int i;
|
||||
|
||||
intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller");
|
||||
if (!intc) {
|
||||
@@ -1736,6 +1738,9 @@ static int rtl8365mb_irq_setup(struct re
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
+ /* Store the irq so that we know to map and free it during teardown */
|
||||
+ mb->irq = irq;
|
||||
+
|
||||
priv->irqdomain = irq_domain_create_linear(of_fwnode_handle(intc), priv->num_ports,
|
||||
&rtl8365mb_irqdomain_ops, priv);
|
||||
if (!priv->irqdomain) {
|
||||
@@ -1744,8 +1749,8 @@ static int rtl8365mb_irq_setup(struct re
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- virq = irq_create_mapping(priv->irqdomain, i);
|
||||
+ dsa_switch_for_each_available_port(dp, ds) {
|
||||
+ virq = irq_create_mapping(priv->irqdomain, dp->index);
|
||||
if (!virq) {
|
||||
dev_err(priv->dev,
|
||||
"failed to create irq domain mapping\n");
|
||||
@@ -1799,9 +1804,6 @@ static int rtl8365mb_irq_setup(struct re
|
||||
goto out_remove_irqdomain;
|
||||
}
|
||||
|
||||
- /* Store the irq so that we know to free it during teardown */
|
||||
- mb->irq = irq;
|
||||
-
|
||||
ret = rtl8365mb_irq_enable(priv);
|
||||
if (ret)
|
||||
goto out_free_irq;
|
||||
@@ -1812,18 +1814,20 @@ static int rtl8365mb_irq_setup(struct re
|
||||
|
||||
out_free_irq:
|
||||
free_irq(mb->irq, priv);
|
||||
- mb->irq = 0;
|
||||
|
||||
out_remove_irqdomain:
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- virq = irq_find_mapping(priv->irqdomain, i);
|
||||
- irq_dispose_mapping(virq);
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ virq = irq_find_mapping(priv->irqdomain, dp->index);
|
||||
+
|
||||
+ if (virq)
|
||||
+ irq_dispose_mapping(virq);
|
||||
}
|
||||
|
||||
irq_domain_remove(priv->irqdomain);
|
||||
priv->irqdomain = NULL;
|
||||
|
||||
out_put_node:
|
||||
+ mb->irq = 0;
|
||||
of_node_put(intc);
|
||||
|
||||
return ret;
|
||||
@@ -1832,8 +1836,9 @@ out_put_node:
|
||||
static void rtl8365mb_irq_teardown(struct realtek_priv *priv)
|
||||
{
|
||||
struct rtl8365mb *mb = priv->chip_data;
|
||||
+ struct dsa_switch *ds = &priv->ds;
|
||||
+ struct dsa_port *dp;
|
||||
int virq;
|
||||
- int i;
|
||||
|
||||
if (mb->irq) {
|
||||
free_irq(mb->irq, priv);
|
||||
@@ -1841,9 +1846,15 @@ static void rtl8365mb_irq_teardown(struc
|
||||
}
|
||||
|
||||
if (priv->irqdomain) {
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- virq = irq_find_mapping(priv->irqdomain, i);
|
||||
- irq_dispose_mapping(virq);
|
||||
+ /* Unused ports with a linked PHY still have an active IRQ
|
||||
+ * mapping that must be disposed of during teardown. Loop
|
||||
+ * through all ports.
|
||||
+ */
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ virq = irq_find_mapping(priv->irqdomain, dp->index);
|
||||
+
|
||||
+ if (virq)
|
||||
+ irq_dispose_mapping(virq);
|
||||
}
|
||||
|
||||
irq_domain_remove(priv->irqdomain);
|
||||
@@ -1961,10 +1972,11 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
{
|
||||
struct realtek_priv *priv = ds->priv;
|
||||
struct rtl8365mb_cpu *cpu;
|
||||
- struct dsa_port *cpu_dp;
|
||||
+ u32 downports_mask = 0;
|
||||
+ u32 upports_mask = 0;
|
||||
struct rtl8365mb *mb;
|
||||
+ struct dsa_port *dp;
|
||||
int ret;
|
||||
- int i;
|
||||
|
||||
mb = priv->chip_data;
|
||||
cpu = &mb->cpu;
|
||||
@@ -1991,67 +2003,99 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
else if (ret)
|
||||
dev_info(priv->dev, "no interrupt support\n");
|
||||
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
/* Cascading (DSA links) is not supported yet.
|
||||
* Historically, the driver has always been broken
|
||||
* without a dedicated CPU port because CPU tagging
|
||||
* would be disabled, rendering the switch entirely
|
||||
* non-functional for DSA operations.
|
||||
*/
|
||||
- if (dsa_is_dsa_port(ds, i)) {
|
||||
+ if (dsa_port_is_dsa(dp)) {
|
||||
dev_err(priv->dev, "Cascading (DSA link) not supported\n");
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out_teardown_irq;
|
||||
}
|
||||
}
|
||||
|
||||
- /* Configure CPU tagging */
|
||||
- dsa_switch_for_each_cpu_port(cpu_dp, ds) {
|
||||
- cpu->mask |= BIT(cpu_dp->index);
|
||||
+ /* Start with all ports blocked, including unused ports */
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
|
||||
|
||||
- if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS)
|
||||
- cpu->trap_port = cpu_dp->index;
|
||||
- }
|
||||
- cpu->enable = cpu->mask > 0;
|
||||
+ /* Set the initial STP state of all ports to DISABLED, otherwise
|
||||
+ * ports will still forward frames to the CPU despite being
|
||||
+ * administratively down by default.
|
||||
+ */
|
||||
+ rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED);
|
||||
|
||||
- if (!cpu->enable) {
|
||||
- dev_err(priv->dev, "no CPU port defined\n");
|
||||
- ret = -EINVAL;
|
||||
- goto out_teardown_irq;
|
||||
- }
|
||||
+ /* Start with all port completely isolated */
|
||||
+ ret = rtl8365mb_port_set_isolation(priv, dp->index, 0);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
|
||||
- ret = rtl8365mb_cpu_config(priv);
|
||||
- if (ret)
|
||||
- goto out_teardown_irq;
|
||||
+ /* Disable learning */
|
||||
+ ret = rtl8365mb_port_set_learning(priv, dp->index, false);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
+
|
||||
+ /* Set up per-port private data */
|
||||
+ p->priv = priv;
|
||||
+ p->index = dp->index;
|
||||
|
||||
- /* Configure ports */
|
||||
- for (i = 0; i < priv->num_ports; i++) {
|
||||
- struct rtl8365mb_port *p = &mb->ports[i];
|
||||
+ /* Collect CPU ports. If we support cascade switches, it should
|
||||
+ * also include the upstream DSA ports.
|
||||
+ */
|
||||
+ if (!dsa_port_is_cpu(dp))
|
||||
+ continue;
|
||||
+
|
||||
+ upports_mask |= BIT(dp->index);
|
||||
+ }
|
||||
|
||||
- if (dsa_is_unused_port(ds, i))
|
||||
+ /* Configure user ports */
|
||||
+ dsa_switch_for_each_port(dp, ds) {
|
||||
+ if (!dsa_port_is_user(dp))
|
||||
continue;
|
||||
|
||||
/* Forward only to the CPU */
|
||||
- ret = rtl8365mb_port_set_isolation(priv, i, cpu->mask);
|
||||
+ ret = rtl8365mb_port_set_isolation(priv, dp->index,
|
||||
+ upports_mask);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
|
||||
- /* Disable learning */
|
||||
- ret = rtl8365mb_port_set_learning(priv, i, false);
|
||||
+ /* If we support cascade switches, it should also include the
|
||||
+ * downstream DSA ports.
|
||||
+ */
|
||||
+ downports_mask |= BIT(dp->index);
|
||||
+ }
|
||||
+
|
||||
+ /* Configure CPU tagging */
|
||||
+ /* If we support cascade switches, it should also include the upstream
|
||||
+ * DSA ports.
|
||||
+ */
|
||||
+ dsa_switch_for_each_cpu_port(dp, ds) {
|
||||
+ /* Use the first CPU port as trap_port */
|
||||
+ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS)
|
||||
+ cpu->trap_port = dp->index;
|
||||
+
|
||||
+ /* Forward to all user ports */
|
||||
+ ret = rtl8365mb_port_set_isolation(priv, dp->index,
|
||||
+ downports_mask);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
+ }
|
||||
|
||||
- /* Set the initial STP state of all ports to DISABLED, otherwise
|
||||
- * ports will still forward frames to the CPU despite being
|
||||
- * administratively down by default.
|
||||
- */
|
||||
- rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED);
|
||||
+ cpu->mask = upports_mask;
|
||||
+ cpu->enable = cpu->mask > 0;
|
||||
|
||||
- /* Set up per-port private data */
|
||||
- p->priv = priv;
|
||||
- p->index = i;
|
||||
+ if (!cpu->enable) {
|
||||
+ dev_err(priv->dev, "no CPU port defined\n");
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_teardown_irq;
|
||||
}
|
||||
|
||||
+ ret = rtl8365mb_cpu_config(priv);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
+
|
||||
ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN);
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
+4631
File diff suppressed because it is too large
Load Diff
+394
@@ -0,0 +1,394 @@
|
||||
From fbafdd3b224a03b7b335de144f44a600de937586 Mon Sep 17 00:00:00 2001
|
||||
From: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Date: Sat, 6 Jun 2026 05:29:29 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: add table lookup interface
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Add a generic table lookup interface to centralize access to
|
||||
the RTL8365MB internal tables.
|
||||
|
||||
This interface abstracts the low-level table access logic and
|
||||
will be used by subsequent commits to implement FDB and VLAN
|
||||
operations.
|
||||
|
||||
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>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-5-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/Makefile | 1 +
|
||||
drivers/net/dsa/realtek/rtl8365mb_table.c | 214 ++++++++++++++++++++++++++++++
|
||||
drivers/net/dsa/realtek/rtl8365mb_table.h | 138 +++++++++++++++++++
|
||||
3 files changed, 353 insertions(+)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/Makefile
|
||||
+++ b/drivers/net/dsa/realtek/Makefile
|
||||
@@ -17,4 +17,5 @@ rtl8366-objs += rtl8366rb-leds.o
|
||||
endif
|
||||
obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
|
||||
rtl8365mb-objs := rtl8365mb_main.o \
|
||||
+ rtl8365mb_table.o \
|
||||
# end of rtl8365mb-objs
|
||||
--- /dev/null
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.c
|
||||
@@ -0,0 +1,214 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0
|
||||
+/* Look-up table query interface for the rtl8365mb switch family
|
||||
+ *
|
||||
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
+ */
|
||||
+
|
||||
+#include "rtl8365mb_table.h"
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+/* Table access control register */
|
||||
+#define RTL8365MB_TABLE_CTRL_REG 0x0500
|
||||
+/* Should be one of rtl8365mb_table enum members */
|
||||
+#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0)
|
||||
+/* Should be one of rtl8365mb_table_op enum members */
|
||||
+#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3)
|
||||
+/* Should be one of rtl8365mb_table_l2_method enum members */
|
||||
+#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4)
|
||||
+#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8)
|
||||
+
|
||||
+/* Table access address register */
|
||||
+#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501
|
||||
+#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0)
|
||||
+
|
||||
+/* Table status register */
|
||||
+#define RTL8365MB_TABLE_STATUS_REG 0x0502
|
||||
+#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0)
|
||||
+/* set for L3, unset for L2 */
|
||||
+#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11)
|
||||
+#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12)
|
||||
+#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13)
|
||||
+#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14)
|
||||
+
|
||||
+/* Table read/write registers */
|
||||
+#define RTL8365MB_TABLE_WRITE_BASE 0x0510
|
||||
+#define RTL8365MB_TABLE_WRITE_REG(_x) \
|
||||
+ (RTL8365MB_TABLE_WRITE_BASE + (_x))
|
||||
+#define RTL8365MB_TABLE_READ_BASE 0x0520
|
||||
+#define RTL8365MB_TABLE_READ_REG(_x) \
|
||||
+ (RTL8365MB_TABLE_READ_BASE + (_x))
|
||||
+#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0)
|
||||
+#define RTL8365MB_TABLE_WRITE_10TH_REG \
|
||||
+ RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1)
|
||||
+
|
||||
+static int rtl8365mb_table_poll_busy(struct realtek_priv *priv)
|
||||
+{
|
||||
+ u32 val;
|
||||
+
|
||||
+ return regmap_read_poll_timeout(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_STATUS_REG, val,
|
||||
+ !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val),
|
||||
+ 10, 10000);
|
||||
+}
|
||||
+
|
||||
+int rtl8365mb_table_query(struct realtek_priv *priv,
|
||||
+ enum rtl8365mb_table table,
|
||||
+ enum rtl8365mb_table_op op, u16 *addr,
|
||||
+ enum rtl8365mb_table_l2_method method,
|
||||
+ u16 port, u16 *data, size_t size)
|
||||
+{
|
||||
+ bool addr_as_input = true;
|
||||
+ bool write_data = false;
|
||||
+ int ret = 0;
|
||||
+ u32 cmd;
|
||||
+ u32 val;
|
||||
+ u32 hit;
|
||||
+
|
||||
+ /* Prepare target table and operation (read or write) */
|
||||
+ cmd = 0;
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table);
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op);
|
||||
+ if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) {
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method);
|
||||
+ switch (method) {
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_MAC:
|
||||
+ /*
|
||||
+ * Method MAC requires as input the same L2 table format
|
||||
+ * you'll get as result. However, it might only use mac
|
||||
+ * address and FID/VID fields.
|
||||
+ */
|
||||
+ write_data = true;
|
||||
+
|
||||
+ /* METHOD_MAC does not use addr as input, but may return
|
||||
+ * the matched index.
|
||||
+ */
|
||||
+ addr_as_input = false;
|
||||
+
|
||||
+ break;
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR:
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT:
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC:
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC:
|
||||
+ break;
|
||||
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT:
|
||||
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port);
|
||||
+ break;
|
||||
+ default:
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+ } else if (op == RTL8365MB_TABLE_OP_WRITE) {
|
||||
+ write_data = true;
|
||||
+
|
||||
+ /* Writing to L2 does not use addr as input, as the table index
|
||||
+ * is derived from key fields.
|
||||
+ */
|
||||
+ if (table == RTL8365MB_TABLE_L2)
|
||||
+ addr_as_input = false;
|
||||
+ }
|
||||
+
|
||||
+ /* To prevent concurrent access to the look-up tables, take the regmap
|
||||
+ * lock manually and access via the map_nolock regmap.
|
||||
+ */
|
||||
+ mutex_lock(&priv->map_lock);
|
||||
+
|
||||
+ /* Protect from a busy table access (i.e. previous access timeouts) */
|
||||
+ ret = rtl8365mb_table_poll_busy(priv);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* Write entry data if writing to the table (or L2_METHOD_MAC) */
|
||||
+ if (write_data) {
|
||||
+ /* bulk write data up to 9th word */
|
||||
+ ret = regmap_bulk_write(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_WRITE_BASE,
|
||||
+ data,
|
||||
+ min_t(size_t, size,
|
||||
+ RTL8365MB_TABLE_ENTRY_MAX_SIZE -
|
||||
+ 1));
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* 10th register uses only 4 least significant bits */
|
||||
+ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
|
||||
+ val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK,
|
||||
+ data[size - 1]);
|
||||
+ ret = regmap_update_bits(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_WRITE_10TH_REG,
|
||||
+ RTL8365MB_TABLE_10TH_DATA_MASK,
|
||||
+ val);
|
||||
+ }
|
||||
+
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ /* Write address (if needed) */
|
||||
+ if (addr_as_input) {
|
||||
+ ret = regmap_write(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_ACCESS_ADDR_REG,
|
||||
+ FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK,
|
||||
+ *addr));
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ /* Execute */
|
||||
+ ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* Poll for completion */
|
||||
+ ret = rtl8365mb_table_poll_busy(priv);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* For both reads and writes to the L2 table, check status */
|
||||
+ if (table == RTL8365MB_TABLE_L2) {
|
||||
+ ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG,
|
||||
+ &val);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* Did the query find an entry? */
|
||||
+ hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val);
|
||||
+ if (!hit) {
|
||||
+ ret = -ENOENT;
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ /* If so, extract the address */
|
||||
+ *addr = 0;
|
||||
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val);
|
||||
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val)
|
||||
+ << 11;
|
||||
+ /* only set if it is a L3 address */
|
||||
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val)
|
||||
+ << 12;
|
||||
+ }
|
||||
+
|
||||
+ /* Finally, get the table entry if we were reading */
|
||||
+ if (op == RTL8365MB_TABLE_OP_READ) {
|
||||
+ ret = regmap_bulk_read(priv->map_nolock,
|
||||
+ RTL8365MB_TABLE_READ_BASE,
|
||||
+ data, size);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ /* For the biggest table entries, the uppermost table
|
||||
+ * entry register has space for only one nibble. Mask
|
||||
+ * out the remainder bits. Empirically I saw nothing
|
||||
+ * wrong with omitting this mask, but it may prevent
|
||||
+ * unwanted behaviour. FYI.
|
||||
+ */
|
||||
+ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
|
||||
+ val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK,
|
||||
+ data[size - 1]);
|
||||
+ data[size - 1] = val;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+out:
|
||||
+ mutex_unlock(&priv->map_lock);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
--- /dev/null
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.h
|
||||
@@ -0,0 +1,138 @@
|
||||
+/* SPDX-License-Identifier: GPL-2.0 */
|
||||
+/* Look-up table query interface for the rtl8365mb switch family
|
||||
+ *
|
||||
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
+ */
|
||||
+
|
||||
+#ifndef _REALTEK_RTL8365MB_TABLE_H
|
||||
+#define _REALTEK_RTL8365MB_TABLE_H
|
||||
+
|
||||
+#include <linux/if_ether.h>
|
||||
+#include <linux/types.h>
|
||||
+
|
||||
+#include "realtek.h"
|
||||
+
|
||||
+#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10
|
||||
+
|
||||
+/*
|
||||
+ * enum rtl8365mb_table - available switch tables
|
||||
+ * @RTL8365MB_TABLE_ACL_RULE: ACL rules
|
||||
+ * @RTL8365MB_TABLE_ACL_ACTION: ACL actions
|
||||
+ * @RTL8365MB_TABLE_CVLAN: VLAN4k configurations
|
||||
+ * @RTL8365MB_TABLE_L2: filtering database (2K hash table)
|
||||
+ * @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly)
|
||||
+ *
|
||||
+ * NOTE: Don't change the enum values. They must concur with the field
|
||||
+ * described by @RTL8365MB_TABLE_CTRL_TABLE_MASK.
|
||||
+ */
|
||||
+enum rtl8365mb_table {
|
||||
+ RTL8365MB_TABLE_ACL_RULE = 1,
|
||||
+ RTL8365MB_TABLE_ACL_ACTION = 2,
|
||||
+ RTL8365MB_TABLE_CVLAN = 3,
|
||||
+ RTL8365MB_TABLE_L2 = 4,
|
||||
+ RTL8365MB_TABLE_IGMP_GROUP = 5,
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * enum rtl8365mb_table_op - table query operation
|
||||
+ * @RTL8365MB_TABLE_OP_READ: read an entry from the target table
|
||||
+ * @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table
|
||||
+ *
|
||||
+ * NOTE: Don't change the enum values. They must concur with the field
|
||||
+ * described by @RTL8365MB_TABLE_CTRL_OP_MASK.
|
||||
+ */
|
||||
+enum rtl8365mb_table_op {
|
||||
+ RTL8365MB_TABLE_OP_READ = 0,
|
||||
+ RTL8365MB_TABLE_OP_WRITE = 1,
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * enum rtl8365mb_table_l2_method - look-up method for read queries of L2 table
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (or
|
||||
+ * VID)
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from the
|
||||
+ * supplied address
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search only
|
||||
+ * unicast addresses
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search only
|
||||
+ * multicast addresses
|
||||
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but
|
||||
+ * search only entries with matching source port
|
||||
+ *
|
||||
+ * NOTE: Don't change the enum values. They must concur with the field
|
||||
+ * described by @RTL8365MB_TABLE_CTRL_METHOD_MASK
|
||||
+ */
|
||||
+enum rtl8365mb_table_l2_method {
|
||||
+ RTL8365MB_TABLE_L2_METHOD_MAC = 0,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR = 1,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT = 2,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC = 3,
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC = 4,
|
||||
+ /*
|
||||
+ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 = 5,
|
||||
+ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 = 6,
|
||||
+ */
|
||||
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT = 7,
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * rtl8365mb_table_query() - read from or write to a switch table
|
||||
+ * @priv: driver context
|
||||
+ * @table: target table, see &enum rtl8365mb_table
|
||||
+ * @op: read or write operation, see &enum rtl8365mb_table_op
|
||||
+ * @addr: table address. For indexed tables, this selects the entry to access.
|
||||
+ * For L2 read queries, it is ignored as input for MAC-based lookup
|
||||
+ * methods and used as input for address-based lookup methods. On
|
||||
+ * successful L2 queries, it is updated with the matched entry address.
|
||||
+ * @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method.
|
||||
+ * Ignored for non-L2 tables.
|
||||
+ * @port: for L2 read queries using method
|
||||
+ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search
|
||||
+ * to entries associated with this source port. Ignored otherwise.
|
||||
+ * @data: data buffer used to read from or write to the table. For L2 MAC
|
||||
+ * lookups, this buffer provides the lookup key and receives the
|
||||
+ * matched entry contents on success.
|
||||
+ * @size: size of @data in 16-bit words. The caller must ensure that @size
|
||||
+ * matches the target table's entry size and does not exceed
|
||||
+ * RTL8365MB_TABLE_ENTRY_MAX_SIZE.
|
||||
+ *
|
||||
+ * This function provides unified access to the internal tables of the switch.
|
||||
+ * All tables except the L2 table are simple indexed tables, where @addr
|
||||
+ * selects the entry and @op determines whether the access is a read or a
|
||||
+ * write operation.
|
||||
+ *
|
||||
+ * The content of @data is used as input when writing to tables or when
|
||||
+ * specifying the lookup key for L2 MAC searches, and as output for all
|
||||
+ * successful read operations. It remains unchanged during write operations or
|
||||
+ * failed read operations that return %-ENOENT. For other errors during read
|
||||
+ * operations, it is undefined.
|
||||
+ *
|
||||
+ * The L2 table is a hash table and supports multiple lookup methods. For
|
||||
+ * %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC
|
||||
+ * address and FID/VID fields provided in @data, using the same format as
|
||||
+ * an L2 table entry. Address-based methods either read a specific entry
|
||||
+ * (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting
|
||||
+ * from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When using
|
||||
+ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated with
|
||||
+ * the specified @port are considered.
|
||||
+ *
|
||||
+ * On successful L2 operations, @addr is updated with the matched table address
|
||||
+ * or allocated entry address. If no matching entry is found, or if an L2 write
|
||||
+ * operation fails (e.g., due to a full table during addition or a missing entry
|
||||
+ * during deletion), %-ENOENT is returned and @addr remains unchanged. It is the
|
||||
+ * caller's responsibility to map the returned error to the appropriate
|
||||
+ * semantic error.
|
||||
+ *
|
||||
+ * @size must match the size of the target table entry, expressed in 16-bit
|
||||
+ * words.
|
||||
+ *
|
||||
+ * Return: 0 on success, or a negative error code on failure.
|
||||
+ */
|
||||
+int rtl8365mb_table_query(struct realtek_priv *priv,
|
||||
+ enum rtl8365mb_table table,
|
||||
+ enum rtl8365mb_table_op op, u16 *addr,
|
||||
+ enum rtl8365mb_table_l2_method method,
|
||||
+ u16 port, u16 *data, size_t size);
|
||||
+
|
||||
+#endif /* _REALTEK_RTL8365MB_TABLE_H */
|
||||
+1461
File diff suppressed because it is too large
Load Diff
+1131
File diff suppressed because it is too large
Load Diff
+335
@@ -0,0 +1,335 @@
|
||||
From 183bd68b1fe1d5ad584355a7449eea32da79334a Mon Sep 17 00:00:00 2001
|
||||
From: Alvin Šipraga <alsi@bang-olufsen.dk>
|
||||
Date: Sat, 6 Jun 2026 05:29:32 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: add port_bridge_{join,leave}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
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>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-8-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
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(-)
|
||||
|
||||
--- 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);
|
||||
--- 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(s
|
||||
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_sw
|
||||
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 rtl83
|
||||
.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,
|
||||
@@ -2590,6 +2631,10 @@ static const struct dsa_switch_ops rtl83
|
||||
|
||||
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,
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.c
|
||||
@@ -329,6 +329,175 @@ void rtl83xx_reset_deassert(struct realt
|
||||
}
|
||||
|
||||
/**
|
||||
+ * 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
|
||||
* @port: port index
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.h
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.h
|
||||
@@ -21,6 +21,13 @@ void rtl83xx_remove(struct realtek_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,
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
From 660a9e399ab02c0cb86d277ed6b0c9d10c350fdd Mon Sep 17 00:00:00 2001
|
||||
From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Date: Sat, 6 Jun 2026 05:29:33 -0300
|
||||
Subject: net: dsa: realtek: rtl8365mb: add bridge port flags
|
||||
|
||||
Implement support for bridge port flags to control learning and flooding
|
||||
behavior. This patch maps hardware functionalities to the following
|
||||
bridge flags:
|
||||
|
||||
- BR_LEARNING
|
||||
- BR_FLOOD
|
||||
- BR_MCAST_FLOOD
|
||||
- BR_BCAST_FLOOD
|
||||
|
||||
By default, all flooding types are enabled during port setup to ensure
|
||||
standard bridge behavior.
|
||||
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
|
||||
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Link: https://patch.msgid.link/20260606-realtek_forward-v13-9-b9e409687cbe@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/dsa/realtek/realtek.h | 6 ++
|
||||
drivers/net/dsa/realtek/rtl8365mb_main.c | 68 +++++++++++++++++++++
|
||||
drivers/net/dsa/realtek/rtl83xx.c | 101 +++++++++++++++++++++++++++++++
|
||||
drivers/net/dsa/realtek/rtl83xx.h | 4 ++
|
||||
4 files changed, 179 insertions(+)
|
||||
|
||||
--- a/drivers/net/dsa/realtek/realtek.h
|
||||
+++ b/drivers/net/dsa/realtek/realtek.h
|
||||
@@ -134,6 +134,12 @@ struct realtek_ops {
|
||||
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 (*port_set_ucast_flood)(struct realtek_priv *priv, int port,
|
||||
+ bool enable);
|
||||
+ int (*port_set_mcast_flood)(struct realtek_priv *priv, int port,
|
||||
+ bool enable);
|
||||
+ int (*port_set_bcast_flood)(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);
|
||||
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
|
||||
@@ -307,6 +307,21 @@
|
||||
#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \
|
||||
(0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport)))
|
||||
|
||||
+/* Unknown unicast DA flooding port mask */
|
||||
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890
|
||||
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF
|
||||
+
|
||||
+/* Unknown multicast DA flooding port mask */
|
||||
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891
|
||||
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF
|
||||
+
|
||||
+/* Broadcast flooding port mask */
|
||||
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892
|
||||
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF
|
||||
+
|
||||
+#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \
|
||||
+ (BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD)
|
||||
+
|
||||
/* Miscellaneous port configuration register, incl. VLAN egress mode */
|
||||
#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E
|
||||
#define RTL8365MB_PORT_MISC_CFG_REG(_p) \
|
||||
@@ -1568,6 +1583,49 @@ static int rtl8365mb_port_set_learning(s
|
||||
enable ? RTL8365MB_LEARN_LIMIT_MAX : 0);
|
||||
}
|
||||
|
||||
+static int rtl8365mb_port_set_ucast_flood(struct realtek_priv *priv, int port,
|
||||
+ bool enable)
|
||||
+{
|
||||
+ /* Frames with unknown unicast DA will be flooded to a programmable
|
||||
+ * port mask that by default includes all ports. Add or remove
|
||||
+ * the specified port from this port mask accordingly.
|
||||
+ */
|
||||
+ return regmap_update_bits(priv->map,
|
||||
+ RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG,
|
||||
+ BIT(port), enable ? BIT(port) : 0);
|
||||
+}
|
||||
+
|
||||
+static int rtl8365mb_port_set_mcast_flood(struct realtek_priv *priv, int port,
|
||||
+ bool enable)
|
||||
+{
|
||||
+ return regmap_update_bits(priv->map,
|
||||
+ RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG,
|
||||
+ BIT(port), enable ? BIT(port) : 0);
|
||||
+}
|
||||
+
|
||||
+static int rtl8365mb_port_set_bcast_flood(struct realtek_priv *priv, int port,
|
||||
+ bool enable)
|
||||
+{
|
||||
+ return regmap_update_bits(priv->map,
|
||||
+ RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG,
|
||||
+ BIT(port), enable ? BIT(port) : 0);
|
||||
+}
|
||||
+
|
||||
+static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
|
||||
+ struct switchdev_brport_flags flags,
|
||||
+ struct netlink_ext_ack *extack)
|
||||
+{
|
||||
+ struct realtek_priv *priv = ds->priv;
|
||||
+
|
||||
+ dev_dbg(priv->dev, "pre_bridge_flags port:%d flags:%lx supported:%lx\n",
|
||||
+ port, flags.mask, RTL8365MB_SUPPORTED_BRIDGE_FLAGS);
|
||||
+
|
||||
+ if (flags.mask & ~RTL8365MB_SUPPORTED_BRIDGE_FLAGS)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port,
|
||||
u32 efid)
|
||||
{
|
||||
@@ -2422,6 +2480,11 @@ static int rtl8365mb_setup(struct dsa_sw
|
||||
if (ret)
|
||||
goto out_teardown_irq;
|
||||
|
||||
+ /* Enable all types of flooding */
|
||||
+ ret = rtl83xx_setup_port_flood_control(priv, dp->index);
|
||||
+ if (ret)
|
||||
+ goto out_teardown_irq;
|
||||
+
|
||||
/* Set up per-port private data */
|
||||
p->priv = priv;
|
||||
p->index = dp->index;
|
||||
@@ -2608,6 +2671,8 @@ static const struct dsa_switch_ops rtl83
|
||||
.phylink_get_caps = rtl8365mb_phylink_get_caps,
|
||||
.port_bridge_join = rtl83xx_port_bridge_join,
|
||||
.port_bridge_leave = rtl83xx_port_bridge_leave,
|
||||
+ .port_pre_bridge_flags = rtl8365mb_port_pre_bridge_flags,
|
||||
+ .port_bridge_flags = rtl83xx_port_bridge_flags,
|
||||
.port_stp_state_set = rtl8365mb_port_stp_state_set,
|
||||
.port_fast_age = rtl83xx_port_fast_age,
|
||||
.port_fdb_add = rtl83xx_port_fdb_add,
|
||||
@@ -2635,6 +2700,9 @@ static const struct realtek_ops rtl8365m
|
||||
.port_remove_isolation = rtl8365mb_port_remove_isolation,
|
||||
.port_set_efid = rtl8365mb_port_set_efid,
|
||||
.port_set_learning = rtl8365mb_port_set_learning,
|
||||
+ .port_set_ucast_flood = rtl8365mb_port_set_ucast_flood,
|
||||
+ .port_set_mcast_flood = rtl8365mb_port_set_mcast_flood,
|
||||
+ .port_set_bcast_flood = rtl8365mb_port_set_bcast_flood,
|
||||
.l2_add_uc = rtl8365mb_l2_add_uc,
|
||||
.l2_del_uc = rtl8365mb_l2_del_uc,
|
||||
.l2_get_next_uc = rtl8365mb_l2_get_next_uc,
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.c
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.c
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/of_mdio.h>
|
||||
+#include <linux/if_bridge.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "realtek.h"
|
||||
@@ -787,6 +788,106 @@ int rtl83xx_port_mdb_del(struct dsa_swit
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA");
|
||||
|
||||
+/**
|
||||
+ * rtl83xx_port_bridge_flags() - set port bridge flags
|
||||
+ * @ds: DSA switch instance
|
||||
+ * @port: port index
|
||||
+ * @flags: bridge port flags
|
||||
+ * @extack: netlink extended ack for reporting errors
|
||||
+ *
|
||||
+ * This function handles setting bridge port flags like learning and flooding.
|
||||
+ *
|
||||
+ * Context: Can sleep.
|
||||
+ * Return: 0 on success, negative value for failure.
|
||||
+ */
|
||||
+int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
|
||||
+ struct switchdev_brport_flags flags,
|
||||
+ struct netlink_ext_ack *extack)
|
||||
+{
|
||||
+ struct realtek_priv *priv = ds->priv;
|
||||
+ bool enable;
|
||||
+ int ret;
|
||||
+
|
||||
+ if (flags.mask & BR_LEARNING) {
|
||||
+ if (!priv->ops->port_set_learning)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_LEARNING);
|
||||
+ ret = priv->ops->port_set_learning(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (flags.mask & BR_FLOOD) {
|
||||
+ if (!priv->ops->port_set_ucast_flood)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_FLOOD);
|
||||
+ ret = priv->ops->port_set_ucast_flood(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (flags.mask & BR_MCAST_FLOOD) {
|
||||
+ if (!priv->ops->port_set_mcast_flood)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_MCAST_FLOOD);
|
||||
+ ret = priv->ops->port_set_mcast_flood(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (flags.mask & BR_BCAST_FLOOD) {
|
||||
+ if (!priv->ops->port_set_bcast_flood)
|
||||
+ return -EOPNOTSUPP;
|
||||
+
|
||||
+ enable = !!(flags.val & BR_BCAST_FLOOD);
|
||||
+ ret = priv->ops->port_set_bcast_flood(priv, port, enable);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, "REALTEK_DSA");
|
||||
+
|
||||
+/**
|
||||
+ * rtl83xx_setup_port_flood_control() - setup default flood control for a port
|
||||
+ * @priv: realtek_priv pointer
|
||||
+ * @port: port index
|
||||
+ *
|
||||
+ * This function enables flooding for a given port.
|
||||
+ *
|
||||
+ * Context: Can sleep.
|
||||
+ * Return: 0 on success, negative value for failure.
|
||||
+ */
|
||||
+int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ if (priv->ops->port_set_ucast_flood) {
|
||||
+ ret = priv->ops->port_set_ucast_flood(priv, port, true);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (priv->ops->port_set_mcast_flood) {
|
||||
+ ret = priv->ops->port_set_mcast_flood(priv, port, true);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (priv->ops->port_set_bcast_flood) {
|
||||
+ ret = priv->ops->port_set_bcast_flood(priv, port, true);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, "REALTEK_DSA");
|
||||
+
|
||||
MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
|
||||
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
|
||||
MODULE_DESCRIPTION("Realtek DSA switches common module");
|
||||
--- a/drivers/net/dsa/realtek/rtl83xx.h
|
||||
+++ b/drivers/net/dsa/realtek/rtl83xx.h
|
||||
@@ -27,6 +27,10 @@ int rtl83xx_port_bridge_join(struct dsa_
|
||||
struct netlink_ext_ack *extack);
|
||||
void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
|
||||
struct dsa_bridge bridge);
|
||||
+int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
|
||||
+ struct switchdev_brport_flags flags,
|
||||
+ struct netlink_ext_ack *extack);
|
||||
+int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port);
|
||||
|
||||
void rtl83xx_port_fast_age(struct dsa_switch *ds, int port);
|
||||
int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
|
||||
Reference in New Issue
Block a user