]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: dsa: realtek: rtl8365mb: add table lookup interface
authorAlvin Šipraga <alsi@bang-olufsen.dk>
Sat, 6 Jun 2026 08:29:29 +0000 (05:29 -0300)
committerJakub Kicinski <kuba@kernel.org>
Wed, 10 Jun 2026 02:35:41 +0000 (19:35 -0700)
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
drivers/net/dsa/realtek/rtl8365mb_table.c [new file with mode: 0644]
drivers/net/dsa/realtek/rtl8365mb_table.h [new file with mode: 0644]

index a1486e7edbc44e5bf5af6549b37794fc63d633aa..27ffe7cd91fafb5be27fb90c29aa8479d9e4f457 100644 (file)
@@ -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
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/realtek/rtl8365mb_table.c
new file mode 100644 (file)
index 0000000..f3c8749
--- /dev/null
@@ -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;
+}
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.h b/drivers/net/dsa/realtek/rtl8365mb_table.h
new file mode 100644 (file)
index 0000000..41280eb
--- /dev/null
@@ -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 */