]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
generic: 6.12: backport PCS standalone feature
authorChristian Marangi <ansuelsmth@gmail.com>
Sat, 9 May 2026 18:12:18 +0000 (20:12 +0200)
committerChristian Marangi <ansuelsmth@gmail.com>
Tue, 12 May 2026 22:19:29 +0000 (00:19 +0200)
Backport pending PCS standalone feature for kernel 6.12 and all the
required dependency patch.

All affected patch automatically refreshed.

Link: https://github.com/openwrt/openwrt/pull/23271
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
16 files changed:
target/linux/airoha/patches-6.12/310-09-net-pcs-airoha-add-PCS-driver-for-Airoha-SoC.patch
target/linux/generic/backport-6.12/650-v6.13-net-phylink-allow-mac_select_pcs-to-remove-a-PCS.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/651-v6.14-net-phylink-add-support-for-PCS-supported_interfaces.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/652-v6.15-net-phylink-force-link-down-on-major_config-failure.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/653-01-v7.0-net-phylink-simplify-phylink_resolve-phylink_major_c.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/653-02-v7.0-net-phylink-introduce-helpers-for-replaying-link-cal.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/702-v7.0-net-phylink-fix-NULL-pointer-deref-in-phylink_major_.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/706-net-phy-populate-host_interfaces-when-attaching-PHY.patch
target/linux/generic/pending-6.12/770-01-net-phylink-keep-and-use-MAC-supported_interfaces-in.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/770-02-net-phylink-introduce-internal-phylink-PCS-handling.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/770-03-net-phylink-add-phylink_release_pcs-to-externally-re.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/770-04-net-pcs-implement-Firmware-node-support-for-PCS-driv.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/770-05-net-phylink-support-late-PCS-provider-attach.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/770-06-dt-bindings-net-ethernet-controller-permit-to-define.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/770-07-net-phylink-add-.pcs_link_down-PCS-OP.patch [moved from target/linux/airoha/patches-6.12/310-08-net-phylink-add-.pcs_link_down-PCS-OP.patch with 69% similarity]
target/linux/layerscape/patches-6.12/702-phy-Add-2.5G-SGMII-interface-mode.patch

index 5bd7da4a430a33e64164b81952bcb53172e85808..618b506f9751658828ab45b64915e66e395bb49f 100644 (file)
@@ -31,7 +31,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 
 --- a/drivers/net/pcs/Kconfig
 +++ b/drivers/net/pcs/Kconfig
-@@ -44,4 +44,6 @@ config PCS_RZN1_MIIC
+@@ -51,4 +51,6 @@ config PCS_RZN1_MIIC
          on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in
          pass-through mode for MII.
  
@@ -40,7 +40,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
  endmenu
 --- a/drivers/net/pcs/Makefile
 +++ b/drivers/net/pcs/Makefile
-@@ -9,3 +9,5 @@ obj-$(CONFIG_PCS_LYNX)         += pcs-lynx.o
+@@ -10,3 +10,5 @@ obj-$(CONFIG_PCS_LYNX)               += pcs-lynx.o
  obj-$(CONFIG_PCS_MTK_LYNXI)   += pcs-mtk-lynxi.o
  obj-$(CONFIG_PCS_RZN1_MIIC)   += pcs-rzn1-miic.o
  obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
