]> git.itanic.dy.fi Git - linux-stable/commitdiff
net: dsa: sja1105: fix multicast forwarding working only for last added mdb entry
authorVladimir Oltean <vladimir.oltean@nxp.com>
Fri, 8 Sep 2023 13:33:50 +0000 (16:33 +0300)
committerDavid S. Miller <davem@davemloft.net>
Mon, 11 Sep 2023 07:32:30 +0000 (08:32 +0100)
The commit cited in Fixes: did 2 things: it refactored the read-back
polling from sja1105_dynamic_config_read() into a new function,
sja1105_dynamic_config_wait_complete(), and it called that from
sja1105_dynamic_config_write() too.

What is problematic is the refactoring.

The refactored code from sja1105_dynamic_config_poll_valid() works like
the previous one, but the problem is that it uses another packed_buf[]
SPI buffer, and there was code at the end of sja1105_dynamic_config_read()
which was relying on the read-back packed_buf[]:

/* 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);

After the change, the packed_buf[] that this code sees is no longer the
entry read back from hardware, but the original entry that the caller
passed to the sja1105_dynamic_config_read(), packed into this buffer.

This difference is the most notable with the SJA1105_SEARCH uses from
sja1105pqrs_fdb_add() - used for both fdb and mdb. There, we have logic
added by commit 728db843df88 ("net: dsa: sja1105: ignore the FDB entry
for unknown multicast when adding a new address") to figure out whether
the address we're trying to add matches on any existing hardware entry,
with the exception of the catch-all multicast address.

That logic was broken, because with sja1105_dynamic_config_read() not
working properly, it doesn't return us the entry read back from
hardware, but the entry that we passed to it. And, since for multicast,
a match will always exist, it will tell us that any mdb entry already
exists at index=0 L2 Address Lookup table. It is index=0 because the
caller doesn't know the index - it wants to find it out, and
sja1105_dynamic_config_read() does:

if (index < 0) { // SJA1105_SEARCH
/* Avoid copying a signed negative number to an u64 */
cmd.index = 0; // <- this
cmd.search = true;
} else {
cmd.index = index;
cmd.search = false;
}

So, to the caller of sja1105_dynamic_config_read(), the returned info
looks entirely legit, and it will add all mdb entries to FDB index 0.
There, they will always overwrite each other (not to mention,
potentially they can also overwrite a pre-existing bridge fdb entry),
and the user-visible impact will be that only the last mdb entry will be
forwarded as it should. The others won't (will be flooded or dropped,
depending on the egress flood settings).

Fixing is a bit more complicated, and involves either passing the same
packed_buf[] to sja1105_dynamic_config_wait_complete(), or moving all
the extra processing on the packed_buf[] to
sja1105_dynamic_config_wait_complete(). I've opted for the latter,
because it makes sja1105_dynamic_config_wait_complete() a bit more
self-contained.

Fixes: df405910ab9f ("net: dsa: sja1105: wait for dynamic config command completion on writes too")
Reported-by: Yanan Yang <yanan.yang@nxp.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/sja1105/sja1105_dynamic_config.c

index 93d47dab8d3e9a56e05aa5411a3750cbffdaa8fc..984c0e604e8de23fcb940bb443c10d94595433c3 100644 (file)
@@ -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,8 +1219,9 @@ 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 err, rc;
 
@@ -1217,7 +1229,8 @@ sja1105_dynamic_config_wait_complete(struct sja1105_private *priv,
                                rc, rc != -EAGAIN,
                                SJA1105_DYNAMIC_CONFIG_SLEEP_US,
                                SJA1105_DYNAMIC_CONFIG_TIMEOUT_US,
-                               false, priv, cmd, ops);
+                               false, priv, ops, entry, check_valident,
+                               check_errors);
        return err < 0 ? err : rc;
 }
 
@@ -1287,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,
@@ -1357,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)