--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+