[net-next PATCH v8 4/8] net: dsa: realtek: rtl8365mb: add table lookup interface
From: Luiz Angelo Daros de Luca <hidden>
Date: 2026-05-25 19:48:32
Also in:
lkml
Subsystem:
networking drivers, networking [dsa], realtek rtl83xx smi dsa router chips, the rest · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Vladimir Oltean, Linus Walleij, Alvin Šipraga, Linus Torvalds
From: Alvin Šipraga <alsi@bang-olufsen.dk> 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 <redacted> Co-developed-by: Luiz Angelo Daros de Luca <redacted> Signed-off-by: Luiz Angelo Daros de Luca <redacted> --- drivers/net/dsa/realtek/Makefile | 1 + drivers/net/dsa/realtek/rtl8365mb_table.c | 220 ++++++++++++++++++++++++++++++ drivers/net/dsa/realtek/rtl8365mb_table.h | 134 ++++++++++++++++++ 3 files changed, 355 insertions(+)
diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Makefile
index 3f986e04912f..99654c4c5a3d 100644
--- a/drivers/net/dsa/realtek/Makefile
+++ b/drivers/net/dsa/realtek/Makefile@@ -17,3 +17,4 @@ rtl8366-objs += rtl8366rb-leds.o endif obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o rtl8365mb-objs := rtl8365mb_main.o \ + rtl8365mb_table.o \
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/realtek/rtl8365mb_table.c
new file mode 100644
index 000000000000..2c679346e0c9
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.c@@ -0,0 +1,220 @@ +// 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(7, 4) +/* NOTE: PORT_MASK is only 4 bit, which suggests that port-based + * look-up of the L2 table only works for physical port addresses + * 0~4. It could be that the Realtek driver is out-of-date and + * actually the mask is something like 0xFF00, but this is + * unconfirmed. + */ +#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; +}
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.h b/drivers/net/dsa/realtek/rtl8365mb_table.h
new file mode 100644
index 000000000000..236d91cb59c8
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.h@@ -0,0 +1,134 @@ +/* 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 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 lookups, @addr is updated with the matched table address + * and @data contains the corresponding table entry. If no matching entry + * is found, -ENOENT is returned. On failed L2 writes, -ENOSPC is returned. + * + * The contents of @data are 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. If an error occurs, the contents of @addr + * and @data are undefined. + * + * @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 */
--
2.54.0