Commit a6e5afa1 authored by David S. Miller's avatar David S. Miller

Merge branch 'FDB-updates-for-SJA1105-DSA-driver'

Vladimir Oltean says:

====================
FDB updates for SJA1105 DSA driver

This patch series adds:

- FDB switchdev support for the second generation of switches (P/Q/R/S).
  I could test/code these now that I got a board with a SJA1105Q.

- Management route support for SJA1105 P/Q/R/S. This is needed to send
  PTP/STP/management frames over the CPU port.

- Logic to hide private DSA VLANs from the 'bridge fdb' commands.

The new FDB code was also tested and still works on SJA1105T.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 014da2a5 93647594
...@@ -55,6 +55,11 @@ struct sja1105_info { ...@@ -55,6 +55,11 @@ struct sja1105_info {
const struct sja1105_regs *regs; const struct sja1105_regs *regs;
int (*reset_cmd)(const void *ctx, const void *data); int (*reset_cmd)(const void *ctx, const void *data);
int (*setup_rgmii_delay)(const void *ctx, int port); int (*setup_rgmii_delay)(const void *ctx, int port);
/* Prototypes from include/net/dsa.h */
int (*fdb_add_cmd)(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int (*fdb_del_cmd)(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
const char *name; const char *name;
}; };
...@@ -142,7 +147,20 @@ int sja1105_dynamic_config_write(struct sja1105_private *priv, ...@@ -142,7 +147,20 @@ int sja1105_dynamic_config_write(struct sja1105_private *priv,
enum sja1105_blk_idx blk_idx, enum sja1105_blk_idx blk_idx,
int index, void *entry, bool keep); int index, void *entry, bool keep);
u8 sja1105_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid); enum sja1105_iotag {
SJA1105_C_TAG = 0, /* Inner VLAN header */
SJA1105_S_TAG = 1, /* Outer VLAN header */
};
u8 sja1105et_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid);
int sja1105et_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int sja1105et_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int sja1105pqrs_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int sja1105pqrs_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
/* Common implementations for the static and dynamic configs */ /* Common implementations for the static and dynamic configs */
size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr, size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr,
......
...@@ -35,17 +35,72 @@ ...@@ -35,17 +35,72 @@
#define SJA1105_MAX_DYN_CMD_SIZE \ #define SJA1105_MAX_DYN_CMD_SIZE \
SJA1105PQRS_SIZE_MAC_CONFIG_DYN_CMD SJA1105PQRS_SIZE_MAC_CONFIG_DYN_CMD
struct sja1105_dyn_cmd {
bool search;
u64 valid;
u64 rdwrset;
u64 errors;
u64 valident;
u64 index;
};
enum sja1105_hostcmd {
SJA1105_HOSTCMD_SEARCH = 1,
SJA1105_HOSTCMD_READ = 2,
SJA1105_HOSTCMD_WRITE = 3,
SJA1105_HOSTCMD_INVALIDATE = 4,
};
static void static void
sja1105pqrs_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, sja1105pqrs_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd,
enum packing_op op) enum packing_op op)
{ {
u8 *p = buf + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; u8 *p = buf + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY;
const int size = SJA1105_SIZE_DYN_CMD; const int size = SJA1105_SIZE_DYN_CMD;
u64 lockeds = 0;
u64 hostcmd;
sja1105_packing(p, &cmd->valid, 31, 31, size, op); sja1105_packing(p, &cmd->valid, 31, 31, size, op);
sja1105_packing(p, &cmd->rdwrset, 30, 30, size, op); sja1105_packing(p, &cmd->rdwrset, 30, 30, size, op);
sja1105_packing(p, &cmd->errors, 29, 29, size, op); sja1105_packing(p, &cmd->errors, 29, 29, size, op);
sja1105_packing(p, &lockeds, 28, 28, size, op);
sja1105_packing(p, &cmd->valident, 27, 27, size, op); sja1105_packing(p, &cmd->valident, 27, 27, size, op);
/* VALIDENT is supposed to indicate "keep or not", but in SJA1105 E/T,
* using it to delete a management route was unsupported. UM10944
* said about it:
*
* In case of a write access with the MGMTROUTE flag set,
* the flag will be ignored. It will always be found cleared
* for read accesses with the MGMTROUTE flag set.
*
* SJA1105 P/Q/R/S keeps the same behavior w.r.t. VALIDENT, but there
* is now another flag called HOSTCMD which does more stuff (quoting
* from UM11040):
*
* A write request is accepted only when HOSTCMD is set to write host
* or invalid. A read request is accepted only when HOSTCMD is set to
* search host or read host.
*
* So it is possible to translate a RDWRSET/VALIDENT combination into
* HOSTCMD so that we keep the dynamic command API in place, and
* at the same time achieve compatibility with the management route
* command structure.
*/
if (cmd->rdwrset == SPI_READ) {
if (cmd->search)
hostcmd = SJA1105_HOSTCMD_SEARCH;
else
hostcmd = SJA1105_HOSTCMD_READ;
} else {
/* SPI_WRITE */
if (cmd->valident)
hostcmd = SJA1105_HOSTCMD_WRITE;
else
hostcmd = SJA1105_HOSTCMD_INVALIDATE;
}
sja1105_packing(p, &hostcmd, 25, 23, size, op);
/* Hack - The hardware takes the 'index' field within /* Hack - The hardware takes the 'index' field within
* struct sja1105_l2_lookup_entry as the index on which this command * struct sja1105_l2_lookup_entry as the index on which this command
* will operate. However it will ignore everything else, so 'index' * will operate. However it will ignore everything else, so 'index'
...@@ -54,9 +109,8 @@ sja1105pqrs_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, ...@@ -54,9 +109,8 @@ sja1105pqrs_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd,
* such that our API doesn't need to ask for a full-blown entry * such that our API doesn't need to ask for a full-blown entry
* structure when e.g. a delete is requested. * structure when e.g. a delete is requested.
*/ */
sja1105_packing(buf, &cmd->index, 29, 20, sja1105_packing(buf, &cmd->index, 15, 6,
SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, op); SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, op);
/* TODO hostcmd */
} }
static void static void
...@@ -107,6 +161,36 @@ static size_t sja1105et_mgmt_route_entry_packing(void *buf, void *entry_ptr, ...@@ -107,6 +161,36 @@ static size_t sja1105et_mgmt_route_entry_packing(void *buf, void *entry_ptr,
return size; return size;
} }
static void
sja1105pqrs_mgmt_route_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd,
enum packing_op op)
{
u8 *p = buf + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY;
u64 mgmtroute = 1;
sja1105pqrs_l2_lookup_cmd_packing(buf, cmd, op);
if (op == PACK)
sja1105_pack(p, &mgmtroute, 26, 26, SJA1105_SIZE_DYN_CMD);
}
static size_t sja1105pqrs_mgmt_route_entry_packing(void *buf, void *entry_ptr,
enum packing_op op)
{
const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY;
struct sja1105_mgmt_entry *entry = entry_ptr;
/* In P/Q/R/S, enfport got renamed to mgmtvalid, but its purpose
* is the same (driver uses it to confirm that frame was sent).
* So just keep the name from E/T.
*/
sja1105_packing(buf, &entry->tsreg, 71, 71, size, op);
sja1105_packing(buf, &entry->takets, 70, 70, size, op);
sja1105_packing(buf, &entry->macaddr, 69, 22, size, op);
sja1105_packing(buf, &entry->destports, 21, 17, size, op);
sja1105_packing(buf, &entry->enfport, 16, 16, size, op);
return size;
}
/* In E/T, entry is at addresses 0x27-0x28. There is a 4 byte gap at 0x29, /* In E/T, entry is at addresses 0x27-0x28. There is a 4 byte gap at 0x29,
* and command is at 0x2a. Similarly in P/Q/R/S there is a 1 register gap * and command is at 0x2a. Similarly in P/Q/R/S there is a 1 register gap
* between entry (0x2d, 0x2e) and command (0x30). * between entry (0x2d, 0x2e) and command (0x30).
...@@ -240,6 +324,7 @@ sja1105et_general_params_entry_packing(void *buf, void *entry_ptr, ...@@ -240,6 +324,7 @@ sja1105et_general_params_entry_packing(void *buf, void *entry_ptr,
#define OP_READ BIT(0) #define OP_READ BIT(0)
#define OP_WRITE BIT(1) #define OP_WRITE BIT(1)
#define OP_DEL BIT(2) #define OP_DEL BIT(2)
#define OP_SEARCH BIT(3)
/* SJA1105E/T: First generation */ /* SJA1105E/T: First generation */
struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = { struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = {
...@@ -304,14 +389,22 @@ struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = { ...@@ -304,14 +389,22 @@ struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = {
[BLK_IDX_XMII_PARAMS] = {0}, [BLK_IDX_XMII_PARAMS] = {0},
}; };
/* SJA1105P/Q/R/S: Second generation: TODO */ /* SJA1105P/Q/R/S: Second generation */
struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = { struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = {
[BLK_IDX_L2_LOOKUP] = { [BLK_IDX_L2_LOOKUP] = {
.entry_packing = sja1105pqrs_l2_lookup_entry_packing, .entry_packing = sja1105pqrs_l2_lookup_entry_packing,
.cmd_packing = sja1105pqrs_l2_lookup_cmd_packing, .cmd_packing = sja1105pqrs_l2_lookup_cmd_packing,
.access = (OP_READ | OP_WRITE | OP_DEL), .access = (OP_READ | OP_WRITE | OP_DEL | OP_SEARCH),
.max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT,
.packed_size = SJA1105ET_SIZE_L2_LOOKUP_DYN_CMD, .packed_size = SJA1105PQRS_SIZE_L2_LOOKUP_DYN_CMD,
.addr = 0x24,
},
[BLK_IDX_MGMT_ROUTE] = {
.entry_packing = sja1105pqrs_mgmt_route_entry_packing,
.cmd_packing = sja1105pqrs_mgmt_route_cmd_packing,
.access = (OP_READ | OP_WRITE | OP_DEL | OP_SEARCH),
.max_entry_count = SJA1105_NUM_PORTS,
.packed_size = SJA1105PQRS_SIZE_L2_LOOKUP_DYN_CMD,
.addr = 0x24, .addr = 0x24,
}, },
[BLK_IDX_L2_POLICING] = {0}, [BLK_IDX_L2_POLICING] = {0},
...@@ -359,6 +452,24 @@ struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = { ...@@ -359,6 +452,24 @@ struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = {
[BLK_IDX_XMII_PARAMS] = {0}, [BLK_IDX_XMII_PARAMS] = {0},
}; };
/* Provides read access to the settings through the dynamic interface
* of the switch.
* @blk_idx is used as key to select from the sja1105_dynamic_table_ops.
* The selection is limited by the hardware in respect to which
* configuration blocks can be read through the dynamic interface.
* @index is used to retrieve a particular table entry. If negative,
* (and if the @blk_idx supports the searching operation) a search
* is performed by the @entry parameter.
* @entry Type-casted to an unpacked structure that holds a table entry
* of the type specified in @blk_idx.
* Usually an output argument. If @index is negative, then this
* argument is used as input/output: it should be pre-populated
* with the element to search for. Entries which support the
* search operation will have an "index" field (not the @index
* argument to this function) and that is where the found index
* will be returned (or left unmodified - thus negative - if not
* found).
*/
int sja1105_dynamic_config_read(struct sja1105_private *priv, int sja1105_dynamic_config_read(struct sja1105_private *priv,
enum sja1105_blk_idx blk_idx, enum sja1105_blk_idx blk_idx,
int index, void *entry) int index, void *entry)
...@@ -375,8 +486,10 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv, ...@@ -375,8 +486,10 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv,
ops = &priv->info->dyn_ops[blk_idx]; ops = &priv->info->dyn_ops[blk_idx];
if (index >= ops->max_entry_count) if (index >= 0 && index >= ops->max_entry_count)
return -ERANGE; return -ERANGE;
if (index < 0 && !(ops->access & OP_SEARCH))
return -EOPNOTSUPP;
if (!(ops->access & OP_READ)) if (!(ops->access & OP_READ))
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (ops->packed_size > SJA1105_MAX_DYN_CMD_SIZE) if (ops->packed_size > SJA1105_MAX_DYN_CMD_SIZE)
...@@ -388,9 +501,20 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv, ...@@ -388,9 +501,20 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv,
cmd.valid = true; /* Trigger action on table entry */ cmd.valid = true; /* Trigger action on table entry */
cmd.rdwrset = SPI_READ; /* Action is read */ cmd.rdwrset = SPI_READ; /* Action is read */
if (index < 0) {
/* Avoid copying a signed negative number to an u64 */
cmd.index = 0;
cmd.search = true;
} else {
cmd.index = index; cmd.index = index;
cmd.search = false;
}
cmd.valident = true;
ops->cmd_packing(packed_buf, &cmd, PACK); ops->cmd_packing(packed_buf, &cmd, PACK);
if (cmd.search)
ops->entry_packing(packed_buf, entry, PACK);
/* Send SPI write operation: read config table entry */ /* Send SPI write operation: read config table entry */
rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, ops->addr, rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, ops->addr,
packed_buf, ops->packed_size); packed_buf, ops->packed_size);
...@@ -416,7 +540,7 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv, ...@@ -416,7 +540,7 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv,
* So don't error out in that case. * So don't error out in that case.
*/ */
if (!cmd.valident && blk_idx != BLK_IDX_MGMT_ROUTE) if (!cmd.valident && blk_idx != BLK_IDX_MGMT_ROUTE)
return -EINVAL; return -ENOENT;
cpu_relax(); cpu_relax();
} while (cmd.valid && --retries); } while (cmd.valid && --retries);
...@@ -448,6 +572,8 @@ int sja1105_dynamic_config_write(struct sja1105_private *priv, ...@@ -448,6 +572,8 @@ int sja1105_dynamic_config_write(struct sja1105_private *priv,
if (index >= ops->max_entry_count) if (index >= ops->max_entry_count)
return -ERANGE; return -ERANGE;
if (index < 0)
return -ERANGE;
if (!(ops->access & OP_WRITE)) if (!(ops->access & OP_WRITE))
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (!keep && !(ops->access & OP_DEL)) if (!keep && !(ops->access & OP_DEL))
...@@ -510,7 +636,7 @@ static u8 sja1105_crc8_add(u8 crc, u8 byte, u8 poly) ...@@ -510,7 +636,7 @@ static u8 sja1105_crc8_add(u8 crc, u8 byte, u8 poly)
* is also received as argument in the Koopman notation that the switch * is also received as argument in the Koopman notation that the switch
* hardware stores it in. * hardware stores it in.
*/ */
u8 sja1105_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid) u8 sja1105et_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid)
{ {
struct sja1105_l2_lookup_params_entry *l2_lookup_params = struct sja1105_l2_lookup_params_entry *l2_lookup_params =
priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS].entries; priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS].entries;
......
...@@ -7,13 +7,10 @@ ...@@ -7,13 +7,10 @@
#include "sja1105.h" #include "sja1105.h"
#include <linux/packing.h> #include <linux/packing.h>
struct sja1105_dyn_cmd { /* Special index that can be used for sja1105_dynamic_config_read */
u64 valid; #define SJA1105_SEARCH -1
u64 rdwrset;
u64 errors; struct sja1105_dyn_cmd;
u64 valident;
u64 index;
};
struct sja1105_dynamic_table_ops { struct sja1105_dynamic_table_ops {
/* This returns size_t just to keep same prototype as the /* This returns size_t just to keep same prototype as the
......
...@@ -210,6 +210,8 @@ static int sja1105_init_l2_lookup_params(struct sja1105_private *priv) ...@@ -210,6 +210,8 @@ static int sja1105_init_l2_lookup_params(struct sja1105_private *priv)
.maxage = SJA1105_AGEING_TIME_MS(300000), .maxage = SJA1105_AGEING_TIME_MS(300000),
/* All entries within a FDB bin are available for learning */ /* All entries within a FDB bin are available for learning */
.dyn_tbsz = SJA1105ET_FDB_BIN_SIZE, .dyn_tbsz = SJA1105ET_FDB_BIN_SIZE,
/* And the P/Q/R/S equivalent setting: */
.start_dynspc = 0,
/* 2^8 + 2^5 + 2^3 + 2^2 + 2^1 + 1 in Koopman notation */ /* 2^8 + 2^5 + 2^3 + 2^2 + 2^1 + 1 in Koopman notation */
.poly = 0x97, .poly = 0x97,
/* This selects between Independent VLAN Learning (IVL) and /* This selects between Independent VLAN Learning (IVL) and
...@@ -225,6 +227,13 @@ static int sja1105_init_l2_lookup_params(struct sja1105_private *priv) ...@@ -225,6 +227,13 @@ static int sja1105_init_l2_lookup_params(struct sja1105_private *priv)
* Maybe correlate with no_linklocal_learn from bridge driver? * Maybe correlate with no_linklocal_learn from bridge driver?
*/ */
.no_mgmt_learn = true, .no_mgmt_learn = true,
/* P/Q/R/S only */
.use_static = true,
/* Dynamically learned FDB entries can overwrite other (older)
* dynamic FDB entries
*/
.owr_dyn = true,
.drpnolearn = true,
}; };
table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS]; table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS];
...@@ -786,7 +795,7 @@ static inline int sja1105et_fdb_index(int bin, int way) ...@@ -786,7 +795,7 @@ static inline int sja1105et_fdb_index(int bin, int way)
return bin * SJA1105ET_FDB_BIN_SIZE + way; return bin * SJA1105ET_FDB_BIN_SIZE + way;
} }
static int sja1105_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin, static int sja1105et_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin,
const u8 *addr, u16 vid, const u8 *addr, u16 vid,
struct sja1105_l2_lookup_entry *match, struct sja1105_l2_lookup_entry *match,
int *last_unused) int *last_unused)
...@@ -818,7 +827,7 @@ static int sja1105_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin, ...@@ -818,7 +827,7 @@ static int sja1105_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin,
return -1; return -1;
} }
static int sja1105_fdb_add(struct dsa_switch *ds, int port, int sja1105et_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid) const unsigned char *addr, u16 vid)
{ {
struct sja1105_l2_lookup_entry l2_lookup = {0}; struct sja1105_l2_lookup_entry l2_lookup = {0};
...@@ -827,9 +836,9 @@ static int sja1105_fdb_add(struct dsa_switch *ds, int port, ...@@ -827,9 +836,9 @@ static int sja1105_fdb_add(struct dsa_switch *ds, int port,
int last_unused = -1; int last_unused = -1;
int bin, way; int bin, way;
bin = sja1105_fdb_hash(priv, addr, vid); bin = sja1105et_fdb_hash(priv, addr, vid);
way = sja1105_is_fdb_entry_in_bin(priv, bin, addr, vid, way = sja1105et_is_fdb_entry_in_bin(priv, bin, addr, vid,
&l2_lookup, &last_unused); &l2_lookup, &last_unused);
if (way >= 0) { if (way >= 0) {
/* We have an FDB entry. Is our port in the destination /* We have an FDB entry. Is our port in the destination
...@@ -874,7 +883,7 @@ static int sja1105_fdb_add(struct dsa_switch *ds, int port, ...@@ -874,7 +883,7 @@ static int sja1105_fdb_add(struct dsa_switch *ds, int port,
true); true);
} }
static int sja1105_fdb_del(struct dsa_switch *ds, int port, int sja1105et_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid) const unsigned char *addr, u16 vid)
{ {
struct sja1105_l2_lookup_entry l2_lookup = {0}; struct sja1105_l2_lookup_entry l2_lookup = {0};
...@@ -882,8 +891,8 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port, ...@@ -882,8 +891,8 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port,
int index, bin, way; int index, bin, way;
bool keep; bool keep;
bin = sja1105_fdb_hash(priv, addr, vid); bin = sja1105et_fdb_hash(priv, addr, vid);
way = sja1105_is_fdb_entry_in_bin(priv, bin, addr, vid, way = sja1105et_is_fdb_entry_in_bin(priv, bin, addr, vid,
&l2_lookup, NULL); &l2_lookup, NULL);
if (way < 0) if (way < 0)
return 0; return 0;
...@@ -894,8 +903,8 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port, ...@@ -894,8 +903,8 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port,
* need to completely evict the FDB entry. * need to completely evict the FDB entry.
* Otherwise we just write it back. * Otherwise we just write it back.
*/ */
if (l2_lookup.destports & BIT(port))
l2_lookup.destports &= ~BIT(port); l2_lookup.destports &= ~BIT(port);
if (l2_lookup.destports) if (l2_lookup.destports)
keep = true; keep = true;
else else
...@@ -905,6 +914,138 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port, ...@@ -905,6 +914,138 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port,
index, &l2_lookup, keep); index, &l2_lookup, keep);
} }
int sja1105pqrs_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid)
{
struct sja1105_l2_lookup_entry l2_lookup = {0};
struct sja1105_private *priv = ds->priv;
int rc, i;
/* Search for an existing entry in the FDB table */
l2_lookup.macaddr = ether_addr_to_u64(addr);
l2_lookup.vlanid = vid;
l2_lookup.iotag = SJA1105_S_TAG;
l2_lookup.mask_macaddr = GENMASK_ULL(ETH_ALEN * 8 - 1, 0);
l2_lookup.mask_vlanid = VLAN_VID_MASK;
l2_lookup.mask_iotag = BIT(0);
l2_lookup.destports = BIT(port);
rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP,
SJA1105_SEARCH, &l2_lookup);
if (rc == 0) {
/* Found and this port is already in the entry's
* port mask => job done
*/
if (l2_lookup.destports & BIT(port))
return 0;
/* l2_lookup.index is populated by the switch in case it
* found something.
*/
l2_lookup.destports |= BIT(port);
goto skip_finding_an_index;
}
/* Not found, so try to find an unused spot in the FDB.
* This is slightly inefficient because the strategy is knock-knock at
* every possible position from 0 to 1023.
*/
for (i = 0; i < SJA1105_MAX_L2_LOOKUP_COUNT; i++) {
rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP,
i, NULL);
if (rc < 0)
break;
}
if (i == SJA1105_MAX_L2_LOOKUP_COUNT) {
dev_err(ds->dev, "FDB is full, cannot add entry.\n");
return -EINVAL;
}
l2_lookup.index = i;
skip_finding_an_index:
return sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP,
l2_lookup.index, &l2_lookup,
true);
}
int sja1105pqrs_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid)
{
struct sja1105_l2_lookup_entry l2_lookup = {0};
struct sja1105_private *priv = ds->priv;
bool keep;
int rc;
l2_lookup.macaddr = ether_addr_to_u64(addr);
l2_lookup.vlanid = vid;
l2_lookup.iotag = SJA1105_S_TAG;
l2_lookup.mask_macaddr = GENMASK_ULL(ETH_ALEN * 8 - 1, 0);
l2_lookup.mask_vlanid = VLAN_VID_MASK;
l2_lookup.mask_iotag = BIT(0);
l2_lookup.destports = BIT(port);
rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP,
SJA1105_SEARCH, &l2_lookup);
if (rc < 0)
return 0;
l2_lookup.destports &= ~BIT(port);
/* Decide whether we remove just this port from the FDB entry,
* or if we remove it completely.
*/
if (l2_lookup.destports)
keep = true;
else
keep = false;
return sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP,
l2_lookup.index, &l2_lookup, keep);
}
static int sja1105_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid)
{
struct sja1105_private *priv = ds->priv;
int rc;
/* Since we make use of VLANs even when the bridge core doesn't tell us
* to, translate these FDB entries into the correct dsa_8021q ones.
*/
if (!dsa_port_is_vlan_filtering(&ds->ports[port])) {
unsigned int upstream = dsa_upstream_port(priv->ds, port);
u16 tx_vid = dsa_8021q_tx_vid(ds, port);
u16 rx_vid = dsa_8021q_rx_vid(ds, port);
rc = priv->info->fdb_add_cmd(ds, port, addr, tx_vid);
if (rc < 0)
return rc;
return priv->info->fdb_add_cmd(ds, upstream, addr, rx_vid);
}
return priv->info->fdb_add_cmd(ds, port, addr, vid);
}
static int sja1105_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid)
{
struct sja1105_private *priv = ds->priv;
int rc;
/* Since we make use of VLANs even when the bridge core doesn't tell us
* to, translate these FDB entries into the correct dsa_8021q ones.
*/
if (!dsa_port_is_vlan_filtering(&ds->ports[port])) {
unsigned int upstream = dsa_upstream_port(priv->ds, port);
u16 tx_vid = dsa_8021q_tx_vid(ds, port);
u16 rx_vid = dsa_8021q_rx_vid(ds, port);
rc = priv->info->fdb_del_cmd(ds, port, addr, tx_vid);
if (rc < 0)
return rc;
return priv->info->fdb_del_cmd(ds, upstream, addr, rx_vid);
}
return priv->info->fdb_del_cmd(ds, port, addr, vid);
}
static int sja1105_fdb_dump(struct dsa_switch *ds, int port, static int sja1105_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data) dsa_fdb_dump_cb_t *cb, void *data)
{ {
...@@ -920,7 +1061,7 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port, ...@@ -920,7 +1061,7 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port,
rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP, rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP,
i, &l2_lookup); i, &l2_lookup);
/* No fdb entry at i, not an issue */ /* No fdb entry at i, not an issue */
if (rc == -EINVAL) if (rc == -ENOENT)
continue; continue;
if (rc) { if (rc) {
dev_err(dev, "Failed to dump FDB: %d\n", rc); dev_err(dev, "Failed to dump FDB: %d\n", rc);
...@@ -936,6 +1077,15 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port, ...@@ -936,6 +1077,15 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port,
if (!(l2_lookup.destports & BIT(port))) if (!(l2_lookup.destports & BIT(port)))
continue; continue;
u64_to_ether_addr(l2_lookup.macaddr, macaddr); u64_to_ether_addr(l2_lookup.macaddr, macaddr);
/* We need to hide the dsa_8021q VLAN from the user.
* Convert the TX VID into the pvid that is active in
* standalone and non-vlan_filtering modes, aka 1.
* The RX VID is applied on the CPU port, which is not seen by
* the bridge core anyway, so there's nothing to hide.
*/
if (!dsa_port_is_vlan_filtering(&ds->ports[port]))
l2_lookup.vlanid = 1;
cb(macaddr, l2_lookup.vlanid, false, data); cb(macaddr, l2_lookup.vlanid, false, data);
} }
return 0; return 0;
...@@ -1447,6 +1597,8 @@ static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot, ...@@ -1447,6 +1597,8 @@ static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
if (!timeout) { if (!timeout) {
/* Clean up the management route so that a follow-up /* Clean up the management route so that a follow-up
* frame may not match on it by mistake. * frame may not match on it by mistake.
* This is only hardware supported on P/Q/R/S - on E/T it is
* a no-op and we are silently discarding the -EOPNOTSUPP.
*/ */
sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE, sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE,
slot, &mgmt_route, false); slot, &mgmt_route, false);
......
...@@ -541,6 +541,8 @@ struct sja1105_info sja1105e_info = { ...@@ -541,6 +541,8 @@ struct sja1105_info sja1105e_info = {
.static_ops = sja1105e_table_ops, .static_ops = sja1105e_table_ops,
.dyn_ops = sja1105et_dyn_ops, .dyn_ops = sja1105et_dyn_ops,
.reset_cmd = sja1105et_reset_cmd, .reset_cmd = sja1105et_reset_cmd,
.fdb_add_cmd = sja1105et_fdb_add,
.fdb_del_cmd = sja1105et_fdb_del,
.regs = &sja1105et_regs, .regs = &sja1105et_regs,
.name = "SJA1105E", .name = "SJA1105E",
}; };
...@@ -550,6 +552,8 @@ struct sja1105_info sja1105t_info = { ...@@ -550,6 +552,8 @@ struct sja1105_info sja1105t_info = {
.static_ops = sja1105t_table_ops, .static_ops = sja1105t_table_ops,
.dyn_ops = sja1105et_dyn_ops, .dyn_ops = sja1105et_dyn_ops,
.reset_cmd = sja1105et_reset_cmd, .reset_cmd = sja1105et_reset_cmd,
.fdb_add_cmd = sja1105et_fdb_add,
.fdb_del_cmd = sja1105et_fdb_del,
.regs = &sja1105et_regs, .regs = &sja1105et_regs,
.name = "SJA1105T", .name = "SJA1105T",
}; };
...@@ -559,6 +563,8 @@ struct sja1105_info sja1105p_info = { ...@@ -559,6 +563,8 @@ struct sja1105_info sja1105p_info = {
.static_ops = sja1105p_table_ops, .static_ops = sja1105p_table_ops,
.dyn_ops = sja1105pqrs_dyn_ops, .dyn_ops = sja1105pqrs_dyn_ops,
.reset_cmd = sja1105pqrs_reset_cmd, .reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.regs = &sja1105pqrs_regs, .regs = &sja1105pqrs_regs,
.name = "SJA1105P", .name = "SJA1105P",
}; };
...@@ -568,6 +574,8 @@ struct sja1105_info sja1105q_info = { ...@@ -568,6 +574,8 @@ struct sja1105_info sja1105q_info = {
.static_ops = sja1105q_table_ops, .static_ops = sja1105q_table_ops,
.dyn_ops = sja1105pqrs_dyn_ops, .dyn_ops = sja1105pqrs_dyn_ops,
.reset_cmd = sja1105pqrs_reset_cmd, .reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.regs = &sja1105pqrs_regs, .regs = &sja1105pqrs_regs,
.name = "SJA1105Q", .name = "SJA1105Q",
}; };
...@@ -577,6 +585,8 @@ struct sja1105_info sja1105r_info = { ...@@ -577,6 +585,8 @@ struct sja1105_info sja1105r_info = {
.static_ops = sja1105r_table_ops, .static_ops = sja1105r_table_ops,
.dyn_ops = sja1105pqrs_dyn_ops, .dyn_ops = sja1105pqrs_dyn_ops,
.reset_cmd = sja1105pqrs_reset_cmd, .reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.regs = &sja1105pqrs_regs, .regs = &sja1105pqrs_regs,
.name = "SJA1105R", .name = "SJA1105R",
}; };
...@@ -587,5 +597,7 @@ struct sja1105_info sja1105s_info = { ...@@ -587,5 +597,7 @@ struct sja1105_info sja1105s_info = {
.dyn_ops = sja1105pqrs_dyn_ops, .dyn_ops = sja1105pqrs_dyn_ops,
.regs = &sja1105pqrs_regs, .regs = &sja1105pqrs_regs,
.reset_cmd = sja1105pqrs_reset_cmd, .reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.name = "SJA1105S", .name = "SJA1105S",
}; };
...@@ -236,10 +236,20 @@ size_t sja1105pqrs_l2_lookup_entry_packing(void *buf, void *entry_ptr, ...@@ -236,10 +236,20 @@ size_t sja1105pqrs_l2_lookup_entry_packing(void *buf, void *entry_ptr,
const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY;
struct sja1105_l2_lookup_entry *entry = entry_ptr; struct sja1105_l2_lookup_entry *entry = entry_ptr;
/* These are static L2 lookup entries, so the structure if (entry->lockeds) {
* should match UM11040 Table 16/17 definitions when sja1105_packing(buf, &entry->tsreg, 159, 159, size, op);
* LOCKEDS is 1. sja1105_packing(buf, &entry->mirrvlan, 158, 147, size, op);
*/ sja1105_packing(buf, &entry->takets, 146, 146, size, op);
sja1105_packing(buf, &entry->mirr, 145, 145, size, op);
sja1105_packing(buf, &entry->retag, 144, 144, size, op);
} else {
sja1105_packing(buf, &entry->touched, 159, 159, size, op);
sja1105_packing(buf, &entry->age, 158, 144, size, op);
}
sja1105_packing(buf, &entry->mask_iotag, 143, 143, size, op);
sja1105_packing(buf, &entry->mask_vlanid, 142, 131, size, op);
sja1105_packing(buf, &entry->mask_macaddr, 130, 83, size, op);
sja1105_packing(buf, &entry->iotag, 82, 82, size, op);
sja1105_packing(buf, &entry->vlanid, 81, 70, size, op); sja1105_packing(buf, &entry->vlanid, 81, 70, size, op);
sja1105_packing(buf, &entry->macaddr, 69, 22, size, op); sja1105_packing(buf, &entry->macaddr, 69, 22, size, op);
sja1105_packing(buf, &entry->destports, 21, 17, size, op); sja1105_packing(buf, &entry->destports, 21, 17, size, op);
......
...@@ -122,9 +122,35 @@ struct sja1105_l2_lookup_entry { ...@@ -122,9 +122,35 @@ struct sja1105_l2_lookup_entry {
u64 destports; u64 destports;
u64 enfport; u64 enfport;
u64 index; u64 index;
/* P/Q/R/S only */
u64 mask_iotag;
u64 mask_vlanid;
u64 mask_macaddr;
u64 iotag;
bool lockeds;
union {
/* LOCKEDS=1: Static FDB entries */
struct {
u64 tsreg;
u64 mirrvlan;
u64 takets;
u64 mirr;
u64 retag;
};
/* LOCKEDS=0: Dynamically learned FDB entries */
struct {
u64 touched;
u64 age;
};
};
}; };
struct sja1105_l2_lookup_params_entry { struct sja1105_l2_lookup_params_entry {
u64 start_dynspc; /* P/Q/R/S only */
u64 drpnolearn; /* P/Q/R/S only */
u64 use_static; /* P/Q/R/S only */
u64 owr_dyn; /* P/Q/R/S only */
u64 learn_once; /* P/Q/R/S only */
u64 maxage; /* Shared */ u64 maxage; /* Shared */
u64 dyn_tbsz; /* E/T only */ u64 dyn_tbsz; /* E/T only */
u64 poly; /* E/T only */ u64 poly; /* E/T only */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment