Merge branch 'sha1105-regressions'
Vladimir Oltean says: ==================== Fixes for SJA1105 DSA FDB regressions A report by Yanan Yang has prompted an investigation into the sja1105 driver's behavior w.r.t. multicast. The report states that when adding multicast L2 addresses with "bridge mdb add", only the most recently added address works - the others seem to be overwritten. This is solved by patch 3/5 (with patch 2/5 as a dependency for it). Patches 4/5 and 5/5 fix a series of race conditions introduced during the same patch set as the bug above, namely this one: https://patchwork.kernel.org/project/netdevbpf/cover/20211024171757.3753288-1-vladimir.oltean@nxp.com/ Finally, patch 1/5 fixes an issue found ever since the introduction of multicast forwarding offload in sja1105, which is that the multicast addresses are visible (with the "self" flag) in "bridge fdb show". ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
904de9858e
@ -266,6 +266,8 @@ struct sja1105_private {
|
||||
* the switch doesn't confuse them with one another.
|
||||
*/
|
||||
struct mutex mgmt_lock;
|
||||
/* Serializes accesses to the FDB */
|
||||
struct mutex fdb_lock;
|
||||
/* PTP two-step TX timestamp ID, and its serialization lock */
|
||||
spinlock_t ts_id_lock;
|
||||
u8 ts_id;
|
||||
|
@ -1175,18 +1175,15 @@ const struct sja1105_dynamic_table_ops sja1110_dyn_ops[BLK_IDX_MAX_DYN] = {
|
||||
|
||||
static int
|
||||
sja1105_dynamic_config_poll_valid(struct sja1105_private *priv,
|
||||
struct sja1105_dyn_cmd *cmd,
|
||||
const struct sja1105_dynamic_table_ops *ops)
|
||||
const struct sja1105_dynamic_table_ops *ops,
|
||||
void *entry, bool check_valident,
|
||||
bool check_errors)
|
||||
{
|
||||
u8 packed_buf[SJA1105_MAX_DYN_CMD_SIZE] = {};
|
||||
struct sja1105_dyn_cmd cmd = {};
|
||||
int rc;
|
||||
|
||||
/* We don't _need_ to read the full entry, just the command area which
|
||||
* is a fixed SJA1105_SIZE_DYN_CMD. But our cmd_packing() API expects a
|
||||
* buffer that contains the full entry too. Additionally, our API
|
||||
* doesn't really know how many bytes into the buffer does the command
|
||||
* area really begin. So just read back the whole entry.
|
||||
*/
|
||||
/* Read back the whole entry + command structure. */
|
||||
rc = sja1105_xfer_buf(priv, SPI_READ, ops->addr, packed_buf,
|
||||
ops->packed_size);
|
||||
if (rc)
|
||||
@ -1195,11 +1192,25 @@ sja1105_dynamic_config_poll_valid(struct sja1105_private *priv,
|
||||
/* Unpack the command structure, and return it to the caller in case it
|
||||
* needs to perform further checks on it (VALIDENT).
|
||||
*/
|
||||
memset(cmd, 0, sizeof(*cmd));
|
||||
ops->cmd_packing(packed_buf, cmd, UNPACK);
|
||||
ops->cmd_packing(packed_buf, &cmd, UNPACK);
|
||||
|
||||
/* Hardware hasn't cleared VALID => still working on it */
|
||||
return cmd->valid ? -EAGAIN : 0;
|
||||
if (cmd.valid)
|
||||
return -EAGAIN;
|
||||
|
||||
if (check_valident && !cmd.valident && !(ops->access & OP_VALID_ANYWAY))
|
||||
return -ENOENT;
|
||||
|
||||
if (check_errors && cmd.errors)
|
||||
return -EINVAL;
|
||||
|
||||
/* Don't dereference possibly NULL pointer - maybe caller
|
||||
* only wanted to see whether the entry existed or not.
|
||||
*/
|
||||
if (entry)
|
||||
ops->entry_packing(packed_buf, entry, UNPACK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Poll the dynamic config entry's control area until the hardware has
|
||||
@ -1208,16 +1219,19 @@ sja1105_dynamic_config_poll_valid(struct sja1105_private *priv,
|
||||
*/
|
||||
static int
|
||||
sja1105_dynamic_config_wait_complete(struct sja1105_private *priv,
|
||||
struct sja1105_dyn_cmd *cmd,
|
||||
const struct sja1105_dynamic_table_ops *ops)
|
||||
const struct sja1105_dynamic_table_ops *ops,
|
||||
void *entry, bool check_valident,
|
||||
bool check_errors)
|
||||
{
|
||||
int rc;
|
||||
int err, rc;
|
||||
|
||||
return read_poll_timeout(sja1105_dynamic_config_poll_valid,
|
||||
rc, rc != -EAGAIN,
|
||||
SJA1105_DYNAMIC_CONFIG_SLEEP_US,
|
||||
SJA1105_DYNAMIC_CONFIG_TIMEOUT_US,
|
||||
false, priv, cmd, ops);
|
||||
err = read_poll_timeout(sja1105_dynamic_config_poll_valid,
|
||||
rc, rc != -EAGAIN,
|
||||
SJA1105_DYNAMIC_CONFIG_SLEEP_US,
|
||||
SJA1105_DYNAMIC_CONFIG_TIMEOUT_US,
|
||||
false, priv, ops, entry, check_valident,
|
||||
check_errors);
|
||||
return err < 0 ? err : rc;
|
||||
}
|
||||
|
||||
/* Provides read access to the settings through the dynamic interface
|
||||
@ -1286,25 +1300,14 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv,
|
||||
mutex_lock(&priv->dynamic_config_lock);
|
||||
rc = sja1105_xfer_buf(priv, SPI_WRITE, ops->addr, packed_buf,
|
||||
ops->packed_size);
|
||||
if (rc < 0) {
|
||||
mutex_unlock(&priv->dynamic_config_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sja1105_dynamic_config_wait_complete(priv, &cmd, ops);
|
||||
mutex_unlock(&priv->dynamic_config_lock);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
goto out;
|
||||
|
||||
if (!cmd.valident && !(ops->access & OP_VALID_ANYWAY))
|
||||
return -ENOENT;
|
||||
rc = sja1105_dynamic_config_wait_complete(priv, ops, entry, true, false);
|
||||
out:
|
||||
mutex_unlock(&priv->dynamic_config_lock);
|
||||
|
||||
/* Don't dereference possibly NULL pointer - maybe caller
|
||||
* only wanted to see whether the entry existed or not.
|
||||
*/
|
||||
if (entry)
|
||||
ops->entry_packing(packed_buf, entry, UNPACK);
|
||||
return 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sja1105_dynamic_config_write(struct sja1105_private *priv,
|
||||
@ -1356,22 +1359,14 @@ int sja1105_dynamic_config_write(struct sja1105_private *priv,
|
||||
mutex_lock(&priv->dynamic_config_lock);
|
||||
rc = sja1105_xfer_buf(priv, SPI_WRITE, ops->addr, packed_buf,
|
||||
ops->packed_size);
|
||||
if (rc < 0) {
|
||||
mutex_unlock(&priv->dynamic_config_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sja1105_dynamic_config_wait_complete(priv, &cmd, ops);
|
||||
mutex_unlock(&priv->dynamic_config_lock);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
goto out;
|
||||
|
||||
cmd = (struct sja1105_dyn_cmd) {0};
|
||||
ops->cmd_packing(packed_buf, &cmd, UNPACK);
|
||||
if (cmd.errors)
|
||||
return -EINVAL;
|
||||
rc = sja1105_dynamic_config_wait_complete(priv, ops, NULL, false, true);
|
||||
out:
|
||||
mutex_unlock(&priv->dynamic_config_lock);
|
||||
|
||||
return 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static u8 sja1105_crc8_add(u8 crc, u8 byte, u8 poly)
|
||||
|
@ -1798,6 +1798,7 @@ static int sja1105_fdb_add(struct dsa_switch *ds, int port,
|
||||
struct dsa_db db)
|
||||
{
|
||||
struct sja1105_private *priv = ds->priv;
|
||||
int rc;
|
||||
|
||||
if (!vid) {
|
||||
switch (db.type) {
|
||||
@ -1812,12 +1813,16 @@ static int sja1105_fdb_add(struct dsa_switch *ds, int port,
|
||||
}
|
||||
}
|
||||
|
||||
return priv->info->fdb_add_cmd(ds, port, addr, vid);
|
||||
mutex_lock(&priv->fdb_lock);
|
||||
rc = priv->info->fdb_add_cmd(ds, port, addr, vid);
|
||||
mutex_unlock(&priv->fdb_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sja1105_fdb_del(struct dsa_switch *ds, int port,
|
||||
const unsigned char *addr, u16 vid,
|
||||
struct dsa_db db)
|
||||
static int __sja1105_fdb_del(struct dsa_switch *ds, int port,
|
||||
const unsigned char *addr, u16 vid,
|
||||
struct dsa_db db)
|
||||
{
|
||||
struct sja1105_private *priv = ds->priv;
|
||||
|
||||
@ -1837,6 +1842,20 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port,
|
||||
return priv->info->fdb_del_cmd(ds, port, addr, vid);
|
||||
}
|
||||
|
||||
static int sja1105_fdb_del(struct dsa_switch *ds, int port,
|
||||
const unsigned char *addr, u16 vid,
|
||||
struct dsa_db db)
|
||||
{
|
||||
struct sja1105_private *priv = ds->priv;
|
||||
int rc;
|
||||
|
||||
mutex_lock(&priv->fdb_lock);
|
||||
rc = __sja1105_fdb_del(ds, port, addr, vid, db);
|
||||
mutex_unlock(&priv->fdb_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sja1105_fdb_dump(struct dsa_switch *ds, int port,
|
||||
dsa_fdb_dump_cb_t *cb, void *data)
|
||||
{
|
||||
@ -1868,13 +1887,14 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port,
|
||||
if (!(l2_lookup.destports & BIT(port)))
|
||||
continue;
|
||||
|
||||
/* We need to hide the FDB entry for unknown multicast */
|
||||
if (l2_lookup.macaddr == SJA1105_UNKNOWN_MULTICAST &&
|
||||
l2_lookup.mask_macaddr == SJA1105_UNKNOWN_MULTICAST)
|
||||
continue;
|
||||
|
||||
u64_to_ether_addr(l2_lookup.macaddr, macaddr);
|
||||
|
||||
/* Hardware FDB is shared for fdb and mdb, "bridge fdb show"
|
||||
* only wants to see unicast
|
||||
*/
|
||||
if (is_multicast_ether_addr(macaddr))
|
||||
continue;
|
||||
|
||||
/* We need to hide the dsa_8021q VLANs from the user. */
|
||||
if (vid_is_dsa_8021q(l2_lookup.vlanid))
|
||||
l2_lookup.vlanid = 0;
|
||||
@ -1898,6 +1918,8 @@ static void sja1105_fast_age(struct dsa_switch *ds, int port)
|
||||
};
|
||||
int i;
|
||||
|
||||
mutex_lock(&priv->fdb_lock);
|
||||
|
||||
for (i = 0; i < SJA1105_MAX_L2_LOOKUP_COUNT; i++) {
|
||||
struct sja1105_l2_lookup_entry l2_lookup = {0};
|
||||
u8 macaddr[ETH_ALEN];
|
||||
@ -1911,7 +1933,7 @@ static void sja1105_fast_age(struct dsa_switch *ds, int port)
|
||||
if (rc) {
|
||||
dev_err(ds->dev, "Failed to read FDB: %pe\n",
|
||||
ERR_PTR(rc));
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(l2_lookup.destports & BIT(port)))
|
||||
@ -1923,14 +1945,16 @@ static void sja1105_fast_age(struct dsa_switch *ds, int port)
|
||||
|
||||
u64_to_ether_addr(l2_lookup.macaddr, macaddr);
|
||||
|
||||
rc = sja1105_fdb_del(ds, port, macaddr, l2_lookup.vlanid, db);
|
||||
rc = __sja1105_fdb_del(ds, port, macaddr, l2_lookup.vlanid, db);
|
||||
if (rc) {
|
||||
dev_err(ds->dev,
|
||||
"Failed to delete FDB entry %pM vid %lld: %pe\n",
|
||||
macaddr, l2_lookup.vlanid, ERR_PTR(rc));
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->fdb_lock);
|
||||
}
|
||||
|
||||
static int sja1105_mdb_add(struct dsa_switch *ds, int port,
|
||||
@ -2273,6 +2297,7 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
|
||||
int rc, i;
|
||||
s64 now;
|
||||
|
||||
mutex_lock(&priv->fdb_lock);
|
||||
mutex_lock(&priv->mgmt_lock);
|
||||
|
||||
mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
|
||||
@ -2385,6 +2410,7 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
|
||||
goto out;
|
||||
out:
|
||||
mutex_unlock(&priv->mgmt_lock);
|
||||
mutex_unlock(&priv->fdb_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@ -2954,7 +2980,9 @@ static int sja1105_port_mcast_flood(struct sja1105_private *priv, int to,
|
||||
{
|
||||
struct sja1105_l2_lookup_entry *l2_lookup;
|
||||
struct sja1105_table *table;
|
||||
int match;
|
||||
int match, rc;
|
||||
|
||||
mutex_lock(&priv->fdb_lock);
|
||||
|
||||
table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP];
|
||||
l2_lookup = table->entries;
|
||||
@ -2967,7 +2995,8 @@ static int sja1105_port_mcast_flood(struct sja1105_private *priv, int to,
|
||||
if (match == table->entry_count) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Could not find FDB entry for unknown multicast");
|
||||
return -ENOSPC;
|
||||
rc = -ENOSPC;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (flags.val & BR_MCAST_FLOOD)
|
||||
@ -2975,10 +3004,13 @@ static int sja1105_port_mcast_flood(struct sja1105_private *priv, int to,
|
||||
else
|
||||
l2_lookup[match].destports &= ~BIT(to);
|
||||
|
||||
return sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP,
|
||||
l2_lookup[match].index,
|
||||
&l2_lookup[match],
|
||||
true);
|
||||
rc = sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP,
|
||||
l2_lookup[match].index,
|
||||
&l2_lookup[match], true);
|
||||
out:
|
||||
mutex_unlock(&priv->fdb_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sja1105_port_pre_bridge_flags(struct dsa_switch *ds, int port,
|
||||
@ -3348,6 +3380,7 @@ static int sja1105_probe(struct spi_device *spi)
|
||||
mutex_init(&priv->ptp_data.lock);
|
||||
mutex_init(&priv->dynamic_config_lock);
|
||||
mutex_init(&priv->mgmt_lock);
|
||||
mutex_init(&priv->fdb_lock);
|
||||
spin_lock_init(&priv->ts_id_lock);
|
||||
|
||||
rc = sja1105_parse_dt(priv);
|
||||
|
Loading…
Reference in New Issue
Block a user