[RFC PATCH 01/10] net: stmmac: move XPCS lifetime management to platform drivers
From: Coia Prant <hidden>
Date: 2026-07-14 19:14:04
Also in:
linux-arm-kernel, linux-devicetree, linux-phy, linux-rockchip
Subsystem:
arm/socfpga dwmac glue layer, networking drivers, renesas rz/n1 dwmac glue layer, renesas rz/v2h(p) dwmac gbeth glue layer driver, stmmac ethernet driver, the rest · Maintainers:
Maxime Chevallier, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Romain Gantois, Lad Prabhakar, Linus Torvalds
The current XPCS creation logic in stmmac_pcs_setup() is problematic
for several reasons.
First, if a device tree specifies a "pcs-handle" but no select_pcs()
callback is provided by the platform driver, the created XPCS is never
used. The phylink framework requires select_pcs() to actually return
the PCS to the core, so the pcs-handle property becomes effectively
useless without the matching callback. This is confusing for developers
who expect that specifying a pcs-handle in their device tree should be
sufficient to enable the PCS.
Second, and more critically, when stmmac_pcs_setup() fails to create
an XPCS (either because no pcs-handle is present and no pcs_mask is
configured), it falls through to the else branch and leaves
priv->hw->xpcs as NULL. This will silently override any XPCS that a
platform driver may have already set up during its own initialization,
for example in a pcs_init() callback or during probe. The platform
driver has no way to prevent this override because the common code
runs unconditionally after the platform-specific initialization.
After commit 93f84152e4ae ("net: stmmac: clean up
stmmac_mac_select_pcs()"), the common code no longer falls back to
priv->hw->phylink_pcs if select_pcs() is not set. This change
reinforces that each platform must manage its own PCS life cycle
explicitly, but the XPCS creation code in stmmac_pcs_setup() was not
updated to match this new expectation, leaving a gap where platform
drivers have no clean way to take control of XPCS creation.
Address all of these issues by introducing pcs_init() and pcs_exit()
callbacks in plat_stmmacenet_data. These callbacks give platform
drivers full control over when and how the XPCS is created, configured,
and destroyed. The common stmmac_pcs_setup() and stmmac_pcs_clean()
functions are simplified to just call these callbacks, removing the
confusing and error-prone XPCS creation logic from the common code.
Platforms that do not need an XPCS simply leave the callbacks as NULL
and no change in behavior occurs. Platforms that do need an XPCS can
now create it with the exact configuration they require, including
wrapping it with custom phylink_pcs_ops when necessary.
Existing platform drivers (intel, rzn1, socfpga) are updated to use
the new callbacks by moving their XPCS creation and cleanup logic into
pcs_init() and pcs_exit(). In their pcs_exit() implementations, the
pointer to the destroyed PCS is explicitly set to NULL to avoid
dangling pointer references.
Signed-off-by: Coia Prant <redacted>
---
.../net/ethernet/stmicro/stmmac/dwmac-intel.c | 44 +++++++++++++++++--
.../stmicro/stmmac/dwmac-renesas-gbeth.c | 7 ++-
.../net/ethernet/stmicro/stmmac/dwmac-rzn1.c | 7 ++-
.../ethernet/stmicro/stmmac/dwmac-socfpga.c | 7 ++-
.../net/ethernet/stmicro/stmmac/stmmac_mdio.c | 37 +++-------------
5 files changed, 61 insertions(+), 41 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
index b8d467ba6d72d..081323c32bcc1 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c@@ -572,13 +572,47 @@ static void common_default_data(struct plat_stmmacenet_data *plat) plat->mdio_bus_data->needs_reset = true; } +static int intel_mgbe_pcs_init(struct stmmac_priv *priv) +{ + struct fwnode_handle *devnode, *pcsnode; + struct dw_xpcs *xpcs = NULL; + int addr; + + devnode = dev_fwnode(priv->device); + + if (fwnode_property_present(devnode, "pcs-handle")) { + pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0); + xpcs = xpcs_create_fwnode(pcsnode); + fwnode_handle_put(pcsnode); + } else { + addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1; + xpcs = xpcs_create_mdiodev(priv->mii, addr); + } + + if (IS_ERR(xpcs)) + return PTR_ERR(xpcs); + + xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns); + + priv->hw->xpcs = xpcs; + return 0; +} + +static void intel_mgbe_pcs_exit(struct stmmac_priv *priv) +{ + if (!priv->hw->xpcs) + return; + + xpcs_destroy(priv->hw->xpcs); + priv->hw->xpcs = NULL; +} + static struct phylink_pcs *intel_mgbe_select_pcs(struct stmmac_priv *priv, phy_interface_t interface) { - /* plat->mdio_bus_data->has_xpcs has been set true, so there - * should always be an XPCS. The original code would always - * return this if present. - */ + if (!priv->hw->xpcs) + return NULL; + return xpcs_to_phylink_pcs(priv->hw->xpcs); }
@@ -702,6 +736,8 @@ static int intel_mgbe_common_data(struct pci_dev *pdev, plat->phy_interface == PHY_INTERFACE_MODE_1000BASEX) { plat->mdio_bus_data->pcs_mask = BIT_U32(INTEL_MGBE_XPCS_ADDR); plat->default_an_inband = true; + plat->pcs_init = intel_mgbe_pcs_init; + plat->pcs_exit = intel_mgbe_pcs_exit; plat->select_pcs = intel_mgbe_select_pcs; }
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
index 19f34e18bfef2..9af32c26f9c14 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c@@ -81,8 +81,11 @@ static int renesas_gmac_pcs_init(struct stmmac_priv *priv) static void renesas_gmac_pcs_exit(struct stmmac_priv *priv) { - if (priv->hw->phylink_pcs) - miic_destroy(priv->hw->phylink_pcs); + if (!priv->hw->phylink_pcs) + return; + + miic_destroy(priv->hw->phylink_pcs); + priv->hw->phylink_pcs = NULL; } static struct phylink_pcs *renesas_gmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
index 13634965bc19a..01df4776edb3f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c@@ -35,8 +35,11 @@ static int rzn1_dwmac_pcs_init(struct stmmac_priv *priv) static void rzn1_dwmac_pcs_exit(struct stmmac_priv *priv) { - if (priv->hw->phylink_pcs) - miic_destroy(priv->hw->phylink_pcs); + if (!priv->hw->phylink_pcs) + return; + + miic_destroy(priv->hw->phylink_pcs); + priv->hw->phylink_pcs = NULL; } static struct phylink_pcs *rzn1_dwmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
index 1d7f0a57d2889..6d4bc1fe8f751 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c@@ -539,8 +539,11 @@ static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv) static void socfpga_dwmac_pcs_exit(struct stmmac_priv *priv) { - if (priv->hw->phylink_pcs) - lynx_pcs_destroy(priv->hw->phylink_pcs); + if (!priv->hw->phylink_pcs) + return; + + lynx_pcs_destroy(priv->hw->phylink_pcs); + priv->hw->phylink_pcs = NULL; } static struct phylink_pcs *socfpga_dwmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
index afe98ff5bdcb0..d2f77f0c223a7 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c@@ -426,36 +426,15 @@ int stmmac_mdio_reset(struct mii_bus *bus) int stmmac_pcs_setup(struct net_device *ndev) { struct stmmac_priv *priv = netdev_priv(ndev); - struct fwnode_handle *devnode, *pcsnode; - struct dw_xpcs *xpcs = NULL; - int addr, ret; - - devnode = dev_fwnode(priv->device); - - if (priv->plat->pcs_init) { - ret = priv->plat->pcs_init(priv); - } else if (fwnode_property_present(devnode, "pcs-handle")) { - pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0); - xpcs = xpcs_create_fwnode(pcsnode); - fwnode_handle_put(pcsnode); - ret = PTR_ERR_OR_ZERO(xpcs); - } else if (priv->plat->mdio_bus_data && - priv->plat->mdio_bus_data->pcs_mask) { - addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1; - xpcs = xpcs_create_mdiodev(priv->mii, addr); - ret = PTR_ERR_OR_ZERO(xpcs); - } else { + int ret; + + if (!priv->plat->pcs_init) return 0; - } + ret = priv->plat->pcs_init(priv); if (ret) return dev_err_probe(priv->device, ret, "No xPCS found\n"); - if (xpcs) - xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns); - - priv->hw->xpcs = xpcs; - return 0; }
@@ -463,14 +442,10 @@ void stmmac_pcs_clean(struct net_device *ndev) { struct stmmac_priv *priv = netdev_priv(ndev); - if (priv->plat->pcs_exit) - priv->plat->pcs_exit(priv); - - if (!priv->hw->xpcs) + if (!priv->plat->pcs_exit) return; - xpcs_destroy(priv->hw->xpcs); - priv->hw->xpcs = NULL; + priv->plat->pcs_exit(priv); } struct stmmac_clk_rate {
--
2.47.3