diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h
index 1c0ca6d..578490f 100644
--- a/drivers/net/dsa/realtek/realtek.h
+++ b/drivers/net/dsa/realtek/realtek.h
@@ -71,7 +71,9 @@ struct realtek_priv {
* 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.
+ * over FDB. On the RTL8367S-VB (family D), l2_del_uc also looks the
+ * entry up before writing it, as two separate table queries, so it
+ * does depend on this lock.
*/
struct mutex l2_lock;
struct mii_bus *user_mii_bus;diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c
index cd5b333..146075f 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_l2.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c
@@ -45,6 +45,21 @@
#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7)
#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10)
+/* Family D unicast (auto-learn / static) entry */
+#define RTL8365MB_D_L2_UC_D3_VID_MSK GENMASK(11, 0)
+#define RTL8365MB_D_L2_UC_D3_IVL_MSK BIT(13)
+#define RTL8365MB_D_L2_UC_D3_PORT_LO_MSK GENMASK(15, 14)
+#define RTL8365MB_D_L2_UC_D4_PORT_HI_MSK BIT(0)
+#define RTL8365MB_D_L2_UC_D4_AGE_MSK GENMASK(3, 1)
+#define RTL8365MB_D_L2_UC_D4_STATIC_MSK BIT(6)
+
+/* Family D multicast entry */
+#define RTL8365MB_D_L2_MC_D3_VID_MSK GENMASK(11, 0)
+#define RTL8365MB_D_L2_MC_D3_IVL_MSK BIT(13)
+#define RTL8365MB_D_L2_MC_D3_MBR_LO_MSK GENMASK(15, 14)
+#define RTL8365MB_D_L2_MC_D4_MBR_HI_MSK GENMASK(5, 0)
+#define RTL8365MB_D_L2_MC_D4_STATIC_MSK BIT(6)
+
/* 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.
@@ -240,6 +255,76 @@ void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc,
data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val);
}
+void rtl8365mb_d_l2_data_to_uc(const u16 *data,
+ struct rtl8365mb_l2_uc *uc)
+{
+ u16 id = FIELD_GET(RTL8365MB_D_L2_UC_D3_VID_MSK, data[3]);
+
+ rtl8365mb_l2_data_to_mac(data, uc->key.mac_addr);
+ uc->key.ivl = FIELD_GET(RTL8365MB_D_L2_UC_D3_IVL_MSK, data[3]);
+ /* D has one shared VID/FID field, selected by IVL */
+ uc->key.vid = uc->key.ivl ? id : 0;
+ uc->key.fid = uc->key.ivl ? 0 : id;
+ uc->key.efid = 0;
+ uc->port = FIELD_GET(RTL8365MB_D_L2_UC_D3_PORT_LO_MSK, data[3]) |
+ (FIELD_GET(RTL8365MB_D_L2_UC_D4_PORT_HI_MSK, data[4]) << 2);
+ uc->age = FIELD_GET(RTL8365MB_D_L2_UC_D4_AGE_MSK, data[4]);
+ uc->is_static = FIELD_GET(RTL8365MB_D_L2_UC_D4_STATIC_MSK, data[4]);
+}
+
+void rtl8365mb_d_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc,
+ u16 *data)
+{
+ /* A VLAN-unaware bridge reaches us with VID 0. The chip learns such
+ * traffic in SVL (IVL=0, FID=0), so use the same key here, otherwise
+ * the static entry never matches the learned one. Only a real VID
+ * selects IVL.
+ */
+ bool ivl = uc->key.ivl && uc->key.vid;
+ u16 id = ivl ? uc->key.vid : uc->key.fid;
+
+ memset(data, 0, RTL8365MB_D_L2_ENTRY_SIZE * 2);
+ rtl8365mb_l2_mac_to_data(uc->key.mac_addr, data);
+ data[3] = FIELD_PREP(RTL8365MB_D_L2_UC_D3_VID_MSK, id) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D3_IVL_MSK, ivl) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D3_PORT_LO_MSK, uc->port & 0x3);
+ data[4] = FIELD_PREP(RTL8365MB_D_L2_UC_D4_PORT_HI_MSK,
+ (uc->port >> 2) & 0x1) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D4_AGE_MSK, uc->age) |
+ FIELD_PREP(RTL8365MB_D_L2_UC_D4_STATIC_MSK, uc->is_static);
+}
+
+void rtl8365mb_d_l2_data_to_mc(const u16 *data,
+ struct rtl8365mb_l2_mc *mc)
+{
+ rtl8365mb_l2_data_to_mac(data, mc->key.mac_addr);
+ mc->key.vid = FIELD_GET(RTL8365MB_D_L2_MC_D3_VID_MSK, data[3]);
+ mc->key.ivl = FIELD_GET(RTL8365MB_D_L2_MC_D3_IVL_MSK, data[3]);
+ mc->member = FIELD_GET(RTL8365MB_D_L2_MC_D3_MBR_LO_MSK, data[3]) |
+ (FIELD_GET(RTL8365MB_D_L2_MC_D4_MBR_HI_MSK, data[4]) << 2);
+ mc->is_static = FIELD_GET(RTL8365MB_D_L2_MC_D4_STATIC_MSK, data[4]);
+ mc->igmpidx = 0;
+ mc->priority = 0;
+ mc->fwd_pri = 0;
+ mc->igmp_asic = 0;
+}
+
+void rtl8365mb_d_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc,
+ u16 *data)
+{
+ /* Same as for unicast: VID 0 means a VLAN-unaware bridge, use SVL. */
+ bool ivl = mc->key.ivl && mc->key.vid;
+
+ memset(data, 0, RTL8365MB_D_L2_ENTRY_SIZE * 2);
+ rtl8365mb_l2_mac_to_data(mc->key.mac_addr, data);
+ data[3] = FIELD_PREP(RTL8365MB_D_L2_MC_D3_VID_MSK, mc->key.vid) |
+ FIELD_PREP(RTL8365MB_D_L2_MC_D3_IVL_MSK, ivl) |
+ FIELD_PREP(RTL8365MB_D_L2_MC_D3_MBR_LO_MSK, mc->member & 0x3);
+ data[4] = FIELD_PREP(RTL8365MB_D_L2_MC_D4_MBR_HI_MSK,
+ (mc->member >> 2) & 0x3f) |
+ FIELD_PREP(RTL8365MB_D_L2_MC_D4_STATIC_MSK, mc->is_static);
+}
+
/*
* rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry
* @priv: realtek_priv pointer@@ -310,8 +395,11 @@ int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
* OS's back. As a trade-off, these will show up as permanent to users.
*/
uc.is_static = true;
- /* age greater than 0 adds/updates entries */
- uc.age = 1;
+ /* age greater than 0 adds/updates entries. D has no EFID, so efid
+ * is not serialized (bridges are isolated by the port isolation
+ * masks only). Vendor code writes age 6 for D.
+ */
+ uc.age = rtl8365mb_is_d(priv) ? 6 : 1;
fi = rtl8365mb_get_family_info(priv);
fi->l2_uc_to_data(&uc, data);
@@ -349,6 +437,35 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
fi = rtl8365mb_get_family_info(priv);
fi->l2_uc_to_data(&uc, data);
+ if (rtl8365mb_is_d(priv)) {
+ struct rtl8365mb_l2_uc found = { 0 };
+
+ /* Do not blind-write: look the entry up first (as the vendor
+ * code does) so a missing entry is not (re)allocated. The
+ * lookup and the write are separate table queries, so this
+ * is not atomic; it relies on FDB operations being
+ * serialized by the caller.
+ */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, &addr,
+ RTL8365MB_TABLE_L2_METHOD_MAC, 0,
+ data, fi->l2_entry_size);
+ if (ret == -ENOENT)
+ return 0;
+ if (ret)
+ return ret;
+
+ /* The key has no EFID on family D, so the entry found may
+ * belong to another port or bridge: leave it alone.
+ */
+ fi->l2_data_to_uc(data, &found);
+ if (found.port != port)
+ return 0;
+
+ /* rebuild the write payload: the read overwrote data[] */
+ fi->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 stilldiff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h
index a9f9ae4..5bb4e32 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_l2.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h
@@ -13,6 +13,7 @@
#include "realtek.h"
#define RTL8365MB_C_L2_ENTRY_SIZE 6
+#define RTL8365MB_D_L2_ENTRY_SIZE 5
/* Highest L2 entry size of any supported chip, used for sizing */
#define RTL8365MB_L2_ENTRY_SIZE RTL8365MB_C_L2_ENTRY_SIZE
@@ -24,6 +25,11 @@ void rtl8365mb_c_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data);
void rtl8365mb_c_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc);
void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data);
+void rtl8365mb_d_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc);
+void rtl8365mb_d_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data);
+void rtl8365mb_d_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc);
+void rtl8365mb_d_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data);
+
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,
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index e9aac7e..c2b83b7 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -460,6 +460,17 @@
#define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport) \
(RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE + (_physport))
+/* Family D LUT configuration */
+#define RTL8365MB_D_LUT_CFG_REG 0x0A30
+#define RTL8365MB_D_LUT_CFG_BCAM_DISABLE_MASK BIT(6)
+#define RTL8365MB_D_LUT_CFG_IPMC_HASH_MASK BIT(4)
+/* On family C this address is the per-port EFID register! */
+#define RTL8365MB_D_LUT_CFG2_REG 0x0A32
+/* Written as a whole register: the field layout and the unit of this value
+ * are not documented.
+ */
+#define RTL8365MB_D_LUT_AGING_DEFAULT 3000
+
/* Port isolation (forwarding mask) registers */
#define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2
#define RTL8365MB_PORT_ISOLATION_REG(_physport) \
@@ -2892,6 +2903,10 @@ static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port,
u32 efid)
{
+ /* Family D has no EFID; see RTL8365MB_D_LUT_CFG2_REG. */
+ if (rtl8365mb_is_d(priv))
+ return 0;
+
return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port),
RTL8365MB_PORT_EFID_MASK(port),
efid << RTL8365MB_PORT_EFID_OFFSET(port));@@ -3628,6 +3643,23 @@ static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds,
return rtl8365mb_cpu_config(priv);
}
+static int rtl8365mb_d_lut_init(struct realtek_priv *priv)
+{
+ int ret;
+
+ /* After the jam table: lookup multicast by MAC (not DIP) and keep
+ * the CAM enabled.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_D_LUT_CFG_REG,
+ RTL8365MB_D_LUT_CFG_IPMC_HASH_MASK |
+ RTL8365MB_D_LUT_CFG_BCAM_DISABLE_MASK, 0);
+ if (ret)
+ return ret;
+
+ return regmap_write(priv->map, RTL8365MB_D_LUT_CFG2_REG,
+ RTL8365MB_D_LUT_AGING_DEFAULT);
+}
+
static int rtl8365mb_switch_init(struct realtek_priv *priv)
{
struct rtl8365mb *mb = priv->chip_data;@@ -3717,6 +3749,15 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
goto out_error;
}
+ if (rtl8365mb_is_d(priv)) {
+ ret = rtl8365mb_d_lut_init(priv);
+ if (ret) {
+ dev_err(priv->dev, "failed to init LUT: %pe\n",
+ ERR_PTR(ret));
+ goto out_error;
+ }
+ }
+
if (mb->sds_supported) {
ret = rtl8365mb_sds_raise_rate_limits(priv);
if (ret) {@@ -3842,9 +3883,16 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
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);
+ if (!rtl8365mb_is_d(priv)) {
+ ds->fdb_isolation = true;
+ /* The EFID is 3 bits, but EFID 0 is reserved for standalone */
+ ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK);
+ } else {
+ /* No EFID, so bridges cannot be told apart in the LUT: offload
+ * only one, others fall back to software forwarding.
+ */
+ ds->max_num_bridges = 1;
+ }
ds->configure_vlan_while_not_filtering = true;
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/realtek/rtl8365mb_table.c
index f3c8749..6b1323f 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_table.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.c
@@ -5,6 +5,7 @@
*/
#include "rtl8365mb_table.h"
+#include "rtl8365mb.h"
#include <linux/regmap.h>
/* Table access control register */
@@ -16,10 +17,13 @@
/* 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)
+/* Family D: SPA is 3 bits */
+#define RTL8365MB_D_TABLE_CTRL_PORT_MASK GENMASK(10, 8)
/* Table access address register */
#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501
#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0)
+#define RTL8365MB_D_TABLE_ADDR_MASK GENMASK(11, 0)
/* Table status register */
#define RTL8365MB_TABLE_STATUS_REG 0x0502
@@ -29,6 +33,8 @@
#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)
+/* Family D: the address is bits [11:0], there is no EXT bit */
+#define RTL8365MB_D_TABLE_STATUS_ADDRESS_MASK GENMASK(11, 0)
/* Table read/write registers */
#define RTL8365MB_TABLE_WRITE_BASE 0x0510
@@ -59,6 +65,7 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
{
bool addr_as_input = true;
bool write_data = false;
+ bool is_d = rtl8365mb_is_d(priv);
int ret = 0;
u32 cmd;
u32 val;@@ -91,7 +98,13 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
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);
+ if (is_d) {
+ cmd |= FIELD_PREP(RTL8365MB_D_TABLE_CTRL_PORT_MASK,
+ port);
+ } else {
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK,
+ port);
+ }
break;
default:
return -EINVAL;@@ -146,8 +159,9 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
if (addr_as_input) {
ret = regmap_write(priv->map_nolock,
RTL8365MB_TABLE_ACCESS_ADDR_REG,
- FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK,
- *addr));
+ is_d ?
+ FIELD_PREP(RTL8365MB_D_TABLE_ADDR_MASK, *addr) :
+ FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, *addr));
if (ret)
goto out;
}@@ -177,13 +191,23 @@ int rtl8365mb_table_query(struct realtek_priv *priv,
}
/* 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;
+ if (is_d) {
+ /* Bit 11 is "TYPE" in the register map, but the entry
+ * index (2048 hash + 64 CAM) needs 12 bits; the vendor
+ * code reads [11:0] as well.
+ */
+ *addr = FIELD_GET(RTL8365MB_D_TABLE_STATUS_ADDRESS_MASK,
+ val);
+ } else {
+ *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 */diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index ad42c05..cfe7d42 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -602,7 +602,8 @@ int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
/* Bridge ports use bridge.num as EFID, while standalone ports use
* EFID 0. FDB entries for the CPU port follow the bridge EFID due
- * to assisted learning.
+ * to assisted learning. Chips without an EFID in their L2 table
+ * (RTL8367S-VB) ignore it in l2_add_uc().
*/
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
@@ -654,6 +655,8 @@ int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
/*
* 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.
+ * Chips without an EFID in their L2 table (RTL8367S-VB) ignore it in
+ * l2_del_uc().
*/
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
--
2.53.0