diff --git a/target/linux/generic/backport-6.12/650-v6.13-net-phylink-allow-mac_select_pcs-to-remove-a-PCS.patch b/target/linux/generic/backport-6.12/650-v6.13-net-phylink-allow-mac_select_pcs-to-remove-a-PCS.patch
new file mode 100644 (file)
index 0000000..199efc6
--- /dev/null
@@ -0,0 +1,38 @@
+From 486dc391ef439d45db3f7eda2229560fd2b52a78 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Wed, 16 Oct 2024 10:58:34 +0100
+Subject: [PATCH] net: phylink: allow mac_select_pcs() to remove a PCS
+
+phylink has historically not permitted a PCS to be removed. An attempt
+to permit this with phylink_set_pcs() resulted in comments indicating
+that there was no need for this. This behaviour has been propagated
+forward to the mac_select_pcs() approach as it was believed from these
+comments that changing this would be NAK'd.
+
+However, with mac_select_pcs(), it takes more code and thus complexity
+to maintain this behaviour, which can - and in this case has - resulted
+in a bug. If mac_select_pcs() returns NULL for a particular interface
+type, but there is already a PCS in-use, then we skip the pcs_validate()
+method, but continue using the old PCS. Also, it wouldn't be expected
+behaviour by implementers of mac_select_pcs().
+
+Allow this by removing this old unnecessary restriction.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/phy/phylink.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1375,7 +1375,7 @@ static void phylink_major_config(struct
+                       return;
+               }
+-              pcs_changed = pcs && pl->pcs != pcs;
++              pcs_changed = pl->pcs != pcs;
+       }
+       phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
diff --git a/target/linux/generic/backport-6.12/651-v6.14-net-phylink-add-support-for-PCS-supported_interfaces.patch b/target/linux/generic/backport-6.12/651-v6.14-net-phylink-add-support-for-PCS-supported_interfaces.patch
new file mode 100644 (file)
index 0000000..044f49b
--- /dev/null
@@ -0,0 +1,61 @@
+From fbb9a9d263a68f60a16c8ba5a51d6198d67171cd Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 3 Jan 2025 11:16:31 +0000
+Subject: [PATCH] net: phylink: add support for PCS supported_interfaces bitmap
+
+Add support for the PCS to specify which interfaces it supports, which
+can be used by MAC drivers to build the main supported_interfaces
+bitmap. Phylink also validates that the PCS returned by the MAC driver
+supports the interface that the MAC was asked for.
+
+An empty supported_interfaces bitmap from the PCS indicates that it
+does not provide this information, and we handle that appropriately.
+
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tTffL-007RoD-1Y@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 11 +++++++++++
+ include/linux/phylink.h   |  3 +++
+ 2 files changed, 14 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -701,6 +701,17 @@ static int phylink_validate_mac_and_pcs(
+                       return -EINVAL;
+               }
++              /* Ensure that this PCS supports the interface which the MAC
++               * returned it for. It is an error for the MAC to return a PCS
++               * that does not support the interface mode.
++               */
++              if (!phy_interface_empty(pcs->supported_interfaces) &&
++                  !test_bit(state->interface, pcs->supported_interfaces)) {
++                      phylink_err(pl, "MAC returned PCS which does not support %s\n",
++                                  phy_modes(state->interface));
++                      return -EINVAL;
++              }
++
+               /* Validate the link parameters with the PCS */
+               if (pcs->ops->pcs_validate) {
+                       ret = pcs->ops->pcs_validate(pcs, supported, state);
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -393,6 +393,8 @@ struct phylink_pcs_ops;
+ /**
+  * struct phylink_pcs - PHYLINK PCS instance
++ * @supported_interfaces: describing which PHY_INTERFACE_MODE_xxx
++ *                        are supported by this PCS.
+  * @ops: a pointer to the &struct phylink_pcs_ops structure
+  * @phylink: pointer to &struct phylink_config
+  * @neg_mode: provide PCS neg mode via "mode" argument
+@@ -409,6 +411,7 @@ struct phylink_pcs_ops;
+  * the PCS driver.
+  */
+ struct phylink_pcs {
++      DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
+       const struct phylink_pcs_ops *ops;
+       struct phylink *phylink;
+       bool neg_mode;
diff --git a/target/linux/generic/backport-6.12/652-v6.15-net-phylink-force-link-down-on-major_config-failure.patch b/target/linux/generic/backport-6.12/652-v6.15-net-phylink-force-link-down-on-major_config-failure.patch
new file mode 100644 (file)
index 0000000..01180b6
--- /dev/null
@@ -0,0 +1,125 @@
+From f1ae32a709e0b525d7963207eb3a4747626f4818 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Mon, 24 Mar 2025 16:40:08 +0000
+Subject: [PATCH] net: phylink: force link down on major_config failure
+
+If we fail to configure the MAC or PCS according to the desired mode,
+do not allow the network link to come up until we have successfully
+configured the MAC and PCS. This improves phylink's behaviour when an
+error occurs.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1twkqO-0006FI-Gm@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 42 +++++++++++++++++++++++++++++++--------
+ 1 file changed, 34 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -82,6 +82,7 @@ struct phylink {
+       bool link_failed;
+       bool using_mac_select_pcs;
++      bool major_config_failed;
+       struct sfp_bus *sfp_bus;
+       bool sfp_may_have_phy;
+@@ -1377,12 +1378,16 @@ static void phylink_major_config(struct
+                   phylink_an_mode_str(pl->req_link_an_mode),
+                   phy_modes(state->interface));
++      pl->major_config_failed = false;
++
+       if (pl->using_mac_select_pcs) {
+               pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+               if (IS_ERR(pcs)) {
+                       phylink_err(pl,
+                                   "mac_select_pcs unexpectedly failed: %pe\n",
+                                   pcs);
++
++                      pl->major_config_failed = true;
+                       return;
+               }
+@@ -1404,6 +1409,7 @@ static void phylink_major_config(struct
+               if (err < 0) {
+                       phylink_err(pl, "mac_prepare failed: %pe\n",
+                                   ERR_PTR(err));
++                      pl->major_config_failed = true;
+                       return;
+               }
+       }
+@@ -1427,8 +1433,15 @@ static void phylink_major_config(struct
+       phylink_mac_config(pl, state);
+-      if (pl->pcs)
+-              phylink_pcs_post_config(pl->pcs, state->interface);
++      if (pl->pcs) {
++              err = phylink_pcs_post_config(pl->pcs, state->interface);
++              if (err < 0) {
++                      phylink_err(pl, "pcs_post_config failed: %pe\n",
++                                  ERR_PTR(err));
++
++                      pl->major_config_failed = true;
++              }
++      }
+       if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
+               phylink_pcs_enable(pl->pcs);
+@@ -1439,11 +1452,12 @@ static void phylink_major_config(struct
+       err = phylink_pcs_config(pl->pcs, neg_mode, state,
+                                !!(pl->link_config.pause & MLO_PAUSE_AN));
+-      if (err < 0)
+-              phylink_err(pl, "pcs_config failed: %pe\n",
+-                          ERR_PTR(err));
+-      else if (err > 0)
++      if (err < 0) {
++              phylink_err(pl, "pcs_config failed: %pe\n", ERR_PTR(err));
++              pl->major_config_failed = true;
++      } else if (err > 0) {
+               restart = true;
++      }
+       if (restart)
+               phylink_pcs_an_restart(pl);
+@@ -1451,16 +1465,22 @@ static void phylink_major_config(struct
+       if (pl->mac_ops->mac_finish) {
+               err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode,
+                                             state->interface);
+-              if (err < 0)
++              if (err < 0) {
+                       phylink_err(pl, "mac_finish failed: %pe\n",
+                                   ERR_PTR(err));
++
++                      pl->major_config_failed = true;
++              }
+       }
+       if (pl->phydev && pl->phy_ib_mode) {
+               err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
+-              if (err < 0)
++              if (err < 0) {
+                       phylink_err(pl, "phy_config_inband: %pe\n",
+                                   ERR_PTR(err));
++
++                      pl->major_config_failed = true;
++              }
+       }
+       if (pl->sfp_bus) {
+@@ -1762,6 +1782,12 @@ static void phylink_resolve(struct work_
+               }
+       }
++      /* If configuration of the interface failed, force the link down
++       * until we get a successful configuration.
++       */
++      if (pl->major_config_failed)
++              link_state.link = false;
++
+       if (link_state.link != cur_link_state) {
+               pl->old_link_state = link_state.link;
+               if (!link_state.link)
diff --git a/target/linux/generic/backport-6.12/653-01-v7.0-net-phylink-simplify-phylink_resolve-phylink_major_c.patch b/target/linux/generic/backport-6.12/653-01-v7.0-net-phylink-simplify-phylink_resolve-phylink_major_c.patch
new file mode 100644 (file)
index 0000000..8533c72
--- /dev/null
@@ -0,0 +1,61 @@
+From 7bf588dc62a05c1866efe098e1b188fd879aa2cf Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Mon, 19 Jan 2026 14:19:51 +0200
+Subject: [PATCH] net: phylink: simplify phylink_resolve() ->
+ phylink_major_config() path
+
+This is a trivial change with no functional effect which replaces the
+pattern:
+
+if (a) {
+       if (b) {
+               do_stuff();
+       }
+}
+
+with:
+
+if (a && b) {
+       do_stuff();
+};
+
+The purpose is to reduce the delta of a subsequent functional change.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/20260119121954.1624535-2-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 20 +++++++++-----------
+ 1 file changed, 9 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1768,18 +1768,16 @@ static void phylink_resolve(struct work_
+       if (pl->act_link_an_mode != MLO_AN_FIXED)
+               phylink_apply_manual_flow(pl, &link_state);
+-      if (mac_config) {
+-              if (link_state.interface != pl->link_config.interface) {
+-                      /* The interface has changed, force the link down and
+-                       * then reconfigure.
+-                       */
+-                      if (cur_link_state) {
+-                              phylink_link_down(pl);
+-                              cur_link_state = false;
+-                      }
+-                      phylink_major_config(pl, false, &link_state);
+-                      pl->link_config.interface = link_state.interface;
++      if (mac_config && link_state.interface != pl->link_config.interface) {
++              /* The interface has changed, so force the link down and then
++               * reconfigure.
++               */
++              if (cur_link_state) {
++                      phylink_link_down(pl);
++                      cur_link_state = false;
+               }
++              phylink_major_config(pl, false, &link_state);
++              pl->link_config.interface = link_state.interface;
+       }
+       /* If configuration of the interface failed, force the link down
diff --git a/target/linux/generic/backport-6.12/653-02-v7.0-net-phylink-introduce-helpers-for-replaying-link-cal.patch b/target/linux/generic/backport-6.12/653-02-v7.0-net-phylink-introduce-helpers-for-replaying-link-cal.patch
new file mode 100644 (file)
index 0000000..462848e
--- /dev/null
@@ -0,0 +1,148 @@
+From 96969b132bf1a5b875ab84fcb41a5c4972c3be9e Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Mon, 19 Jan 2026 14:19:52 +0200
+Subject: [PATCH 2/2] net: phylink: introduce helpers for replaying link
+ callbacks
+
+Some drivers of MAC + tightly integrated PCS (example: SJA1105 + XPCS
+covered by same reset domain) need to perform resets at runtime.
+
+The reset is triggered by the MAC driver, and it needs to restore its
+and the PCS' registers, all invisible to phylink.
+
+However, there is a desire to simplify the API through which the MAC and
+the PCS interact, so this becomes challenging.
+
+Phylink holds all the necessary state to help with this operation, and
+can offer two helpers which walk the MAC and PCS drivers again through
+the callbacks required during a destructive reset operation. The
+procedure is as follows:
+
+Before reset, MAC driver calls phylink_replay_link_begin():
+- Triggers phylink mac_link_down() and pcs_link_down() methods
+
+After reset, MAC driver calls phylink_replay_link_end():
+- Triggers phylink mac_config() -> pcs_config() -> mac_link_up() ->
+  pcs_link_up() methods.
+
+MAC and PCS registers are restored with no other custom driver code.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/20260119121954.1624535-3-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 61 +++++++++++++++++++++++++++++++++++++--
+ include/linux/phylink.h   |  5 ++++
+ 2 files changed, 63 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -34,6 +34,7 @@ enum {
+       PHYLINK_DISABLE_STOPPED,
+       PHYLINK_DISABLE_LINK,
+       PHYLINK_DISABLE_MAC_WOL,
++      PHYLINK_DISABLE_REPLAY,
+       PCS_STATE_DOWN = 0,
+       PCS_STATE_STARTING,
+@@ -83,6 +84,7 @@ struct phylink {
+       bool link_failed;
+       bool using_mac_select_pcs;
+       bool major_config_failed;
++      bool force_major_config;
+       struct sfp_bus *sfp_bus;
+       bool sfp_may_have_phy;
+@@ -1768,9 +1770,10 @@ static void phylink_resolve(struct work_
+       if (pl->act_link_an_mode != MLO_AN_FIXED)
+               phylink_apply_manual_flow(pl, &link_state);
+-      if (mac_config && link_state.interface != pl->link_config.interface) {
+-              /* The interface has changed, so force the link down and then
+-               * reconfigure.
++      if ((mac_config && link_state.interface != pl->link_config.interface) ||
++          pl->force_major_config) {
++              /* The interface has changed or a forced major configuration
++               * was requested, so force the link down and then reconfigure.
+                */
+               if (cur_link_state) {
+                       phylink_link_down(pl);
+@@ -1778,6 +1781,7 @@ static void phylink_resolve(struct work_
+               }
+               phylink_major_config(pl, false, &link_state);
+               pl->link_config.interface = link_state.interface;
++              pl->force_major_config = false;
+       }
+       /* If configuration of the interface failed, force the link down
+@@ -4130,6 +4134,57 @@ void phylink_mii_c45_pcs_get_state(struc
+ }
+ EXPORT_SYMBOL_GPL(phylink_mii_c45_pcs_get_state);
++/**
++ * phylink_replay_link_begin() - begin replay of link callbacks for driver
++ *                             which loses state
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ *
++ * Helper for MAC drivers which may perform a destructive reset at runtime.
++ * Both the own driver's mac_link_down() method is called, as well as the
++ * pcs_link_down() method of the split PCS (if any).
++ *
++ * This is similar to phylink_stop(), except it does not alter the state of
++ * the phylib PHY (it is assumed that it is not affected by the MAC destructive
++ * reset).
++ */
++void phylink_replay_link_begin(struct phylink *pl)
++{
++      ASSERT_RTNL();
++
++      phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_REPLAY);
++}
++EXPORT_SYMBOL_GPL(phylink_replay_link_begin);
++
++/**
++ * phylink_replay_link_end() - end replay of link callbacks for driver
++ *                           which lost state
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ *
++ * Helper for MAC drivers which may perform a destructive reset at runtime.
++ * Both the own driver's mac_config() and mac_link_up() methods, as well as the
++ * pcs_config() and pcs_link_up() method of the split PCS (if any), are called.
++ *
++ * This is similar to phylink_start(), except it does not alter the state of
++ * the phylib PHY.
++ *
++ * One must call this method only within the same rtnl_lock() critical section
++ * as a previous phylink_replay_link_start().
++ */
++void phylink_replay_link_end(struct phylink *pl)
++{
++      ASSERT_RTNL();
++
++      if (WARN(!test_bit(PHYLINK_DISABLE_REPLAY,
++                         &pl->phylink_disable_state),
++               "phylink_replay_link_end() called without a prior phylink_replay_link_begin()\n"))
++              return;
++
++      pl->force_major_config = true;
++      phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_REPLAY);
++      flush_work(&pl->resolve);
++}
++EXPORT_SYMBOL_GPL(phylink_replay_link_end);
++
+ static int __init phylink_init(void)
+ {
+       for (int i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); ++i)
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -707,4 +707,9 @@ void phylink_mii_c45_pcs_get_state(struc
+ void phylink_decode_usxgmii_word(struct phylink_link_state *state,
+                                uint16_t lpa);
++
++void phylink_replay_link_begin(struct phylink *pl);
++
++void phylink_replay_link_end(struct phylink *pl);
++
+ #endif
diff --git a/target/linux/generic/backport-6.12/702-v7.0-net-phylink-fix-NULL-pointer-deref-in-phylink_major_.patch b/target/linux/generic/backport-6.12/702-v7.0-net-phylink-fix-NULL-pointer-deref-in-phylink_major_.patch
new file mode 100644 (file)
index 0000000..c579ffc
--- /dev/null
@@ -0,0 +1,79 @@
+From 0e4d7df2f3b2e59c1bccc09ea099b7a6c2dda886 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Wed, 28 Jan 2026 10:51:56 +0000
+Subject: [PATCH] net: phylink: fix NULL pointer deref in
+ phylink_major_config()
+
+When a MAC driver returns a PCS for an interface mode, and then we
+attempt to switch to a different mode that doesn't require a PCS,
+this causes phylink to oops:
+
+Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010
+Mem abort info:
+  ESR = 0x0000000096000044
+  EC = 0x25: DABT (current EL), IL = 32 bits
+  SET = 0, FnV = 0
+  EA = 0, S1PTW = 0
+  FSC = 0x04: level 0 translation fault
+Data abort info:
+  ISV = 0, ISS = 0x00000044, ISS2 = 0x00000000
+  CM = 0, WnR = 1, TnD = 0, TagAccess = 0
+  GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
+user pgtable: 4k pages, 48-bit VAs, pgdp=0000000137f96000
+[0000000000000010] pgd=0000000000000000, p4d=0000000000000000
+Internal error: Oops: 0000000096000044 [#1]  SMP
+Modules linked in: --
+CPU: 1 UID: 0 PID: 55 Comm: kworker/u33:0 Not tainted 6.19.0-rc5-00581-g73cb8467a63e #1 PREEMPT
+Hardware name: Qualcomm Technologies, Inc. Lemans Ride Rev3 (DT)
+Workqueue: events_power_efficient phylink_resolve
+pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS    +BTYPE=--)
+pc : phylink_major_config+0x408/0x948
+lr : phylink_major_config+0x3fc/0x948
+sp : ffff800080353c60
+x29: ffff800080353cb0 x28: ffffb305068a8a00 x27: ffffb305068a8000
+x26: ffff000080092100 x25: 0000000000000000 x24: 0000000000000000
+x23: 0000000000000001 x22: 0000000000000000 x21: ffffb3050555b3d0
+x20: ffff800080353d10 x19: ffff0000b6059400 x18: 00000000ffffffff
+x17: 74756f2f79687020 x16: ffffb305045e4f18 x15: 6769666e6f632072
+x14: 6f6a616d203a3168 x13: 782d657361623030 x12: ffffb305068c6a98
+x11: 0000000000000583 x10: 0000000000000018 x9 : ffffb305068c6a98
+x8 : 0000000100006583 x7 : 0000000000000000 x6 : ffff00008083cc40
+x5 : ffff00008083cc40 x4 : 0000000000000001 x3 : 0000000000000001
+x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff0000b269e5a8
+Call trace:
+ phylink_major_config+0x408/0x948 (P)
+ phylink_resolve+0x294/0x6e4
+ process_one_work+0x148/0x28c
+ worker_thread+0x2d8/0x3d8
+ kthread+0x134/0x208
+ ret_from_fork+0x10/0x20
+Code: d63f0020 f9400e60 b4000040 f900081f (f9000ad3)
+---[ end trace 0000000000000000 ]---
+
+This is caused by "pcs" being NULL when we attempt to execute:
+
+               pcs->phylink = pl;
+
+Make this conditional on pcs being non-null.
+
+Fixes: 486dc391ef43 ("net: phylink: allow mac_select_pcs() to remove a PCS")
+Reported-by: Mohd Ayaan Anwar <mohd.anwar@oss.qualcomm.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1vl39Q-00000006utm-229h@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1425,7 +1425,8 @@ static void phylink_major_config(struct
+               if (pl->pcs)
+                       pl->pcs->phylink = NULL;
+-              pcs->phylink = pl;
++              if (pcs)
++                      pcs->phylink = pl;
+               pl->pcs = pcs;
+       }
index dea84c70797904ee8d7dec63fa20bb65eff125cb..b60045b1e765b6159caeee5752c1cae8c8dd35be 100644 (file)
@@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
 
 --- a/drivers/net/phy/phylink.c
 +++ b/drivers/net/phy/phylink.c
-@@ -2273,7 +2273,7 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2313,7 +2313,7 @@ int phylink_fwnode_phy_connect(struct ph
  {
        struct fwnode_handle *phy_fwnode;
        struct phy_device *phy_dev;
@@ -29,7 +29,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
  
        /* Fixed links and 802.3z are handled without needing a PHY */
        if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
-@@ -2303,6 +2303,25 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2343,6 +2343,25 @@ int phylink_fwnode_phy_connect(struct ph
        if (pl->config->mac_requires_rxc)
                flags |= PHY_F_RXC_ALWAYS_ON;
  
diff --git a/target/linux/generic/pending-6.12/770-01-net-phylink-keep-and-use-MAC-supported_interfaces-in.patch b/target/linux/generic/pending-6.12/770-01-net-phylink-keep-and-use-MAC-supported_interfaces-in.patch
new file mode 100644 (file)
index 0000000..6ea8d57
--- /dev/null
@@ -0,0 +1,88 @@
+From c6f6cb55c3c316f6169e07eacc5ccb214116719a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 31 Mar 2025 15:40:12 +0200
+Subject: [PATCH 1/7] net: phylink: keep and use MAC supported_interfaces in
+ phylink struct
+
+Add in phylink struct a copy of supported_interfaces from phylink_config
+and make use of that instead of relying on phylink_config value.
+
+This in preparation for support of PCS handling internally to phylink
+where a PCS can be removed or added after the phylink is created and we
+need both a reference of the supported_interfaces value from
+phylink_config and an internal value that can be updated with the new
+PCS info.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/phy/phylink.c | 22 +++++++++++++++-------
+ 1 file changed, 15 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -65,6 +65,11 @@ struct phylink {
+       /* The link configuration settings */
+       struct phylink_link_state link_config;
++      /* What interface are supported by the current link.
++       * Can change on removal or addition of new PCS.
++       */
++      DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
++
+       /* The current settings */
+       phy_interface_t cur_interface;
+@@ -793,7 +798,7 @@ static int phylink_validate_mask(struct
+ static int phylink_validate(struct phylink *pl, unsigned long *supported,
+                           struct phylink_link_state *state)
+ {
+-      const unsigned long *interfaces = pl->config->supported_interfaces;
++      const unsigned long *interfaces = pl->supported_interfaces;
+       if (state->interface == PHY_INTERFACE_MODE_NA)
+               return phylink_validate_mask(pl, NULL, supported, state,
+@@ -1948,6 +1953,9 @@ struct phylink *phylink_create(struct ph
+       mutex_init(&pl->state_mutex);
+       INIT_WORK(&pl->resolve, phylink_resolve);
++      phy_interface_copy(pl->supported_interfaces,
++                         config->supported_interfaces);
++
+       pl->config = config;
+       if (config->type == PHYLINK_NETDEV) {
+               pl->netdev = to_net_dev(config->dev);
+@@ -2091,7 +2099,7 @@ static int phylink_validate_phy(struct p
+                * those which the host supports.
+                */
+               phy_interface_and(interfaces, phy->possible_interfaces,
+-                                pl->config->supported_interfaces);
++                                pl->supported_interfaces);
+               if (phy_interface_empty(interfaces)) {
+                       phylink_err(pl, "PHY has no common interfaces\n");
+@@ -3565,14 +3573,14 @@ static int phylink_sfp_config_optical(st
+       phylink_dbg(pl, "optical SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n",
+                   (int)PHY_INTERFACE_MODE_MAX,
+-                  pl->config->supported_interfaces,
++                  pl->supported_interfaces,
+                   (int)PHY_INTERFACE_MODE_MAX,
+                   pl->sfp_interfaces);
+       /* Find the union of the supported interfaces by the PCS/MAC and
+        * the SFP module.
+        */
+-      phy_interface_and(interfaces, pl->config->supported_interfaces,
++      phy_interface_and(interfaces, pl->supported_interfaces,
+                         pl->sfp_interfaces);
+       if (phy_interface_empty(interfaces)) {
+               phylink_err(pl, "unsupported SFP module: no common interface modes\n");
+@@ -3730,7 +3738,7 @@ static int phylink_sfp_connect_phy(void
+       /* Set the PHY's host supported interfaces */
+       phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
+-                        pl->config->supported_interfaces);
++                        pl->supported_interfaces);
+       /* Do the initial configuration */
+       ret = phylink_sfp_config_phy(pl, phy);
diff --git a/target/linux/generic/pending-6.12/770-02-net-phylink-introduce-internal-phylink-PCS-handling.patch b/target/linux/generic/pending-6.12/770-02-net-phylink-introduce-internal-phylink-PCS-handling.patch
new file mode 100644 (file)
index 0000000..d7c00f3
--- /dev/null
@@ -0,0 +1,376 @@
+From d134e22b540226a7404cabb88c86a54857486b4f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 31 Mar 2025 16:03:26 +0200
+Subject: [PATCH 2/7] net: phylink: introduce internal phylink PCS handling
+
+Introduce internal handling of PCS for phylink. This is an alternative
+to .mac_select_pcs that moves the selection logic of the PCS entirely to
+phylink with the usage of supported_interface value in the PCS struct.
+
+MAC should now provide an array of available PCS in phylink_config in
+.available_pcs and fill the .num_available_pcs with the number of
+elements in the array. MAC should also define a new bitmap,
+pcs_interfaces, in phylink_config to define for what interface mode a
+dedicated PCS is required.
+
+On phylink_create() this array is parsed and a linked list of PCS is
+created based on the PCS passed in phylink_config.
+Also the supported_interface value in phylink struct is updated with the
+new supported_interface from the provided PCS.
+
+On phylink_start() every PCS in phylink PCS list gets attached to the
+phylink instance. This is done by setting the phylink value in
+phylink_pcs struct to the phylink instance.
+
+On phylink_stop(), every PCS in phylink PCS list is detached from the
+phylink instance. This is done by setting the phylink value in
+phylink_pcs struct to NULL.
+
+On phylink_stop(), every PCS in phylink PCS list is removed from the
+list.
+
+phylink_validate_mac_and_pcs(), phylink_major_config() and
+phylink_inband_caps() are updated to support this new implementation
+with the PCS list stored in phylink.
+
+They will make use of phylink_validate_pcs_interface() that will loop
+for every PCS in the phylink PCS available list and find one that supports
+the passed interface.
+
+phylink_validate_pcs_interface() apply the same logic of .mac_select_pcs
+where if a supported_interface value is not set for the PCS struct, then
+it's assumed every interface is supported.
+
+It's required for a MAC that implement either a .mac_select_pcs or make
+use of the PCS list implementation. Implementing both will result in a fail
+on MAC/PCS validation.
+
+phylink value in phylink_pcs struct with this implementation is used to
+track from PCS side when it's attached to a phylink instance. PCS driver
+will make use of this information to correctly detach from a phylink
+instance if needed.
+
+The .mac_select_pcs implementation is not changed but it's expected that
+every MAC driver migrates to the new implementation to later deprecate
+and remove .mac_select_pcs.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/phy/phylink.c | 149 +++++++++++++++++++++++++++++++++-----
+ include/linux/phylink.h   |  11 +++
+ 2 files changed, 141 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -65,6 +65,9 @@ struct phylink {
+       /* The link configuration settings */
+       struct phylink_link_state link_config;
++      /* List of available PCS */
++      struct list_head pcs_list;
++
+       /* What interface are supported by the current link.
+        * Can change on removal or addition of new PCS.
+        */
+@@ -144,6 +147,8 @@ static const phy_interface_t phylink_sfp
+ static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
++static void phylink_run_resolve(struct phylink *pl);
++
+ /**
+  * phylink_set_port_modes() - set the port type modes in the ethtool mask
+  * @mask: ethtool link mode mask
+@@ -680,24 +685,59 @@ static void phylink_validate_mask_caps(u
+       linkmode_and(state->advertising, state->advertising, mask);
+ }
++static int phylink_validate_pcs_interface(struct phylink_pcs *pcs,
++                                        phy_interface_t interface)
++{
++      /* If PCS define an empty supported_interfaces value, assume
++       * all interface are supported.
++       */
++      if (phy_interface_empty(pcs->supported_interfaces))
++              return 0;
++
++      /* Ensure that this PCS supports the interface mode */
++      if (!test_bit(interface, pcs->supported_interfaces))
++              return -EINVAL;
++
++      return 0;
++}
++
+ static int phylink_validate_mac_and_pcs(struct phylink *pl,
+                                       unsigned long *supported,
+                                       struct phylink_link_state *state)
+ {
+       unsigned long capabilities;
+       struct phylink_pcs *pcs;
++      bool pcs_found = false;
+       int ret;
+       /* Get the PCS for this interface mode */
+-      if (pl->using_mac_select_pcs) {
++      if (pl->mac_ops->mac_select_pcs) {
++              /* Make sure either PCS internal validation or .mac_select_pcs
++               * is used. Return error if both are defined.
++               */
++              if (!list_empty(&pl->pcs_list)) {
++                      phylink_err(pl, "either phylink_pcs_add() or .mac_select_pcs must be used\n");
++                      return -EINVAL;
++              }
++
+               pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+               if (IS_ERR(pcs))
+                       return PTR_ERR(pcs);
++
++              pcs_found = !!pcs;
+       } else {
+-              pcs = pl->pcs;
++              /* Check every assigned PCS and search for one that supports
++               * the interface.
++               */
++              list_for_each_entry(pcs, &pl->pcs_list, list) {
++                      if (!phylink_validate_pcs_interface(pcs, state->interface)) {
++                              pcs_found = true;
++                              break;
++                      }
++              }
+       }
+-      if (pcs) {
++      if (pcs_found) {
+               /* The PCS, if present, must be setup before phylink_create()
+                * has been called. If the ops is not initialised, print an
+                * error and backtrace rather than oopsing the kernel.
+@@ -709,13 +749,10 @@ static int phylink_validate_mac_and_pcs(
+                       return -EINVAL;
+               }
+-              /* Ensure that this PCS supports the interface which the MAC
+-               * returned it for. It is an error for the MAC to return a PCS
+-               * that does not support the interface mode.
+-               */
+-              if (!phy_interface_empty(pcs->supported_interfaces) &&
+-                  !test_bit(state->interface, pcs->supported_interfaces)) {
+-                      phylink_err(pl, "MAC returned PCS which does not support %s\n",
++              /* Recheck PCS to handle legacy way for .mac_select_pcs */
++              ret = phylink_validate_pcs_interface(pcs, state->interface);
++              if (ret) {
++                      phylink_err(pl, "selected PCS does not support %s\n",
+                                   phy_modes(state->interface));
+                       return -EINVAL;
+               }
+@@ -1096,12 +1133,22 @@ static unsigned int phylink_inband_caps(
+                                        phy_interface_t interface)
+ {
+       struct phylink_pcs *pcs;
++      bool pcs_found = false;
+-      if (!pl->mac_ops->mac_select_pcs)
+-              return 0;
++      if (pl->mac_ops->mac_select_pcs) {
++              pcs = pl->mac_ops->mac_select_pcs(pl->config,
++                                                interface);
++              pcs_found = !!pcs;
++      } else {
++              list_for_each_entry(pcs, &pl->pcs_list, list) {
++                      if (!phylink_validate_pcs_interface(pcs, interface)) {
++                              pcs_found = true;
++                              break;
++                      }
++              }
++      }
+-      pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
+-      if (!pcs)
++      if (!pcs_found)
+               return 0;
+       return phylink_pcs_inband_caps(pcs, interface);
+@@ -1397,10 +1444,36 @@ static void phylink_major_config(struct
+                       pl->major_config_failed = true;
+                       return;
+               }
++      /* Find a PCS in available PCS list for the requested interface.
++       * This doesn't overwrite the previous .mac_select_pcs as either
++       * .mac_select_pcs or PCS list implementation are permitted.
++       *
++       * Skip searching if the MAC doesn't require a dedicaed PCS for
++       * the requested interface.
++       */
++      } else if (test_bit(state->interface, pl->config->pcs_interfaces)) {
++              bool pcs_found = false;
++
++              list_for_each_entry(pcs, &pl->pcs_list, list) {
++                      if (!phylink_validate_pcs_interface(pcs,
++                                                          state->interface)) {
++                              pcs_found = true;
++                              break;
++                      }
++              }
++
++              if (!pcs_found) {
++                      phylink_err(pl,
++                                  "couldn't find a PCS for %s\n",
++                                  phy_modes(state->interface));
+-              pcs_changed = pl->pcs != pcs;
++                      pl->major_config_failed = true;
++                      return;
++              }
+       }
++      pcs_changed = pl->pcs != pcs;
++
+       phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
+       phylink_dbg(pl, "major config, active %s/%s/%s\n",
+@@ -1427,11 +1500,13 @@ static void phylink_major_config(struct
+       if (pcs_changed) {
+               phylink_pcs_disable(pl->pcs);
+-              if (pl->pcs)
+-                      pl->pcs->phylink = NULL;
++              if (pl->mac_ops->mac_select_pcs) {
++                      if (pl->pcs)
++                              pl->pcs->phylink = NULL;
+-              if (pcs)
+-                      pcs->phylink = pl;
++                      if (pcs)
++                              pcs->phylink = pl;
++              }
+               pl->pcs = pcs;
+       }
+@@ -1931,8 +2006,9 @@ struct phylink *phylink_create(struct ph
+                              const struct phylink_mac_ops *mac_ops)
+ {
+       bool using_mac_select_pcs = false;
++      struct phylink_pcs *pcs;
+       struct phylink *pl;
+-      int ret;
++      int i, ret;
+       /* Validate the supplied configuration */
+       if (phy_interface_empty(config->supported_interfaces)) {
+@@ -1952,9 +2028,21 @@ struct phylink *phylink_create(struct ph
+       mutex_init(&pl->state_mutex);
+       INIT_WORK(&pl->resolve, phylink_resolve);
++      INIT_LIST_HEAD(&pl->pcs_list);
++
++      /* Fill the PCS list with available PCS from phylink config */
++      for (i = 0; i < config->num_available_pcs; i++) {
++              pcs = config->available_pcs[i];
++
++              list_add(&pcs->list, &pl->pcs_list);
++      }
+       phy_interface_copy(pl->supported_interfaces,
+                          config->supported_interfaces);
++      list_for_each_entry(pcs, &pl->pcs_list, list)
++              phy_interface_or(pl->supported_interfaces,
++                               pl->supported_interfaces,
++                               pcs->supported_interfaces);
+       pl->config = config;
+       if (config->type == PHYLINK_NETDEV) {
+@@ -2024,10 +2112,16 @@ EXPORT_SYMBOL_GPL(phylink_create);
+  */
+ void phylink_destroy(struct phylink *pl)
+ {
++      struct phylink_pcs *pcs, *tmp;
++
+       sfp_bus_del_upstream(pl->sfp_bus);
+       if (pl->link_gpio)
+               gpiod_put(pl->link_gpio);
++      /* Remove every PCS from phylink PCS list */
++      list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list)
++              list_del(&pcs->list);
++
+       cancel_work_sync(&pl->resolve);
+       kfree(pl);
+ }
+@@ -2472,6 +2566,7 @@ static irqreturn_t phylink_link_handler(
+  */
+ void phylink_start(struct phylink *pl)
+ {
++      struct phylink_pcs *pcs;
+       bool poll = false;
+       ASSERT_RTNL();
+@@ -2498,6 +2593,10 @@ void phylink_start(struct phylink *pl)
+       pl->pcs_state = PCS_STATE_STARTED;
++      /* link available PCS to phylink struct */
++      list_for_each_entry(pcs, &pl->pcs_list, list)
++              pcs->phylink = pl;
++
+       phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
+       if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
+@@ -2542,6 +2641,8 @@ EXPORT_SYMBOL_GPL(phylink_start);
+  */
+ void phylink_stop(struct phylink *pl)
+ {
++      struct phylink_pcs *pcs;
++
+       ASSERT_RTNL();
+       if (pl->sfp_bus)
+@@ -2559,6 +2660,14 @@ void phylink_stop(struct phylink *pl)
+       pl->pcs_state = PCS_STATE_DOWN;
+       phylink_pcs_disable(pl->pcs);
++
++      /* Drop link between phylink and PCS */
++      list_for_each_entry(pcs, &pl->pcs_list, list)
++              pcs->phylink = NULL;
++
++      /* Restore original supported interfaces */
++      phy_interface_copy(pl->supported_interfaces,
++                         pl->config->supported_interfaces);
+ }
+ EXPORT_SYMBOL_GPL(phylink_stop);
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -147,7 +147,11 @@ enum phylink_op_type {
+  *                 if MAC link is at %MLO_AN_FIXED mode.
+  * @supported_interfaces: bitmap describing which PHY_INTERFACE_MODE_xxx
+  *                        are supported by the MAC/PCS.
++ * @pcs_interfaces: bitmap describing for which PHY_INTERFACE_MODE_xxx a
++ *                dedicated PCS is required.
+  * @mac_capabilities: MAC pause/speed/duplex capabilities.
++ * @available_pcs: array of available phylink_pcs PCS
++ * @num_available_pcs: num of available phylink_pcs PCS
+  */
+ struct phylink_config {
+       struct device *dev;
+@@ -159,7 +163,11 @@ struct phylink_config {
+       void (*get_fixed_state)(struct phylink_config *config,
+                               struct phylink_link_state *state);
+       DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
++      DECLARE_PHY_INTERFACE_MASK(pcs_interfaces);
+       unsigned long mac_capabilities;
++
++      struct phylink_pcs **available_pcs;
++      unsigned int num_available_pcs;
+ };
+ void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed);
+@@ -417,6 +425,9 @@ struct phylink_pcs {
+       bool neg_mode;
+       bool poll;
+       bool rxc_always_on;
++
++      /* private: */
++      struct list_head list;
+ };
+ /**
diff --git a/target/linux/generic/pending-6.12/770-03-net-phylink-add-phylink_release_pcs-to-externally-re.patch b/target/linux/generic/pending-6.12/770-03-net-phylink-add-phylink_release_pcs-to-externally-re.patch
new file mode 100644 (file)
index 0000000..72d920c
--- /dev/null
@@ -0,0 +1,134 @@
+From 1cb4e56c3ba32ac1bce89dc9c34ef2dbc9b89ad4 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 31 Mar 2025 19:10:24 +0200
+Subject: [PATCH 3/7] net: phylink: add phylink_release_pcs() to externally
+ release a PCS
+
+Add phylink_release_pcs() to externally release a PCS from a phylink
+instance. This can be used to handle case when a single PCS needs to be
+removed and the phylink instance needs to be refreshed.
+
+On calling phylink_release_pcs(), the PCS will be removed from the
+phylink internal PCS list and the phylink supported_interfaces value is
+reparsed with the remaining PCS interfaces.
+
+Also a phylink resolve is triggered to handle the PCS removal.
+
+It's also added to phylink a flag to make phylink resolve reconfigure
+the interface mode (even if it didn't change). This is needed to handle
+the special case when the current PCS used by phylink is removed and a
+major_config is needed to propagae the configuration change. With this
+option enabled we also force mac_config even if the PHY link is not up
+for the in-band case.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/phy/phylink.c | 57 ++++++++++++++++++++++++++++++++++++++-
+ include/linux/phylink.h   |  2 ++
+ 2 files changed, 58 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -93,6 +93,7 @@ struct phylink {
+       bool using_mac_select_pcs;
+       bool major_config_failed;
+       bool force_major_config;
++      bool reconfig_interface;
+       struct sfp_bus *sfp_bus;
+       bool sfp_may_have_phy;
+@@ -1064,6 +1065,55 @@ static void phylink_resolve_an_pause(str
+       }
+ }
++/**
++ * phylink_release_pcs - Removes a PCS from the phylink PCS available list
++ * @pcs: a pointer to the phylink_pcs struct to be released
++ *
++ * This function release a PCS from the phylink PCS available list if
++ * actually in use. It also refreshes the supported interfaces of the
++ * phylink instance by copying the supported interfaces from the phylink
++ * conf and merging the supported interfaces of the remaining available PCS
++ * in the list and trigger a resolve.
++ */
++void phylink_release_pcs(struct phylink_pcs *pcs)
++{
++      struct phylink *pl;
++
++      ASSERT_RTNL();
++
++      pl = pcs->phylink;
++      if (!pl)
++              return;
++
++      list_del(&pcs->list);
++      pcs->phylink = NULL;
++
++      /* Check if we are removing the PCS currently
++       * in use by phylink. If this is the case,
++       * force phylink resolve to reconfigure the interface
++       * mode and set the phylink PCS to NULL.
++       */
++      if (pl->pcs == pcs) {
++              mutex_lock(&pl->state_mutex);
++
++              pl->reconfig_interface = true;
++              pl->pcs = NULL;
++
++              mutex_unlock(&pl->state_mutex);
++      }
++
++      /* Refresh supported interfaces */
++      phy_interface_copy(pl->supported_interfaces,
++                         pl->config->supported_interfaces);
++      list_for_each_entry(pcs, &pl->pcs_list, list)
++              phy_interface_or(pl->supported_interfaces,
++                               pl->supported_interfaces,
++                               pcs->supported_interfaces);
++
++      phylink_run_resolve(pl);
++}
++EXPORT_SYMBOL_GPL(phylink_release_pcs);
++
+ static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
+                                   phy_interface_t interface)
+ {
+@@ -1818,6 +1868,10 @@ static void phylink_resolve(struct work_
+               if (pl->phydev)
+                       link_state.link &= pl->phy_state.link;
++              /* Force mac_config if we need to reconfig the interface */
++              if (pl->reconfig_interface)
++                      mac_config = true;
++
+               /* Only update if the PHY link is up */
+               if (pl->phydev && pl->phy_state.link) {
+                       /* If the interface has changed, force a link down
+@@ -1852,7 +1906,7 @@ static void phylink_resolve(struct work_
+               phylink_apply_manual_flow(pl, &link_state);
+       if ((mac_config && link_state.interface != pl->link_config.interface) ||
+-          pl->force_major_config) {
++          pl->force_major_config || pl->reconfig_interface) {
+               /* The interface has changed or a forced major configuration
+                * was requested, so force the link down and then reconfigure.
+                */
+@@ -1863,6 +1917,7 @@ static void phylink_resolve(struct work_
+               phylink_major_config(pl, false, &link_state);
+               pl->link_config.interface = link_state.interface;
+               pl->force_major_config = false;
++              pl->reconfig_interface = false;
+       }
+       /* If configuration of the interface failed, force the link down
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -632,6 +632,8 @@ void phylink_disconnect_phy(struct phyli
+ int phylink_set_fixed_link(struct phylink *,
+                          const struct phylink_link_state *);
++void phylink_release_pcs(struct phylink_pcs *pcs);
++
+ void phylink_mac_change(struct phylink *, bool up);
+ void phylink_pcs_change(struct phylink_pcs *, bool up);
diff --git a/target/linux/generic/pending-6.12/770-04-net-pcs-implement-Firmware-node-support-for-PCS-driv.patch b/target/linux/generic/pending-6.12/770-04-net-pcs-implement-Firmware-node-support-for-PCS-driv.patch
new file mode 100644 (file)
index 0000000..676e23c
--- /dev/null
@@ -0,0 +1,384 @@
+From a90c644c73bbffd400cd3839fc17ffdfc69ea1e8 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Mar 2025 01:46:53 +0100
+Subject: [PATCH 4/7] net: pcs: implement Firmware node support for PCS driver
+
+Implement the foundation of Firmware node support for PCS driver.
+
+To support this, implement a simple Provider API where a PCS driver can
+expose multiple PCS with an xlate .get function.
+
+PCS driver will have to call fwnode_pcs_add_provider() and pass the
+firmware node pointer and a xlate function to return the correct PCS for
+the passed #pcs-cells.
+
+This will register the PCS in a global list of providers so that
+consumer can access it.
+
+Consumer will then use fwnode_pcs_get() to get the actual PCS by passing
+the firmware node pointer and the index for #pcs-cells.
+
+For simple implementation where #pcs-cells is 0 and the PCS driver
+expose a single PCS, the xlate function fwnode_pcs_simple_get() is
+provided.
+
+For advanced implementation a custom xlate function is required.
+
+PCS driver on removal should first delete as a provider with
+the usage of fwnode_pcs_del_provider() and then call
+phylink_release_pcs() on every PCS the driver provides and
+
+A generic function fwnode_phylink_pcs_parse() is provided for any MAC
+driver that will declare PCS in DT (or ACPI).
+This function will parse "pcs-handle" property and fill the passed array
+with the parsed PCS in availabel_pcs up to the passed num_pcs value.
+It's also possible to pass NULL as array to only parse the PCS and
+update the num_pcs value with the count of scanned PCS.
+
+Co-developed-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/pcs/Kconfig          |   7 ++
+ drivers/net/pcs/Makefile         |   1 +
+ drivers/net/pcs/pcs.c            | 201 +++++++++++++++++++++++++++++++
+ include/linux/pcs/pcs-provider.h |  41 +++++++
+ include/linux/pcs/pcs.h          |  56 +++++++++
+ 5 files changed, 306 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs.c
+ create mode 100644 include/linux/pcs/pcs-provider.h
+ create mode 100644 include/linux/pcs/pcs.h
+
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -5,6 +5,13 @@
+ menu "PCS device drivers"
++config FWNODE_PCS
++      tristate
++      depends on (ACPI || OF)
++      depends on PHYLINK
++      help
++              Firmware node PCS accessors
++
+ config PCS_XPCS
+       tristate "Synopsys DesignWare Ethernet XPCS"
+       select PHYLINK
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0
+ # Makefile for Linux PCS drivers
++obj-$(CONFIG_FWNODE_PCS)      += pcs.o
+ pcs_xpcs-$(CONFIG_PCS_XPCS)   := pcs-xpcs.o pcs-xpcs-plat.o \
+                                  pcs-xpcs-nxp.o pcs-xpcs-wx.o
+--- /dev/null
++++ b/drivers/net/pcs/pcs.c
+@@ -0,0 +1,201 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++
++#include <linux/mutex.h>
++#include <linux/property.h>
++#include <linux/phylink.h>
++#include <linux/pcs/pcs.h>
++#include <linux/pcs/pcs-provider.h>
++
++MODULE_DESCRIPTION("PCS library");
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_LICENSE("GPL");
++
++struct fwnode_pcs_provider {
++      struct list_head link;
++
++      struct fwnode_handle *fwnode;
++      struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
++                                 void *data);
++
++      void *data;
++};
++
++static LIST_HEAD(fwnode_pcs_providers);
++static DEFINE_MUTEX(fwnode_pcs_mutex);
++
++struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
++                                        void *data)
++{
++      return data;
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_simple_get);
++
++int fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
++                          struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
++                                                     void *data),
++                          void *data)
++{
++      struct fwnode_pcs_provider *pp;
++
++      if (!fwnode)
++              return 0;
++
++      pp = kzalloc(sizeof(*pp), GFP_KERNEL);
++      if (!pp)
++              return -ENOMEM;
++
++      pp->fwnode = fwnode_handle_get(fwnode);
++      pp->data = data;
++      pp->get = get;
++
++      mutex_lock(&fwnode_pcs_mutex);
++      list_add(&pp->link, &fwnode_pcs_providers);
++      mutex_unlock(&fwnode_pcs_mutex);
++      pr_debug("Added pcs provider from %pfwf\n", fwnode);
++
++      fwnode_dev_initialized(fwnode, true);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider);
++
++void fwnode_pcs_del_provider(struct fwnode_handle *fwnode)
++{
++      struct fwnode_pcs_provider *pp;
++
++      if (!fwnode)
++              return;
++
++      mutex_lock(&fwnode_pcs_mutex);
++      list_for_each_entry(pp, &fwnode_pcs_providers, link) {
++              if (pp->fwnode == fwnode) {
++                      list_del(&pp->link);
++                      fwnode_dev_initialized(pp->fwnode, false);
++                      fwnode_handle_put(pp->fwnode);
++                      kfree(pp);
++                      break;
++              }
++      }
++      mutex_unlock(&fwnode_pcs_mutex);
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_del_provider);
++
++static int fwnode_parse_pcsspec(const struct fwnode_handle *fwnode, int index,
++                              const char *name,
++                              struct fwnode_reference_args *out_args)
++{
++      int ret = -ENOENT;
++
++      if (!fwnode)
++              return -ENOENT;
++
++      if (name)
++              index = fwnode_property_match_string(fwnode, "pcs-names",
++                                                   name);
++
++      ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
++                                               "#pcs-cells",
++                                               -1, index, out_args);
++      if (ret || (name && index < 0))
++              return ret;
++
++      return 0;
++}
++
++static struct phylink_pcs *
++fwnode_pcs_get_from_pcsspec(struct fwnode_reference_args *pcsspec)
++{
++      struct fwnode_pcs_provider *provider;
++      struct phylink_pcs *pcs = ERR_PTR(-EPROBE_DEFER);
++
++      if (!pcsspec)
++              return ERR_PTR(-EINVAL);
++
++      mutex_lock(&fwnode_pcs_mutex);
++      list_for_each_entry(provider, &fwnode_pcs_providers, link) {
++              if (provider->fwnode == pcsspec->fwnode) {
++                      pcs = provider->get(pcsspec, provider->data);
++                      if (!IS_ERR(pcs))
++                              break;
++              }
++      }
++      mutex_unlock(&fwnode_pcs_mutex);
++
++      return pcs;
++}
++
++static struct phylink_pcs *__fwnode_pcs_get(struct fwnode_handle *fwnode,
++                                          int index, const char *con_id)
++{
++      struct fwnode_reference_args pcsspec;
++      struct phylink_pcs *pcs;
++      int ret;
++
++      ret = fwnode_parse_pcsspec(fwnode, index, con_id, &pcsspec);
++      if (ret)
++              return ERR_PTR(ret);
++
++      pcs = fwnode_pcs_get_from_pcsspec(&pcsspec);
++      fwnode_handle_put(pcsspec.fwnode);
++
++      return pcs;
++}
++
++struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode, int index)
++{
++      return __fwnode_pcs_get(fwnode, index, NULL);
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_get);
++
++static int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode,
++                                  unsigned int *num_pcs)
++{
++      struct fwnode_reference_args out_args;
++      int index = 0;
++      int ret;
++
++      while (true) {
++              ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
++                                                       "#pcs-cells",
++                                                       -1, index, &out_args);
++              /* We expect to reach an -ENOENT error while counting */
++              if (ret)
++                      break;
++
++              fwnode_handle_put(out_args.fwnode);
++              index++;
++      }
++
++      /* Update num_pcs with parsed PCS */
++      *num_pcs = index;
++
++      /* Return error if we didn't found any PCS */
++      return index > 0 ? 0 : -ENOENT;
++}
++
++int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
++                           struct phylink_pcs **available_pcs,
++                           unsigned int *num_pcs)
++{
++      int i;
++
++      if (!fwnode_property_present(fwnode, "pcs-handle"))
++              return -ENODEV;
++
++      /* With available_pcs NULL, only count the PCS */
++      if (!available_pcs)
++              return fwnode_phylink_pcs_count(fwnode, num_pcs);
++
++      for (i = 0; i < *num_pcs; i++) {
++              struct phylink_pcs *pcs;
++
++              pcs = fwnode_pcs_get(fwnode, i);
++              if (IS_ERR(pcs))
++                      return PTR_ERR(pcs);
++
++              available_pcs[i] = pcs;
++      }
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_parse);
+--- /dev/null
++++ b/include/linux/pcs/pcs-provider.h
+@@ -0,0 +1,41 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++#ifndef __LINUX_PCS_PROVIDER_H
++#define __LINUX_PCS_PROVIDER_H
++
++/**
++ * fwnode_pcs_simple_get - Simple xlate function to retrieve PCS
++ * @pcsspec: reference arguments
++ * @data: Context data (assumed assigned to the single PCS)
++ *
++ * Returns the PCS. (pointed by data)
++ */
++struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
++                                        void *data);
++
++/**
++ * fwnode_pcs_add_provider - Registers a new PCS provider
++ * @np: Firmware node
++ * @get: xlate function to retrieve the PCS
++ * @data: Context data
++ *
++ * Register and add a new PCS to the global providers list
++ * for the firmware node. A function to get the PCS from
++ * firmware node with the use fwnode reference arguments.
++ * To the get function is also passed the interface type
++ * requested for the PHY. PCS driver will use the passed
++ * interface to understand if the PCS can support it or not.
++ *
++ * Returns 0 on success or -ENOMEM on allocation failure.
++ */
++int fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
++                          struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
++                                                     void *data),
++                          void *data);
++
++/**
++ * fwnode_pcs_del_provider - Removes a PCS provider
++ * @fwnode: Firmware node
++ */
++void fwnode_pcs_del_provider(struct fwnode_handle *fwnode);
++
++#endif /* __LINUX_PCS_PROVIDER_H */
+--- /dev/null
++++ b/include/linux/pcs/pcs.h
+@@ -0,0 +1,56 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++#ifndef __LINUX_PCS_H
++#define __LINUX_PCS_H
++
++#include <linux/phylink.h>
++
++#if IS_ENABLED(CONFIG_FWNODE_PCS)
++/**
++ * fwnode_pcs_get - Retrieves a PCS from a firmware node
++ * @fwnode: firmware node
++ * @index: index fwnode PCS handle in firmware node
++ *
++ * Get a PCS from the firmware node at index.
++ *
++ * Returns a pointer to the phylink_pcs or a negative
++ * error pointer. Can return -EPROBE_DEFER if the PCS is not
++ * present in global providers list (either due to driver
++ * still needs to be probed or it failed to probe/removed)
++ */
++struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
++                                 int index);
++
++/**
++ * fwnode_phylink_pcs_parse - generic PCS parse for fwnode PCS provider
++ * @fwnode: firmware node
++ * @available_pcs: pointer to preallocated array of PCS
++ * @num_pcs: where to store count of parsed PCS
++ *
++ * Generic helper function to fill available_pcs array with PCS parsed
++ * from a "pcs-handle" fwnode property defined in firmware node up to
++ * passed num_pcs.
++ *
++ * If available_pcs is NULL, num_pcs is updated with the count of the
++ * parsed PCS.
++ *
++ * Returns 0 or a negative error.
++ */
++int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
++                           struct phylink_pcs **available_pcs,
++                           unsigned int *num_pcs);
++#else
++static inline struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
++                                               int index)
++{
++      return ERR_PTR(-ENOENT);
++}
++
++static inline int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
++                                         struct phylink_pcs **available_pcs,
++                                         unsigned int *num_pcs)
++{
++      return -EOPNOTSUPP;
++}
++#endif
++
++#endif /* __LINUX_PCS_H */
diff --git a/target/linux/generic/pending-6.12/770-05-net-phylink-support-late-PCS-provider-attach.patch b/target/linux/generic/pending-6.12/770-05-net-phylink-support-late-PCS-provider-attach.patch
new file mode 100644 (file)
index 0000000..d54779c
--- /dev/null
@@ -0,0 +1,254 @@
+From 684e49a015f2c5ae95ba968bb21ffc8fc36a2c7f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 6 Apr 2025 03:28:22 +0200
+Subject: [PATCH 5/7] net: phylink: support late PCS provider attach
+
+Add support in phylink for late PCS provider attach to a phylink
+instance. This works by creating a global notifier for the PCS provider
+and making each phylink instance that makes use of fwnode subscribe to
+this notifier.
+
+The PCS notifier will emit the event FWNODE_PCS_PROVIDER_ADD every time
+a new PCS provider is added.
+
+phylink will then react to this event and will call the new function
+fwnode_phylink_pcs_get_from_fwnode() that will check if the PCS fwnode
+provided by the event is present in the phy-handle property of the
+phylink instance.
+
+If a related PCS is found, then such PCS is added to the phylink
+instance PCS list.
+
+Then we link the PCS to the phylink instance if it's not disable and we
+refresh the supported interfaces of the phylink instance.
+
+Finally we check if we are in a major_config_failed scenario and trigger
+an interface reconfiguration in the next phylink resolve.
+
+If link was previously torn down due to removal of PCS, the link will be
+established again as the PCS came back and is not available to phylink.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/pcs/pcs.c     | 34 +++++++++++++++++++++++++
+ drivers/net/phy/phylink.c | 52 +++++++++++++++++++++++++++++++++++++++
+ include/linux/pcs/pcs.h   | 48 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 134 insertions(+)
+
+--- a/drivers/net/pcs/pcs.c
++++ b/drivers/net/pcs/pcs.c
+@@ -22,6 +22,13 @@ struct fwnode_pcs_provider {
+ static LIST_HEAD(fwnode_pcs_providers);
+ static DEFINE_MUTEX(fwnode_pcs_mutex);
++static BLOCKING_NOTIFIER_HEAD(fwnode_pcs_notify_list);
++
++int register_fwnode_pcs_notifier(struct notifier_block *nb)
++{
++      return blocking_notifier_chain_register(&fwnode_pcs_notify_list, nb);
++}
++EXPORT_SYMBOL_GPL(register_fwnode_pcs_notifier);
+ struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
+                                         void *data)
+@@ -55,6 +62,10 @@ int fwnode_pcs_add_provider(struct fwnod
+       fwnode_dev_initialized(fwnode, true);
++      blocking_notifier_call_chain(&fwnode_pcs_notify_list,
++                                   FWNODE_PCS_PROVIDER_ADD,
++                                   fwnode);
++
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider);
+@@ -147,6 +158,29 @@ struct phylink_pcs *fwnode_pcs_get(struc
+ }
+ EXPORT_SYMBOL_GPL(fwnode_pcs_get);
++struct phylink_pcs *
++fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
++                                 struct fwnode_handle *pcs_fwnode)
++{
++      struct fwnode_reference_args pcsspec;
++      int i = 0;
++      int ret;
++
++      while (true) {
++              ret = fwnode_parse_pcsspec(fwnode, i, NULL, &pcsspec);
++              if (ret)
++                      break;
++
++              if (pcsspec.fwnode == pcs_fwnode)
++                      break;
++
++              i++;
++      }
++
++      return fwnode_pcs_get(fwnode, i);
++}
++EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_get_from_fwnode);
++
+ static int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode,
+                                   unsigned int *num_pcs)
+ {
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -12,6 +12,7 @@
+ #include <linux/netdevice.h>
+ #include <linux/of.h>
+ #include <linux/of_mdio.h>
++#include <linux/pcs/pcs.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
+ #include <linux/phylink.h>
+@@ -67,6 +68,7 @@ struct phylink {
+       /* List of available PCS */
+       struct list_head pcs_list;
++      struct notifier_block fwnode_pcs_nb;
+       /* What interface are supported by the current link.
+        * Can change on removal or addition of new PCS.
+@@ -2039,6 +2041,51 @@ int phylink_set_fixed_link(struct phylin
+ }
+ EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
++static int pcs_provider_notify(struct notifier_block *self,
++                             unsigned long val, void *data)
++{
++      struct phylink *pl = container_of(self, struct phylink, fwnode_pcs_nb);
++      struct fwnode_handle *pcs_fwnode = data;
++      struct phylink_pcs *pcs;
++
++      /* Check if the just added PCS provider is
++       * in the phylink instance phy-handle property
++       */
++      pcs = fwnode_phylink_pcs_get_from_fwnode(dev_fwnode(pl->config->dev),
++                                               pcs_fwnode);
++      if (IS_ERR(pcs))
++              return NOTIFY_DONE;
++
++      /* Add the PCS */
++      rtnl_lock();
++
++      list_add(&pcs->list, &pl->pcs_list);
++
++      /* Link phylink if we are started */
++      if (!pl->phylink_disable_state)
++              pcs->phylink = pl;
++
++      /* Refresh supported interfaces */
++      phy_interface_copy(pl->supported_interfaces,
++                         pl->config->supported_interfaces);
++      list_for_each_entry(pcs, &pl->pcs_list, list)
++              phy_interface_or(pl->supported_interfaces,
++                               pl->supported_interfaces,
++                               pcs->supported_interfaces);
++
++      mutex_lock(&pl->state_mutex);
++      /* Force an interface reconfig if major config fail */
++      if (pl->major_config_failed)
++              pl->reconfig_interface = true;
++      mutex_unlock(&pl->state_mutex);
++
++      rtnl_unlock();
++
++      phylink_run_resolve(pl);
++
++      return NOTIFY_OK;
++}
++
+ /**
+  * phylink_create() - create a phylink instance
+  * @config: a pointer to the target &struct phylink_config
+@@ -2099,6 +2146,11 @@ struct phylink *phylink_create(struct ph
+                                pl->supported_interfaces,
+                                pcs->supported_interfaces);
++      if (!phy_interface_empty(config->pcs_interfaces)) {
++              pl->fwnode_pcs_nb.notifier_call = pcs_provider_notify;
++              register_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
++      }
++
+       pl->config = config;
+       if (config->type == PHYLINK_NETDEV) {
+               pl->netdev = to_net_dev(config->dev);
+--- a/include/linux/pcs/pcs.h
++++ b/include/linux/pcs/pcs.h
+@@ -4,8 +4,25 @@
+ #include <linux/phylink.h>
++enum fwnode_pcs_notify_event {
++      FWNODE_PCS_PROVIDER_ADD,
++};
++
+ #if IS_ENABLED(CONFIG_FWNODE_PCS)
+ /**
++ * register_fwnode_pcs_notifier - Register a notifier block for fwnode
++ *                              PCS events
++ * @nb: pointer to the notifier block
++ *
++ * Registers a notifier block to the fwnode_pcs_notify_list blocking
++ * notifier chain. This allows phylink instance to subscribe for
++ * PCS provider events.
++ *
++ * Returns 0 or a negative error.
++ */
++int register_fwnode_pcs_notifier(struct notifier_block *nb);
++
++/**
+  * fwnode_pcs_get - Retrieves a PCS from a firmware node
+  * @fwnode: firmware node
+  * @index: index fwnode PCS handle in firmware node
+@@ -21,6 +38,25 @@ struct phylink_pcs *fwnode_pcs_get(struc
+                                  int index);
+ /**
++ * fwnode_phylink_pcs_get_from_fwnode - Retrieves the PCS provided
++ *                                    by the firmware node from a
++ *                                    firmware node
++ * @fwnode: firmware node
++ * @pcs_fwnode: PCS firmware node
++ *
++ * Parse 'pcs-handle' in 'fwnode' and get the PCS that match
++ * 'pcs_fwnode' firmware node.
++ *
++ * Returns a pointer to the phylink_pcs or a negative
++ * error pointer. Can return -EPROBE_DEFER if the PCS is not
++ * present in global providers list (either due to driver
++ * still needs to be probed or it failed to probe/removed)
++ */
++struct phylink_pcs *
++fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
++                                 struct fwnode_handle *pcs_fwnode);
++
++/**
+  * fwnode_phylink_pcs_parse - generic PCS parse for fwnode PCS provider
+  * @fwnode: firmware node
+  * @available_pcs: pointer to preallocated array of PCS
+@@ -39,11 +75,23 @@ int fwnode_phylink_pcs_parse(struct fwno
+                            struct phylink_pcs **available_pcs,
+                            unsigned int *num_pcs);
+ #else
++static inline int register_fwnode_pcs_notifier(struct notifier_block *nb)
++{
++      return -EOPNOTSUPP;
++}
++
+ static inline struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
+                                                int index)
+ {
+       return ERR_PTR(-ENOENT);
+ }
++
++static inline struct phylink_pcs *
++fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
++                                 struct fwnode_handle *pcs_fwnode)
++{
++      return ERR_PTR(-ENOENT);
++}
+ static inline int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+                                          struct phylink_pcs **available_pcs,
diff --git a/target/linux/generic/pending-6.12/770-06-dt-bindings-net-ethernet-controller-permit-to-define.patch b/target/linux/generic/pending-6.12/770-06-dt-bindings-net-ethernet-controller-permit-to-define.patch
new file mode 100644 (file)
index 0000000..4e43331
--- /dev/null
@@ -0,0 +1,29 @@
+From c5d151dccce7deb62620a7b16418c0d6d6a59720 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Mar 2025 23:07:45 +0100
+Subject: [PATCH 6/7] dt-bindings: net: ethernet-controller: permit to define
+ multiple PCS
+
+Drop the limitation of a single PCS in pcs-handle property. Multiple PCS
+can be defined for an ethrnet-controller node to support various PHY
+interface mode type.
+
+It's very common for SoCs to have a dedicated PCS for SGMII mode and one
+for USXGMII mode.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ Documentation/devicetree/bindings/net/ethernet-controller.yaml | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/Documentation/devicetree/bindings/net/ethernet-controller.yaml
++++ b/Documentation/devicetree/bindings/net/ethernet-controller.yaml
+@@ -110,8 +110,6 @@ properties:
+   pcs-handle:
+     $ref: /schemas/types.yaml#/definitions/phandle-array
+-    items:
+-      maxItems: 1
+     description:
+       Specifies a reference to a node representing a PCS PHY device on a MDIO
+       bus to link with an external PHY (phy-handle) if exists.
similarity index 69%
rename from target/linux/airoha/patches-6.12/310-08-net-phylink-add-.pcs_link_down-PCS-OP.patch
rename to target/linux/generic/pending-6.12/770-07-net-phylink-add-.pcs_link_down-PCS-OP.patch
index 34e8d21ebd83453e48a2ab6a0d66d35621f45cfd..648aa8322085b1e62d55604c50fad9d4894c81a5 100644 (file)
@@ -1,7 +1,7 @@
-From d5fb4ad1beec53ca5d3b44d9b88598ed4ab0b34d Mon Sep 17 00:00:00 2001
+From 4b1dde131a237455e41985fdc95306cd2f1b8a0a Mon Sep 17 00:00:00 2001
 From: Christian Marangi <ansuelsmth@gmail.com>
 Date: Fri, 9 May 2025 16:36:22 +0200
-Subject: [PATCH 1/6] net: phylink: add .pcs_link_down PCS OP
+Subject: [PATCH 7/7] net: phylink: add .pcs_link_down PCS OP
 
 Permit for PCS driver to define specific operation to torn down the link
 between the MAC and the PCS.
@@ -22,8 +22,8 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 
 --- a/drivers/net/phy/phylink.c
 +++ b/drivers/net/phy/phylink.c
-@@ -1088,6 +1088,12 @@ static unsigned int phylink_inband_caps(
-       return phylink_pcs_inband_caps(pcs, interface);
+@@ -1178,6 +1178,12 @@ static void phylink_pcs_link_up(struct p
+               pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
  }
  
 +static void phylink_pcs_link_down(struct phylink_pcs *pcs)
@@ -32,10 +32,10 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 +              pcs->ops->pcs_link_down(pcs);
 +}
 +
- static void phylink_pcs_poll_stop(struct phylink *pl)
- {
-       if (pl->cfg_link_an_mode == MLO_AN_INBAND)
-@@ -1651,6 +1657,8 @@ static void phylink_link_down(struct phy
+ /* Query inband for a specific interface mode, asking the MAC for the
+  * PCS which will be used to handle the interface mode.
+  */
+@@ -1817,6 +1823,8 @@ static void phylink_link_down(struct phy
  
        if (ndev)
                netif_carrier_off(ndev);
@@ -46,19 +46,19 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
        phylink_info(pl, "Link is Down\n");
 --- a/include/linux/phylink.h
 +++ b/include/linux/phylink.h
-@@ -431,6 +431,7 @@ struct phylink_pcs {
+@@ -443,6 +443,7 @@ struct phylink_pcs {
+  * @pcs_an_restart: restart 802.3z BaseX autonegotiation.
+  * @pcs_link_up: program the PCS for the resolved link configuration
   *               (where necessary).
++ * @pcs_link_down: torn down link between MAC and PCS.
   * @pcs_pre_init: configure PCS components necessary for MAC hardware
   *                initialization e.g. RX clock for stmmac.
-+ * @pcs_link_down: torn down link between MAC and PCS.
   */
- struct phylink_pcs_ops {
-       int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
-@@ -453,6 +454,7 @@ struct phylink_pcs_ops {
+@@ -466,6 +467,7 @@ struct phylink_pcs_ops {
+       void (*pcs_an_restart)(struct phylink_pcs *pcs);
        void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int neg_mode,
                            phy_interface_t interface, int speed, int duplex);
-       int (*pcs_pre_init)(struct phylink_pcs *pcs);
 +      void (*pcs_link_down)(struct phylink_pcs *pcs);
+       int (*pcs_pre_init)(struct phylink_pcs *pcs);
  };
  
- #if 0 /* For kernel-doc purposes only. */
index dc63c7e3dd52fde147a517fb893dc667ce51faaa..e44f651c25e0e282eb062753eb45658c36d7351b 100644 (file)
@@ -36,7 +36,7 @@ Signed-off-by: Bhaskar Upadhaya <Bhaskar.Upadhaya@nxp.com>
        case PHY_INTERFACE_MODE_QUSGMII:
 --- a/drivers/net/phy/phylink.c
 +++ b/drivers/net/phy/phylink.c
-@@ -250,6 +250,7 @@ static int phylink_interface_max_speed(p
+@@ -266,6 +266,7 @@ static int phylink_interface_max_speed(p
        case PHY_INTERFACE_MODE_GMII:
                return SPEED_1000;
  
@@ -44,7 +44,7 @@ Signed-off-by: Bhaskar Upadhaya <Bhaskar.Upadhaya@nxp.com>
        case PHY_INTERFACE_MODE_2500BASEX:
        case PHY_INTERFACE_MODE_10G_QXGMII:
                return SPEED_2500;
-@@ -564,6 +565,7 @@ static unsigned long phylink_get_capabil
+@@ -580,6 +581,7 @@ static unsigned long phylink_get_capabil
                break;
  
        case PHY_INTERFACE_MODE_2500BASEX: