generic: 5.15: backport mdio improvement patch for qca8k
authorAnsuel Smith <ansuelsmth@gmail.com>
Wed, 2 Feb 2022 21:32:57 +0000 (22:32 +0100)
committerDaniel Golle <daniel@makrotopia.org>
Sun, 27 Mar 2022 03:51:38 +0000 (04:51 +0100)
Backport qca8k mdio improvement patch merged upstream,
where we use eth packet when available to send mdio commands.

This should improve speed and cause less load on the CPU.

Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
24 files changed:
target/linux/generic/backport-5.15/700-net-next-net-dsa-introduce-tagger-owned-storage-for-private.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/701-net-dsa-make-tagging-protocols-connect-to-individual-switches.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/765-1-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/765-2-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/765-3-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/765-4-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/765-5-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/765-6-net-next-net-dsa-setup-master-before-ports.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/766-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch [new file with mode: 0644]

diff --git a/target/linux/generic/backport-5.15/700-net-next-net-dsa-introduce-tagger-owned-storage-for-private.patch b/target/linux/generic/backport-5.15/700-net-next-net-dsa-introduce-tagger-owned-storage-for-private.patch
new file mode 100644 (file)
index 0000000..019d0d5
--- /dev/null
@@ -0,0 +1,290 @@
+From dc452a471dbae8aca8257c565174212620880093 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Fri, 10 Dec 2021 01:34:37 +0200
+Subject: net: dsa: introduce tagger-owned storage for private and shared data
+
+Ansuel is working on register access over Ethernet for the qca8k switch
+family. This requires the qca8k tagging protocol driver to receive
+frames which aren't intended for the network stack, but instead for the
+qca8k switch driver itself.
+
+The dp->priv is currently the prevailing method for passing data back
+and forth between the tagging protocol driver and the switch driver.
+However, this method is riddled with caveats.
+
+The DSA design allows in principle for any switch driver to return any
+protocol it desires in ->get_tag_protocol(). The dsa_loop driver can be
+modified to do just that. But in the current design, the memory behind
+dp->priv has to be allocated by the switch driver, so if the tagging
+protocol is paired to an unexpected switch driver, we may end up in NULL
+pointer dereferences inside the kernel, or worse (a switch driver may
+allocate dp->priv according to the expectations of a different tagger).
+
+The latter possibility is even more plausible considering that DSA
+switches can dynamically change tagging protocols in certain cases
+(dsa <-> edsa, ocelot <-> ocelot-8021q), and the current design lends
+itself to mistakes that are all too easy to make.
+
+This patch proposes that the tagging protocol driver should manage its
+own memory, instead of relying on the switch driver to do so.
+After analyzing the different in-tree needs, it can be observed that the
+required tagger storage is per switch, therefore a ds->tagger_data
+pointer is introduced. In principle, per-port storage could also be
+introduced, although there is no need for it at the moment. Future
+changes will replace the current usage of dp->priv with ds->tagger_data.
+
+We define a "binding" event between the DSA switch tree and the tagging
+protocol. During this binding event, the tagging protocol's ->connect()
+method is called first, and this may allocate some memory for each
+switch of the tree. Then a cross-chip notifier is emitted for the
+switches within that tree, and they are given the opportunity to fix up
+the tagger's memory (for example, they might set up some function
+pointers that represent virtual methods for consuming packets).
+Because the memory is owned by the tagger, there exists a ->disconnect()
+method for the tagger (which is the place to free the resources), but
+there doesn't exist a ->disconnect() method for the switch driver.
+This is part of the design. The switch driver should make minimal use of
+the public part of the tagger data, and only after type-checking it
+using the supplied "proto" argument.
+
+In the code there are in fact two binding events, one is the initial
+event in dsa_switch_setup_tag_protocol(). At this stage, the cross chip
+notifier chains aren't initialized, so we call each switch's connect()
+method by hand. Then there is dsa_tree_bind_tag_proto() during
+dsa_tree_change_tag_proto(), and here we have an old protocol and a new
+one. We first connect to the new one before disconnecting from the old
+one, to simplify error handling a bit and to ensure we remain in a valid
+state at all times.
+
+Co-developed-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/net/dsa.h  | 12 +++++++++
+ net/dsa/dsa2.c     | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++---
+ net/dsa/dsa_priv.h |  1 +
+ net/dsa/switch.c   | 14 +++++++++++
+ 4 files changed, 96 insertions(+), 4 deletions(-)
+
+diff --git a/include/net/dsa.h b/include/net/dsa.h
+index bdf308a5c55ee..8b496c7e62ef8 100644
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -82,12 +82,15 @@ enum dsa_tag_protocol {
+ };
+ struct dsa_switch;
++struct dsa_switch_tree;
+ struct dsa_device_ops {
+       struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
+       struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
+       void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
+                            int *offset);
++      int (*connect)(struct dsa_switch_tree *dst);
++      void (*disconnect)(struct dsa_switch_tree *dst);
+       unsigned int needed_headroom;
+       unsigned int needed_tailroom;
+       const char *name;
+@@ -337,6 +340,8 @@ struct dsa_switch {
+        */
+       void *priv;
++      void *tagger_data;
++
+       /*
+        * Configuration data for this switch.
+        */
+@@ -689,6 +694,13 @@ struct dsa_switch_ops {
+                                                 enum dsa_tag_protocol mprot);
+       int     (*change_tag_protocol)(struct dsa_switch *ds, int port,
+                                      enum dsa_tag_protocol proto);
++      /*
++       * Method for switch drivers to connect to the tagging protocol driver
++       * in current use. The switch driver can provide handlers for certain
++       * types of packets for switch management.
++       */
++      int     (*connect_tag_protocol)(struct dsa_switch *ds,
++                                      enum dsa_tag_protocol proto);
+       /* Optional switch-wide initialization and destruction methods */
+       int     (*setup)(struct dsa_switch *ds);
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index 8814fa0e44c84..cf65661686209 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -248,8 +248,12 @@ static struct dsa_switch_tree *dsa_tree_alloc(int index)
+ static void dsa_tree_free(struct dsa_switch_tree *dst)
+ {
+-      if (dst->tag_ops)
++      if (dst->tag_ops) {
++              if (dst->tag_ops->disconnect)
++                      dst->tag_ops->disconnect(dst);
++
+               dsa_tag_driver_put(dst->tag_ops);
++      }
+       list_del(&dst->list);
+       kfree(dst);
+ }
+@@ -822,7 +826,7 @@ static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)
+       int err;
+       if (tag_ops->proto == dst->default_proto)
+-              return 0;
++              goto connect;
+       dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+               rtnl_lock();
+@@ -836,6 +840,17 @@ static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)
+               }
+       }
++connect:
++      if (ds->ops->connect_tag_protocol) {
++              err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
++              if (err) {
++                      dev_err(ds->dev,
++                              "Unable to connect to tag protocol \"%s\": %pe\n",
++                              tag_ops->name, ERR_PTR(err));
++                      return err;
++              }
++      }
++
+       return 0;
+ }
+@@ -1136,6 +1151,46 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)
+       dst->setup = false;
+ }
++static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
++                                 const struct dsa_device_ops *tag_ops)
++{
++      const struct dsa_device_ops *old_tag_ops = dst->tag_ops;
++      struct dsa_notifier_tag_proto_info info;
++      int err;
++
++      dst->tag_ops = tag_ops;
++
++      /* Notify the new tagger about the connection to this tree */
++      if (tag_ops->connect) {
++              err = tag_ops->connect(dst);
++              if (err)
++                      goto out_revert;
++      }
++
++      /* Notify the switches from this tree about the connection
++       * to the new tagger
++       */
++      info.tag_ops = tag_ops;
++      err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info);
++      if (err && err != -EOPNOTSUPP)
++              goto out_disconnect;
++
++      /* Notify the old tagger about the disconnection from this tree */
++      if (old_tag_ops->disconnect)
++              old_tag_ops->disconnect(dst);
++
++      return 0;
++
++out_disconnect:
++      /* Revert the new tagger's connection to this tree */
++      if (tag_ops->disconnect)
++              tag_ops->disconnect(dst);
++out_revert:
++      dst->tag_ops = old_tag_ops;
++
++      return err;
++}
++
+ /* Since the dsa/tagging sysfs device attribute is per master, the assumption
+  * is that all DSA switches within a tree share the same tagger, otherwise
+  * they would have formed disjoint trees (different "dsa,member" values).
+@@ -1168,12 +1223,15 @@ int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
+                       goto out_unlock;
+       }
++      /* Notify the tag protocol change */
+       info.tag_ops = tag_ops;
+       err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
+       if (err)
+-              goto out_unwind_tagger;
++              return err;
+-      dst->tag_ops = tag_ops;
++      err = dsa_tree_bind_tag_proto(dst, tag_ops);
++      if (err)
++              goto out_unwind_tagger;
+       rtnl_unlock();
+@@ -1260,6 +1318,7 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master,
+       struct dsa_switch_tree *dst = ds->dst;
+       const struct dsa_device_ops *tag_ops;
+       enum dsa_tag_protocol default_proto;
++      int err;
+       /* Find out which protocol the switch would prefer. */
+       default_proto = dsa_get_tag_protocol(dp, master);
+@@ -1307,6 +1366,12 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master,
+                */
+               dsa_tag_driver_put(tag_ops);
+       } else {
++              if (tag_ops->connect) {
++                      err = tag_ops->connect(dst);
++                      if (err)
++                              return err;
++              }
++
+               dst->tag_ops = tag_ops;
+       }
+diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
+index 38ce5129a33dc..0db2b26b0c83c 100644
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -37,6 +37,7 @@ enum {
+       DSA_NOTIFIER_VLAN_DEL,
+       DSA_NOTIFIER_MTU,
+       DSA_NOTIFIER_TAG_PROTO,
++      DSA_NOTIFIER_TAG_PROTO_CONNECT,
+       DSA_NOTIFIER_MRP_ADD,
+       DSA_NOTIFIER_MRP_DEL,
+       DSA_NOTIFIER_MRP_ADD_RING_ROLE,
+diff --git a/net/dsa/switch.c b/net/dsa/switch.c
+index 9c92edd969612..06948f5368296 100644
+--- a/net/dsa/switch.c
++++ b/net/dsa/switch.c
+@@ -647,6 +647,17 @@ static int dsa_switch_change_tag_proto(struct dsa_switch *ds,
+       return 0;
+ }
++static int dsa_switch_connect_tag_proto(struct dsa_switch *ds,
++                                      struct dsa_notifier_tag_proto_info *info)
++{
++      const struct dsa_device_ops *tag_ops = info->tag_ops;
++
++      if (!ds->ops->connect_tag_protocol)
++              return -EOPNOTSUPP;
++
++      return ds->ops->connect_tag_protocol(ds, tag_ops->proto);
++}
++
+ static int dsa_switch_mrp_add(struct dsa_switch *ds,
+                             struct dsa_notifier_mrp_info *info)
+ {
+@@ -766,6 +777,9 @@ static int dsa_switch_event(struct notifier_block *nb,
+       case DSA_NOTIFIER_TAG_PROTO:
+               err = dsa_switch_change_tag_proto(ds, info);
+               break;
++      case DSA_NOTIFIER_TAG_PROTO_CONNECT:
++              err = dsa_switch_connect_tag_proto(ds, info);
++              break;
+       case DSA_NOTIFIER_MRP_ADD:
+               err = dsa_switch_mrp_add(ds, info);
+               break;
+-- 
+cgit 1.2.3-1.el7
+
diff --git a/target/linux/generic/backport-5.15/701-net-dsa-make-tagging-protocols-connect-to-individual-switches.patch b/target/linux/generic/backport-5.15/701-net-dsa-make-tagging-protocols-connect-to-individual-switches.patch
new file mode 100644 (file)
index 0000000..fa3752a
--- /dev/null
@@ -0,0 +1,285 @@
+From 7f2973149c22e7a6fee4c0c9fa6b8e4108e9c208 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Tue, 14 Dec 2021 03:45:36 +0200
+Subject: net: dsa: make tagging protocols connect to individual switches from
+ a tree
+
+On the NXP Bluebox 3 board which uses a multi-switch setup with sja1105,
+the mechanism through which the tagger connects to the switch tree is
+broken, due to improper DSA code design. At the time when tag_ops->connect()
+is called in dsa_port_parse_cpu(), DSA hasn't finished "touching" all
+the ports, so it doesn't know how large the tree is and how many ports
+it has. It has just seen the first CPU port by this time. As a result,
+this function will call the tagger's ->connect method too early, and the
+tagger will connect only to the first switch from the tree.
+
+This could be perhaps addressed a bit more simply by just moving the
+tag_ops->connect(dst) call a bit later (for example in dsa_tree_setup),
+but there is already a design inconsistency at present: on the switch
+side, the notification is on a per-switch basis, but on the tagger side,
+it is on a per-tree basis. Furthermore, the persistent storage itself is
+per switch (ds->tagger_data). And the tagger connect and disconnect
+procedures (at least the ones that exist currently) could see a fair bit
+of simplification if they didn't have to iterate through the switches of
+a tree.
+
+To fix the issue, this change transforms tag_ops->connect(dst) into
+tag_ops->connect(ds) and moves it somewhere where we already iterate
+over all switches of a tree. That is in dsa_switch_setup_tag_protocol(),
+which is a good placement because we already have there the connection
+call to the switch side of things.
+
+As for the dsa_tree_bind_tag_proto() method (called from the code path
+that changes the tag protocol), things are a bit more complicated
+because we receive the tree as argument, yet when we unwind on errors,
+it would be nice to not call tag_ops->disconnect(ds) where we didn't
+previously call tag_ops->connect(ds). We didn't have this problem before
+because the tag_ops connection operations passed the entire dst before,
+and this is more fine grained now. To solve the error rewind case using
+the new API, we have to create yet one more cross-chip notifier for
+disconnection, and stay connected with the old tag protocol to all the
+switches in the tree until we've succeeded to connect with the new one
+as well. So if something fails half way, the whole tree is still
+connected to the old tagger. But there may still be leaks if the tagger
+fails to connect to the 2nd out of 3 switches in a tree: somebody needs
+to tell the tagger to disconnect from the first switch. Nothing comes
+for free, and this was previously handled privately by the tagging
+protocol driver before, but now we need to emit a disconnect cross-chip
+notifier for that, because DSA has to take care of the unwind path. We
+assume that the tagging protocol has connected to a switch if it has set
+ds->tagger_data to something, otherwise we avoid calling its
+disconnection method in the error rewind path.
+
+The rest of the changes are in the tagging protocol drivers, and have to
+do with the replacement of dst with ds. The iteration is removed and the
+error unwind path is simplified, as mentioned above.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/net/dsa.h          |  5 ++--
+ net/dsa/dsa2.c             | 44 +++++++++++++-----------------
+ net/dsa/dsa_priv.h         |  1 +
+ net/dsa/switch.c           | 52 ++++++++++++++++++++++++++++++++---
+ net/dsa/tag_ocelot_8021q.c | 53 +++++++++++-------------------------
+ net/dsa/tag_sja1105.c      | 67 ++++++++++++++++------------------------------
+ 6 files changed, 109 insertions(+), 113 deletions(-)
+
+diff --git a/include/net/dsa.h b/include/net/dsa.h
+index 64d71968aa91a..f16959444ae12 100644
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -82,15 +82,14 @@ enum dsa_tag_protocol {
+ };
+ struct dsa_switch;
+-struct dsa_switch_tree;
+ struct dsa_device_ops {
+       struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
+       struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
+       void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
+                            int *offset);
+-      int (*connect)(struct dsa_switch_tree *dst);
+-      void (*disconnect)(struct dsa_switch_tree *dst);
++      int (*connect)(struct dsa_switch *ds);
++      void (*disconnect)(struct dsa_switch *ds);
+       unsigned int needed_headroom;
+       unsigned int needed_tailroom;
+       const char *name;
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index cf65661686209..c18b22c0bf55e 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -248,12 +248,8 @@ static struct dsa_switch_tree *dsa_tree_alloc(int index)
+ static void dsa_tree_free(struct dsa_switch_tree *dst)
+ {
+-      if (dst->tag_ops) {
+-              if (dst->tag_ops->disconnect)
+-                      dst->tag_ops->disconnect(dst);
+-
++      if (dst->tag_ops)
+               dsa_tag_driver_put(dst->tag_ops);
+-      }
+       list_del(&dst->list);
+       kfree(dst);
+ }
+@@ -841,17 +837,29 @@ static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)
+       }
+ connect:
++      if (tag_ops->connect) {
++              err = tag_ops->connect(ds);
++              if (err)
++                      return err;
++      }
++
+       if (ds->ops->connect_tag_protocol) {
+               err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
+               if (err) {
+                       dev_err(ds->dev,
+                               "Unable to connect to tag protocol \"%s\": %pe\n",
+                               tag_ops->name, ERR_PTR(err));
+-                      return err;
++                      goto disconnect;
+               }
+       }
+       return 0;
++
++disconnect:
++      if (tag_ops->disconnect)
++              tag_ops->disconnect(ds);
++
++      return err;
+ }
+ static int dsa_switch_setup(struct dsa_switch *ds)
+@@ -1160,13 +1168,6 @@ static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
+       dst->tag_ops = tag_ops;
+-      /* Notify the new tagger about the connection to this tree */
+-      if (tag_ops->connect) {
+-              err = tag_ops->connect(dst);
+-              if (err)
+-                      goto out_revert;
+-      }
+-
+       /* Notify the switches from this tree about the connection
+        * to the new tagger
+        */
+@@ -1176,16 +1177,14 @@ static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
+               goto out_disconnect;
+       /* Notify the old tagger about the disconnection from this tree */
+-      if (old_tag_ops->disconnect)
+-              old_tag_ops->disconnect(dst);
++      info.tag_ops = old_tag_ops;
++      dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
+       return 0;
+ out_disconnect:
+-      /* Revert the new tagger's connection to this tree */
+-      if (tag_ops->disconnect)
+-              tag_ops->disconnect(dst);
+-out_revert:
++      info.tag_ops = tag_ops;
++      dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
+       dst->tag_ops = old_tag_ops;
+       return err;
+@@ -1318,7 +1317,6 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master,
+       struct dsa_switch_tree *dst = ds->dst;
+       const struct dsa_device_ops *tag_ops;
+       enum dsa_tag_protocol default_proto;
+-      int err;
+       /* Find out which protocol the switch would prefer. */
+       default_proto = dsa_get_tag_protocol(dp, master);
+@@ -1366,12 +1364,6 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master,
+                */
+               dsa_tag_driver_put(tag_ops);
+       } else {
+-              if (tag_ops->connect) {
+-                      err = tag_ops->connect(dst);
+-                      if (err)
+-                              return err;
+-              }
+-
+               dst->tag_ops = tag_ops;
+       }
+diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
+index 0db2b26b0c83c..edfaae7b59672 100644
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -38,6 +38,7 @@ enum {
+       DSA_NOTIFIER_MTU,
+       DSA_NOTIFIER_TAG_PROTO,
+       DSA_NOTIFIER_TAG_PROTO_CONNECT,
++      DSA_NOTIFIER_TAG_PROTO_DISCONNECT,
+       DSA_NOTIFIER_MRP_ADD,
+       DSA_NOTIFIER_MRP_DEL,
+       DSA_NOTIFIER_MRP_ADD_RING_ROLE,
+diff --git a/net/dsa/switch.c b/net/dsa/switch.c
+index 06948f5368296..393f2d8a860a9 100644
+--- a/net/dsa/switch.c
++++ b/net/dsa/switch.c
+@@ -647,15 +647,58 @@ static int dsa_switch_change_tag_proto(struct dsa_switch *ds,
+       return 0;
+ }
+-static int dsa_switch_connect_tag_proto(struct dsa_switch *ds,
+-                                      struct dsa_notifier_tag_proto_info *info)
++/* We use the same cross-chip notifiers to inform both the tagger side, as well
++ * as the switch side, of connection and disconnection events.
++ * Since ds->tagger_data is owned by the tagger, it isn't a hard error if the
++ * switch side doesn't support connecting to this tagger, and therefore, the
++ * fact that we don't disconnect the tagger side doesn't constitute a memory
++ * leak: the tagger will still operate with persistent per-switch memory, just
++ * with the switch side unconnected to it. What does constitute a hard error is
++ * when the switch side supports connecting but fails.
++ */
++static int
++dsa_switch_connect_tag_proto(struct dsa_switch *ds,
++                           struct dsa_notifier_tag_proto_info *info)
+ {
+       const struct dsa_device_ops *tag_ops = info->tag_ops;
++      int err;
++
++      /* Notify the new tagger about the connection to this switch */
++      if (tag_ops->connect) {
++              err = tag_ops->connect(ds);
++              if (err)
++                      return err;
++      }
+       if (!ds->ops->connect_tag_protocol)
+               return -EOPNOTSUPP;
+-      return ds->ops->connect_tag_protocol(ds, tag_ops->proto);
++      /* Notify the switch about the connection to the new tagger */
++      err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
++      if (err) {
++              /* Revert the new tagger's connection to this tree */
++              if (tag_ops->disconnect)
++                      tag_ops->disconnect(ds);
++              return err;
++      }
++
++      return 0;
++}
++
++static int
++dsa_switch_disconnect_tag_proto(struct dsa_switch *ds,
++                              struct dsa_notifier_tag_proto_info *info)
++{
++      const struct dsa_device_ops *tag_ops = info->tag_ops;
++
++      /* Notify the tagger about the disconnection from this switch */
++      if (tag_ops->disconnect && ds->tagger_data)
++              tag_ops->disconnect(ds);
++
++      /* No need to notify the switch, since it shouldn't have any
++       * resources to tear down
++       */
++      return 0;
+ }
+ static int dsa_switch_mrp_add(struct dsa_switch *ds,
+@@ -780,6 +823,9 @@ static int dsa_switch_event(struct notifier_block *nb,
+       case DSA_NOTIFIER_TAG_PROTO_CONNECT:
+               err = dsa_switch_connect_tag_proto(ds, info);
+               break;
++      case DSA_NOTIFIER_TAG_PROTO_DISCONNECT:
++              err = dsa_switch_disconnect_tag_proto(ds, info);
++              break;
+       case DSA_NOTIFIER_MRP_ADD:
+               err = dsa_switch_mrp_add(ds, info);
+               break;
+-- 
+cgit 1.2.3-1.el7
+
diff --git a/target/linux/generic/backport-5.15/765-1-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch b/target/linux/generic/backport-5.15/765-1-net-next-net-dsa-reorder-PHY-initialization-with-MTU-setup-in.patch
new file mode 100644 (file)
index 0000000..25c6ac2
--- /dev/null
@@ -0,0 +1,57 @@
+From 904e112ad431492b34f235f59738e8312802bbf9 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Thu, 6 Jan 2022 01:11:12 +0200
+Subject: [PATCH 1/6] net: dsa: reorder PHY initialization with MTU setup in
+ slave.c
+
+In dsa_slave_create() there are 2 sections that take rtnl_lock():
+MTU change and netdev registration. They are separated by PHY
+initialization.
+
+There isn't any strict ordering requirement except for the fact that
+netdev registration should be last. Therefore, we can perform the MTU
+change a bit later, after the PHY setup. A future change will then be
+able to merge the two rtnl_lock sections into one.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/slave.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 88f7b8686dac..88bcdba92fa7 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2011,13 +2011,6 @@ int dsa_slave_create(struct dsa_port *port)
+       port->slave = slave_dev;
+       dsa_slave_setup_tagger(slave_dev);
+-      rtnl_lock();
+-      ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
+-      rtnl_unlock();
+-      if (ret && ret != -EOPNOTSUPP)
+-              dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
+-                       ret, ETH_DATA_LEN, port->index);
+-
+       netif_carrier_off(slave_dev);
+       ret = dsa_slave_phy_setup(slave_dev);
+@@ -2028,6 +2021,13 @@ int dsa_slave_create(struct dsa_port *port)
+               goto out_gcells;
+       }
++      rtnl_lock();
++      ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
++      rtnl_unlock();
++      if (ret && ret != -EOPNOTSUPP)
++              dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
++                       ret, ETH_DATA_LEN, port->index);
++
+       rtnl_lock();
+       ret = register_netdevice(slave_dev);
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/765-2-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch b/target/linux/generic/backport-5.15/765-2-net-next-net-dsa-merge-rtnl_lock-sections-in-dsa_slave_create.patch
new file mode 100644 (file)
index 0000000..c383bd9
--- /dev/null
@@ -0,0 +1,39 @@
+From e31dbd3b6aba585231cd84a87adeb22e7c6a8c19 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Thu, 6 Jan 2022 01:11:13 +0200
+Subject: [PATCH 2/6] net: dsa: merge rtnl_lock sections in dsa_slave_create
+
+Currently dsa_slave_create() has two sequences of rtnl_lock/rtnl_unlock
+in a row. Remove the rtnl_unlock() and rtnl_lock() in between, such that
+the operation can execute slighly faster.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/slave.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 88bcdba92fa7..22241afcac81 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2022,14 +2022,12 @@ int dsa_slave_create(struct dsa_port *port)
+       }
+       rtnl_lock();
++
+       ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
+-      rtnl_unlock();
+       if (ret && ret != -EOPNOTSUPP)
+               dev_warn(ds->dev, "nonfatal error %d setting MTU to %d on port %d\n",
+                        ret, ETH_DATA_LEN, port->index);
+-      rtnl_lock();
+-
+       ret = register_netdevice(slave_dev);
+       if (ret) {
+               netdev_err(master, "error %d registering interface %s\n",
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/765-3-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch b/target/linux/generic/backport-5.15/765-3-net-next-net-dsa-stop-updating-master-MTU-from-master.c.patch
new file mode 100644 (file)
index 0000000..c81beda
--- /dev/null
@@ -0,0 +1,96 @@
+From a1ff94c2973c43bc1e2677ac63ebb15b1d1ff846 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Thu, 6 Jan 2022 01:11:14 +0200
+Subject: [PATCH 3/6] net: dsa: stop updating master MTU from master.c
+
+At present there are two paths for changing the MTU of the DSA master.
+
+The first is:
+
+dsa_tree_setup
+-> dsa_tree_setup_ports
+   -> dsa_port_setup
+      -> dsa_slave_create
+         -> dsa_slave_change_mtu
+            -> dev_set_mtu(master)
+
+The second is:
+
+dsa_tree_setup
+-> dsa_tree_setup_master
+   -> dsa_master_setup
+      -> dev_set_mtu(dev)
+
+So the dev_set_mtu() call from dsa_master_setup() has been effectively
+superseded by the dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN) that is
+done from dsa_slave_create() for each user port. The later function also
+updates the master MTU according to the largest user port MTU from the
+tree. Therefore, updating the master MTU through a separate code path
+isn't needed.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/master.c | 25 +------------------------
+ 1 file changed, 1 insertion(+), 24 deletions(-)
+
+diff --git a/net/dsa/master.c b/net/dsa/master.c
+index e8e19857621b..f4efb244f91d 100644
+--- a/net/dsa/master.c
++++ b/net/dsa/master.c
+@@ -330,28 +330,13 @@ static const struct attribute_group dsa_group = {
+       .attrs  = dsa_slave_attrs,
+ };
+-static void dsa_master_reset_mtu(struct net_device *dev)
+-{
+-      int err;
+-
+-      rtnl_lock();
+-      err = dev_set_mtu(dev, ETH_DATA_LEN);
+-      if (err)
+-              netdev_dbg(dev,
+-                         "Unable to reset MTU to exclude DSA overheads\n");
+-      rtnl_unlock();
+-}
+-
+ static struct lock_class_key dsa_master_addr_list_lock_key;
+ int dsa_master_setup(struct net_device *dev, struct dsa_port *cpu_dp)
+ {
+-      const struct dsa_device_ops *tag_ops = cpu_dp->tag_ops;
+       struct dsa_switch *ds = cpu_dp->ds;
+       struct device_link *consumer_link;
+-      int mtu, ret;
+-
+-      mtu = ETH_DATA_LEN + dsa_tag_protocol_overhead(tag_ops);
++      int ret;
+       /* The DSA master must use SET_NETDEV_DEV for this to work. */
+       consumer_link = device_link_add(ds->dev, dev->dev.parent,
+@@ -361,13 +346,6 @@ int dsa_master_setup(struct net_device *dev, struct dsa_port *cpu_dp)
+                          "Failed to create a device link to DSA switch %s\n",
+                          dev_name(ds->dev));
+-      rtnl_lock();
+-      ret = dev_set_mtu(dev, mtu);
+-      rtnl_unlock();
+-      if (ret)
+-              netdev_warn(dev, "error %d setting MTU to %d to include DSA overhead\n",
+-                          ret, mtu);
+-
+       /* If we use a tagging format that doesn't have an ethertype
+        * field, make sure that all packets from this point on get
+        * sent to the tag format's receive function.
+@@ -405,7 +383,6 @@ void dsa_master_teardown(struct net_device *dev)
+       sysfs_remove_group(&dev->dev.kobj, &dsa_group);
+       dsa_netdev_ops_set(dev, NULL);
+       dsa_master_ethtool_teardown(dev);
+-      dsa_master_reset_mtu(dev);
+       dsa_master_set_promiscuity(dev, -1);
+       dev->dsa_ptr = NULL;
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/765-4-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch b/target/linux/generic/backport-5.15/765-4-net-next-net-dsa-hold-rtnl_mutex-when-calling-dsa_master_-set.patch
new file mode 100644 (file)
index 0000000..cbbe52b
--- /dev/null
@@ -0,0 +1,85 @@
+From c146f9bc195a9dc3ad7fd000a14540e7c9df952d Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Thu, 6 Jan 2022 01:11:15 +0200
+Subject: [PATCH 4/6] net: dsa: hold rtnl_mutex when calling
+ dsa_master_{setup,teardown}
+
+DSA needs to simulate master tracking events when a binding is first
+with a DSA master established and torn down, in order to give drivers
+the simplifying guarantee that ->master_state_change calls are made
+only when the master's readiness state to pass traffic changes.
+master_state_change() provide a operational bool that DSA driver can use
+to understand if DSA master is operational or not.
+To avoid races, we need to block the reception of
+NETDEV_UP/NETDEV_CHANGE/NETDEV_GOING_DOWN events in the netdev notifier
+chain while we are changing the master's dev->dsa_ptr (this changes what
+netdev_uses_dsa(dev) reports).
+
+The dsa_master_setup() and dsa_master_teardown() functions optionally
+require the rtnl_mutex to be held, if the tagger needs the master to be
+promiscuous, these functions call dev_set_promiscuity(). Move the
+rtnl_lock() from that function and make it top-level.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/dsa2.c   | 8 ++++++++
+ net/dsa/master.c | 4 ++--
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index a0d84f9f864f..52fb1958b535 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -1038,6 +1038,8 @@ static int dsa_tree_setup_master(struct dsa_switch_tree *dst)
+       struct dsa_port *dp;
+       int err;
++      rtnl_lock();
++
+       list_for_each_entry(dp, &dst->ports, list) {
+               if (dsa_port_is_cpu(dp)) {
+                       err = dsa_master_setup(dp->master, dp);
+@@ -1046,6 +1048,8 @@ static int dsa_tree_setup_master(struct dsa_switch_tree *dst)
+               }
+       }
++      rtnl_unlock();
++
+       return 0;
+ }
+@@ -1053,9 +1057,13 @@ static void dsa_tree_teardown_master(struct dsa_switch_tree *dst)
+ {
+       struct dsa_port *dp;
++      rtnl_lock();
++
+       list_for_each_entry(dp, &dst->ports, list)
+               if (dsa_port_is_cpu(dp))
+                       dsa_master_teardown(dp->master);
++
++      rtnl_unlock();
+ }
+ static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
+diff --git a/net/dsa/master.c b/net/dsa/master.c
+index f4efb244f91d..2199104ca7df 100644
+--- a/net/dsa/master.c
++++ b/net/dsa/master.c
+@@ -267,9 +267,9 @@ static void dsa_master_set_promiscuity(struct net_device *dev, int inc)
+       if (!ops->promisc_on_master)
+               return;
+-      rtnl_lock();
++      ASSERT_RTNL();
++
+       dev_set_promiscuity(dev, inc);
+-      rtnl_unlock();
+ }
+ static ssize_t tagging_show(struct device *d, struct device_attribute *attr,
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/765-5-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch b/target/linux/generic/backport-5.15/765-5-net-next-net-dsa-first-set-up-shared-ports-then-non-shared-po.patch
new file mode 100644 (file)
index 0000000..fbd63a5
--- /dev/null
@@ -0,0 +1,123 @@
+From 1e3f407f3cacc5dcfe27166c412ed9bc263d82bf Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Thu, 6 Jan 2022 01:11:16 +0200
+Subject: [PATCH 5/6] net: dsa: first set up shared ports, then non-shared
+ ports
+
+After commit a57d8c217aad ("net: dsa: flush switchdev workqueue before
+tearing down CPU/DSA ports"), the port setup and teardown procedure
+became asymmetric.
+
+The fact of the matter is that user ports need the shared ports to be up
+before they can be used for CPU-initiated termination. And since we
+register net devices for the user ports, those won't be functional until
+we also call the setup for the shared (CPU, DSA) ports. But we may do
+that later, depending on the port numbering scheme of the hardware we
+are dealing with.
+
+It just makes sense that all shared ports are brought up before any user
+port is. I can't pinpoint any issue due to the current behavior, but
+let's change it nonetheless, for consistency's sake.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/dsa2.c | 50 +++++++++++++++++++++++++++++++++++++-------------
+ 1 file changed, 37 insertions(+), 13 deletions(-)
+
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index 52fb1958b535..ea0f02a24b8b 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -1003,23 +1003,28 @@ static void dsa_tree_teardown_switches(struct dsa_switch_tree *dst)
+               dsa_switch_teardown(dp->ds);
+ }
+-static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
++/* Bring shared ports up first, then non-shared ports */
++static int dsa_tree_setup_ports(struct dsa_switch_tree *dst)
+ {
+       struct dsa_port *dp;
+-      int err;
++      int err = 0;
+       list_for_each_entry(dp, &dst->ports, list) {
+-              err = dsa_switch_setup(dp->ds);
+-              if (err)
+-                      goto teardown;
++              if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) {
++                      err = dsa_port_setup(dp);
++                      if (err)
++                              goto teardown;
++              }
+       }
+       list_for_each_entry(dp, &dst->ports, list) {
+-              err = dsa_port_setup(dp);
+-              if (err) {
+-                      err = dsa_port_reinit_as_unused(dp);
+-                      if (err)
+-                              goto teardown;
++              if (dsa_port_is_user(dp) || dsa_port_is_unused(dp)) {
++                      err = dsa_port_setup(dp);
++                      if (err) {
++                              err = dsa_port_reinit_as_unused(dp);
++                              if (err)
++                                      goto teardown;
++                      }
+               }
+       }
+@@ -1028,7 +1033,21 @@ static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
+ teardown:
+       dsa_tree_teardown_ports(dst);
+-      dsa_tree_teardown_switches(dst);
++      return err;
++}
++
++static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
++{
++      struct dsa_port *dp;
++      int err = 0;
++
++      list_for_each_entry(dp, &dst->ports, list) {
++              err = dsa_switch_setup(dp->ds);
++              if (err) {
++                      dsa_tree_teardown_switches(dst);
++                      break;
++              }
++      }
+       return err;
+ }
+@@ -1115,10 +1134,14 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
+       if (err)
+               goto teardown_cpu_ports;
+-      err = dsa_tree_setup_master(dst);
++      err = dsa_tree_setup_ports(dst);
+       if (err)
+               goto teardown_switches;
++      err = dsa_tree_setup_master(dst);
++      if (err)
++              goto teardown_ports;
++
+       err = dsa_tree_setup_lags(dst);
+       if (err)
+               goto teardown_master;
+@@ -1131,8 +1154,9 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
+ teardown_master:
+       dsa_tree_teardown_master(dst);
+-teardown_switches:
++teardown_ports:
+       dsa_tree_teardown_ports(dst);
++teardown_switches:
+       dsa_tree_teardown_switches(dst);
+ teardown_cpu_ports:
+       dsa_tree_teardown_cpu_ports(dst);
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/765-6-net-next-net-dsa-setup-master-before-ports.patch b/target/linux/generic/backport-5.15/765-6-net-next-net-dsa-setup-master-before-ports.patch
new file mode 100644 (file)
index 0000000..3f5b719
--- /dev/null
@@ -0,0 +1,120 @@
+From 11fd667dac315ea3f2469961f6d2869271a46cae Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Thu, 6 Jan 2022 01:11:17 +0200
+Subject: [PATCH 6/6] net: dsa: setup master before ports
+
+It is said that as soon as a network interface is registered, all its
+resources should have already been prepared, so that it is available for
+sending and receiving traffic. One of the resources needed by a DSA
+slave interface is the master.
+
+dsa_tree_setup
+-> dsa_tree_setup_ports
+   -> dsa_port_setup
+      -> dsa_slave_create
+         -> register_netdevice
+-> dsa_tree_setup_master
+   -> dsa_master_setup
+      -> sets up master->dsa_ptr, which enables reception
+
+Therefore, there is a short period of time after register_netdevice()
+during which the master isn't prepared to pass traffic to the DSA layer
+(master->dsa_ptr is checked by eth_type_trans). Same thing during
+unregistration, there is a time frame in which packets might be missed.
+
+Note that this change opens us to another race: dsa_master_find_slave()
+will get invoked potentially earlier than the slave creation, and later
+than the slave deletion. Since dp->slave starts off as a NULL pointer,
+the earlier calls aren't a problem, but the later calls are. To avoid
+use-after-free, we should zeroize dp->slave before calling
+dsa_slave_destroy().
+
+In practice I cannot really test real life improvements brought by this
+change, since in my systems, netdevice creation races with PHY autoneg
+which takes a few seconds to complete, and that masks quite a few races.
+Effects might be noticeable in a setup with fixed links all the way to
+an external system.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/dsa2.c | 23 +++++++++++++----------
+ 1 file changed, 13 insertions(+), 10 deletions(-)
+
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index ea0f02a24b8b..3d21521453fe 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -561,6 +561,7 @@ static void dsa_port_teardown(struct dsa_port *dp)
+       struct devlink_port *dlp = &dp->devlink_port;
+       struct dsa_switch *ds = dp->ds;
+       struct dsa_mac_addr *a, *tmp;
++      struct net_device *slave;
+       if (!dp->setup)
+               return;
+@@ -582,9 +583,11 @@ static void dsa_port_teardown(struct dsa_port *dp)
+               dsa_port_link_unregister_of(dp);
+               break;
+       case DSA_PORT_TYPE_USER:
+-              if (dp->slave) {
+-                      dsa_slave_destroy(dp->slave);
++              slave = dp->slave;
++
++              if (slave) {
+                       dp->slave = NULL;
++                      dsa_slave_destroy(slave);
+               }
+               break;
+       }
+@@ -1134,17 +1137,17 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
+       if (err)
+               goto teardown_cpu_ports;
+-      err = dsa_tree_setup_ports(dst);
++      err = dsa_tree_setup_master(dst);
+       if (err)
+               goto teardown_switches;
+-      err = dsa_tree_setup_master(dst);
++      err = dsa_tree_setup_ports(dst);
+       if (err)
+-              goto teardown_ports;
++              goto teardown_master;
+       err = dsa_tree_setup_lags(dst);
+       if (err)
+-              goto teardown_master;
++              goto teardown_ports;
+       dst->setup = true;
+@@ -1152,10 +1155,10 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
+       return 0;
+-teardown_master:
+-      dsa_tree_teardown_master(dst);
+ teardown_ports:
+       dsa_tree_teardown_ports(dst);
++teardown_master:
++      dsa_tree_teardown_master(dst);
+ teardown_switches:
+       dsa_tree_teardown_switches(dst);
+ teardown_cpu_ports:
+@@ -1173,10 +1176,10 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)
+       dsa_tree_teardown_lags(dst);
+-      dsa_tree_teardown_master(dst);
+-
+       dsa_tree_teardown_ports(dst);
++      dsa_tree_teardown_master(dst);
++
+       dsa_tree_teardown_switches(dst);
+       dsa_tree_teardown_cpu_ports(dst);
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch b/target/linux/generic/backport-5.15/766-01-net-dsa-provide-switch-operations-for-tracking-the-m.patch
new file mode 100644 (file)
index 0000000..2cfb488
--- /dev/null
@@ -0,0 +1,267 @@
+From 295ab96f478d0fa56393e85406f19a867e26ce22 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 2 Feb 2022 01:03:20 +0100
+Subject: [PATCH 01/16] net: dsa: provide switch operations for tracking the
+ master state
+
+Certain drivers may need to send management traffic to the switch for
+things like register access, FDB dump, etc, to accelerate what their
+slow bus (SPI, I2C, MDIO) can already do.
+
+Ethernet is faster (especially in bulk transactions) but is also more
+unreliable, since the user may decide to bring the DSA master down (or
+not bring it up), therefore severing the link between the host and the
+attached switch.
+
+Drivers needing Ethernet-based register access already should have
+fallback logic to the slow bus if the Ethernet method fails, but that
+fallback may be based on a timeout, and the I/O to the switch may slow
+down to a halt if the master is down, because every Ethernet packet will
+have to time out. The driver also doesn't have the option to turn off
+Ethernet-based I/O momentarily, because it wouldn't know when to turn it
+back on.
+
+Which is where this change comes in. By tracking NETDEV_CHANGE,
+NETDEV_UP and NETDEV_GOING_DOWN events on the DSA master, we should know
+the exact interval of time during which this interface is reliably
+available for traffic. Provide this information to switches so they can
+use it as they wish.
+
+An helper is added dsa_port_master_is_operational() to check if a master
+port is operational.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/net/dsa.h  | 17 +++++++++++++++++
+ net/dsa/dsa2.c     | 46 ++++++++++++++++++++++++++++++++++++++++++++++
+ net/dsa/dsa_priv.h | 13 +++++++++++++
+ net/dsa/slave.c    | 32 ++++++++++++++++++++++++++++++++
+ net/dsa/switch.c   | 15 +++++++++++++++
+ 5 files changed, 123 insertions(+)
+
+diff --git a/include/net/dsa.h b/include/net/dsa.h
+index 57b3e4e7413b..43c4153ef53a 100644
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -278,6 +278,10 @@ struct dsa_port {
+       struct list_head        mdbs;
+       bool setup;
++      /* Master state bits, valid only on CPU ports */
++      u8                      master_admin_up:1;
++      u8                      master_oper_up:1;
++
+ };
+ /* TODO: ideally DSA ports would have a single dp->link_dp member,
+@@ -478,6 +482,12 @@ static inline bool dsa_port_is_unused(struct dsa_port *dp)
+       return dp->type == DSA_PORT_TYPE_UNUSED;
+ }
++static inline bool dsa_port_master_is_operational(struct dsa_port *dp)
++{
++      return dsa_port_is_cpu(dp) && dp->master_admin_up &&
++             dp->master_oper_up;
++}
++
+ static inline bool dsa_is_unused_port(struct dsa_switch *ds, int p)
+ {
+       return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_UNUSED;
+@@ -1036,6 +1046,13 @@ struct dsa_switch_ops {
+       int     (*tag_8021q_vlan_add)(struct dsa_switch *ds, int port, u16 vid,
+                                     u16 flags);
+       int     (*tag_8021q_vlan_del)(struct dsa_switch *ds, int port, u16 vid);
++
++      /*
++       * DSA master tracking operations
++       */
++      void    (*master_state_change)(struct dsa_switch *ds,
++                                     const struct net_device *master,
++                                     bool operational);
+ };
+ #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)          \
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index 3d21521453fe..ff998c0ede02 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -1279,6 +1279,52 @@ int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
+       return err;
+ }
++static void dsa_tree_master_state_change(struct dsa_switch_tree *dst,
++                                       struct net_device *master)
++{
++      struct dsa_notifier_master_state_info info;
++      struct dsa_port *cpu_dp = master->dsa_ptr;
++
++      info.master = master;
++      info.operational = dsa_port_master_is_operational(cpu_dp);
++
++      dsa_tree_notify(dst, DSA_NOTIFIER_MASTER_STATE_CHANGE, &info);
++}
++
++void dsa_tree_master_admin_state_change(struct dsa_switch_tree *dst,
++                                      struct net_device *master,
++                                      bool up)
++{
++      struct dsa_port *cpu_dp = master->dsa_ptr;
++      bool notify = false;
++
++      if ((dsa_port_master_is_operational(cpu_dp)) !=
++          (up && cpu_dp->master_oper_up))
++              notify = true;
++
++      cpu_dp->master_admin_up = up;
++
++      if (notify)
++              dsa_tree_master_state_change(dst, master);
++}
++
++void dsa_tree_master_oper_state_change(struct dsa_switch_tree *dst,
++                                     struct net_device *master,
++                                     bool up)
++{
++      struct dsa_port *cpu_dp = master->dsa_ptr;
++      bool notify = false;
++
++      if ((dsa_port_master_is_operational(cpu_dp)) !=
++          (cpu_dp->master_admin_up && up))
++              notify = true;
++
++      cpu_dp->master_oper_up = up;
++
++      if (notify)
++              dsa_tree_master_state_change(dst, master);
++}
++
+ static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
+ {
+       struct dsa_switch_tree *dst = ds->dst;
+diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
+index 760306f0012f..2bbfa9efe9f8 100644
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -40,6 +40,7 @@ enum {
+       DSA_NOTIFIER_TAG_PROTO_DISCONNECT,
+       DSA_NOTIFIER_TAG_8021Q_VLAN_ADD,
+       DSA_NOTIFIER_TAG_8021Q_VLAN_DEL,
++      DSA_NOTIFIER_MASTER_STATE_CHANGE,
+ };
+ /* DSA_NOTIFIER_AGEING_TIME */
+@@ -109,6 +110,12 @@ struct dsa_notifier_tag_8021q_vlan_info {
+       u16 vid;
+ };
++/* DSA_NOTIFIER_MASTER_STATE_CHANGE */
++struct dsa_notifier_master_state_info {
++      const struct net_device *master;
++      bool operational;
++};
++
+ struct dsa_switchdev_event_work {
+       struct dsa_switch *ds;
+       int port;
+@@ -482,6 +489,12 @@ int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
+                             struct net_device *master,
+                             const struct dsa_device_ops *tag_ops,
+                             const struct dsa_device_ops *old_tag_ops);
++void dsa_tree_master_admin_state_change(struct dsa_switch_tree *dst,
++                                      struct net_device *master,
++                                      bool up);
++void dsa_tree_master_oper_state_change(struct dsa_switch_tree *dst,
++                                     struct net_device *master,
++                                     bool up);
+ int dsa_bridge_num_get(const struct net_device *bridge_dev, int max);
+ void dsa_bridge_num_put(const struct net_device *bridge_dev, int bridge_num);
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 22241afcac81..2b5b0f294233 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2346,6 +2346,36 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
+               err = dsa_port_lag_change(dp, info->lower_state_info);
+               return notifier_from_errno(err);
+       }
++      case NETDEV_CHANGE:
++      case NETDEV_UP: {
++              /* Track state of master port.
++               * DSA driver may require the master port (and indirectly
++               * the tagger) to be available for some special operation.
++               */
++              if (netdev_uses_dsa(dev)) {
++                      struct dsa_port *cpu_dp = dev->dsa_ptr;
++                      struct dsa_switch_tree *dst = cpu_dp->ds->dst;
++
++                      /* Track when the master port is UP */
++                      dsa_tree_master_oper_state_change(dst, dev,
++                                                        netif_oper_up(dev));
++
++                      /* Track when the master port is ready and can accept
++                       * packet.
++                       * NETDEV_UP event is not enough to flag a port as ready.
++                       * We also have to wait for linkwatch_do_dev to dev_activate
++                       * and emit a NETDEV_CHANGE event.
++                       * We check if a master port is ready by checking if the dev
++                       * have a qdisc assigned and is not noop.
++                       */
++                      dsa_tree_master_admin_state_change(dst, dev,
++                                                         !qdisc_tx_is_noop(dev));
++
++                      return NOTIFY_OK;
++              }
++
++              return NOTIFY_DONE;
++      }
+       case NETDEV_GOING_DOWN: {
+               struct dsa_port *dp, *cpu_dp;
+               struct dsa_switch_tree *dst;
+@@ -2357,6 +2387,8 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
+               cpu_dp = dev->dsa_ptr;
+               dst = cpu_dp->ds->dst;
++              dsa_tree_master_admin_state_change(dst, dev, false);
++
+               list_for_each_entry(dp, &dst->ports, list) {
+                       if (!dsa_port_is_user(dp))
+                               continue;
+diff --git a/net/dsa/switch.c b/net/dsa/switch.c
+index 517cc83d13cc..4866b58649e4 100644
+--- a/net/dsa/switch.c
++++ b/net/dsa/switch.c
+@@ -697,6 +697,18 @@ dsa_switch_disconnect_tag_proto(struct dsa_switch *ds,
+       return 0;
+ }
++static int
++dsa_switch_master_state_change(struct dsa_switch *ds,
++                             struct dsa_notifier_master_state_info *info)
++{
++      if (!ds->ops->master_state_change)
++              return 0;
++
++      ds->ops->master_state_change(ds, info->master, info->operational);
++
++      return 0;
++}
++
+ static int dsa_switch_event(struct notifier_block *nb,
+                           unsigned long event, void *info)
+ {
+@@ -770,6 +782,9 @@ static int dsa_switch_event(struct notifier_block *nb,
+       case DSA_NOTIFIER_TAG_8021Q_VLAN_DEL:
+               err = dsa_switch_tag_8021q_vlan_del(ds, info);
+               break;
++      case DSA_NOTIFIER_MASTER_STATE_CHANGE:
++              err = dsa_switch_master_state_change(ds, info);
++              break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch b/target/linux/generic/backport-5.15/766-02-net-dsa-replay-master-state-events-in-dsa_tree_-setu.patch
new file mode 100644 (file)
index 0000000..d9356c3
--- /dev/null
@@ -0,0 +1,94 @@
+From e83d56537859849f2223b90749e554831b1f3c27 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 2 Feb 2022 01:03:21 +0100
+Subject: [PATCH 02/16] net: dsa: replay master state events in
+ dsa_tree_{setup,teardown}_master
+
+In order for switch driver to be able to make simple and reliable use of
+the master tracking operations, they must also be notified of the
+initial state of the DSA master, not just of the changes. This is
+because they might enable certain features only during the time when
+they know that the DSA master is up and running.
+
+Therefore, this change explicitly checks the state of the DSA master
+under the same rtnl_mutex as we were holding during the
+dsa_master_setup() and dsa_master_teardown() call. The idea being that
+if the DSA master became operational in between the moment in which it
+became a DSA master (dsa_master_setup set dev->dsa_ptr) and the moment
+when we checked for the master being up, there is a chance that we
+would emit a ->master_state_change() call with no actual state change.
+We need to avoid that by serializing the concurrent netdevice event with
+us. If the netdevice event started before, we force it to finish before
+we begin, because we take rtnl_lock before making netdev_uses_dsa()
+return true. So we also handle that early event and do nothing on it.
+Similarly, if the dev_open() attempt is concurrent with us, it will
+attempt to take the rtnl_mutex, but we're holding it. We'll see that
+the master flag IFF_UP isn't set, then when we release the rtnl_mutex
+we'll process the NETDEV_UP notifier.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/dsa2.c | 28 ++++++++++++++++++++++++----
+ 1 file changed, 24 insertions(+), 4 deletions(-)
+
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index ff998c0ede02..909b045c9b11 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -15,6 +15,7 @@
+ #include <linux/of.h>
+ #include <linux/of_net.h>
+ #include <net/devlink.h>
++#include <net/sch_generic.h>
+ #include "dsa_priv.h"
+@@ -1064,9 +1065,18 @@ static int dsa_tree_setup_master(struct dsa_switch_tree *dst)
+       list_for_each_entry(dp, &dst->ports, list) {
+               if (dsa_port_is_cpu(dp)) {
+-                      err = dsa_master_setup(dp->master, dp);
++                      struct net_device *master = dp->master;
++                      bool admin_up = (master->flags & IFF_UP) &&
++                                      !qdisc_tx_is_noop(master);
++
++                      err = dsa_master_setup(master, dp);
+                       if (err)
+                               return err;
++
++                      /* Replay master state event */
++                      dsa_tree_master_admin_state_change(dst, master, admin_up);
++                      dsa_tree_master_oper_state_change(dst, master,
++                                                        netif_oper_up(master));
+               }
+       }
+@@ -1081,9 +1091,19 @@ static void dsa_tree_teardown_master(struct dsa_switch_tree *dst)
+       rtnl_lock();
+-      list_for_each_entry(dp, &dst->ports, list)
+-              if (dsa_port_is_cpu(dp))
+-                      dsa_master_teardown(dp->master);
++      list_for_each_entry(dp, &dst->ports, list) {
++              if (dsa_port_is_cpu(dp)) {
++                      struct net_device *master = dp->master;
++
++                      /* Synthesizing an "admin down" state is sufficient for
++                       * the switches to get a notification if the master is
++                       * currently up and running.
++                       */
++                      dsa_tree_master_admin_state_change(dst, master, false);
++
++                      dsa_master_teardown(master);
++              }
++      }
+       rtnl_unlock();
+ }
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch b/target/linux/generic/backport-5.15/766-03-net-dsa-tag_qca-convert-to-FIELD-macro.patch
new file mode 100644 (file)
index 0000000..84fd918
--- /dev/null
@@ -0,0 +1,92 @@
+From 6b0458299297ca4ab6fb295800e29a4e501d50c1 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:22 +0100
+Subject: [PATCH 03/16] net: dsa: tag_qca: convert to FIELD macro
+
+Convert driver to FIELD macro to drop redundant define.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/tag_qca.c | 34 +++++++++++++++-------------------
+ 1 file changed, 15 insertions(+), 19 deletions(-)
+
+diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
+index 1ea9401b8ace..55fa6b96b4eb 100644
+--- a/net/dsa/tag_qca.c
++++ b/net/dsa/tag_qca.c
+@@ -4,29 +4,24 @@
+  */
+ #include <linux/etherdevice.h>
++#include <linux/bitfield.h>
+ #include "dsa_priv.h"
+ #define QCA_HDR_LEN   2
+ #define QCA_HDR_VERSION       0x2
+-#define QCA_HDR_RECV_VERSION_MASK     GENMASK(15, 14)
+-#define QCA_HDR_RECV_VERSION_S                14
+-#define QCA_HDR_RECV_PRIORITY_MASK    GENMASK(13, 11)
+-#define QCA_HDR_RECV_PRIORITY_S               11
+-#define QCA_HDR_RECV_TYPE_MASK                GENMASK(10, 6)
+-#define QCA_HDR_RECV_TYPE_S           6
++#define QCA_HDR_RECV_VERSION          GENMASK(15, 14)
++#define QCA_HDR_RECV_PRIORITY         GENMASK(13, 11)
++#define QCA_HDR_RECV_TYPE             GENMASK(10, 6)
+ #define QCA_HDR_RECV_FRAME_IS_TAGGED  BIT(3)
+-#define QCA_HDR_RECV_SOURCE_PORT_MASK GENMASK(2, 0)
+-
+-#define QCA_HDR_XMIT_VERSION_MASK     GENMASK(15, 14)
+-#define QCA_HDR_XMIT_VERSION_S                14
+-#define QCA_HDR_XMIT_PRIORITY_MASK    GENMASK(13, 11)
+-#define QCA_HDR_XMIT_PRIORITY_S               11
+-#define QCA_HDR_XMIT_CONTROL_MASK     GENMASK(10, 8)
+-#define QCA_HDR_XMIT_CONTROL_S                8
++#define QCA_HDR_RECV_SOURCE_PORT      GENMASK(2, 0)
++
++#define QCA_HDR_XMIT_VERSION          GENMASK(15, 14)
++#define QCA_HDR_XMIT_PRIORITY         GENMASK(13, 11)
++#define QCA_HDR_XMIT_CONTROL          GENMASK(10, 8)
+ #define QCA_HDR_XMIT_FROM_CPU         BIT(7)
+-#define QCA_HDR_XMIT_DP_BIT_MASK      GENMASK(6, 0)
++#define QCA_HDR_XMIT_DP_BIT           GENMASK(6, 0)
+ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
+ {
+@@ -40,8 +35,9 @@ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
+       phdr = dsa_etype_header_pos_tx(skb);
+       /* Set the version field, and set destination port information */
+-      hdr = QCA_HDR_VERSION << QCA_HDR_XMIT_VERSION_S |
+-              QCA_HDR_XMIT_FROM_CPU | BIT(dp->index);
++      hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
++      hdr |= QCA_HDR_XMIT_FROM_CPU;
++      hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(dp->index));
+       *phdr = htons(hdr);
+@@ -62,7 +58,7 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+       hdr = ntohs(*phdr);
+       /* Make sure the version is correct */
+-      ver = (hdr & QCA_HDR_RECV_VERSION_MASK) >> QCA_HDR_RECV_VERSION_S;
++      ver = FIELD_GET(QCA_HDR_RECV_VERSION, hdr);
+       if (unlikely(ver != QCA_HDR_VERSION))
+               return NULL;
+@@ -71,7 +67,7 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+       dsa_strip_etype_header(skb, QCA_HDR_LEN);
+       /* Get source port information */
+-      port = (hdr & QCA_HDR_RECV_SOURCE_PORT_MASK);
++      port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, hdr);
+       skb->dev = dsa_master_find_slave(dev, 0, port);
+       if (!skb->dev)
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch b/target/linux/generic/backport-5.15/766-04-net-dsa-tag_qca-move-define-to-include-linux-dsa.patch
new file mode 100644 (file)
index 0000000..21221db
--- /dev/null
@@ -0,0 +1,79 @@
+From 3ec762fb13c7e7273800b94c80db1c2cc37590d1 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:23 +0100
+Subject: [PATCH 04/16] net: dsa: tag_qca: move define to include linux/dsa
+
+Move tag_qca define to include dir linux/dsa as the qca8k require access
+to the tagger define to support in-band mdio read/write using ethernet
+packet.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/dsa/tag_qca.h | 21 +++++++++++++++++++++
+ net/dsa/tag_qca.c           | 16 +---------------
+ 2 files changed, 22 insertions(+), 15 deletions(-)
+ create mode 100644 include/linux/dsa/tag_qca.h
+
+diff --git a/include/linux/dsa/tag_qca.h b/include/linux/dsa/tag_qca.h
+new file mode 100644
+index 000000000000..c02d2d39ff4a
+--- /dev/null
++++ b/include/linux/dsa/tag_qca.h
+@@ -0,0 +1,21 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++
++#ifndef __TAG_QCA_H
++#define __TAG_QCA_H
++
++#define QCA_HDR_LEN   2
++#define QCA_HDR_VERSION       0x2
++
++#define QCA_HDR_RECV_VERSION          GENMASK(15, 14)
++#define QCA_HDR_RECV_PRIORITY         GENMASK(13, 11)
++#define QCA_HDR_RECV_TYPE             GENMASK(10, 6)
++#define QCA_HDR_RECV_FRAME_IS_TAGGED  BIT(3)
++#define QCA_HDR_RECV_SOURCE_PORT      GENMASK(2, 0)
++
++#define QCA_HDR_XMIT_VERSION          GENMASK(15, 14)
++#define QCA_HDR_XMIT_PRIORITY         GENMASK(13, 11)
++#define QCA_HDR_XMIT_CONTROL          GENMASK(10, 8)
++#define QCA_HDR_XMIT_FROM_CPU         BIT(7)
++#define QCA_HDR_XMIT_DP_BIT           GENMASK(6, 0)
++
++#endif /* __TAG_QCA_H */
+diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
+index 55fa6b96b4eb..34e565e00ece 100644
+--- a/net/dsa/tag_qca.c
++++ b/net/dsa/tag_qca.c
+@@ -5,24 +5,10 @@
+ #include <linux/etherdevice.h>
+ #include <linux/bitfield.h>
++#include <linux/dsa/tag_qca.h>
+ #include "dsa_priv.h"
+-#define QCA_HDR_LEN   2
+-#define QCA_HDR_VERSION       0x2
+-
+-#define QCA_HDR_RECV_VERSION          GENMASK(15, 14)
+-#define QCA_HDR_RECV_PRIORITY         GENMASK(13, 11)
+-#define QCA_HDR_RECV_TYPE             GENMASK(10, 6)
+-#define QCA_HDR_RECV_FRAME_IS_TAGGED  BIT(3)
+-#define QCA_HDR_RECV_SOURCE_PORT      GENMASK(2, 0)
+-
+-#define QCA_HDR_XMIT_VERSION          GENMASK(15, 14)
+-#define QCA_HDR_XMIT_PRIORITY         GENMASK(13, 11)
+-#define QCA_HDR_XMIT_CONTROL          GENMASK(10, 8)
+-#define QCA_HDR_XMIT_FROM_CPU         BIT(7)
+-#define QCA_HDR_XMIT_DP_BIT           GENMASK(6, 0)
+-
+ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
+ {
+       struct dsa_port *dp = dsa_slave_to_port(dev);
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch b/target/linux/generic/backport-5.15/766-05-net-dsa-tag_qca-enable-promisc_on_master-flag.patch
new file mode 100644 (file)
index 0000000..d0dd451
--- /dev/null
@@ -0,0 +1,32 @@
+From 101c04c3463b87061e6a3d4f72c1bc57670685a6 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:24 +0100
+Subject: [PATCH 05/16] net: dsa: tag_qca: enable promisc_on_master flag
+
+Ethernet MDIO packets are non-standard and DSA master expects the first
+6 octets to be the MAC DA. To address these kind of packet, enable
+promisc_on_master flag for the tagger.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ net/dsa/tag_qca.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
+index 34e565e00ece..f8df49d5956f 100644
+--- a/net/dsa/tag_qca.c
++++ b/net/dsa/tag_qca.c
+@@ -68,6 +68,7 @@ static const struct dsa_device_ops qca_netdev_ops = {
+       .xmit   = qca_tag_xmit,
+       .rcv    = qca_tag_rcv,
+       .needed_headroom = QCA_HDR_LEN,
++      .promisc_on_master = true,
+ };
+ MODULE_LICENSE("GPL");
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch b/target/linux/generic/backport-5.15/766-06-net-dsa-tag_qca-add-define-for-handling-mgmt-Etherne.patch
new file mode 100644 (file)
index 0000000..6981e7b
--- /dev/null
@@ -0,0 +1,117 @@
+From c2ee8181fddb293d296477f60b3eb4fa3ce4e1a6 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:25 +0100
+Subject: [PATCH 06/16] net: dsa: tag_qca: add define for handling mgmt
+ Ethernet packet
+
+Add all the required define to prepare support for mgmt read/write in
+Ethernet packet. Any packet of this type has to be dropped as the only
+use of these special packet is receive ack for an mgmt write request or
+receive data for an mgmt read request.
+A struct is used that emulates the Ethernet header but is used for a
+different purpose.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/dsa/tag_qca.h | 44 +++++++++++++++++++++++++++++++++++++
+ net/dsa/tag_qca.c           | 15 ++++++++++---
+ 2 files changed, 56 insertions(+), 3 deletions(-)
+
+diff --git a/include/linux/dsa/tag_qca.h b/include/linux/dsa/tag_qca.h
+index c02d2d39ff4a..f366422ab7a0 100644
+--- a/include/linux/dsa/tag_qca.h
++++ b/include/linux/dsa/tag_qca.h
+@@ -12,10 +12,54 @@
+ #define QCA_HDR_RECV_FRAME_IS_TAGGED  BIT(3)
+ #define QCA_HDR_RECV_SOURCE_PORT      GENMASK(2, 0)
++/* Packet type for recv */
++#define QCA_HDR_RECV_TYPE_NORMAL      0x0
++#define QCA_HDR_RECV_TYPE_MIB         0x1
++#define QCA_HDR_RECV_TYPE_RW_REG_ACK  0x2
++
+ #define QCA_HDR_XMIT_VERSION          GENMASK(15, 14)
+ #define QCA_HDR_XMIT_PRIORITY         GENMASK(13, 11)
+ #define QCA_HDR_XMIT_CONTROL          GENMASK(10, 8)
+ #define QCA_HDR_XMIT_FROM_CPU         BIT(7)
+ #define QCA_HDR_XMIT_DP_BIT           GENMASK(6, 0)
++/* Packet type for xmit */
++#define QCA_HDR_XMIT_TYPE_NORMAL      0x0
++#define QCA_HDR_XMIT_TYPE_RW_REG      0x1
++
++/* Check code for a valid mgmt packet. Switch will ignore the packet
++ * with this wrong.
++ */
++#define QCA_HDR_MGMT_CHECK_CODE_VAL   0x5
++
++/* Specific define for in-band MDIO read/write with Ethernet packet */
++#define QCA_HDR_MGMT_SEQ_LEN          4 /* 4 byte for the seq */
++#define QCA_HDR_MGMT_COMMAND_LEN      4 /* 4 byte for the command */
++#define QCA_HDR_MGMT_DATA1_LEN                4 /* First 4 byte for the mdio data */
++#define QCA_HDR_MGMT_HEADER_LEN               (QCA_HDR_MGMT_SEQ_LEN + \
++                                      QCA_HDR_MGMT_COMMAND_LEN + \
++                                      QCA_HDR_MGMT_DATA1_LEN)
++
++#define QCA_HDR_MGMT_DATA2_LEN                12 /* Other 12 byte for the mdio data */
++#define QCA_HDR_MGMT_PADDING_LEN      34 /* Padding to reach the min Ethernet packet */
++
++#define QCA_HDR_MGMT_PKT_LEN          (QCA_HDR_MGMT_HEADER_LEN + \
++                                      QCA_HDR_LEN + \
++                                      QCA_HDR_MGMT_DATA2_LEN + \
++                                      QCA_HDR_MGMT_PADDING_LEN)
++
++#define QCA_HDR_MGMT_SEQ_NUM          GENMASK(31, 0)  /* 63, 32 */
++#define QCA_HDR_MGMT_CHECK_CODE               GENMASK(31, 29) /* 31, 29 */
++#define QCA_HDR_MGMT_CMD              BIT(28)         /* 28 */
++#define QCA_HDR_MGMT_LENGTH           GENMASK(23, 20) /* 23, 20 */
++#define QCA_HDR_MGMT_ADDR             GENMASK(18, 0)  /* 18, 0 */
++
++/* Special struct emulating a Ethernet header */
++struct qca_mgmt_ethhdr {
++      u32 command;            /* command bit 31:0 */
++      u32 seq;                /* seq 63:32 */
++      u32 mdio_data;          /* first 4byte mdio */
++      __be16 hdr;             /* qca hdr */
++} __packed;
++
+ #endif /* __TAG_QCA_H */
+diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
+index f8df49d5956f..f17ed5be20c2 100644
+--- a/net/dsa/tag_qca.c
++++ b/net/dsa/tag_qca.c
+@@ -32,10 +32,12 @@ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
+ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+ {
+-      u8 ver;
+-      u16  hdr;
+-      int port;
++      u8 ver, pk_type;
+       __be16 *phdr;
++      int port;
++      u16 hdr;
++
++      BUILD_BUG_ON(sizeof(struct qca_mgmt_ethhdr) != QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
+       if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN)))
+               return NULL;
+@@ -48,6 +50,13 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+       if (unlikely(ver != QCA_HDR_VERSION))
+               return NULL;
++      /* Get pk type */
++      pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr);
++
++      /* Ethernet MDIO read/write packet */
++      if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
++              return NULL;
++
+       /* Remove QCA tag and recalculate checksum */
+       skb_pull_rcsum(skb, QCA_HDR_LEN);
+       dsa_strip_etype_header(skb, QCA_HDR_LEN);
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch b/target/linux/generic/backport-5.15/766-07-net-dsa-tag_qca-add-define-for-handling-MIB-packet.patch
new file mode 100644 (file)
index 0000000..4975180
--- /dev/null
@@ -0,0 +1,52 @@
+From 18be654a4345f7d937b4bfbad74bea8093e3a93c Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:26 +0100
+Subject: [PATCH 07/16] net: dsa: tag_qca: add define for handling MIB packet
+
+Add struct to correctly parse a mib Ethernet packet.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/dsa/tag_qca.h | 10 ++++++++++
+ net/dsa/tag_qca.c           |  4 ++++
+ 2 files changed, 14 insertions(+)
+
+diff --git a/include/linux/dsa/tag_qca.h b/include/linux/dsa/tag_qca.h
+index f366422ab7a0..1fff57f2937b 100644
+--- a/include/linux/dsa/tag_qca.h
++++ b/include/linux/dsa/tag_qca.h
+@@ -62,4 +62,14 @@ struct qca_mgmt_ethhdr {
+       __be16 hdr;             /* qca hdr */
+ } __packed;
++enum mdio_cmd {
++      MDIO_WRITE = 0x0,
++      MDIO_READ
++};
++
++struct mib_ethhdr {
++      u32 data[3];            /* first 3 mib counter */
++      __be16 hdr;             /* qca hdr */
++} __packed;
++
+ #endif /* __TAG_QCA_H */
+diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
+index f17ed5be20c2..be792cf687d9 100644
+--- a/net/dsa/tag_qca.c
++++ b/net/dsa/tag_qca.c
+@@ -57,6 +57,10 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+       if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
+               return NULL;
++      /* Ethernet MIB counter packet */
++      if (pk_type == QCA_HDR_RECV_TYPE_MIB)
++              return NULL;
++
+       /* Remove QCA tag and recalculate checksum */
+       skb_pull_rcsum(skb, QCA_HDR_LEN);
+       dsa_strip_etype_header(skb, QCA_HDR_LEN);
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch b/target/linux/generic/backport-5.15/766-08-net-dsa-tag_qca-add-support-for-handling-mgmt-and-MI.patch
new file mode 100644 (file)
index 0000000..4375cd0
--- /dev/null
@@ -0,0 +1,123 @@
+From 31eb6b4386ad91930417e3f5c8157a4b5e31cbd5 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:27 +0100
+Subject: [PATCH 08/16] net: dsa: tag_qca: add support for handling mgmt and
+ MIB Ethernet packet
+
+Add connect/disconnect helper to assign private struct to the DSA switch.
+Add support for Ethernet mgmt and MIB if the DSA driver provide an handler
+to correctly parse and elaborate the data.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/dsa/tag_qca.h |  7 +++++++
+ net/dsa/tag_qca.c           | 39 ++++++++++++++++++++++++++++++++++---
+ 2 files changed, 43 insertions(+), 3 deletions(-)
+
+diff --git a/include/linux/dsa/tag_qca.h b/include/linux/dsa/tag_qca.h
+index 1fff57f2937b..4359fb0221cf 100644
+--- a/include/linux/dsa/tag_qca.h
++++ b/include/linux/dsa/tag_qca.h
+@@ -72,4 +72,11 @@ struct mib_ethhdr {
+       __be16 hdr;             /* qca hdr */
+ } __packed;
++struct qca_tagger_data {
++      void (*rw_reg_ack_handler)(struct dsa_switch *ds,
++                                 struct sk_buff *skb);
++      void (*mib_autocast_handler)(struct dsa_switch *ds,
++                                   struct sk_buff *skb);
++};
++
+ #endif /* __TAG_QCA_H */
+diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
+index be792cf687d9..57d2e00f1e5d 100644
+--- a/net/dsa/tag_qca.c
++++ b/net/dsa/tag_qca.c
+@@ -5,6 +5,7 @@
+ #include <linux/etherdevice.h>
+ #include <linux/bitfield.h>
++#include <net/dsa.h>
+ #include <linux/dsa/tag_qca.h>
+ #include "dsa_priv.h"
+@@ -32,6 +33,9 @@ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
+ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+ {
++      struct qca_tagger_data *tagger_data;
++      struct dsa_port *dp = dev->dsa_ptr;
++      struct dsa_switch *ds = dp->ds;
+       u8 ver, pk_type;
+       __be16 *phdr;
+       int port;
+@@ -39,6 +43,8 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+       BUILD_BUG_ON(sizeof(struct qca_mgmt_ethhdr) != QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
++      tagger_data = ds->tagger_data;
++
+       if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN)))
+               return NULL;
+@@ -53,13 +59,19 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+       /* Get pk type */
+       pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr);
+-      /* Ethernet MDIO read/write packet */
+-      if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK)
++      /* Ethernet mgmt read/write packet */
++      if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK) {
++              if (likely(tagger_data->rw_reg_ack_handler))
++                      tagger_data->rw_reg_ack_handler(ds, skb);
+               return NULL;
++      }
+       /* Ethernet MIB counter packet */
+-      if (pk_type == QCA_HDR_RECV_TYPE_MIB)
++      if (pk_type == QCA_HDR_RECV_TYPE_MIB) {
++              if (likely(tagger_data->mib_autocast_handler))
++                      tagger_data->mib_autocast_handler(ds, skb);
+               return NULL;
++      }
+       /* Remove QCA tag and recalculate checksum */
+       skb_pull_rcsum(skb, QCA_HDR_LEN);
+@@ -75,9 +87,30 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
+       return skb;
+ }
++static int qca_tag_connect(struct dsa_switch *ds)
++{
++      struct qca_tagger_data *tagger_data;
++
++      tagger_data = kzalloc(sizeof(*tagger_data), GFP_KERNEL);
++      if (!tagger_data)
++              return -ENOMEM;
++
++      ds->tagger_data = tagger_data;
++
++      return 0;
++}
++
++static void qca_tag_disconnect(struct dsa_switch *ds)
++{
++      kfree(ds->tagger_data);
++      ds->tagger_data = NULL;
++}
++
+ static const struct dsa_device_ops qca_netdev_ops = {
+       .name   = "qca",
+       .proto  = DSA_TAG_PROTO_QCA,
++      .connect = qca_tag_connect,
++      .disconnect = qca_tag_disconnect,
+       .xmit   = qca_tag_xmit,
+       .rcv    = qca_tag_rcv,
+       .needed_headroom = QCA_HDR_LEN,
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch b/target/linux/generic/backport-5.15/766-09-net-dsa-qca8k-add-tracking-state-of-master-port.patch
new file mode 100644 (file)
index 0000000..d2799f9
--- /dev/null
@@ -0,0 +1,74 @@
+From cddbec19466a1dfb4d45ddd507d9f09f991d54ae Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:28 +0100
+Subject: [PATCH 09/16] net: dsa: qca8k: add tracking state of master port
+
+MDIO/MIB Ethernet require the master port and the tagger availabale to
+correctly work. Use the new api master_state_change to track when master
+is operational or not and set a bool in qca8k_priv.
+We cache the first cached master available and we check if other cpu
+port are operational when the cached one goes down.
+This cached master will later be used by mdio read/write and mib request to
+correctly use the working function.
+
+qca8k implementation for MDIO/MIB Ethernet is bad. CPU port0 is the only
+one that answers with the ack packet or sends MIB Ethernet packets. For
+this reason the master_state_change ignore CPU port6 and only checks
+CPU port0 if it's operational and enables this mode.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 15 +++++++++++++++
+ drivers/net/dsa/qca8k.h |  1 +
+ 2 files changed, 16 insertions(+)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index 039694518788..ec062b9a918d 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -2383,6 +2383,20 @@ qca8k_port_lag_leave(struct dsa_switch *ds, int port,
+       return qca8k_lag_refresh_portmap(ds, port, lag, true);
+ }
++static void
++qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
++                  bool operational)
++{
++      struct dsa_port *dp = master->dsa_ptr;
++      struct qca8k_priv *priv = ds->priv;
++
++      /* Ethernet MIB/MDIO is only supported for CPU port 0 */
++      if (dp->index != 0)
++              return;
++
++      priv->mgmt_master = operational ? (struct net_device *)master : NULL;
++}
++
+ static const struct dsa_switch_ops qca8k_switch_ops = {
+       .get_tag_protocol       = qca8k_get_tag_protocol,
+       .setup                  = qca8k_setup,
+@@ -2418,6 +2432,7 @@ static const struct dsa_switch_ops qca8k_switch_ops = {
+       .get_phy_flags          = qca8k_get_phy_flags,
+       .port_lag_join          = qca8k_port_lag_join,
+       .port_lag_leave         = qca8k_port_lag_leave,
++      .master_state_change    = qca8k_master_change,
+ };
+ static int qca8k_read_switch_id(struct qca8k_priv *priv)
+diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h
+index ab4a417b25a9..b81aad98a116 100644
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -353,6 +353,7 @@ struct qca8k_priv {
+       struct dsa_switch_ops ops;
+       struct gpio_desc *reset_gpio;
+       unsigned int port_mtu[QCA8K_NUM_PORTS];
++      struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
+ };
+ struct qca8k_mib_desc {
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch b/target/linux/generic/backport-5.15/766-10-net-dsa-qca8k-add-support-for-mgmt-read-write-in-Eth.patch
new file mode 100644 (file)
index 0000000..dedffbc
--- /dev/null
@@ -0,0 +1,370 @@
+From 5950c7c0a68c915b336c70f79388626e2d576ab7 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:29 +0100
+Subject: [PATCH 10/16] net: dsa: qca8k: add support for mgmt read/write in
+ Ethernet packet
+
+Add qca8k side support for mgmt read/write in Ethernet packet.
+qca8k supports some specially crafted Ethernet packet that can be used
+for mgmt read/write instead of the legacy method uart/internal mdio.
+This add support for the qca8k side to craft the packet and enqueue it.
+Each port and the qca8k_priv have a special struct to put data in it.
+The completion API is used to wait for the packet to be received back
+with the requested data.
+
+The various steps are:
+1. Craft the special packet with the qca hdr set to mgmt read/write
+   mode.
+2. Set the lock in the dedicated mgmt struct.
+3. Increment the seq number and set it in the mgmt pkt
+4. Reinit the completion.
+5. Enqueue the packet.
+6. Wait the packet to be received.
+7. Use the data set by the tagger to complete the mdio operation.
+
+If the completion timeouts or the ack value is not true, the legacy
+mdio way is used.
+
+It has to be considered that in the initial setup mdio is still used and
+mdio is still used until DSA is ready to accept and tag packet.
+
+tag_proto_connect() is used to fill the required handler for the tagger
+to correctly parse and elaborate the special Ethernet mdio packet.
+
+Locking is added to qca8k_master_change() to make sure no mgmt Ethernet
+are in progress.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 225 ++++++++++++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca8k.h |  13 +++
+ 2 files changed, 238 insertions(+)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index ec062b9a918d..e3a215f04559 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -20,6 +20,7 @@
+ #include <linux/phylink.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/etherdevice.h>
++#include <linux/dsa/tag_qca.h>
+ #include "qca8k.h"
+@@ -170,6 +171,194 @@ qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+       return regmap_update_bits(priv->regmap, reg, mask, write_val);
+ }
++static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
++{
++      struct qca8k_mgmt_eth_data *mgmt_eth_data;
++      struct qca8k_priv *priv = ds->priv;
++      struct qca_mgmt_ethhdr *mgmt_ethhdr;
++      u8 len, cmd;
++
++      mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
++      mgmt_eth_data = &priv->mgmt_eth_data;
++
++      cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
++      len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
++
++      /* Make sure the seq match the requested packet */
++      if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
++              mgmt_eth_data->ack = true;
++
++      if (cmd == MDIO_READ) {
++              mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
++
++              /* Get the rest of the 12 byte of data */
++              if (len > QCA_HDR_MGMT_DATA1_LEN)
++                      memcpy(mgmt_eth_data->data + 1, skb->data,
++                             QCA_HDR_MGMT_DATA2_LEN);
++      }
++
++      complete(&mgmt_eth_data->rw_done);
++}
++
++static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
++                                             int priority)
++{
++      struct qca_mgmt_ethhdr *mgmt_ethhdr;
++      struct sk_buff *skb;
++      u16 hdr;
++
++      skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
++      if (!skb)
++              return NULL;
++
++      skb_reset_mac_header(skb);
++      skb_set_network_header(skb, skb->len);
++
++      mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
++
++      hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
++      hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
++      hdr |= QCA_HDR_XMIT_FROM_CPU;
++      hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
++      hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
++
++      mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
++      mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, 4);
++      mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
++      mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
++                                         QCA_HDR_MGMT_CHECK_CODE_VAL);
++
++      if (cmd == MDIO_WRITE)
++              mgmt_ethhdr->mdio_data = *val;
++
++      mgmt_ethhdr->hdr = htons(hdr);
++
++      skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
++
++      return skb;
++}
++
++static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
++{
++      struct qca_mgmt_ethhdr *mgmt_ethhdr;
++
++      mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
++      mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
++}
++
++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
++{
++      struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++      struct sk_buff *skb;
++      bool ack;
++      int ret;
++
++      skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
++                                    QCA8K_ETHERNET_MDIO_PRIORITY);
++      if (!skb)
++              return -ENOMEM;
++
++      mutex_lock(&mgmt_eth_data->mutex);
++
++      /* Check mgmt_master if is operational */
++      if (!priv->mgmt_master) {
++              kfree_skb(skb);
++              mutex_unlock(&mgmt_eth_data->mutex);
++              return -EINVAL;
++      }
++
++      skb->dev = priv->mgmt_master;
++
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the mdio pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(skb);
++
++      ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                        msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++
++      *val = mgmt_eth_data->data[0];
++      ack = mgmt_eth_data->ack;
++
++      mutex_unlock(&mgmt_eth_data->mutex);
++
++      if (ret <= 0)
++              return -ETIMEDOUT;
++
++      if (!ack)
++              return -EINVAL;
++
++      return 0;
++}
++
++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 val)
++{
++      struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
++      struct sk_buff *skb;
++      bool ack;
++      int ret;
++
++      skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, &val,
++                                    QCA8K_ETHERNET_MDIO_PRIORITY);
++      if (!skb)
++              return -ENOMEM;
++
++      mutex_lock(&mgmt_eth_data->mutex);
++
++      /* Check mgmt_master if is operational */
++      if (!priv->mgmt_master) {
++              kfree_skb(skb);
++              mutex_unlock(&mgmt_eth_data->mutex);
++              return -EINVAL;
++      }
++
++      skb->dev = priv->mgmt_master;
++
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the mdio pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(skb);
++
++      ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                        msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
++
++      ack = mgmt_eth_data->ack;
++
++      mutex_unlock(&mgmt_eth_data->mutex);
++
++      if (ret <= 0)
++              return -ETIMEDOUT;
++
++      if (!ack)
++              return -EINVAL;
++
++      return 0;
++}
++
++static int
++qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
++{
++      u32 val = 0;
++      int ret;
++
++      ret = qca8k_read_eth(priv, reg, &val);
++      if (ret)
++              return ret;
++
++      val &= ~mask;
++      val |= write_val;
++
++      return qca8k_write_eth(priv, reg, val);
++}
++
+ static int
+ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+ {
+@@ -178,6 +367,9 @@ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+       u16 r1, r2, page;
+       int ret;
++      if (!qca8k_read_eth(priv, reg, val))
++              return 0;
++
+       qca8k_split_addr(reg, &r1, &r2, &page);
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+@@ -201,6 +393,9 @@ qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
+       u16 r1, r2, page;
+       int ret;
++      if (!qca8k_write_eth(priv, reg, val))
++              return 0;
++
+       qca8k_split_addr(reg, &r1, &r2, &page);
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+@@ -225,6 +420,9 @@ qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_
+       u32 val;
+       int ret;
++      if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
++              return 0;
++
+       qca8k_split_addr(reg, &r1, &r2, &page);
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+@@ -2394,7 +2592,30 @@ qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
+       if (dp->index != 0)
+               return;
++      mutex_lock(&priv->mgmt_eth_data.mutex);
++
+       priv->mgmt_master = operational ? (struct net_device *)master : NULL;
++
++      mutex_unlock(&priv->mgmt_eth_data.mutex);
++}
++
++static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
++                                    enum dsa_tag_protocol proto)
++{
++      struct qca_tagger_data *tagger_data;
++
++      switch (proto) {
++      case DSA_TAG_PROTO_QCA:
++              tagger_data = ds->tagger_data;
++
++              tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
++
++              break;
++      default:
++              return -EOPNOTSUPP;
++      }
++
++      return 0;
+ }
+ static const struct dsa_switch_ops qca8k_switch_ops = {
+@@ -2433,6 +2654,7 @@ static const struct dsa_switch_ops qca8k_switch_ops = {
+       .port_lag_join          = qca8k_port_lag_join,
+       .port_lag_leave         = qca8k_port_lag_leave,
+       .master_state_change    = qca8k_master_change,
++      .connect_tag_protocol   = qca8k_connect_tag_protocol,
+ };
+ static int qca8k_read_switch_id(struct qca8k_priv *priv)
+@@ -2512,6 +2734,9 @@ qca8k_sw_probe(struct mdio_device *mdiodev)
+       if (!priv->ds)
+               return -ENOMEM;
++      mutex_init(&priv->mgmt_eth_data.mutex);
++      init_completion(&priv->mgmt_eth_data.rw_done);
++
+       priv->ds->dev = &mdiodev->dev;
+       priv->ds->num_ports = QCA8K_NUM_PORTS;
+       priv->ds->priv = priv;
+diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h
+index b81aad98a116..75c28689a652 100644
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -11,6 +11,10 @@
+ #include <linux/delay.h>
+ #include <linux/regmap.h>
+ #include <linux/gpio.h>
++#include <linux/dsa/tag_qca.h>
++
++#define QCA8K_ETHERNET_MDIO_PRIORITY                  7
++#define QCA8K_ETHERNET_TIMEOUT                                100
+ #define QCA8K_NUM_PORTS                                       7
+ #define QCA8K_NUM_CPU_PORTS                           2
+@@ -328,6 +332,14 @@ enum {
+       QCA8K_CPU_PORT6,
+ };
++struct qca8k_mgmt_eth_data {
++      struct completion rw_done;
++      struct mutex mutex; /* Enforce one mdio read/write at time */
++      bool ack;
++      u32 seq;
++      u32 data[4];
++};
++
+ struct qca8k_ports_config {
+       bool sgmii_rx_clk_falling_edge;
+       bool sgmii_tx_clk_falling_edge;
+@@ -354,6 +366,7 @@ struct qca8k_priv {
+       struct gpio_desc *reset_gpio;
+       unsigned int port_mtu[QCA8K_NUM_PORTS];
+       struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
++      struct qca8k_mgmt_eth_data mgmt_eth_data;
+ };
+ struct qca8k_mib_desc {
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch b/target/linux/generic/backport-5.15/766-11-net-dsa-qca8k-add-support-for-mib-autocast-in-Ethern.patch
new file mode 100644 (file)
index 0000000..69882e9
--- /dev/null
@@ -0,0 +1,233 @@
+From 5c957c7ca78cce5e4b96866722b0115bd758d945 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:30 +0100
+Subject: [PATCH 11/16] net: dsa: qca8k: add support for mib autocast in
+ Ethernet packet
+
+The switch can autocast MIB counter using Ethernet packet.
+Add support for this and provide a handler for the tagger.
+The switch will send packet with MIB counter for each port, the switch
+will use completion API to wait for the correct packet to be received
+and will complete the task only when each packet is received.
+Although the handler will drop all the other packet, we still have to
+consume each MIB packet to complete the request. This is done to prevent
+mixed data with concurrent ethtool request.
+
+connect_tag_protocol() is used to add the handler to the tag_qca tagger,
+master_state_change() use the MIB lock to make sure no MIB Ethernet is
+in progress.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 106 +++++++++++++++++++++++++++++++++++++++-
+ drivers/net/dsa/qca8k.h |  17 ++++++-
+ 2 files changed, 121 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index e3a215f04559..199cf4f761c0 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -830,7 +830,10 @@ qca8k_mib_init(struct qca8k_priv *priv)
+       int ret;
+       mutex_lock(&priv->reg_mutex);
+-      ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
++      ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
++                               QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
++                               FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) |
++                               QCA8K_MIB_BUSY);
+       if (ret)
+               goto exit;
+@@ -1901,6 +1904,97 @@ qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data)
+                       ETH_GSTRING_LEN);
+ }
++static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
++{
++      const struct qca8k_match_data *match_data;
++      struct qca8k_mib_eth_data *mib_eth_data;
++      struct qca8k_priv *priv = ds->priv;
++      const struct qca8k_mib_desc *mib;
++      struct mib_ethhdr *mib_ethhdr;
++      int i, mib_len, offset = 0;
++      u64 *data;
++      u8 port;
++
++      mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
++      mib_eth_data = &priv->mib_eth_data;
++
++      /* The switch autocast every port. Ignore other packet and
++       * parse only the requested one.
++       */
++      port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
++      if (port != mib_eth_data->req_port)
++              goto exit;
++
++      match_data = device_get_match_data(priv->dev);
++      data = mib_eth_data->data;
++
++      for (i = 0; i < match_data->mib_count; i++) {
++              mib = &ar8327_mib[i];
++
++              /* First 3 mib are present in the skb head */
++              if (i < 3) {
++                      data[i] = mib_ethhdr->data[i];
++                      continue;
++              }
++
++              mib_len = sizeof(uint32_t);
++
++              /* Some mib are 64 bit wide */
++              if (mib->size == 2)
++                      mib_len = sizeof(uint64_t);
++
++              /* Copy the mib value from packet to the */
++              memcpy(data + i, skb->data + offset, mib_len);
++
++              /* Set the offset for the next mib */
++              offset += mib_len;
++      }
++
++exit:
++      /* Complete on receiving all the mib packet */
++      if (refcount_dec_and_test(&mib_eth_data->port_parsed))
++              complete(&mib_eth_data->rw_done);
++}
++
++static int
++qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
++{
++      struct dsa_port *dp = dsa_to_port(ds, port);
++      struct qca8k_mib_eth_data *mib_eth_data;
++      struct qca8k_priv *priv = ds->priv;
++      int ret;
++
++      mib_eth_data = &priv->mib_eth_data;
++
++      mutex_lock(&mib_eth_data->mutex);
++
++      reinit_completion(&mib_eth_data->rw_done);
++
++      mib_eth_data->req_port = dp->index;
++      mib_eth_data->data = data;
++      refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
++
++      mutex_lock(&priv->reg_mutex);
++
++      /* Send mib autocast request */
++      ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
++                               QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
++                               FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
++                               QCA8K_MIB_BUSY);
++
++      mutex_unlock(&priv->reg_mutex);
++
++      if (ret)
++              goto exit;
++
++      ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
++
++exit:
++      mutex_unlock(&mib_eth_data->mutex);
++
++      return ret;
++}
++
+ static void
+ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
+                       uint64_t *data)
+@@ -1912,6 +2006,10 @@ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
+       u32 hi = 0;
+       int ret;
++      if (priv->mgmt_master &&
++          qca8k_get_ethtool_stats_eth(ds, port, data) > 0)
++              return;
++
+       match_data = of_device_get_match_data(priv->dev);
+       for (i = 0; i < match_data->mib_count; i++) {
+@@ -2593,9 +2691,11 @@ qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
+               return;
+       mutex_lock(&priv->mgmt_eth_data.mutex);
++      mutex_lock(&priv->mib_eth_data.mutex);
+       priv->mgmt_master = operational ? (struct net_device *)master : NULL;
++      mutex_unlock(&priv->mib_eth_data.mutex);
+       mutex_unlock(&priv->mgmt_eth_data.mutex);
+ }
+@@ -2609,6 +2709,7 @@ static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
+               tagger_data = ds->tagger_data;
+               tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
++              tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
+               break;
+       default:
+@@ -2737,6 +2838,9 @@ qca8k_sw_probe(struct mdio_device *mdiodev)
+       mutex_init(&priv->mgmt_eth_data.mutex);
+       init_completion(&priv->mgmt_eth_data.rw_done);
++      mutex_init(&priv->mib_eth_data.mutex);
++      init_completion(&priv->mib_eth_data.rw_done);
++
+       priv->ds->dev = &mdiodev->dev;
+       priv->ds->num_ports = QCA8K_NUM_PORTS;
+       priv->ds->priv = priv;
+diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h
+index 75c28689a652..2d7d084db089 100644
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -67,7 +67,7 @@
+ #define QCA8K_REG_MODULE_EN                           0x030
+ #define   QCA8K_MODULE_EN_MIB                         BIT(0)
+ #define QCA8K_REG_MIB                                 0x034
+-#define   QCA8K_MIB_FLUSH                             BIT(24)
++#define   QCA8K_MIB_FUNC                              GENMASK(26, 24)
+ #define   QCA8K_MIB_CPU_KEEP                          BIT(20)
+ #define   QCA8K_MIB_BUSY                              BIT(17)
+ #define QCA8K_MDIO_MASTER_CTRL                                0x3c
+@@ -317,6 +317,12 @@ enum qca8k_vlan_cmd {
+       QCA8K_VLAN_READ = 6,
+ };
++enum qca8k_mid_cmd {
++      QCA8K_MIB_FLUSH = 1,
++      QCA8K_MIB_FLUSH_PORT = 2,
++      QCA8K_MIB_CAST = 3,
++};
++
+ struct ar8xxx_port_status {
+       int enabled;
+ };
+@@ -340,6 +346,14 @@ struct qca8k_mgmt_eth_data {
+       u32 data[4];
+ };
++struct qca8k_mib_eth_data {
++      struct completion rw_done;
++      struct mutex mutex; /* Process one command at time */
++      refcount_t port_parsed; /* Counter to track parsed port */
++      u8 req_port;
++      u64 *data; /* pointer to ethtool data */
++};
++
+ struct qca8k_ports_config {
+       bool sgmii_rx_clk_falling_edge;
+       bool sgmii_tx_clk_falling_edge;
+@@ -367,6 +381,7 @@ struct qca8k_priv {
+       unsigned int port_mtu[QCA8K_NUM_PORTS];
+       struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
+       struct qca8k_mgmt_eth_data mgmt_eth_data;
++      struct qca8k_mib_eth_data mib_eth_data;
+ };
+ struct qca8k_mib_desc {
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch b/target/linux/generic/backport-5.15/766-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
new file mode 100644 (file)
index 0000000..5c65626
--- /dev/null
@@ -0,0 +1,294 @@
+From 2cd5485663847d468dc207b3ff85fb1fab44d97f Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:31 +0100
+Subject: [PATCH 12/16] net: dsa: qca8k: add support for phy read/write with
+ mgmt Ethernet
+
+Use mgmt Ethernet also for phy read/write if availabale. Use a different
+seq number to make sure we receive the correct packet.
+On any error, we fallback to the legacy mdio read/write.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 216 ++++++++++++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca8k.h |   1 +
+ 2 files changed, 217 insertions(+)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index 199cf4f761c0..0ce5b7ca0b7f 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -867,6 +867,199 @@ qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
+               regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+ }
++static int
++qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
++                      struct sk_buff *read_skb, u32 *val)
++{
++      struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
++      bool ack;
++      int ret;
++
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the copy pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(skb);
++
++      ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                        QCA8K_ETHERNET_TIMEOUT);
++
++      ack = mgmt_eth_data->ack;
++
++      if (ret <= 0)
++              return -ETIMEDOUT;
++
++      if (!ack)
++              return -EINVAL;
++
++      *val = mgmt_eth_data->data[0];
++
++      return 0;
++}
++
++static int
++qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
++                    int regnum, u16 data)
++{
++      struct sk_buff *write_skb, *clear_skb, *read_skb;
++      struct qca8k_mgmt_eth_data *mgmt_eth_data;
++      u32 write_val, clear_val = 0, val;
++      struct net_device *mgmt_master;
++      int ret, ret1;
++      bool ack;
++
++      if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++              return -EINVAL;
++
++      mgmt_eth_data = &priv->mgmt_eth_data;
++
++      write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++                  QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++                  QCA8K_MDIO_MASTER_REG_ADDR(regnum);
++
++      if (read) {
++              write_val |= QCA8K_MDIO_MASTER_READ;
++      } else {
++              write_val |= QCA8K_MDIO_MASTER_WRITE;
++              write_val |= QCA8K_MDIO_MASTER_DATA(data);
++      }
++
++      /* Prealloc all the needed skb before the lock */
++      write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
++                                          &write_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      if (!write_skb)
++              return -ENOMEM;
++
++      clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
++                                          &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      if (!write_skb) {
++              ret = -ENOMEM;
++              goto err_clear_skb;
++      }
++
++      read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL,
++                                         &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      if (!write_skb) {
++              ret = -ENOMEM;
++              goto err_read_skb;
++      }
++
++      /* Actually start the request:
++       * 1. Send mdio master packet
++       * 2. Busy Wait for mdio master command
++       * 3. Get the data if we are reading
++       * 4. Reset the mdio master (even with error)
++       */
++      mutex_lock(&mgmt_eth_data->mutex);
++
++      /* Check if mgmt_master is operational */
++      mgmt_master = priv->mgmt_master;
++      if (!mgmt_master) {
++              mutex_unlock(&mgmt_eth_data->mutex);
++              ret = -EINVAL;
++              goto err_mgmt_master;
++      }
++
++      read_skb->dev = mgmt_master;
++      clear_skb->dev = mgmt_master;
++      write_skb->dev = mgmt_master;
++
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the write pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(write_skb);
++
++      ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                        QCA8K_ETHERNET_TIMEOUT);
++
++      ack = mgmt_eth_data->ack;
++
++      if (ret <= 0) {
++              ret = -ETIMEDOUT;
++              kfree_skb(read_skb);
++              goto exit;
++      }
++
++      if (!ack) {
++              ret = -EINVAL;
++              kfree_skb(read_skb);
++              goto exit;
++      }
++
++      ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
++                              !(val & QCA8K_MDIO_MASTER_BUSY), 0,
++                              QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++                              mgmt_eth_data, read_skb, &val);
++
++      if (ret < 0 && ret1 < 0) {
++              ret = ret1;
++              goto exit;
++      }
++
++      if (read) {
++              reinit_completion(&mgmt_eth_data->rw_done);
++
++              /* Increment seq_num and set it in the read pkt */
++              mgmt_eth_data->seq++;
++              qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
++              mgmt_eth_data->ack = false;
++
++              dev_queue_xmit(read_skb);
++
++              ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                                QCA8K_ETHERNET_TIMEOUT);
++
++              ack = mgmt_eth_data->ack;
++
++              if (ret <= 0) {
++                      ret = -ETIMEDOUT;
++                      goto exit;
++              }
++
++              if (!ack) {
++                      ret = -EINVAL;
++                      goto exit;
++              }
++
++              ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
++      } else {
++              kfree_skb(read_skb);
++      }
++exit:
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the clear pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(clear_skb);
++
++      wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                  QCA8K_ETHERNET_TIMEOUT);
++
++      mutex_unlock(&mgmt_eth_data->mutex);
++
++      return ret;
++
++      /* Error handling before lock */
++err_mgmt_master:
++      kfree_skb(read_skb);
++err_read_skb:
++      kfree_skb(clear_skb);
++err_clear_skb:
++      kfree_skb(write_skb);
++
++      return ret;
++}
++
+ static u32
+ qca8k_port_to_phy(int port)
+ {
+@@ -989,6 +1182,12 @@ qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 da
+ {
+       struct qca8k_priv *priv = slave_bus->priv;
+       struct mii_bus *bus = priv->bus;
++      int ret;
++
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
++      if (!ret)
++              return 0;
+       return qca8k_mdio_write(bus, phy, regnum, data);
+ }
+@@ -998,6 +1197,12 @@ qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
+ {
+       struct qca8k_priv *priv = slave_bus->priv;
+       struct mii_bus *bus = priv->bus;
++      int ret;
++
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
++      if (ret >= 0)
++              return ret;
+       return qca8k_mdio_read(bus, phy, regnum);
+ }
+@@ -1006,6 +1211,7 @@ static int
+ qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
+ {
+       struct qca8k_priv *priv = ds->priv;
++      int ret;
+       /* Check if the legacy mapping should be used and the
+        * port is not correctly mapped to the right PHY in the
+@@ -1014,6 +1220,11 @@ qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
+       if (priv->legacy_phy_port_mapping)
+               port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, false, port, regnum, 0);
++      if (!ret)
++              return ret;
++
+       return qca8k_mdio_write(priv->bus, port, regnum, data);
+ }
+@@ -1030,6 +1241,11 @@ qca8k_phy_read(struct dsa_switch *ds, int port, int regnum)
+       if (priv->legacy_phy_port_mapping)
+               port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, true, port, regnum, 0);
++      if (ret >= 0)
++              return ret;
++
+       ret = qca8k_mdio_read(priv->bus, port, regnum);
+       if (ret < 0)
+diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h
+index 2d7d084db089..c6f6abd2108e 100644
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -14,6 +14,7 @@
+ #include <linux/dsa/tag_qca.h>
+ #define QCA8K_ETHERNET_MDIO_PRIORITY                  7
++#define QCA8K_ETHERNET_PHY_PRIORITY                   6
+ #define QCA8K_ETHERNET_TIMEOUT                                100
+ #define QCA8K_NUM_PORTS                                       7
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch b/target/linux/generic/backport-5.15/766-13-net-dsa-qca8k-move-page-cache-to-driver-priv.patch
new file mode 100644 (file)
index 0000000..d69b959
--- /dev/null
@@ -0,0 +1,215 @@
+From 4264350acb75430d5021a1d7de56a33faf69a097 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:32 +0100
+Subject: [PATCH 13/16] net: dsa: qca8k: move page cache to driver priv
+
+There can be multiple qca8k switch on the same system. Move the static
+qca8k_current_page to qca8k_priv and make it specific for each switch.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 42 ++++++++++++++++++++---------------------
+ drivers/net/dsa/qca8k.h |  9 +++++++++
+ 2 files changed, 29 insertions(+), 22 deletions(-)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index 0ce5b7ca0b7f..86d3742b1038 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -75,12 +75,6 @@ static const struct qca8k_mib_desc ar8327_mib[] = {
+       MIB_DESC(1, 0xac, "TXUnicast"),
+ };
+-/* The 32bit switch registers are accessed indirectly. To achieve this we need
+- * to set the page of the register. Track the last page that was set to reduce
+- * mdio writes
+- */
+-static u16 qca8k_current_page = 0xffff;
+-
+ static void
+ qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+ {
+@@ -134,11 +128,13 @@ qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
+ }
+ static int
+-qca8k_set_page(struct mii_bus *bus, u16 page)
++qca8k_set_page(struct qca8k_priv *priv, u16 page)
+ {
++      u16 *cached_page = &priv->mdio_cache.page;
++      struct mii_bus *bus = priv->bus;
+       int ret;
+-      if (page == qca8k_current_page)
++      if (page == *cached_page)
+               return 0;
+       ret = bus->write(bus, 0x18, 0, page);
+@@ -148,7 +144,7 @@ qca8k_set_page(struct mii_bus *bus, u16 page)
+               return ret;
+       }
+-      qca8k_current_page = page;
++      *cached_page = page;
+       usleep_range(1000, 2000);
+       return 0;
+ }
+@@ -374,7 +370,7 @@ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+-      ret = qca8k_set_page(bus, page);
++      ret = qca8k_set_page(priv, page);
+       if (ret < 0)
+               goto exit;
+@@ -400,7 +396,7 @@ qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+-      ret = qca8k_set_page(bus, page);
++      ret = qca8k_set_page(priv, page);
+       if (ret < 0)
+               goto exit;
+@@ -427,7 +423,7 @@ qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+-      ret = qca8k_set_page(bus, page);
++      ret = qca8k_set_page(priv, page);
+       if (ret < 0)
+               goto exit;
+@@ -1098,8 +1094,9 @@ qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
+ }
+ static int
+-qca8k_mdio_write(struct mii_bus *bus, int phy, int regnum, u16 data)
++qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
+ {
++      struct mii_bus *bus = priv->bus;
+       u16 r1, r2, page;
+       u32 val;
+       int ret;
+@@ -1116,7 +1113,7 @@ qca8k_mdio_write(struct mii_bus *bus, int phy, int regnum, u16 data)
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+-      ret = qca8k_set_page(bus, page);
++      ret = qca8k_set_page(priv, page);
+       if (ret)
+               goto exit;
+@@ -1135,8 +1132,9 @@ qca8k_mdio_write(struct mii_bus *bus, int phy, int regnum, u16 data)
+ }
+ static int
+-qca8k_mdio_read(struct mii_bus *bus, int phy, int regnum)
++qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
+ {
++      struct mii_bus *bus = priv->bus;
+       u16 r1, r2, page;
+       u32 val;
+       int ret;
+@@ -1152,7 +1150,7 @@ qca8k_mdio_read(struct mii_bus *bus, int phy, int regnum)
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+-      ret = qca8k_set_page(bus, page);
++      ret = qca8k_set_page(priv, page);
+       if (ret)
+               goto exit;
+@@ -1181,7 +1179,6 @@ static int
+ qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
+ {
+       struct qca8k_priv *priv = slave_bus->priv;
+-      struct mii_bus *bus = priv->bus;
+       int ret;
+       /* Use mdio Ethernet when available, fallback to legacy one on error */
+@@ -1189,14 +1186,13 @@ qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 da
+       if (!ret)
+               return 0;
+-      return qca8k_mdio_write(bus, phy, regnum, data);
++      return qca8k_mdio_write(priv, phy, regnum, data);
+ }
+ static int
+ qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
+ {
+       struct qca8k_priv *priv = slave_bus->priv;
+-      struct mii_bus *bus = priv->bus;
+       int ret;
+       /* Use mdio Ethernet when available, fallback to legacy one on error */
+@@ -1204,7 +1200,7 @@ qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
+       if (ret >= 0)
+               return ret;
+-      return qca8k_mdio_read(bus, phy, regnum);
++      return qca8k_mdio_read(priv, phy, regnum);
+ }
+ static int
+@@ -1225,7 +1221,7 @@ qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
+       if (!ret)
+               return ret;
+-      return qca8k_mdio_write(priv->bus, port, regnum, data);
++      return qca8k_mdio_write(priv, port, regnum, data);
+ }
+ static int
+@@ -1246,7 +1242,7 @@ qca8k_phy_read(struct dsa_switch *ds, int port, int regnum)
+       if (ret >= 0)
+               return ret;
+-      ret = qca8k_mdio_read(priv->bus, port, regnum);
++      ret = qca8k_mdio_read(priv, port, regnum);
+       if (ret < 0)
+               return 0xffff;
+@@ -3042,6 +3038,8 @@ qca8k_sw_probe(struct mdio_device *mdiodev)
+               return PTR_ERR(priv->regmap);
+       }
++      priv->mdio_cache.page = 0xffff;
++
+       /* Check the detected switch id */
+       ret = qca8k_read_switch_id(priv);
+       if (ret)
+diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h
+index c6f6abd2108e..57368acae41b 100644
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -363,6 +363,14 @@ struct qca8k_ports_config {
+       u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */
+ };
++struct qca8k_mdio_cache {
++/* The 32bit switch registers are accessed indirectly. To achieve this we need
++ * to set the page of the register. Track the last page that was set to reduce
++ * mdio writes
++ */
++      u16 page;
++};
++
+ struct qca8k_priv {
+       u8 switch_id;
+       u8 switch_revision;
+@@ -383,6 +391,7 @@ struct qca8k_priv {
+       struct net_device *mgmt_master; /* Track if mdio/mib Ethernet is available */
+       struct qca8k_mgmt_eth_data mgmt_eth_data;
+       struct qca8k_mib_eth_data mib_eth_data;
++      struct qca8k_mdio_cache mdio_cache;
+ };
+ struct qca8k_mib_desc {
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch b/target/linux/generic/backport-5.15/766-14-net-dsa-qca8k-cache-lo-and-hi-for-mdio-write.patch
new file mode 100644 (file)
index 0000000..72de25c
--- /dev/null
@@ -0,0 +1,171 @@
+From 2481d206fae7884cd07014fd1318e63af35e99eb Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:33 +0100
+Subject: [PATCH 14/16] net: dsa: qca8k: cache lo and hi for mdio write
+
+From Documentation, we can cache lo and hi the same way we do with the
+page. This massively reduce the mdio write as 3/4 of the time as we only
+require to write the lo or hi part for a mdio write.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 61 +++++++++++++++++++++++++++++++++--------
+ drivers/net/dsa/qca8k.h |  5 ++++
+ 2 files changed, 54 insertions(+), 12 deletions(-)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index 86d3742b1038..0cce3a6030af 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -88,6 +88,44 @@ qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+       *page = regaddr & 0x3ff;
+ }
++static int
++qca8k_set_lo(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 lo)
++{
++      u16 *cached_lo = &priv->mdio_cache.lo;
++      struct mii_bus *bus = priv->bus;
++      int ret;
++
++      if (lo == *cached_lo)
++              return 0;
++
++      ret = bus->write(bus, phy_id, regnum, lo);
++      if (ret < 0)
++              dev_err_ratelimited(&bus->dev,
++                                  "failed to write qca8k 32bit lo register\n");
++
++      *cached_lo = lo;
++      return 0;
++}
++
++static int
++qca8k_set_hi(struct qca8k_priv *priv, int phy_id, u32 regnum, u16 hi)
++{
++      u16 *cached_hi = &priv->mdio_cache.hi;
++      struct mii_bus *bus = priv->bus;
++      int ret;
++
++      if (hi == *cached_hi)
++              return 0;
++
++      ret = bus->write(bus, phy_id, regnum, hi);
++      if (ret < 0)
++              dev_err_ratelimited(&bus->dev,
++                                  "failed to write qca8k 32bit hi register\n");
++
++      *cached_hi = hi;
++      return 0;
++}
++
+ static int
+ qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
+ {
+@@ -111,7 +149,7 @@ qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
+ }
+ static void
+-qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
++qca8k_mii_write32(struct qca8k_priv *priv, int phy_id, u32 regnum, u32 val)
+ {
+       u16 lo, hi;
+       int ret;
+@@ -119,12 +157,9 @@ qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
+       lo = val & 0xffff;
+       hi = (u16)(val >> 16);
+-      ret = bus->write(bus, phy_id, regnum, lo);
++      ret = qca8k_set_lo(priv, phy_id, regnum, lo);
+       if (ret >= 0)
+-              ret = bus->write(bus, phy_id, regnum + 1, hi);
+-      if (ret < 0)
+-              dev_err_ratelimited(&bus->dev,
+-                                  "failed to write qca8k 32bit register\n");
++              ret = qca8k_set_hi(priv, phy_id, regnum + 1, hi);
+ }
+ static int
+@@ -400,7 +435,7 @@ qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
+       if (ret < 0)
+               goto exit;
+-      qca8k_mii_write32(bus, 0x10 | r2, r1, val);
++      qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+ exit:
+       mutex_unlock(&bus->mdio_lock);
+@@ -433,7 +468,7 @@ qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_
+       val &= ~mask;
+       val |= write_val;
+-      qca8k_mii_write32(bus, 0x10 | r2, r1, val);
++      qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+ exit:
+       mutex_unlock(&bus->mdio_lock);
+@@ -1117,14 +1152,14 @@ qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
+       if (ret)
+               goto exit;
+-      qca8k_mii_write32(bus, 0x10 | r2, r1, val);
++      qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+       ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+                                  QCA8K_MDIO_MASTER_BUSY);
+ exit:
+       /* even if the busy_wait timeouts try to clear the MASTER_EN */
+-      qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
++      qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
+       mutex_unlock(&bus->mdio_lock);
+@@ -1154,7 +1189,7 @@ qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
+       if (ret)
+               goto exit;
+-      qca8k_mii_write32(bus, 0x10 | r2, r1, val);
++      qca8k_mii_write32(priv, 0x10 | r2, r1, val);
+       ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+                                  QCA8K_MDIO_MASTER_BUSY);
+@@ -1165,7 +1200,7 @@ qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
+ exit:
+       /* even if the busy_wait timeouts try to clear the MASTER_EN */
+-      qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
++      qca8k_mii_write32(priv, 0x10 | r2, r1, 0);
+       mutex_unlock(&bus->mdio_lock);
+@@ -3039,6 +3074,8 @@ qca8k_sw_probe(struct mdio_device *mdiodev)
+       }
+       priv->mdio_cache.page = 0xffff;
++      priv->mdio_cache.lo = 0xffff;
++      priv->mdio_cache.hi = 0xffff;
+       /* Check the detected switch id */
+       ret = qca8k_read_switch_id(priv);
+diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h
+index 57368acae41b..c3d3c2269b1d 100644
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -369,6 +369,11 @@ struct qca8k_mdio_cache {
+  * mdio writes
+  */
+       u16 page;
++/* lo and hi can also be cached and from Documentation we can skip one
++ * extra mdio write if lo or hi is didn't change.
++ */
++      u16 lo;
++      u16 hi;
+ };
+ struct qca8k_priv {
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch b/target/linux/generic/backport-5.15/766-15-net-dsa-qca8k-add-support-for-larger-read-write-size.patch
new file mode 100644 (file)
index 0000000..4b73522
--- /dev/null
@@ -0,0 +1,211 @@
+From 90386223f44e2a751d7e9e9ac8f78ea33358a891 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:34 +0100
+Subject: [PATCH 15/16] net: dsa: qca8k: add support for larger read/write size
+ with mgmt Ethernet
+
+mgmt Ethernet packet can read/write up to 16byte at times. The len reg
+is limited to 15 (0xf). The switch actually sends and accepts data in 4
+different steps of len values.
+Len steps:
+- 0: nothing
+- 1-4: first 4 byte
+- 5-6: first 12 byte
+- 7-15: all 16 byte
+
+In the alloc skb function we check if the len is 16 and we fix it to a
+len of 15. It the read/write function interest to extract the real asked
+data. The tagger handler will always copy the fully 16byte with a READ
+command. This is useful for some big regs like the fdb reg that are
+more than 4byte of data. This permits to introduce a bulk function that
+will send and request the entire entry in one go.
+Write function is changed and it does now require to pass the pointer to
+val to also handle array val.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 61 +++++++++++++++++++++++++++--------------
+ 1 file changed, 41 insertions(+), 20 deletions(-)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index 0cce3a6030af..a1b76dcd2eb6 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -222,7 +222,9 @@ static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
+       if (cmd == MDIO_READ) {
+               mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
+-              /* Get the rest of the 12 byte of data */
++              /* Get the rest of the 12 byte of data.
++               * The read/write function will extract the requested data.
++               */
+               if (len > QCA_HDR_MGMT_DATA1_LEN)
+                       memcpy(mgmt_eth_data->data + 1, skb->data,
+                              QCA_HDR_MGMT_DATA2_LEN);
+@@ -232,16 +234,30 @@ static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
+ }
+ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
+-                                             int priority)
++                                             int priority, unsigned int len)
+ {
+       struct qca_mgmt_ethhdr *mgmt_ethhdr;
++      unsigned int real_len;
+       struct sk_buff *skb;
++      u32 *data2;
+       u16 hdr;
+       skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
+       if (!skb)
+               return NULL;
++      /* Max value for len reg is 15 (0xf) but the switch actually return 16 byte
++       * Actually for some reason the steps are:
++       * 0: nothing
++       * 1-4: first 4 byte
++       * 5-6: first 12 byte
++       * 7-15: all 16 byte
++       */
++      if (len == 16)
++              real_len = 15;
++      else
++              real_len = len;
++
+       skb_reset_mac_header(skb);
+       skb_set_network_header(skb, skb->len);
+@@ -254,7 +270,7 @@ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *
+       hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
+       mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
+-      mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, 4);
++      mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
+       mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
+       mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
+                                          QCA_HDR_MGMT_CHECK_CODE_VAL);
+@@ -264,7 +280,9 @@ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *
+       mgmt_ethhdr->hdr = htons(hdr);
+-      skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
++      data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
++      if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
++              memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
+       return skb;
+ }
+@@ -277,7 +295,7 @@ static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
+       mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
+ }
+-static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
++static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+ {
+       struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+       struct sk_buff *skb;
+@@ -285,7 +303,7 @@ static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
+       int ret;
+       skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
+-                                    QCA8K_ETHERNET_MDIO_PRIORITY);
++                                    QCA8K_ETHERNET_MDIO_PRIORITY, len);
+       if (!skb)
+               return -ENOMEM;
+@@ -313,6 +331,9 @@ static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
+                                         msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
+       *val = mgmt_eth_data->data[0];
++      if (len > QCA_HDR_MGMT_DATA1_LEN)
++              memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
++
+       ack = mgmt_eth_data->ack;
+       mutex_unlock(&mgmt_eth_data->mutex);
+@@ -326,15 +347,15 @@ static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val)
+       return 0;
+ }
+-static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 val)
++static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+ {
+       struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
+       struct sk_buff *skb;
+       bool ack;
+       int ret;
+-      skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, &val,
+-                                    QCA8K_ETHERNET_MDIO_PRIORITY);
++      skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
++                                    QCA8K_ETHERNET_MDIO_PRIORITY, len);
+       if (!skb)
+               return -ENOMEM;
+@@ -380,14 +401,14 @@ qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 wri
+       u32 val = 0;
+       int ret;
+-      ret = qca8k_read_eth(priv, reg, &val);
++      ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
+       if (ret)
+               return ret;
+       val &= ~mask;
+       val |= write_val;
+-      return qca8k_write_eth(priv, reg, val);
++      return qca8k_write_eth(priv, reg, &val, sizeof(val));
+ }
+ static int
+@@ -398,7 +419,7 @@ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+       u16 r1, r2, page;
+       int ret;
+-      if (!qca8k_read_eth(priv, reg, val))
++      if (!qca8k_read_eth(priv, reg, val, sizeof(val)))
+               return 0;
+       qca8k_split_addr(reg, &r1, &r2, &page);
+@@ -424,7 +445,7 @@ qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
+       u16 r1, r2, page;
+       int ret;
+-      if (!qca8k_write_eth(priv, reg, val))
++      if (!qca8k_write_eth(priv, reg, &val, sizeof(val)))
+               return 0;
+       qca8k_split_addr(reg, &r1, &r2, &page);
+@@ -959,21 +980,21 @@ qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
+       }
+       /* Prealloc all the needed skb before the lock */
+-      write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
+-                                          &write_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
++                                          QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
+       if (!write_skb)
+               return -ENOMEM;
+-      clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
+-                                          &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
++                                          QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
+       if (!write_skb) {
+               ret = -ENOMEM;
+               goto err_clear_skb;
+       }
+-      read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL,
+-                                         &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
+-      if (!write_skb) {
++      read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
++                                         QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
++      if (!read_skb) {
+               ret = -ENOMEM;
+               goto err_read_skb;
+       }
+-- 
+2.34.1
+
diff --git a/target/linux/generic/backport-5.15/766-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch b/target/linux/generic/backport-5.15/766-16-net-dsa-qca8k-introduce-qca8k_bulk_read-write-functi.patch
new file mode 100644 (file)
index 0000000..ebd0884
--- /dev/null
@@ -0,0 +1,109 @@
+From 4f3701fc599820568ba4395070d34e4248800fc0 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:35 +0100
+Subject: [PATCH 16/16] net: dsa: qca8k: introduce qca8k_bulk_read/write
+ function
+
+Introduce qca8k_bulk_read/write() function to use mgmt Ethernet way to
+read/write packet in bulk. Make use of this new function in the fdb
+function and while at it reduce the reg for fdb_read from 4 to 3 as the
+max bit for the ARL(fdb) table is 83 bits.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 55 ++++++++++++++++++++++++++++++++---------
+ 1 file changed, 43 insertions(+), 12 deletions(-)
+
+diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
+index a1b76dcd2eb6..52ec2800dd89 100644
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -411,6 +411,43 @@ qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 wri
+       return qca8k_write_eth(priv, reg, &val, sizeof(val));
+ }
++static int
++qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++{
++      int i, count = len / sizeof(u32), ret;
++
++      if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len))
++              return 0;
++
++      for (i = 0; i < count; i++) {
++              ret = regmap_read(priv->regmap, reg + (i * 4), val + i);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return 0;
++}
++
++static int
++qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
++{
++      int i, count = len / sizeof(u32), ret;
++      u32 tmp;
++
++      if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len))
++              return 0;
++
++      for (i = 0; i < count; i++) {
++              tmp = val[i];
++
++              ret = regmap_write(priv->regmap, reg + (i * 4), tmp);
++              if (ret < 0)
++                      return ret;
++      }
++
++      return 0;
++}
++
+ static int
+ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+ {
+@@ -546,17 +583,13 @@ qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
+ static int
+ qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
+ {
+-      u32 reg[4], val;
+-      int i, ret;
++      u32 reg[3];
++      int ret;
+       /* load the ARL table into an array */
+-      for (i = 0; i < 4; i++) {
+-              ret = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4), &val);
+-              if (ret < 0)
+-                      return ret;
+-
+-              reg[i] = val;
+-      }
++      ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
++      if (ret)
++              return ret;
+       /* vid - 83:72 */
+       fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
+@@ -580,7 +613,6 @@ qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
+               u8 aging)
+ {
+       u32 reg[3] = { 0 };
+-      int i;
+       /* vid - 83:72 */
+       reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
+@@ -597,8 +629,7 @@ qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
+       reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
+       /* load the array into the ARL table */
+-      for (i = 0; i < 3; i++)
+-              qca8k_write(priv, QCA8K_REG_ATU_DATA0 + (i * 4), reg[i]);
++      qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
+ }
+ static int
+-- 
+2.34.1
+