From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
To: "Andrew Lunn" <andrew@lunn.ch>,
"Vladimir Oltean" <olteanv@gmail.com>,
"David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@google.com>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Simon Horman" <horms@kernel.org>,
"Linus Walleij" <linusw@kernel.org>,
"Alvin Šipraga" <alsi@bang-olufsen.dk>,
"Yury Norov" <yury.norov@gmail.com>,
"Rasmus Villemoes" <linux@rasmusvillemoes.dk>,
"Russell King" <linux@armlinux.org.uk>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
Luiz Angelo Daros de Luca <luizluca@gmail.com>
Subject: [net-next PATCH v2 7/8] net: dsa: realtek: rtl8365mb: add FDB support
Date: Sun, 03 May 2026 03:18:27 -0300 [thread overview]
Message-ID: <20260503-realtek_forward-v2-7-d064e220b391@gmail.com> (raw)
In-Reply-To: <20260503-realtek_forward-v2-0-d064e220b391@gmail.com>
From: Alvin Šipraga <alsi@bang-olufsen.dk>
Implement support for FDB and MDB management for the RTL8365MB series
switches.
The hardware supports IVL by keying the forwarding database with the
{VID, MAC, EFID} tuple. The Extended Filtering ID (EFID) is 3 bits
wide, providing 8 unique filtering domains. This driver reserves EFID 0
for standalone ports, effectively limiting the hardware offload to a
maximum of 7 bridges.
Introduce a mutex lock (l2_lock) to protect concurrent L2 table updates.
Add support for forwarding database operations, including unicast and
multicast entry handling as well as fast aging support.
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>
---
drivers/net/dsa/realtek/Makefile | 1 +
drivers/net/dsa/realtek/realtek.h | 29 ++
drivers/net/dsa/realtek/rtl8365mb_l2.c | 494 +++++++++++++++++++++++++++++++
drivers/net/dsa/realtek/rtl8365mb_l2.h | 32 ++
drivers/net/dsa/realtek/rtl8365mb_main.c | 21 +-
drivers/net/dsa/realtek/rtl83xx.c | 268 +++++++++++++++++
drivers/net/dsa/realtek/rtl83xx.h | 16 +
7 files changed, 860 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Makefile
index b7fc4e852fd8..6c329e046d0b 100644
--- a/drivers/net/dsa/realtek/Makefile
+++ b/drivers/net/dsa/realtek/Makefile
@@ -19,3 +19,4 @@ obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
rtl8365mb-objs := rtl8365mb_main.o \
rtl8365mb_table.o \
rtl8365mb_vlan.o \
+ rtl8365mb_l2.o \
diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h
index 0942f534834d..ef2d3ddfef60 100644
--- a/drivers/net/dsa/realtek/realtek.h
+++ b/drivers/net/dsa/realtek/realtek.h
@@ -45,6 +45,12 @@ struct rtl8366_vlan_4k {
u8 fid;
};
+struct realtek_fdb_entry {
+ u8 mac_addr[ETH_ALEN];
+ u16 vid;
+ bool is_static;
+};
+
struct realtek_priv {
struct device *dev;
struct reset_control *reset_ctl;
@@ -54,6 +60,15 @@ struct realtek_priv {
struct regmap *map;
struct regmap *map_nolock;
struct mutex map_lock;
+ /* l2_lock is used to prevent concurrent modifications of L2 table
+ * entries while another function is reading it. l2_(add,del)_mc
+ * is an example that first read current table entry and then
+ * create/update it. l2_(add|del)_uc uses a single table op and,
+ * internally, it might not need this lock. However, altering FDB
+ * may still collide, as well as l2_flush, with fdb_dump iterating
+ * over FDB.
+ */
+ struct mutex l2_lock;
struct mii_bus *user_mii_bus;
struct mii_bus *bus;
int mdio_addr;
@@ -112,6 +127,19 @@ struct realtek_ops {
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 (*l2_add_uc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN],
+ u16 efid, u16 vid);
+ int (*l2_del_uc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN],
+ u16 efid, u16 vid);
+ int (*l2_get_next_uc)(struct realtek_priv *priv, u16 *addr, int port,
+ struct realtek_fdb_entry *entry);
+ int (*l2_add_mc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN], u16 vid);
+ int (*l2_del_mc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN], u16 vid);
+ int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid);
int (*phy_read)(struct realtek_priv *priv, int phy, int regnum);
int (*phy_write)(struct realtek_priv *priv, int phy, int regnum,
u16 val);
@@ -124,6 +152,7 @@ struct realtek_variant {
unsigned int clk_delay;
u8 cmd_read;
u8 cmd_write;
+ u16 l2_table_size;
size_t chip_data_sz;
};
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c
new file mode 100644
index 000000000000..6aa3ce5fa4a3
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Forwarding and multicast database interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ */
+
+#include <linux/etherdevice.h>
+
+#include "rtl8365mb_l2.h"
+#include "rtl8365mb_table.h"
+#include <linux/regmap.h>
+
+#define RTL8365MB_L2_ENTRY_SIZE 6
+
+#define RTL8365MB_L2_UC_D0_MAC5_MASK GENMASK(7, 0)
+#define RTL8365MB_L2_UC_D0_MAC4_MASK GENMASK(15, 8)
+#define RTL8365MB_L2_UC_D1_MAC3_MASK GENMASK(7, 0)
+#define RTL8365MB_L2_UC_D1_MAC2_MASK GENMASK(15, 8)
+#define RTL8365MB_L2_UC_D2_MAC1_MASK GENMASK(7, 0)
+#define RTL8365MB_L2_UC_D2_MAC0_MASK GENMASK(15, 8)
+#define RTL8365MB_L2_UC_D3_VID_MASK GENMASK(11, 0)
+#define RTL8365MB_L2_UC_D3_IVL_MASK GENMASK(13, 13)
+#define RTL8365MB_L2_UC_D3_PORT_EXT_MASK GENMASK(15, 15)
+#define RTL8365MB_L2_UC_D4_EFID_MASK GENMASK(2, 0)
+#define RTL8365MB_L2_UC_D4_FID_MASK GENMASK(6, 3)
+#define RTL8365MB_L2_UC_D4_SA_PRI_MASK GENMASK(7, 7)
+#define RTL8365MB_L2_UC_D4_PORT_MASK GENMASK(10, 8)
+#define RTL8365MB_L2_UC_D4_AGE_MASK GENMASK(13, 11)
+#define RTL8365MB_L2_UC_D4_AUTH_MASK GENMASK(14, 14)
+#define RTL8365MB_L2_UC_D4_SA_BLOCK_MASK GENMASK(15, 15)
+#define RTL8365MB_L2_UC_D5_DA_BLOCK_MASK GENMASK(0, 0)
+#define RTL8365MB_L2_UC_D5_PRIORITY_MASK GENMASK(3, 1)
+#define RTL8365MB_L2_UC_D5_FWD_PRI_MASK GENMASK(4, 4)
+#define RTL8365MB_L2_UC_D5_STATIC_MASK GENMASK(5, 5)
+
+#define RTL8365MB_L2_MC_MAC5_MASK GENMASK(7, 0) /* D0 */
+#define RTL8365MB_L2_MC_MAC4_MASK GENMASK(15, 8) /* D0 */
+#define RTL8365MB_L2_MC_MAC3_MASK GENMASK(7, 0) /* D1 */
+#define RTL8365MB_L2_MC_MAC2_MASK GENMASK(15, 8) /* D1 */
+#define RTL8365MB_L2_MC_MAC1_MASK GENMASK(7, 0) /* D2 */
+#define RTL8365MB_L2_MC_MAC0_MASK GENMASK(15, 8) /* D2 */
+#define RTL8365MB_L2_MC_VID_MASK GENMASK(11, 0) /* D3 */
+#define RTL8365MB_L2_MC_IVL_MASK GENMASK(13, 13) /* D3 */
+#define RTL8365MB_L2_MC_MBR_EXT1_MASK GENMASK(15, 14) /* D3 */
+
+#define RTL8365MB_L2_MC_MBR_MASK GENMASK(7, 0) /* D4 */
+#define RTL8365MB_L2_MC_IGMPIDX_MASK GENMASK(15, 8) /* D4 */
+
+#define RTL8365MB_L2_MC_IGMP_ASIC_MASK GENMASK(0, 0) /* D5 */
+#define RTL8365MB_L2_MC_PRIORITY_MASK GENMASK(3, 1) /* D5 */
+#define RTL8365MB_L2_MC_FWD_PRI_MASK GENMASK(4, 4) /* D5 */
+#define RTL8365MB_L2_MC_STATIC_MASK GENMASK(5, 5) /* D5 */
+#define RTL8365MB_L2_MC_MBR_EXT2_MASK GENMASK(7, 7) /* D5 */
+
+/* Port flush command registers - writing a 1 to the port's MASK bit will
+ * initiate the flush procedure. Completion is signalled when the corresponding
+ * BUSY bit is 0.
+ */
+#define RTL8365MB_L2_FLUSH_PORT_REG 0x0A36
+#define RTL8365MB_L2_FLUSH_PORT_MASK_MASK GENMASK(7, 0)
+#define RTL8365MB_L2_FLUSH_PORT_BUSY_MASK GENMASK(15, 8)
+
+#define RTL8365MB_L2_FLUSH_PORT_EXT_REG 0x0A35
+#define RTL8365MB_L2_FLUSH_PORT_EXT_MASK_MASK GENMASK(2, 0)
+#define RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MASK GENMASK(5, 3)
+
+#define RTL8365MB_L2_FLUSH_CTRL1_REG 0x0A37
+#define RTL8365MB_L2_FLUSH_CTRL1_VID_MASK GENMASK(11, 0)
+#define RTL8365MB_L2_FLUSH_CTRL1_FID_MASK GENMASK(15, 12)
+
+#define RTL8365MB_L2_FLUSH_CTRL2_REG 0x0A38
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_MASK GENMASK(1, 0)
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT 0
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID 1
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_FID 2
+#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_MASK GENMASK(2, 2)
+#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC 0
+#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_BOTH 0
+
+/* This flushes the entire LUT, reading it back it will turn 0 when the
+ * operation is complete
+ */
+#define RTL8365MB_L2_FLUSH_CTRL3_REG 0x0A39
+#define RTL8365MB_L2_FLUSH_CTRL3_MASK GENMASK(0, 0)
+
+struct rtl8365mb_l2_uc_key {
+ u8 mac_addr[ETH_ALEN];
+ union {
+ u16 vid; /* IVL */
+ u16 fid; /* SVL */
+ };
+ bool ivl;
+ u16 efid;
+};
+
+struct rtl8365mb_l2_uc {
+ struct rtl8365mb_l2_uc_key key;
+ u8 port;
+ u8 age;
+ u8 priority;
+
+ bool sa_block;
+ bool da_block;
+ bool auth;
+ bool is_static;
+ bool sa_pri;
+ bool fwd_pri;
+};
+
+struct rtl8365mb_l2_mc_key {
+ u8 mac_addr[ETH_ALEN];
+ union {
+ u16 vid; /* IVL */
+ u16 fid; /* SVL */
+ };
+ bool ivl;
+};
+
+struct rtl8365mb_l2_mc {
+ struct rtl8365mb_l2_mc_key key;
+ u16 member;
+ u8 priority;
+ u8 igmpidx;
+
+ bool is_static;
+ bool fwd_pri;
+ bool igmp_asic;
+};
+
+static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc)
+{
+ uc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MASK, data[0]);
+ uc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MASK, data[0]);
+ uc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MASK, data[1]);
+ uc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MASK, data[1]);
+ uc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MASK, data[2]);
+ uc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MASK, data[2]);
+ uc->key.efid = FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MASK, data[4]);
+ uc->key.vid = FIELD_GET(RTL8365MB_L2_UC_D3_VID_MASK, data[3]);
+ uc->key.ivl = FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MASK, data[3]);
+ uc->key.fid = FIELD_GET(RTL8365MB_L2_UC_D4_FID_MASK, data[4]);
+ uc->age = FIELD_GET(RTL8365MB_L2_UC_D4_AGE_MASK, data[4]);
+ uc->auth = FIELD_GET(RTL8365MB_L2_UC_D4_AUTH_MASK, data[4]);
+ uc->port = FIELD_GET(RTL8365MB_L2_UC_D4_PORT_MASK, data[4]) |
+ (FIELD_GET(RTL8365MB_L2_UC_D3_PORT_EXT_MASK, data[3]) << 3);
+ uc->sa_pri = FIELD_GET(RTL8365MB_L2_UC_D4_SA_PRI_MASK, data[4]);
+ uc->fwd_pri = FIELD_GET(RTL8365MB_L2_UC_D5_FWD_PRI_MASK, data[5]);
+ uc->sa_block = FIELD_GET(RTL8365MB_L2_UC_D4_SA_BLOCK_MASK, data[4]);
+ uc->da_block = FIELD_GET(RTL8365MB_L2_UC_D5_DA_BLOCK_MASK, data[5]);
+ uc->priority = FIELD_GET(RTL8365MB_L2_UC_D5_PRIORITY_MASK, data[5]);
+ uc->is_static = FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MASK, data[5]);
+}
+
+static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data)
+{
+ memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2);
+ data[0] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MASK, uc->key.mac_addr[5]);
+ data[0] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MASK, uc->key.mac_addr[4]);
+ data[1] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MASK, uc->key.mac_addr[3]);
+ data[1] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MASK, uc->key.mac_addr[2]);
+ data[2] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MASK, uc->key.mac_addr[1]);
+ data[2] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MASK, uc->key.mac_addr[0]);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MASK, uc->key.vid);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MASK, uc->key.ivl);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_PORT_EXT_MASK, uc->port >> 3);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_FID_MASK, uc->key.fid);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_EFID_MASK, uc->key.efid);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AGE_MASK, uc->age);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AUTH_MASK, uc->auth);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_PORT_MASK, uc->port);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_PRI_MASK, uc->sa_pri);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_BLOCK_MASK, uc->sa_block);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_FWD_PRI_MASK, uc->fwd_pri);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_DA_BLOCK_MASK, uc->da_block);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_PRIORITY_MASK, uc->priority);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MASK, uc->is_static);
+}
+
+static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc)
+{
+ mc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_MC_MAC5_MASK, data[0]);
+ mc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_MC_MAC4_MASK, data[0]);
+ mc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_MC_MAC3_MASK, data[1]);
+ mc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_MC_MAC2_MASK, data[1]);
+ mc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_MC_MAC1_MASK, data[2]);
+ mc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_MC_MAC0_MASK, data[2]);
+ mc->key.vid = FIELD_GET(RTL8365MB_L2_MC_VID_MASK, data[3]);
+ mc->key.ivl = FIELD_GET(RTL8365MB_L2_MC_IVL_MASK, data[3]);
+ mc->priority = FIELD_GET(RTL8365MB_L2_MC_PRIORITY_MASK, data[5]);
+ mc->fwd_pri = FIELD_GET(RTL8365MB_L2_MC_FWD_PRI_MASK, data[5]);
+ mc->is_static = FIELD_GET(RTL8365MB_L2_MC_STATIC_MASK, data[5]);
+ mc->member = FIELD_GET(RTL8365MB_L2_MC_MBR_MASK, data[4]) |
+ (FIELD_GET(RTL8365MB_L2_MC_MBR_EXT1_MASK, data[3]) << 8) |
+ (FIELD_GET(RTL8365MB_L2_MC_MBR_EXT2_MASK, data[5]) << 8);
+ mc->igmpidx = FIELD_GET(RTL8365MB_L2_MC_IGMPIDX_MASK, data[4]);
+ mc->igmp_asic = FIELD_GET(RTL8365MB_L2_MC_IGMP_ASIC_MASK, data[5]);
+}
+
+static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data)
+{
+ memset(data, 0, 12);
+ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_MAC5_MASK, mc->key.mac_addr[5]);
+ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_MAC4_MASK, mc->key.mac_addr[4]);
+ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_MAC3_MASK, mc->key.mac_addr[3]);
+ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_MAC2_MASK, mc->key.mac_addr[2]);
+ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_MAC1_MASK, mc->key.mac_addr[1]);
+ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_MAC0_MASK, mc->key.mac_addr[0]);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_VID_MASK, mc->key.vid);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_IVL_MASK, mc->key.ivl);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_MBR_EXT1_MASK, mc->member >> 8);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_MBR_MASK, mc->member);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_IGMPIDX_MASK, mc->igmpidx);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_IGMP_ASIC_MASK, mc->igmp_asic);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_PRIORITY_MASK, mc->priority);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_FWD_PRI_MASK, mc->fwd_pri);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_STATIC_MASK, mc->is_static);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_MBR_EXT2_MASK, mc->member >> 10);
+}
+
+/**
+ * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry
+ *
+ * @priv: realtek_priv pointer
+ * @addr: as input, the table index to start the walk
+ * as output, the found table index
+ * @port: restrict the walk on entries related to port
+ * @uc: returned L2 Unicast table entry
+ *
+ * This function get the next unicast L2 table entry starting from @addr
+ * and checking exclusively entries related to @port. If no more entries
+ * were found, the output @addr will be lower than the input @addr and @uc
+ * will not be overwritten.
+ *
+ * Return: Returns 0 on success, a negative error on failure.
+ **/
+int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
+ struct realtek_fdb_entry *entry)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_uc uc;
+ int ret;
+
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, addr,
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT,
+ port, data, RTL8365MB_L2_ENTRY_SIZE);
+ if (ret)
+ return ret;
+
+ rtl8365mb_l2_data_to_uc(data, &uc);
+
+ ether_addr_copy(entry->mac_addr, uc.key.mac_addr);
+ entry->vid = uc.key.vid;
+ entry->is_static = uc.is_static;
+
+ return 0;
+}
+
+int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 efid, u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_uc uc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN);
+ uc.key.efid = efid;
+ uc.key.ivl = true;
+ uc.key.vid = vid;
+ uc.port = port;
+ /* do not let HW decrease age */
+ uc.is_static = true;
+ /* age greater than 0 adds/updates entries */
+ uc.age = 1;
+ rtl8365mb_l2_uc_to_data(&uc, data);
+
+ /* add the new entry or update an existing one */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ /* addr will hold the table index, but it is not used here */
+ if (ret == -ENOENT) {
+ /* -ENOENT means the just added entry was not found (and @addr
+ * does not hold the table index. Although any error will be
+ * treated equally by the caller, assume that the missing entry
+ * means the table is full (tested in real HW).
+ */
+ return -ENOSPC;
+ }
+ return ret;
+}
+
+int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 efid, u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_uc uc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN);
+ uc.key.efid = efid;
+ uc.key.ivl = true;
+ uc.key.vid = vid;
+ /* age 0 deletes the entry */
+ uc.age = 0;
+ rtl8365mb_l2_uc_to_data(&uc, data);
+
+ /* it looks like the switch will always add/update the entry,
+ * even when age is 0 or uc.key did not match an existing entry,
+ * just to immediately drop it because age is zero. You can still
+ * get the added/updated address from @addr
+ */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ /* addr will hold the table index, but it is not used here */
+ return ret;
+}
+
+int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid)
+{
+ int mode = vid ? RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID :
+ RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT;
+ u32 val, mask;
+ int ret;
+
+ mutex_lock(&priv->map_lock);
+
+ /* Configure flushing mode; only flush dynamic entries */
+ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL2_REG,
+ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_MODE_MASK,
+ mode) |
+ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_TYPE_MASK,
+ RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC));
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL1_REG,
+ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL1_VID_MASK, vid));
+
+ if (ret)
+ goto out;
+ /* Now issue the flush command and wait for its completion. There are
+ * two registers for this purpose, and which one to use depends on the
+ * port number. The _EXT register is for ports 8 or higher.
+ */
+ if (port < 8) {
+ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_MASK_MASK,
+ BIT(port) & 0xFF);
+ ret = regmap_write(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_REG, val);
+ if (ret)
+ goto out;
+
+ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_BUSY_MASK,
+ BIT(port) & 0xFF);
+ ret = regmap_read_poll_timeout(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_REG,
+ val, !(val & mask), 10, 100);
+ if (ret)
+ goto out;
+ } else {
+ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_MASK_MASK,
+ BIT(port) >> 8);
+ ret = regmap_write(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_EXT_REG, val);
+ if (ret)
+ goto out;
+
+ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MASK,
+ BIT(port) >> 8);
+ ret = regmap_read_poll_timeout(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_EXT_REG,
+ val, !(val & mask), 10, 100);
+ if (ret)
+ goto out;
+ }
+
+out:
+ mutex_unlock(&priv->map_lock);
+
+ return ret;
+}
+
+int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_mc mc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN);
+ mc.key.vid = vid;
+ mc.key.ivl = true;
+ /* Already set the port and is_static, although not used in OP_READ,
+ * data will be ready for OP_WRITE if it is a new entry.
+ */
+ mc.member |= BIT(port);
+ mc.is_static = 1;
+ rtl8365mb_l2_mc_to_data(&mc, data);
+
+ /* First look for an existing entry (to get existing port members) */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, &addr,
+ RTL8365MB_TABLE_L2_METHOD_MAC, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ if (!ret) {
+ /* There is already an entry... */
+ rtl8365mb_l2_data_to_mc(data, &mc);
+ /* the port must be added as a member */
+ mc.member |= BIT(port);
+ rtl8365mb_l2_mc_to_data(&mc, data);
+ } else if (ret == -ENOENT) {
+ /* New entry, no need to update data again as it already
+ * includes the member
+ */
+ } else {
+ return ret;
+ }
+
+ /* add the new entry or update an existing one */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ /* addr will hold the table index, but it is not used here */
+ if (ret == -ENOENT) {
+ /* -ENOENT means the just added entry was not found (and @addr
+ * does not hold the table index. Although any error will be
+ * treated equally by the caller, assume that the missing entry
+ * means the table is full (tested in real HW).
+ */
+ return -ENOSPC;
+ }
+
+ return ret;
+}
+
+int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_mc mc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN);
+ mc.key.vid = vid;
+ mc.key.ivl = true;
+ rtl8365mb_l2_mc_to_data(&mc, data);
+
+ /* First look for an existing entry (to get existing port members) */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, &addr,
+ RTL8365MB_TABLE_L2_METHOD_MAC, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ if (ret)
+ /* Any error, including -ENOENT is unexpected */
+ return ret;
+
+ rtl8365mb_l2_data_to_mc(data, &mc);
+ /* the port must be removed as a member */
+ mc.member &= ~BIT(port);
+ if (!mc.member) {
+ /* With no members, zero all non-key fields to delete the
+ * entry. However is_static is everything else we wrote.
+ * (and probably all that is needed by the HW)
+ */
+ mc.is_static = 0;
+ }
+ rtl8365mb_l2_mc_to_data(&mc, data);
+
+ /* update the existing entry. */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ return ret;
+}
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h
new file mode 100644
index 000000000000..9470cf059ce5
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Forwarding and multicast database interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ */
+
+#ifndef _REALTEK_RTL8365MB_L2_H
+#define _REALTEK_RTL8365MB_L2_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+
+#include "realtek.h"
+
+int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
+ struct realtek_fdb_entry *entry);
+int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
+ const unsigned char addr[static ETH_ALEN],
+ u16 efid, u16 vid);
+int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
+ const unsigned char addr[static ETH_ALEN],
+ u16 efid, u16 vid);
+int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid);
+
+int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid);
+int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid);
+
+#endif /* _REALTEK_RTL8365MB_L2_H */
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 576bec52d863..1b8034311b17 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -104,6 +104,7 @@
#include "realtek-smi.h"
#include "realtek-mdio.h"
#include "rtl83xx.h"
+#include "rtl8365mb_l2.h"
#include "rtl8365mb_vlan.h"
/* Family-specific data and limits */
@@ -111,8 +112,9 @@
#define RTL8365MB_NUM_PHYREGS 32
#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1)
#define RTL8365MB_MAX_NUM_PORTS 11
-#define RTL8365MB_MAX_NUM_EXTINTS 3
+/* Valid for the whole family except RTL8370B, which has 4160 entries. */
#define RTL8365MB_LEARN_LIMIT_MAX 2112
+#define RTL8365MB_MAX_NUM_EXTINTS 3
/* Chip identification registers */
#define RTL8365MB_CHIP_ID_REG 0x1300
@@ -2229,6 +2231,8 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
mb = priv->chip_data;
cpu = &mb->cpu;
+ mutex_init(&priv->l2_lock);
+
ret = rtl8365mb_reset_chip(priv);
if (ret) {
dev_err(priv->dev, "failed to reset chip: %pe\n",
@@ -2314,6 +2318,8 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
if (ret)
goto out_teardown_irq;
+ ds->assisted_learning_on_cpu_port = true;
+ ds->fdb_isolation = true;
/* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */
ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK);
ds->configure_vlan_while_not_filtering = true;
@@ -2442,6 +2448,12 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = {
.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,
+ .port_fdb_del = rtl83xx_port_fdb_del,
+ .port_fdb_dump = rtl83xx_port_fdb_dump,
+ .port_mdb_add = rtl83xx_port_mdb_add,
+ .port_mdb_del = rtl83xx_port_mdb_del,
.port_vlan_add = rtl8365mb_port_vlan_add,
.port_vlan_del = rtl8365mb_port_vlan_del,
.port_vlan_filtering = rtl8365mb_port_vlan_filtering,
@@ -2463,6 +2475,12 @@ static const struct realtek_ops rtl8365mb_ops = {
.port_add_isolation = rtl8365mb_port_add_isolation,
.port_remove_isolation = rtl8365mb_port_remove_isolation,
.port_set_efid = rtl8365mb_port_set_efid,
+ .l2_add_uc = rtl8365mb_l2_add_uc,
+ .l2_del_uc = rtl8365mb_l2_del_uc,
+ .l2_get_next_uc = rtl8365mb_l2_get_next_uc,
+ .l2_add_mc = rtl8365mb_l2_add_mc,
+ .l2_del_mc = rtl8365mb_l2_del_mc,
+ .l2_flush = rtl8365mb_l2_flush,
.phy_read = rtl8365mb_phy_read,
.phy_write = rtl8365mb_phy_write,
};
@@ -2474,6 +2492,7 @@ const struct realtek_variant rtl8365mb_variant = {
.clk_delay = 10,
.cmd_read = 0xb9,
.cmd_write = 0xb8,
+ .l2_table_size = RTL8365MB_LEARN_LIMIT_MAX,
.chip_data_sz = sizeof(struct rtl8365mb),
};
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index 3ab91cd82743..36158209a192 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -439,6 +439,274 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
}
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
+ *
+ * This function requests the switch to age out dynamic FDB entries learned on
+ * @port.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+ */
+void rtl83xx_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct realtek_priv *priv = ds->priv;
+ int ret;
+
+ if (!priv->ops->l2_flush) {
+ dev_warn_once(priv->dev, "l2_flush op not defined\n");
+ return;
+ }
+
+ dev_dbg(priv->dev, "fast_age port %d\n", port);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_flush(priv, port, 0);
+ mutex_unlock(&priv->l2_lock);
+ if (ret)
+ dev_err(priv->dev, "failed to fast age on port %d: %d\n", port,
+ ret);
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_add() - add a static FDB entry to a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @addr: MAC address to add
+ * @vid: VLAN ID associated with @addr
+ * @db: database where the entry should be added
+ *
+ * This function adds a static unicast FDB entry to the standalone port
+ * database or to a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int efid;
+ int ret;
+
+ if (!priv->ops->l2_add_uc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /*
+ * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while
+ * DSA_DB_PORT use the default EFID (0), not used by any bridge.
+ */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "fdb_add port %d addr %pM efid %d vid %d\n",
+ port, addr, efid, vid);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_add_uc(priv, port, addr, efid, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_del() - delete a static FDB entry from a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @addr: MAC address to delete
+ * @vid: VLAN ID associated with @addr
+ * @db: database where the entry should be removed
+ *
+ * This function deletes a static unicast FDB entry from the standalone port
+ * database or from a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int efid;
+ int ret;
+
+ if (!priv->ops->l2_del_uc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /*
+ * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while
+ * DSA_DB_PORT use the default EFID (0), not used by any bridge.
+ */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "fdb_del port %d addr %pM efid %d vid %d\n",
+ port, addr, efid, vid);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_del_uc(priv, port, addr, efid, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a port
+ * @ds: DSA switch instance
+ * @port: port index
+ * @cb: callback invoked for each entry
+ * @data: opaque pointer passed to @cb
+ *
+ * This function walks the unicast FDB entries associated with @port and calls
+ * @cb for each matching entry.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, -ENOENT when the table walk reaches the end, or
+ * another negative value for failure.
+ */
+int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct realtek_fdb_entry entry;
+ u16 l2_table_size = priv->variant->l2_table_size;
+ u16 start_addr, addr = 0;
+ int ret = 0;
+
+ if (!priv->ops->l2_get_next_uc)
+ return -EOPNOTSUPP;
+ if (!l2_table_size) {
+ dev_warn_once(priv->dev, "l2_table_size not defined\n");
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&priv->l2_lock);
+ while (true) {
+ start_addr = addr;
+
+ dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n",
+ addr, port);
+ ret = priv->ops->l2_get_next_uc(priv, &addr, port, &entry);
+ dev_dbg(priv->dev,
+ "l2_get_next_uc addr:%d mac:%pM vid:%d static:%d ret:%pe\n",
+ addr, entry.mac_addr, entry.vid, entry.is_static,
+ ERR_PTR(ret));
+
+ if (ret == -ENOENT)
+ break;
+ if (ret)
+ break;
+
+ if (addr < start_addr)
+ break;
+
+ cb(entry.mac_addr, entry.vid, entry.is_static, data);
+
+ addr++;
+
+ /* Avoid querying beyond the valid L2 table range. */
+ if (addr > l2_table_size)
+ break;
+ }
+ mutex_unlock(&priv->l2_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_mdb_add() - add a multicast database entry to a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @mdb: multicast database entry to add
+ * @db: database where the entry should be added
+ *
+ * This function adds a multicast database entry to the standalone port
+ * database or to a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int ret;
+
+ if (!priv->ops->l2_add_mc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ dev_dbg(priv->dev, "mdb_add port %d addr %pM vid %d\n",
+ port, mdb->addr, mdb->vid);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_add_mc(priv, port, mdb->addr, mdb->vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_mdb_del() - delete a multicast database entry from a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @mdb: multicast database entry to delete
+ * @db: database where the entry should be removed
+ *
+ * This function deletes a multicast database entry from the standalone port
+ * database or from a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int ret;
+
+ if (!priv->ops->l2_del_mc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ dev_dbg(priv->dev, "mdb_del port %d addr %pM vid %d\n",
+ port, mdb->addr, mdb->vid);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_del_mc(priv, port, mdb->addr, mdb->vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "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");
diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rtl83xx.h
index 2481a1aaa226..dcb819fe567f 100644
--- a/drivers/net/dsa/realtek/rtl83xx.h
+++ b/drivers/net/dsa/realtek/rtl83xx.h
@@ -28,4 +28,20 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
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,
+ struct dsa_db db);
+int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db);
+int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data);
+int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db);
+int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db);
+
#endif /* _RTL83XX_H */
--
2.53.0
next prev parent reply other threads:[~2026-05-03 6:19 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-03 6:18 [net-next PATCH v2 0/8] net: dsa: realtek: rtl8365mb: bridge offloading and VLAN support Luiz Angelo Daros de Luca
2026-05-03 6:18 ` [net-next PATCH v2 1/8] net: dsa: realtek: rtl8365mb: use ERR_PTR Luiz Angelo Daros de Luca
2026-05-05 12:21 ` Linus Walleij
2026-05-03 6:18 ` [net-next PATCH v2 2/8] net: dsa: realtek: rtl8365mb: use dsa helpers for port iteration Luiz Angelo Daros de Luca
2026-05-05 12:23 ` Linus Walleij
2026-05-03 6:18 ` [net-next PATCH v2 3/8] net: dsa: realtek: rtl8365mb: prepare for multiple source files Luiz Angelo Daros de Luca
2026-05-03 6:18 ` [net-next PATCH v2 4/8] net: dsa: realtek: rtl8365mb: add table lookup interface Luiz Angelo Daros de Luca
2026-05-06 1:25 ` Jakub Kicinski
2026-05-06 1:26 ` Jakub Kicinski
2026-05-07 2:51 ` Luiz Angelo Daros de Luca
2026-05-03 6:18 ` [net-next PATCH v2 5/8] net: dsa: realtek: rtl8365mb: add VLAN support Luiz Angelo Daros de Luca
2026-05-05 12:25 ` Linus Walleij
2026-05-03 6:18 ` [net-next PATCH v2 6/8] net: dsa: realtek: rtl8365mb: add port_bridge_{join,leave} Luiz Angelo Daros de Luca
2026-05-05 12:25 ` Linus Walleij
2026-05-03 6:18 ` Luiz Angelo Daros de Luca [this message]
2026-05-05 12:27 ` [net-next PATCH v2 7/8] net: dsa: realtek: rtl8365mb: add FDB support Linus Walleij
2026-05-06 1:27 ` Jakub Kicinski
2026-05-07 2:50 ` Luiz Angelo Daros de Luca
2026-05-03 6:18 ` [net-next PATCH v2 8/8] net: dsa: realtek: rtl8365mb: add bridge port flags Luiz Angelo Daros de Luca
2026-05-05 12:27 ` Linus Walleij
2026-05-05 21:01 ` Luiz Angelo Daros de Luca
2026-05-10 8:54 ` [net-next PATCH v2 0/8] net: dsa: realtek: rtl8365mb: bridge offloading and VLAN support Simon Horman
2026-05-11 4:53 ` Luiz Angelo Daros de Luca
2026-05-12 9:36 ` Simon Horman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260503-realtek_forward-v2-7-d064e220b391@gmail.com \
--to=luizluca@gmail.com \
--cc=alsi@bang-olufsen.dk \
--cc=andrew@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=linux@rasmusvillemoes.dk \
--cc=netdev@vger.kernel.org \
--cc=olteanv@gmail.com \
--cc=pabeni@redhat.com \
--cc=yury.norov@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.