--- /dev/null
+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
+@@ -28,6 +28,7 @@ enum {
+ PHYLINK_DISABLE_STOPPED,
+ PHYLINK_DISABLE_LINK,
+ PHYLINK_DISABLE_MAC_WOL,
++ PHYLINK_DISABLE_REPLAY,
+
+ PCS_STATE_DOWN = 0,
+ PCS_STATE_STARTING,
+@@ -77,6 +78,7 @@ struct phylink {
+
+ bool link_failed;
+ bool suspend_link_up;
++ bool force_major_config;
+ bool major_config_failed;
+ bool mac_supports_eee_ops;
+ bool mac_supports_eee;
+@@ -1681,9 +1683,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);
+@@ -1691,6 +1694,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
+@@ -4273,6 +4277,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
+@@ -808,4 +808,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
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -2278,7 +2278,7 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2282,7 +2282,7 @@ int phylink_fwnode_phy_connect(struct ph
{
struct fwnode_handle *phy_fwnode;
struct phy_device *phy_dev;
/* Fixed links and 802.3z are handled without needing a PHY */
if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
-@@ -2308,6 +2308,25 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2312,6 +2312,25 @@ int phylink_fwnode_phy_connect(struct ph
if (pl->config->mac_requires_rxc)
flags |= PHY_F_RXC_ALWAYS_ON;
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -59,6 +59,11 @@ struct phylink {
+@@ -60,6 +60,11 @@ struct phylink {
/* The link configuration settings */
struct phylink_link_state link_config;
/* The current settings */
phy_interface_t cur_interface;
-@@ -623,7 +628,7 @@ static int phylink_validate_mask(struct
+@@ -625,7 +630,7 @@ static int phylink_validate_mask(struct
static int phylink_validate(struct phylink *pl, unsigned long *supported,
struct phylink_link_state *state)
{
if (state->interface == PHY_INTERFACE_MODE_NA)
return phylink_validate_mask(pl, NULL, supported, state,
-@@ -1853,6 +1858,9 @@ struct phylink *phylink_create(struct ph
+@@ -1857,6 +1862,9 @@ struct phylink *phylink_create(struct ph
mutex_init(&pl->state_mutex);
INIT_WORK(&pl->resolve, phylink_resolve);
pl->config = config;
if (config->type == PHYLINK_NETDEV) {
pl->netdev = to_net_dev(config->dev);
-@@ -2011,7 +2019,7 @@ static int phylink_validate_phy(struct p
+@@ -2015,7 +2023,7 @@ static int phylink_validate_phy(struct p
* those which the host supports.
*/
phy_interface_and(interfaces, phy->possible_interfaces,
if (phy_interface_empty(interfaces)) {
phylink_err(pl, "PHY has no common interfaces\n");
-@@ -2753,12 +2761,12 @@ static phy_interface_t phylink_sfp_selec
+@@ -2757,12 +2765,12 @@ static phy_interface_t phylink_sfp_selec
return interface;
}
return PHY_INTERFACE_MODE_NA;
}
-@@ -3686,14 +3694,14 @@ static int phylink_sfp_config_optical(st
+@@ -3690,14 +3698,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->sfp_interfaces);
if (phy_interface_empty(pl->sfp_interfaces)) {
phylink_err(pl, "unsupported SFP module: no common interface modes\n");
-@@ -3864,7 +3872,7 @@ static int phylink_sfp_connect_phy(void
+@@ -3868,7 +3876,7 @@ static int phylink_sfp_connect_phy(void
/* Set the PHY's host supported interfaces */
phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -59,6 +59,9 @@ struct phylink {
+@@ -60,6 +60,9 @@ 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.
*/
-@@ -149,6 +152,8 @@ static const phy_interface_t phylink_sfp
+@@ -151,6 +154,8 @@ static const phy_interface_t phylink_sfp
static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
/**
* phylink_set_port_modes() - set the port type modes in the ethtool mask
* @mask: ethtool link mode mask
-@@ -512,22 +517,59 @@ static void phylink_validate_mask_caps(u
+@@ -514,22 +519,59 @@ static void phylink_validate_mask_caps(u
linkmode_and(state->advertising, state->advertising, mask);
}
/* 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.
-@@ -539,13 +581,10 @@ static int phylink_validate_mac_and_pcs(
+@@ -541,13 +583,10 @@ static int phylink_validate_mac_and_pcs(
return -EINVAL;
}
phy_modes(state->interface));
return -EINVAL;
}
-@@ -959,12 +998,22 @@ static unsigned int phylink_inband_caps(
+@@ -961,12 +1000,22 @@ static unsigned int phylink_inband_caps(
phy_interface_t interface)
{
struct phylink_pcs *pcs;
return 0;
return phylink_pcs_inband_caps(pcs, interface);
-@@ -1259,10 +1308,36 @@ static void phylink_major_config(struct
+@@ -1261,10 +1310,36 @@ static void phylink_major_config(struct
pl->major_config_failed = true;
return;
}
phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
phylink_dbg(pl, "major config, active %s/%s/%s\n",
-@@ -1289,11 +1364,13 @@ static void phylink_major_config(struct
+@@ -1291,11 +1366,13 @@ static void phylink_major_config(struct
if (pcs_changed) {
phylink_pcs_disable(pl->pcs);
pl->pcs = pcs;
}
-@@ -1840,8 +1917,9 @@ struct phylink *phylink_create(struct ph
+@@ -1844,8 +1921,9 @@ struct phylink *phylink_create(struct ph
phy_interface_t iface,
const struct phylink_mac_ops *mac_ops)
{
/* Validate the supplied configuration */
if (phy_interface_empty(config->supported_interfaces)) {
-@@ -1857,9 +1935,21 @@ struct phylink *phylink_create(struct ph
+@@ -1861,9 +1939,21 @@ struct phylink *phylink_create(struct ph
mutex_init(&pl->phydev_mutex);
mutex_init(&pl->state_mutex);
INIT_WORK(&pl->resolve, phylink_resolve);
pl->config = config;
if (config->type == PHYLINK_NETDEV) {
-@@ -1938,10 +2028,16 @@ EXPORT_SYMBOL_GPL(phylink_create);
+@@ -1942,10 +2032,16 @@ EXPORT_SYMBOL_GPL(phylink_create);
*/
void phylink_destroy(struct phylink *pl)
{
cancel_work_sync(&pl->resolve);
kfree(pl);
}
-@@ -2443,6 +2539,7 @@ static irqreturn_t phylink_link_handler(
+@@ -2447,6 +2543,7 @@ static irqreturn_t phylink_link_handler(
*/
void phylink_start(struct phylink *pl)
{
bool poll = false;
ASSERT_RTNL();
-@@ -2469,6 +2566,10 @@ void phylink_start(struct phylink *pl)
+@@ -2473,6 +2570,10 @@ void phylink_start(struct phylink *pl)
pl->pcs_state = PCS_STATE_STARTED;
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
-@@ -2513,6 +2614,8 @@ EXPORT_SYMBOL_GPL(phylink_start);
+@@ -2517,6 +2618,8 @@ EXPORT_SYMBOL_GPL(phylink_start);
*/
void phylink_stop(struct phylink *pl)
{
ASSERT_RTNL();
if (pl->sfp_bus)
-@@ -2530,6 +2633,14 @@ void phylink_stop(struct phylink *pl)
+@@ -2534,6 +2637,14 @@ void phylink_stop(struct phylink *pl)
pl->pcs_state = PCS_STATE_DOWN;
phylink_pcs_disable(pl->pcs);
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -86,6 +86,7 @@ struct phylink {
- bool link_failed;
+@@ -88,6 +88,7 @@ struct phylink {
bool suspend_link_up;
+ bool force_major_config;
bool major_config_failed;
+ bool reconfig_interface;
bool mac_supports_eee_ops;
bool mac_supports_eee;
bool phy_enable_tx_lpi;
-@@ -917,6 +918,55 @@ static void phylink_resolve_an_pause(str
+@@ -919,6 +920,55 @@ static void phylink_resolve_an_pause(str
}
}
static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
-@@ -1730,6 +1780,10 @@ static void phylink_resolve(struct work_
+@@ -1732,6 +1782,10 @@ static void phylink_resolve(struct work_
if (phy)
link_state.link &= pl->phy_state.link;
/* Only update if the PHY link is up */
if (phy && pl->phy_state.link) {
/* If the interface has changed, force a link down
-@@ -1763,7 +1817,8 @@ static void phylink_resolve(struct work_
- if (pl->act_link_an_mode != MLO_AN_FIXED)
+@@ -1766,7 +1820,7 @@ static void phylink_resolve(struct work_
phylink_apply_manual_flow(pl, &link_state);
-- if (mac_config && link_state.interface != pl->link_config.interface) {
-+ if ((mac_config && link_state.interface != pl->link_config.interface) ||
-+ pl->reconfig_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) {
++ 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.
*/
-@@ -1773,6 +1828,7 @@ static void phylink_resolve(struct work_
- }
+@@ -1777,6 +1831,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;
}
#include <linux/phy.h>
#include <linux/phy_fixed.h>
#include <linux/phylink.h>
-@@ -61,6 +62,7 @@ struct phylink {
+@@ -62,6 +63,7 @@ struct phylink {
/* 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.
-@@ -1952,6 +1954,51 @@ int phylink_set_fixed_link(struct phylin
+@@ -1955,6 +1957,51 @@ int phylink_set_fixed_link(struct phylin
}
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
/**
* phylink_create() - create a phylink instance
* @config: a pointer to the target &struct phylink_config
-@@ -2007,6 +2054,11 @@ struct phylink *phylink_create(struct ph
+@@ -2010,6 +2057,11 @@ struct phylink *phylink_create(struct ph
pl->supported_interfaces,
pcs->supported_interfaces);
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -1031,6 +1031,12 @@ static void phylink_pcs_link_up(struct p
+@@ -1033,6 +1033,12 @@ static void phylink_pcs_link_up(struct p
pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
}
static void phylink_pcs_disable_eee(struct phylink_pcs *pcs)
{
if (pcs && pcs->ops->pcs_disable_eee)
-@@ -1723,6 +1729,8 @@ static void phylink_link_down(struct phy
+@@ -1725,6 +1731,8 @@ static void phylink_link_down(struct phy
phylink_deactivate_lpi(pl);