mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 10:09:10 +02:00
net: dsa: realtek: rtl8365mb: add table lookup interface
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>
This commit is contained in:
parent
0e692c27fe
commit
fbafdd3b22
|
|
@ -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
|
||||
|
|
|
|||
214
drivers/net/dsa/realtek/rtl8365mb_table.c
Normal file
214
drivers/net/dsa/realtek/rtl8365mb_table.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
138
drivers/net/dsa/realtek/rtl8365mb_table.h
Normal file
138
drivers/net/dsa/realtek/rtl8365mb_table.h
Normal file
|
|
@ -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 */
|
||||
Loading…
Reference in New Issue
Block a user