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