* [PATCH net v4 01/15] net: stmmac: unwind the WoL IRQ after a safety IRQ request failure
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
@ 2026-09-26 15:48 ` James Hilliard
2026-09-26 15:48 ` [PATCH net v4 02/15] net: stmmac: reuse the MDIO reset GPIO on resume James Hilliard
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:48 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
The IRQ setup paths request the MAC IRQ, then the optional WoL IRQ, then
the common safety IRQ. If requesting the safety IRQ fails, cleanup must
release the WoL and MAC IRQs, but not the failed safety IRQ.
The REQ_IRQ_ERR_SFTY case instead frees the safety IRQ and skips the WoL
IRQ. Allocation fault injection during live XDP reopening reproduces a
"Trying to free already-free IRQ" warning and leaves the WoL handler
registered after the datapath resources have been released. A subsequent
open can then fail to request that still-owned IRQ.
Move safety IRQ cleanup before REQ_IRQ_ERR_SFTY and WoL IRQ cleanup
after it. This restores reverse acquisition order for both shared and
MSI IRQ setup, including unwind after failures in later per-queue IRQ
requests.
Fixes: 5c2215167d12 ("net: stmmac: Add driver support for common safety IRQ")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 3f34d491c959..4f3d452c3503 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -3797,13 +3797,13 @@ static void stmmac_free_irq(struct net_device *dev,
free_irq(msi->sfty_ce_irq, dev);
fallthrough;
case REQ_IRQ_ERR_SFTY_CE:
- if (priv->wol_irq > 0 && priv->wol_irq != dev->irq)
- free_irq(priv->wol_irq, dev);
- fallthrough;
- case REQ_IRQ_ERR_SFTY:
if (priv->sfty_irq > 0 && priv->sfty_irq != dev->irq)
free_irq(priv->sfty_irq, dev);
fallthrough;
+ case REQ_IRQ_ERR_SFTY:
+ if (priv->wol_irq > 0 && priv->wol_irq != dev->irq)
+ free_irq(priv->wol_irq, dev);
+ fallthrough;
case REQ_IRQ_ERR_WOL:
free_irq(dev->irq, dev);
fallthrough;
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 02/15] net: stmmac: reuse the MDIO reset GPIO on resume
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
2026-09-26 15:48 ` [PATCH net v4 01/15] net: stmmac: unwind the WoL IRQ after a safety IRQ request failure James Hilliard
@ 2026-09-26 15:48 ` James Hilliard
2026-09-26 15:48 ` [PATCH net v4 03/15] net: phylink: allow stopping a suspended instance James Hilliard
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:48 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
MDIO bus registration acquires the optional snps,reset GPIO through
devres. A later bus reset must reuse that descriptor: requesting the
same exclusive GPIO again returns -EBUSY while devres still owns it.
Cache the successfully acquired descriptor for subsequent resets.
Propagate acquisition errors without caching an error pointer.
Fixes: 7c86f20d15b7 ("net: stmmac: use GPIO descriptors in stmmac_mdio_reset")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 +
drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c | 18 ++++++++++++------
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 7582fca63741..c7c876408d85 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -287,6 +287,7 @@ struct stmmac_priv {
unsigned int pause_time;
struct mii_bus *mii;
+ struct gpio_desc *mdio_reset_gpio;
struct stmmac_pcs *integrated_pcs;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
index afe98ff5bdcb..07d5eab05996 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
@@ -386,14 +386,20 @@ int stmmac_mdio_reset(struct mii_bus *bus)
#ifdef CONFIG_OF
if (priv->device->of_node) {
- struct gpio_desc *reset_gpio;
+ struct gpio_desc *reset_gpio = priv->mdio_reset_gpio;
u32 delays[3] = { 0, 0, 0 };
- reset_gpio = devm_gpiod_get_optional(priv->device,
- "snps,reset",
- GPIOD_OUT_LOW);
- if (IS_ERR(reset_gpio))
- return PTR_ERR(reset_gpio);
+ /* The bus reset also runs on resume. Devres retains the GPIO
+ * until unbind, so do not request an already owned line again.
+ */
+ if (!reset_gpio) {
+ reset_gpio = devm_gpiod_get_optional(priv->device,
+ "snps,reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+ priv->mdio_reset_gpio = reset_gpio;
+ }
device_property_read_u32_array(priv->device,
"snps,reset-delays-us",
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 03/15] net: phylink: allow stopping a suspended instance
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
2026-09-26 15:48 ` [PATCH net v4 01/15] net: stmmac: unwind the WoL IRQ after a safety IRQ request failure James Hilliard
2026-09-26 15:48 ` [PATCH net v4 02/15] net: stmmac: reuse the MDIO reset GPIO on resume James Hilliard
@ 2026-09-26 15:48 ` James Hilliard
2026-09-26 15:48 ` [PATCH net v4 04/15] xsk: freeze deferred pool teardown during system sleep James Hilliard
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:48 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
If a network driver's system resume fails before phylink_resume(), the
network device can remain administratively up with phylink suspended.
Closing the interface then needs to terminate that suspended instance.
Calling phylink_resume() merely to make phylink_stop() work is not a
safe substitute: resume reconfigures the MAC and restarts link
resolution, although the driver has not successfully restored the MAC.
This is a missing suspend-to-stop transition, independent of the reason
hardware restoration failed. No MAC recovery policy belongs in phylink;
the driver still decides whether to retry resume or wait for an ordinary
administrative down/up cycle.
Without MAC Wake-on-LAN, phylink_suspend() has already called
phylink_stop(). Do not repeat PHY, SFP and PCS shutdown. However,
phylink_prepare_resume() may have powered that stopped PHY back up to
provide the receive clock for MAC reset. Suspend it again if WoL
permits, without repeating phy_stop() on a PHY which is already halted.
With MAC Wake-on-LAN, suspend deliberately defers mac_link_down() and
sets PHYLINK_DISABLE_MAC_WOL. Finish that deferred link-down, drain
resolution work and clear the WoL disable bit while retaining
PHYLINK_DISABLE_STOPPED. Otherwise a subsequent start cannot resolve the
link.
Also undo PHY speed control performed by phylink_suspend(). As Andrew
Lunn pointed out, phylink_start() does not restore the advertised speeds
that phylink_resume() normally restores. Track suspend-owned speed
control and restore the saved advertisement from either resume or
suspended stop, including when PHY shutdown has already completed.
An explicit driver speed-down request, such as stmmac's close-time power
saving, must remain in effect until its matching speed-up. Restore any
suspend-owned reduction before applying that request, so it cannot save
the reduced advertisement over the original one. The following stop must
not undo the driver's new reduction.
Document that a suspended instance can be stopped directly. This neither
resumes the PHY nor reconfigures or brings up the MAC.
Fixes: f97493657c63 ("net: phylink: add suspend/resume support")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/phy/phylink.c | 51 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 48 insertions(+), 3 deletions(-)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 1bbcf46c8356..b7d30ca34031 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -78,6 +78,7 @@ struct phylink {
bool link_failed;
bool suspend_link_up;
+ bool suspend_speed_down;
bool force_major_config;
bool major_config_failed;
bool mac_supports_eee_ops;
@@ -2498,6 +2499,14 @@ void phylink_start(struct phylink *pl)
}
EXPORT_SYMBOL_GPL(phylink_start);
+static void phylink_restore_suspend_speed(struct phylink *pl)
+{
+ if (pl->suspend_speed_down) {
+ phylink_speed_up(pl);
+ pl->suspend_speed_down = false;
+ }
+}
+
/**
* phylink_stop() - stop a phylink instance
* @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -2509,11 +2518,30 @@ EXPORT_SYMBOL_GPL(phylink_start);
*
* This will synchronously bring down the link if the link is not already
* down (in other words, it will trigger a mac_link_down() method call.)
+ * A suspended instance may be stopped without first calling phylink_resume().
+ * In particular, closing a device after a failed resume must not restart the
+ * link or reconfigure the MAC just to finish shutting it down.
+ * Any PHY advertisement reduced by phylink_suspend() is restored as part
+ * of this transition.
+ * If phylink_prepare_resume() powered up an already stopped PHY, suspend
+ * it again when Wake-on-LAN permits.
*/
void phylink_stop(struct phylink *pl)
{
ASSERT_RTNL();
+ /* Also undo PHY speed control when terminating a suspended instance. */
+ phylink_restore_suspend_speed(pl);
+
+ if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) {
+ /* A failed MAC resume may have called phylink_prepare_resume()
+ * and powered the stopped PHY back up to supply its RX clock.
+ */
+ if (pl->phydev)
+ phy_suspend(pl->phydev);
+ return;
+ }
+
if (pl->sfp_bus)
sfp_upstream_stop(pl->sfp_bus);
if (pl->phydev)
@@ -2526,6 +2554,16 @@ void phylink_stop(struct phylink *pl)
phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_STOPPED);
+ if (test_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state)) {
+ /* Finish the link-down deferred by MAC WoL, without restarting. */
+ flush_work(&pl->resolve);
+ mutex_lock(&pl->state_mutex);
+ if (pl->suspend_link_up)
+ phylink_link_down(pl);
+ __clear_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state);
+ mutex_unlock(&pl->state_mutex);
+ }
+
pl->pcs_state = PCS_STATE_DOWN;
phylink_pcs_disable(pl->pcs);
@@ -2657,8 +2695,10 @@ void phylink_suspend(struct phylink *pl, bool mac_wol)
phylink_stop(pl);
}
- if (phylink_phy_pm_speed_ctrl(pl))
+ if (phylink_phy_pm_speed_ctrl(pl)) {
phylink_speed_down(pl, false);
+ pl->suspend_speed_down = true;
+ }
}
EXPORT_SYMBOL_GPL(phylink_suspend);
@@ -2698,8 +2738,7 @@ void phylink_resume(struct phylink *pl)
{
ASSERT_RTNL();
- if (phylink_phy_pm_speed_ctrl(pl))
- phylink_speed_up(pl);
+ phylink_restore_suspend_speed(pl);
if (test_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state)) {
/* Wake-on-Lan enabled, MAC handling */
@@ -3616,6 +3655,12 @@ int phylink_speed_down(struct phylink *pl, bool sync)
ASSERT_RTNL();
+ /* An explicit request takes over from suspend-time speed control.
+ * Restore the original advertisement before saving it again, so a
+ * repeated speed-down cannot replace it with the reduced advertisement.
+ */
+ phylink_restore_suspend_speed(pl);
+
if (!pl->sfp_bus && pl->phydev)
ret = phy_speed_down(pl->phydev, sync);
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 04/15] xsk: freeze deferred pool teardown during system sleep
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (2 preceding siblings ...)
2026-09-26 15:48 ` [PATCH net v4 03/15] net: phylink: allow stopping a suspended instance James Hilliard
@ 2026-09-26 15:48 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 05/15] net: stmmac: serialize and retain PHC configuration across reset James Hilliard
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:48 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Pool destruction calls the driver under RTNL from system_wq. That queue
is not frozen during system sleep, so ndo_bpf() can run after the device
suspend callback has gated its clocks or after the noirq phase.
Use the freezable workqueue for deferred pool release. Work already
running completes before device suspend, and newly queued destruction
waits until device resume. The pool, UMEM and netdev references remain
owned by the work until then. This does not replace driver error
handling after a failed resume.
Fixes: 1c1efc2af158 ("xsk: Create and free buffer pool independently from umem")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
net/xdp/xsk_buff_pool.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c
index 9d2d94f1fb75..c58f56f24a9c 100644
--- a/net/xdp/xsk_buff_pool.c
+++ b/net/xdp/xsk_buff_pool.c
@@ -337,7 +337,10 @@ bool xp_put_pool(struct xsk_buff_pool *pool)
if (refcount_dec_and_test(&pool->users)) {
INIT_WORK(&pool->work, xp_release_deferred);
- schedule_work(&pool->work);
+ /* Teardown calls ndo_bpf(), which may need powered hardware.
+ * RTNL alone does not exclude the device's system PM callbacks.
+ */
+ queue_work(system_freezable_wq, &pool->work);
return true;
}
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 05/15] net: stmmac: serialize and retain PHC configuration across reset
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (3 preceding siblings ...)
2026-09-26 15:48 ` [PATCH net v4 04/15] xsk: freeze deferred pool teardown during system sleep James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 06/15] net: stmmac: leave the datapath running for normal-size MTU changes James Hilliard
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Keep the frequency correction, PEROUT requests and EXTTS selection
independently of timestamp register contents. Cache PPS requests before
converting units so they can be replayed after reset.
Serialize timestamp writers and devlink timestamp-mode updates with a
mutex, and let atomic gettime callers observe a reset gate under
ptp_lock. Provide a common replay helper for later reset transactions,
without changing PHC registration lifetime.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 7 ++
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 19 +++-
drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c | 131 +++++++++++++++++++---
3 files changed, 141 insertions(+), 16 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index c7c876408d85..aa09b2defcd9 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -337,6 +337,12 @@ struct stmmac_priv {
int use_riwt;
int irq_wake;
rwlock_t ptp_lock;
+ /* Serialize PHC changes with a hardware reset; gettime uses ptp_lock. */
+ struct mutex ptp_mutex;
+ bool ptp_blocked;
+ long ptp_scaled_ppm;
+ u32 ptp_perout;
+ u32 ptp_extts;
/* Protects auxiliary snapshot registers from concurrent access. */
struct mutex aux_ts_lock;
wait_queue_head_t tstamp_busy_wait;
@@ -404,6 +410,7 @@ void stmmac_set_ethtool_ops(struct net_device *netdev);
void stmmac_ptp_register(struct stmmac_priv *priv);
void stmmac_ptp_unregister(struct stmmac_priv *priv);
+int stmmac_ptp_restore(struct stmmac_priv *priv);
int stmmac_xdp_open(struct net_device *dev);
void stmmac_xdp_release(struct net_device *dev);
int stmmac_get_phy_intf_sel(phy_interface_t interface);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 4f3d452c3503..8b0bc7c46df9 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -941,6 +941,11 @@ static void stmmac_setup_ptp(struct stmmac_priv *priv)
{
int ret;
+ priv->ptp_scaled_ppm = 0;
+ priv->ptp_perout = 0;
+ priv->ptp_extts = 0;
+ priv->ptp_blocked = false;
+
ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
if (ret < 0)
netdev_warn(priv->dev,
@@ -6285,7 +6290,8 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv)
for (queue = 0; queue < queues_count; queue++)
stmmac_host_mtl_irq_status(priv, priv->hw, queue);
- stmmac_timestamp_interrupt(priv, priv);
+ if (!READ_ONCE(priv->ptp_blocked))
+ stmmac_timestamp_interrupt(priv, priv);
}
}
@@ -7696,6 +7702,14 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
{
struct stmmac_devlink_priv *dl_priv = devlink_priv(dl);
struct stmmac_priv *priv = dl_priv->stmmac_priv;
+ unsigned long flags;
+
+ mutex_lock(&priv->ptp_mutex);
+ if (priv->ptp_blocked) {
+ mutex_unlock(&priv->ptp_mutex);
+ return -EBUSY;
+ }
+ write_lock_irqsave(&priv->ptp_lock, flags);
priv->tsfupdt_coarse = ctx->val.vbool;
@@ -7708,6 +7722,8 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
* reconfigure the systime, subsecond increment and addend.
*/
stmmac_update_subsecond_increment(priv);
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+ mutex_unlock(&priv->ptp_mutex);
return 0;
}
@@ -8048,6 +8064,7 @@ static int __stmmac_dvr_probe(struct device *device,
mutex_init(&priv->lock);
rwlock_init(&priv->ptp_lock);
+ mutex_init(&priv->ptp_mutex);
stmmac_fpe_init(priv);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
index 3bfcc9760dce..bc6fe0c87e01 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
@@ -12,6 +12,16 @@
#define PTP_SAFE_TIME_OFFSET_NS 500000
+static int stmmac_ptp_begin(struct stmmac_priv *priv)
+{
+ mutex_lock(&priv->ptp_mutex);
+ if (priv->ptp_blocked) {
+ mutex_unlock(&priv->ptp_mutex);
+ return -EBUSY;
+ }
+ return 0;
+}
+
/**
* stmmac_adjust_freq
*
@@ -28,14 +38,21 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm)
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
unsigned long flags;
u32 addend;
+ int ret;
- addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm);
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
write_lock_irqsave(&priv->ptp_lock, flags);
- stmmac_config_addend(priv, priv->ptpaddr, addend);
+ addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm);
+ ret = stmmac_config_addend(priv, priv->ptpaddr, addend);
+ if (!ret)
+ priv->ptp_scaled_ppm = scaled_ppm;
write_unlock_irqrestore(&priv->ptp_lock, flags);
+ mutex_unlock(&priv->ptp_mutex);
- return 0;
+ return ret;
}
/**
@@ -57,6 +74,10 @@ static int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta)
bool xmac, est_rst = false;
int ret;
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
+
xmac = dwmac_is_xmac(priv->plat->core_type);
if (delta < 0) {
@@ -110,6 +131,7 @@ static int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta)
netdev_err(priv->dev, "failed to configure EST\n");
}
+ mutex_unlock(&priv->ptp_mutex);
return 0;
}
@@ -128,14 +150,18 @@ static int stmmac_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts)
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
unsigned long flags;
u64 ns = 0;
+ int ret = 0;
read_lock_irqsave(&priv->ptp_lock, flags);
- stmmac_get_systime(priv, priv->ptpaddr, &ns);
+ if (priv->ptp_blocked)
+ ret = -EBUSY;
+ else
+ stmmac_get_systime(priv, priv->ptpaddr, &ns);
read_unlock_irqrestore(&priv->ptp_lock, flags);
*ts = ns_to_timespec64(ns);
- return 0;
+ return ret;
}
/**
@@ -153,25 +179,34 @@ static int stmmac_set_time(struct ptp_clock_info *ptp,
struct stmmac_priv *priv =
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
unsigned long flags;
+ int ret;
+
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
write_lock_irqsave(&priv->ptp_lock, flags);
- stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec);
+ ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec);
write_unlock_irqrestore(&priv->ptp_lock, flags);
+ mutex_unlock(&priv->ptp_mutex);
- return 0;
+ return ret;
}
-static int stmmac_enable(struct ptp_clock_info *ptp,
- struct ptp_clock_request *rq, int on)
+static int __stmmac_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
{
struct stmmac_priv *priv =
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
void __iomem *ptpaddr = priv->ptpaddr;
- struct stmmac_pps_cfg *cfg;
+ struct stmmac_pps_cfg pps, saved, *cfg = &pps;
int ret = -EOPNOTSUPP;
unsigned long flags;
u32 acr_value;
+ if (priv->plat->core_type == DWMAC_CORE_GMAC)
+ return dwmac1000_ptp_enable(ptp, rq, on);
+
switch (rq->type) {
case PTP_CLK_REQ_PEROUT: {
struct timespec64 curr_time;
@@ -182,8 +217,6 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
if (rq->perout.flags)
return -EOPNOTSUPP;
- cfg = &priv->pps[rq->perout.index];
-
cfg->start.tv_sec = rq->perout.start.sec;
cfg->start.tv_nsec = rq->perout.start.nsec;
@@ -213,6 +246,7 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
cfg->period.tv_sec = rq->perout.period.sec;
cfg->period.tv_nsec = rq->perout.period.nsec;
+ saved = *cfg;
write_lock_irqsave(&priv->ptp_lock, flags);
ret = stmmac_flex_pps_config(priv, priv->ioaddr,
@@ -220,6 +254,9 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
priv->sub_second_inc,
priv->systime_flags);
write_unlock_irqrestore(&priv->ptp_lock, flags);
+ /* Some cores convert cfg->start to binary rollover units. */
+ if (!ret)
+ priv->pps[rq->perout.index] = saved;
break;
}
case PTP_CLK_REQ_EXTTS: {
@@ -265,6 +302,64 @@ static int stmmac_enable(struct ptp_clock_info *ptp,
return ret;
}
+static int stmmac_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ int ret;
+
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
+ ret = __stmmac_enable(ptp, rq, on);
+ if (!ret) {
+ if (rq->type == PTP_CLK_REQ_PEROUT) {
+ if (on)
+ priv->ptp_perout |= BIT(rq->perout.index);
+ else
+ priv->ptp_perout &= ~BIT(rq->perout.index);
+ } else if (rq->type == PTP_CLK_REQ_EXTTS) {
+ priv->ptp_extts = on ? BIT(rq->extts.index) : 0;
+ }
+ }
+ mutex_unlock(&priv->ptp_mutex);
+ return ret;
+}
+
+/* Called with ptp_mutex held and PHC access blocked across the MAC reset. */
+int stmmac_ptp_restore(struct stmmac_priv *priv)
+{
+ struct ptp_clock_request rq = { .type = PTP_CLK_REQ_EXTTS };
+ unsigned long flags;
+ u64 ns = 0, period;
+ u32 addend;
+ int i, ret;
+
+ write_lock_irqsave(&priv->ptp_lock, flags);
+ addend = adjust_by_scaled_ppm(priv->default_addend, priv->ptp_scaled_ppm);
+ ret = stmmac_config_addend(priv, priv->ptpaddr, addend);
+ for (i = 0; !ret && i < STMMAC_PPS_MAX; i++) {
+ struct stmmac_pps_cfg cfg = priv->pps[i];
+
+ if (!(priv->ptp_perout & BIT(i)))
+ continue;
+ stmmac_get_systime(priv, priv->ptpaddr, &ns);
+ period = timespec64_to_ns(&cfg.period);
+ /* Retain phase, but move an expired target into the future. */
+ cfg.start = stmmac_calc_tas_basetime(timespec64_to_ktime(cfg.start),
+ ns + PTP_SAFE_TIME_OFFSET_NS, period);
+ ret = stmmac_flex_pps_config(priv, priv->ioaddr, i, &cfg, true,
+ priv->sub_second_inc, priv->systime_flags);
+ }
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+ if (ret || !priv->ptp_extts)
+ return ret;
+
+ rq.extts.index = __ffs(priv->ptp_extts);
+ return __stmmac_enable(&priv->ptp_clock_ops, &rq, 1);
+}
+
/**
* stmmac_get_syncdevicetime
* @device: current device time
@@ -287,9 +382,15 @@ static int stmmac_getcrosststamp(struct ptp_clock_info *ptp,
{
struct stmmac_priv *priv =
container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ int ret;
- return get_device_system_crosststamp(stmmac_get_syncdevicetime,
- priv, NULL, xtstamp);
+ ret = stmmac_ptp_begin(priv);
+ if (ret)
+ return ret;
+ ret = get_device_system_crosststamp(stmmac_get_syncdevicetime,
+ priv, NULL, xtstamp);
+ mutex_unlock(&priv->ptp_mutex);
+ return ret;
}
/* structure describing a PTP hardware clock */
@@ -323,7 +424,7 @@ const struct ptp_clock_info dwmac1000_ptp_clock_ops = {
.adjtime = stmmac_adjust_time,
.gettime64 = stmmac_get_time,
.settime64 = stmmac_set_time,
- .enable = dwmac1000_ptp_enable,
+ .enable = stmmac_enable,
};
/**
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 06/15] net: stmmac: leave the datapath running for normal-size MTU changes
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (4 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 05/15] net: stmmac: serialize and retain PHC configuration across reset James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 07/15] net: stmmac: unwind partially allocated DMA configurations James Hilliard
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Changing an MTU at or below ETH_DATA_LEN does not change the receive
buffer size or the MAC receive limit when the previous MTU was also in
that range. Do not release and reopen the datapath for those changes.
Besides avoiding unnecessary hardware resets and their failure paths,
this keeps live AF_XDP pool bindings intact. Preparing replacement rings
before stopping the old rings otherwise binds the pool to a temporary
RXQ and consumes fill-ring entries while its current RXQ is still
active. XDP already rejects jumbo MTUs, so all supported live XDP MTU
changes can use this path without preparing replacement rings.
Keep jumbo transitions on the existing reinitialization path for now.
The later ownership and rollback changes address that path separately.
Fixes: 3470079687448 ("net: ethernet: stmicro: stmmac: permit MTU change with interface up")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 8b0bc7c46df9..bdebc5bfa2b8 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -6166,7 +6166,12 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
if ((txfifosz < new_mtu) || (new_mtu > BUF_SIZE_16KiB))
return -EINVAL;
- if (netif_running(dev)) {
+ /* Normal-size frames use the same buffers and MAC receive limits.
+ * In particular, do not disturb a live AF_XDP pool: XDP does not
+ * support jumbo frames, so it never needs the ring replacement below.
+ */
+ if (netif_running(dev) &&
+ (dev->mtu > ETH_DATA_LEN || mtu > ETH_DATA_LEN)) {
netdev_dbg(priv->dev, "restarting interface to change its MTU\n");
/* Try to allocate the new DMA conf with the new mtu */
dma_conf = stmmac_setup_dma_desc(priv, mtu);
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 07/15] net: stmmac: unwind partially allocated DMA configurations
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (5 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 06/15] net: stmmac: leave the datapath running for normal-size MTU changes James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 08/15] net: stmmac: keep DMA configurations at stable addresses James Hilliard
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
RX and TX allocation failure paths release all configured queues, even
when the failing queue has not allocated its buffer array or
descriptors. Only walk buffer arrays and free coherent memory when those
allocations exist. If TX allocation fails after RX allocation succeeds,
release the RX resources too.
Propagate RXQ memory-model registration errors instead of continuing
with an unusable RXQ. Clear XSK RXQ bindings before the RXQ goes away,
release any saved partial packet, and clear released resource pointers.
This also leaves configurations which are reused after close or XDP
reconfiguration with a consistent empty state.
The MTU transaction added later relies on preparation failures being
fully unwound without touching the currently active configuration.
Take ownership of saved partial RX state at poll entry by clearing the
saved flag and skb pointer immediately. Preserve incomplete state if the
next descriptor is still DMA-owned. A budget-one completion must not
leave an already delivered or freed skb reachable by the new teardown
cleanup.
Fixes: 71fedb0198cb ("net: stmmac: break some functions into RX and TX scopes")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 69 +++++++++++++++++------
1 file changed, 52 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index bdebc5bfa2b8..1cc68c1cf37d 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -1766,6 +1766,9 @@ static void dma_free_rx_skbufs(struct stmmac_priv *priv,
struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
int i;
+ if (!rx_q->buf_pool)
+ return;
+
for (i = 0; i < dma_conf->dma_rx_size; i++)
stmmac_free_rx_buffer(priv, rx_q, i);
}
@@ -1889,17 +1892,19 @@ static int __init_dma_rx_desc_rings(struct stmmac_priv *priv,
rx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue);
if (rx_q->xsk_pool) {
- WARN_ON(xdp_rxq_info_reg_mem_model(&rx_q->xdp_rxq,
- MEM_TYPE_XSK_BUFF_POOL,
- NULL));
+ ret = xdp_rxq_info_reg_mem_model(&rx_q->xdp_rxq,
+ MEM_TYPE_XSK_BUFF_POOL, NULL);
+ if (ret)
+ return ret;
netdev_info(priv->dev,
"Register MEM_TYPE_XSK_BUFF_POOL RxQ-%d\n",
queue);
xsk_pool_set_rxq_info(rx_q->xsk_pool, &rx_q->xdp_rxq);
} else {
- WARN_ON(xdp_rxq_info_reg_mem_model(&rx_q->xdp_rxq,
- MEM_TYPE_PAGE_POOL,
- rx_q->page_pool));
+ ret = xdp_rxq_info_reg_mem_model(&rx_q->xdp_rxq,
+ MEM_TYPE_PAGE_POOL, rx_q->page_pool);
+ if (ret)
+ return ret;
netdev_info(priv->dev,
"Register MEM_TYPE_PAGE_POOL RxQ-%d\n",
queue);
@@ -1961,6 +1966,8 @@ static int init_dma_rx_desc_rings(struct net_device *dev,
dma_free_rx_skbufs(priv, dma_conf, queue);
rx_q->buf_alloc_num = 0;
+ if (rx_q->xsk_pool)
+ xsk_pool_set_rxq_info(rx_q->xsk_pool, NULL);
rx_q->xsk_pool = NULL;
queue--;
@@ -2104,6 +2111,9 @@ static void dma_free_tx_skbufs(struct stmmac_priv *priv,
tx_q->xsk_frames_done = 0;
+ if (!tx_q->tx_skbuff_dma || !tx_q->tx_skbuff)
+ return;
+
for (i = 0; i < dma_conf->dma_tx_size; i++)
stmmac_free_tx_buffer(priv, dma_conf, queue, i);
@@ -2142,10 +2152,16 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
void *addr;
/* Release the DMA RX socket buffers */
- if (rx_q->xsk_pool)
+ if (rx_q->xsk_pool) {
dma_free_rx_xskbufs(priv, dma_conf, queue);
- else
+ xsk_pool_set_rxq_info(rx_q->xsk_pool, NULL);
+ } else {
dma_free_rx_skbufs(priv, dma_conf, queue);
+ }
+ if (rx_q->state_saved)
+ dev_kfree_skb_any(rx_q->state.skb);
+ rx_q->state.skb = NULL;
+ rx_q->state_saved = 0;
rx_q->buf_alloc_num = 0;
rx_q->xsk_pool = NULL;
@@ -2158,7 +2174,8 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
size = stmmac_get_rx_desc_size(priv) * dma_conf->dma_rx_size;
- dma_free_coherent(priv->device, size, addr, rx_q->dma_rx_phy);
+ if (addr)
+ dma_free_coherent(priv->device, size, addr, rx_q->dma_rx_phy);
if (xdp_rxq_info_is_reg(&rx_q->xdp_rxq))
xdp_rxq_info_unreg(&rx_q->xdp_rxq);
@@ -2166,6 +2183,10 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
kfree(rx_q->buf_pool);
if (rx_q->page_pool)
page_pool_destroy(rx_q->page_pool);
+ rx_q->buf_pool = NULL;
+ rx_q->page_pool = NULL;
+ rx_q->dma_erx = NULL;
+ rx_q->dma_rx = NULL;
}
static void free_dma_rx_desc_resources(struct stmmac_priv *priv,
@@ -2206,10 +2227,16 @@ static void __free_dma_tx_desc_resources(struct stmmac_priv *priv,
size = stmmac_get_tx_desc_size(priv, tx_q) * dma_conf->dma_tx_size;
- dma_free_coherent(priv->device, size, addr, tx_q->dma_tx_phy);
+ if (addr)
+ dma_free_coherent(priv->device, size, addr, tx_q->dma_tx_phy);
kfree(tx_q->tx_skbuff_dma);
kfree(tx_q->tx_skbuff);
+ tx_q->tx_skbuff_dma = NULL;
+ tx_q->tx_skbuff = NULL;
+ tx_q->dma_etx = NULL;
+ tx_q->dma_entx = NULL;
+ tx_q->dma_tx = NULL;
}
static void free_dma_tx_desc_resources(struct stmmac_priv *priv,
@@ -2416,6 +2443,8 @@ static int alloc_dma_desc_resources(struct stmmac_priv *priv,
return ret;
ret = alloc_dma_tx_desc_resources(priv, dma_conf);
+ if (ret)
+ free_dma_rx_desc_resources(priv, dma_conf);
return ret;
}
@@ -5726,6 +5755,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
struct sk_buff *skb = NULL;
struct stmmac_xdp_buff ctx;
int xdp_status = 0;
+ bool in_progress = rx_q->state_saved;
int bufsz;
dma_dir = page_pool_get_dma_dir(rx_q->page_pool);
@@ -5740,6 +5770,14 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
rx_q->dma_rx_phy, desc_size);
}
+ if (in_progress) {
+ skb = rx_q->state.skb;
+ error = rx_q->state.error;
+ len = rx_q->state.len;
+ rx_q->state.skb = NULL;
+ rx_q->state_saved = false;
+ }
+
while (count < limit) {
unsigned int buf1_len = 0, buf2_len = 0;
enum pkt_hash_types hash_type;
@@ -5748,12 +5786,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
int entry;
u32 hash;
- if (!count && rx_q->state_saved) {
- skb = rx_q->state.skb;
- error = rx_q->state.error;
- len = rx_q->state.len;
- } else {
- rx_q->state_saved = false;
+ if (!in_progress) {
skb = NULL;
error = 0;
len = 0;
@@ -5787,6 +5820,8 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
prefetch(np);
+ in_progress = status & rx_not_ls;
+
if (priv->extend_desc)
stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry);
if (unlikely(status == discard_frame)) {
@@ -5961,7 +5996,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
count++;
}
- if (status & rx_not_ls || skb) {
+ if (in_progress || skb) {
rx_q->state_saved = true;
rx_q->state.skb = skb;
rx_q->state.error = error;
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 08/15] net: stmmac: keep DMA configurations at stable addresses
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (6 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 07/15] net: stmmac: unwind partially allocated DMA configurations James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 09/15] net: stmmac: track datapath and power ownership across failed reopening James Hilliard
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
The allocated DMA configuration contains RXQ metadata registered with
XDP and referenced by AF_XDP pools. Copying the configuration into priv
and freeing its original allocation leaves those references pointing at
the old storage. Retain the allocated object instead and keep a pointer
in priv, preserving ring sizes and per-queue settings while down.
Use persistent channel objects for per-queue IRQ contexts instead of
deriving priv from an embedded DMA configuration. XSK wakeup must also
avoid accessing replaceable queue objects. Drain transmitters and NAPI
poll tails before cancelling TX timers so a late rearm cannot outlive
the configuration containing the timer.
Update both open callers with the ownership change. Successful open
keeps the new configuration and frees the empty old object. Failed open
restores the old pointer before freeing the failed replacement. Detach
around the existing MTU reopen so XDP transmit cannot enter while that
pointer is being replaced, and reattach after success. The later MTU
transaction replaces this reopen path with retained-resource rollback.
The empty configuration remains allocated while down because ethtool, TC
and the next open still use its ring sizes and per-queue settings. The
probe-managed action frees the current object after netdev teardown.
Cancel the software EEE timer after the poll/transmit drain as well: a
TX completion may have passed its enable check before phylink cancelled
it.
GSO feature checks are not excluded by stopped queues or netdev detach.
Read the immutable platform TBS capability instead of dereferencing the
replaceable configuration from ndo_features_check().
Fixes: ba39b344e924 ("net: ethernet: stmicro: stmmac: generate stmmac dma conf before open")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 6 +-
drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 4 +-
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 +-
.../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 4 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 324 +++++++++++----------
.../net/ethernet/stmicro/stmmac/stmmac_selftests.c | 8 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 6 +-
7 files changed, 189 insertions(+), 165 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
index 66025e2509e9..65243c5e539e 100644
--- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
+++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
@@ -48,7 +48,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
while (len != 0) {
tx_q->tx_skbuff[entry] = NULL;
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_tx_size);
desc = tx_q->dma_tx + entry;
if (len > bmax) {
@@ -137,7 +137,7 @@ static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p)
*/
p->des3 = cpu_to_le32((unsigned int)(rx_q->dma_rx_phy +
(((rx_q->dirty_rx) + 1) %
- priv->dma_conf.dma_rx_size) *
+ priv->dma_conf->dma_rx_size) *
sizeof(struct dma_desc)));
}
@@ -154,7 +154,7 @@ static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p)
*/
p->des3 = cpu_to_le32((unsigned int)((tx_q->dma_tx_phy +
((tx_q->dirty_tx + 1) %
- priv->dma_conf.dma_tx_size))
+ priv->dma_conf->dma_tx_size))
* sizeof(struct dma_desc)));
}
diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
index d2f0c321661d..0299d6a6c32b 100644
--- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
+++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
@@ -52,7 +52,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
stmmac_prepare_tx_desc(priv, desc, 1, bmax, csum,
STMMAC_RING_MODE, 0, false, skb->len);
tx_q->tx_skbuff[entry] = NULL;
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_tx_size);
if (priv->extend_desc)
desc = (struct dma_desc *)(tx_q->dma_etx + entry);
@@ -102,7 +102,7 @@ static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p)
struct stmmac_priv *priv = rx_q->priv_data;
/* Fill DES3 in case of RING mode */
- if (priv->dma_conf.dma_buf_sz == BUF_SIZE_16KiB)
+ if (priv->dma_conf->dma_buf_sz == BUF_SIZE_16KiB)
p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB);
}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index aa09b2defcd9..3365ed237953 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -280,7 +280,7 @@ struct stmmac_priv {
int (*hwif_quirks)(struct stmmac_priv *priv);
struct mutex lock;
- struct stmmac_dma_conf dma_conf;
+ struct stmmac_dma_conf *dma_conf;
/* Generic channel for NAPI */
struct stmmac_channel channel[STMMAC_CH_MAX];
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
index 1be5310ca766..994350fcdbfa 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
@@ -396,8 +396,8 @@ static void stmmac_get_ringparam(struct net_device *netdev,
ring->rx_max_pending = DMA_MAX_RX_SIZE;
ring->tx_max_pending = DMA_MAX_TX_SIZE;
- ring->rx_pending = priv->dma_conf.dma_rx_size;
- ring->tx_pending = priv->dma_conf.dma_tx_size;
+ ring->rx_pending = priv->dma_conf->dma_rx_size;
+ ring->tx_pending = priv->dma_conf->dma_tx_size;
}
static int stmmac_set_ringparam(struct net_device *netdev,
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 1cc68c1cf37d..c4ba97d57b47 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -79,7 +79,7 @@ static int phyaddr = -1;
module_param(phyaddr, int, 0444);
MODULE_PARM_DESC(phyaddr, "Physical device address");
-#define STMMAC_TX_THRESH(x) ((x)->dma_conf.dma_tx_size / 4)
+#define STMMAC_TX_THRESH(x) ((x)->dma_conf->dma_tx_size / 4)
/* Limit to make sure XDP TX and slow path can coexist */
#define STMMAC_XSK_TX_BUDGET_MAX 256
@@ -297,7 +297,7 @@ static void stmmac_disable_all_queues(struct stmmac_priv *priv)
/* synchronize_rcu() needed for pending XDP buffers to drain */
for (queue = 0; queue < rx_queues_cnt; queue++) {
- rx_q = &priv->dma_conf.rx_queue[queue];
+ rx_q = &priv->dma_conf->rx_queue[queue];
if (rx_q->xsk_pool) {
synchronize_rcu();
break;
@@ -356,10 +356,10 @@ static void print_pkt(unsigned char *buf, int len)
static inline u32 stmmac_tx_avail(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
return CIRC_SPACE(tx_q->cur_tx, tx_q->dirty_tx,
- priv->dma_conf.dma_tx_size);
+ priv->dma_conf->dma_tx_size);
}
static size_t stmmac_get_tx_desc_size(struct stmmac_priv *priv,
@@ -438,7 +438,7 @@ static void stmmac_set_queue_rx_buf_size(struct stmmac_priv *priv,
if (rx_q->xsk_pool && rx_q->buf_alloc_num)
buf_size = xsk_pool_get_rx_frame_size(rx_q->xsk_pool);
else
- buf_size = priv->dma_conf.dma_buf_sz;
+ buf_size = priv->dma_conf->dma_buf_sz;
stmmac_set_dma_bfsize(priv, priv->ioaddr, buf_size, chan);
}
@@ -450,10 +450,10 @@ static void stmmac_set_queue_rx_buf_size(struct stmmac_priv *priv,
*/
static inline u32 stmmac_rx_dirty(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
return CIRC_CNT(rx_q->cur_rx, rx_q->dirty_rx,
- priv->dma_conf.dma_rx_size);
+ priv->dma_conf->dma_rx_size);
}
static bool stmmac_eee_tx_busy(struct stmmac_priv *priv)
@@ -463,7 +463,7 @@ static bool stmmac_eee_tx_busy(struct stmmac_priv *priv)
/* check if all TX queues have the work finished */
for (queue = 0; queue < tx_cnt; queue++) {
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
if (tx_q->dirty_tx != tx_q->cur_tx)
return true; /* still unfinished work */
@@ -2134,7 +2134,7 @@ static void stmmac_free_tx_skbufs(struct stmmac_priv *priv)
u8 queue;
for (queue = 0; queue < tx_queue_cnt; queue++)
- dma_free_tx_skbufs(priv, &priv->dma_conf, queue);
+ dma_free_tx_skbufs(priv, priv->dma_conf, queue);
}
/**
@@ -2643,7 +2643,7 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv)
/* configure all channels */
for (chan = 0; chan < rx_channels_count; chan++) {
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[chan];
qmode = priv->plat->rx_queues_cfg[chan].mode_to_use;
@@ -2715,7 +2715,7 @@ static const struct xsk_tx_metadata_ops stmmac_xsk_tx_metadata_ops = {
static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
{
struct netdev_queue *nq = netdev_get_tx_queue(priv->dev, queue);
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[queue];
bool csum = !priv->plat->tx_queues_cfg[queue].coe_unsupported;
struct xsk_buff_pool *pool = tx_q->xsk_pool;
@@ -2804,7 +2804,7 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
xsk_tx_metadata_to_compl(meta,
&tx_q->tx_skbuff_dma[entry].xsk_meta);
- tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf->dma_tx_size);
entry = tx_q->cur_tx;
}
u64_stats_update_begin(&txq_stats->napi_syncp);
@@ -2852,7 +2852,7 @@ static void stmmac_bump_dma_threshold(struct stmmac_priv *priv, u32 chan)
static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
bool *pending_packets)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[queue];
unsigned int bytes_compl = 0, pkts_compl = 0;
unsigned int entry, xmits = 0, count = 0;
@@ -2865,7 +2865,7 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
entry = tx_q->dirty_tx;
/* Try to clean all TX complete frame in 1 shot */
- while ((entry != tx_q->cur_tx) && count < priv->dma_conf.dma_tx_size) {
+ while ((entry != tx_q->cur_tx) && count < priv->dma_conf->dma_tx_size) {
struct xdp_frame *xdpf;
struct sk_buff *skb;
struct dma_desc *p;
@@ -2972,7 +2972,7 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
stmmac_release_tx_desc(priv, p, priv->descriptor_mode);
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_tx_size);
}
tx_q->dirty_tx = entry;
@@ -3040,13 +3040,13 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
*/
static void stmmac_tx_err(struct stmmac_priv *priv, u32 chan)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[chan];
netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, chan));
stmmac_stop_tx_dma(priv, chan);
- dma_free_tx_skbufs(priv, &priv->dma_conf, chan);
- stmmac_clear_tx_descriptors(priv, &priv->dma_conf, chan);
+ dma_free_tx_skbufs(priv, priv->dma_conf, chan);
+ stmmac_clear_tx_descriptors(priv, priv->dma_conf, chan);
stmmac_reset_tx_queue(priv, chan);
stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
tx_q->dma_tx_phy, chan);
@@ -3107,8 +3107,8 @@ static int stmmac_napi_check(struct stmmac_priv *priv, u32 chan, u32 dir)
{
int status = stmmac_dma_interrupt_status(priv, priv->ioaddr,
&priv->xstats, chan, dir);
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan];
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[chan];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[chan];
struct stmmac_channel *ch = &priv->channel[chan];
struct napi_struct *rx_napi;
struct napi_struct *tx_napi;
@@ -3328,7 +3328,7 @@ static int stmmac_init_dma_engine(struct stmmac_priv *priv)
/* DMA RX Channel Configuration */
for (chan = 0; chan < rx_channels_count; chan++) {
- rx_q = &priv->dma_conf.rx_queue[chan];
+ rx_q = &priv->dma_conf->rx_queue[chan];
stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
rx_q->dma_rx_phy, chan);
@@ -3339,7 +3339,7 @@ static int stmmac_init_dma_engine(struct stmmac_priv *priv)
/* DMA TX Channel Configuration */
for (chan = 0; chan < tx_channels_count; chan++) {
- tx_q = &priv->dma_conf.tx_queue[chan];
+ tx_q = &priv->dma_conf->tx_queue[chan];
stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
tx_q->dma_tx_phy, chan);
@@ -3352,7 +3352,7 @@ static int stmmac_init_dma_engine(struct stmmac_priv *priv)
static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
u32 tx_coal_timer = priv->tx_coal_timer[queue];
struct stmmac_channel *ch;
struct napi_struct *napi;
@@ -3420,7 +3420,7 @@ static void stmmac_init_coalesce(struct stmmac_priv *priv)
u8 chan;
for (chan = 0; chan < tx_channel_count; chan++) {
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[chan];
priv->tx_coal_frames[chan] = STMMAC_TX_FRAMES;
priv->tx_coal_timer[chan] = STMMAC_COAL_TX_TIMER;
@@ -3441,12 +3441,12 @@ static void stmmac_set_rings_length(struct stmmac_priv *priv)
/* set TX ring length */
for (chan = 0; chan < tx_channels_count; chan++)
stmmac_set_tx_ring_len(priv, priv->ioaddr,
- (priv->dma_conf.dma_tx_size - 1), chan);
+ (priv->dma_conf->dma_tx_size - 1), chan);
/* set RX ring length */
for (chan = 0; chan < rx_channels_count; chan++)
stmmac_set_rx_ring_len(priv, priv->ioaddr,
- (priv->dma_conf.dma_rx_size - 1), chan);
+ (priv->dma_conf->dma_rx_size - 1), chan);
}
/**
@@ -3654,8 +3654,10 @@ static void stmmac_safety_feat_configuration(struct stmmac_priv *priv)
static bool stmmac_tso_channel_permitted(struct stmmac_priv *priv,
unsigned int chan)
{
- /* TSO and TBS cannot co-exist */
- return !(priv->dma_conf.tx_queue[chan].tbs & STMMAC_TBS_AVAIL);
+ /* TSO and TBS cannot co-exist. Feature checks also run while the
+ * datapath is detached, so use the lifetime-stable platform setting.
+ */
+ return !priv->plat->tx_queues_cfg[chan].tbs_en;
}
/**
@@ -3773,7 +3775,7 @@ static int stmmac_hw_setup(struct net_device *dev)
/* TBS */
for (chan = 0; chan < tx_cnt; chan++) {
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[chan];
int enable = tx_q->tbs & STMMAC_TBS_AVAIL;
stmmac_enable_tbs(priv, priv->ioaddr, enable, chan);
@@ -3809,7 +3811,7 @@ static void stmmac_free_irq(struct net_device *dev,
if (msi->tx_irq[j] > 0) {
irq_set_affinity_hint(msi->tx_irq[j], NULL);
free_irq(msi->tx_irq[j],
- &priv->dma_conf.tx_queue[j]);
+ &priv->channel[j]);
}
}
irq_idx = priv->plat->rx_queues_to_use;
@@ -3819,7 +3821,7 @@ static void stmmac_free_irq(struct net_device *dev,
if (msi->rx_irq[j] > 0) {
irq_set_affinity_hint(msi->rx_irq[j], NULL);
free_irq(msi->rx_irq[j],
- &priv->dma_conf.rx_queue[j]);
+ &priv->channel[j]);
}
}
@@ -3973,7 +3975,7 @@ static int stmmac_request_irq_multi_msi(struct net_device *dev)
sprintf(int_name, "%s:%s-%d", dev->name, "rx", i);
ret = request_irq(msi->rx_irq[i],
stmmac_msi_intr_rx,
- 0, int_name, &priv->dma_conf.rx_queue[i]);
+ 0, int_name, &priv->channel[i]);
if (unlikely(ret < 0)) {
netdev_err(priv->dev,
"%s: alloc rx-%d MSI %d (error: %d)\n",
@@ -3997,7 +3999,7 @@ static int stmmac_request_irq_multi_msi(struct net_device *dev)
sprintf(int_name, "%s:%s-%d", dev->name, "tx", i);
ret = request_irq(msi->tx_irq[i],
stmmac_msi_intr_tx,
- 0, int_name, &priv->dma_conf.tx_queue[i]);
+ 0, int_name, &priv->channel[i]);
if (unlikely(ret < 0)) {
netdev_err(priv->dev,
"%s: alloc tx-%d MSI %d (error: %d)\n",
@@ -4121,8 +4123,8 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
/* Chose the tx/rx size from the already defined one in the
* priv struct. (if defined)
*/
- dma_conf->dma_tx_size = priv->dma_conf.dma_tx_size;
- dma_conf->dma_rx_size = priv->dma_conf.dma_rx_size;
+ dma_conf->dma_tx_size = priv->dma_conf->dma_tx_size;
+ dma_conf->dma_rx_size = priv->dma_conf->dma_rx_size;
if (!dma_conf->dma_tx_size)
dma_conf->dma_tx_size = DMA_DEFAULT_TX_SIZE;
@@ -4136,6 +4138,7 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
/* Setup per-TXQ tbs flag before TX descriptor alloc */
tx_q->tbs |= tbs_en ? STMMAC_TBS_AVAIL : 0;
+ tx_q->tbs |= priv->dma_conf->tx_queue[chan].tbs & STMMAC_TBS_EN;
}
ret = alloc_dma_desc_resources(priv, dma_conf);
@@ -4178,10 +4181,8 @@ static int __stmmac_open(struct net_device *dev,
u8 chan;
int ret;
- for (int i = 0; i < priv->plat->tx_queues_to_use; i++)
- if (priv->dma_conf.tx_queue[i].tbs & STMMAC_TBS_EN)
- dma_conf->tx_queue[i].tbs = priv->dma_conf.tx_queue[i].tbs;
- memcpy(&priv->dma_conf, dma_conf, sizeof(*dma_conf));
+ /* Keep RXQ metadata and timers at their registered addresses. */
+ priv->dma_conf = dma_conf;
/* The PHY is suspended when the interface is reopened without
* disconnecting the PHY, e.g. on MTU change. IEEE 802.3 allows PHYs
@@ -4222,7 +4223,7 @@ static int __stmmac_open(struct net_device *dev,
phylink_stop(priv->phylink);
for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
- hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+ hrtimer_cancel(&priv->dma_conf->tx_queue[chan].txtimer);
stmmac_release_ptp(priv);
init_error:
@@ -4232,6 +4233,7 @@ static int __stmmac_open(struct net_device *dev,
static int stmmac_open(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ struct stmmac_dma_conf *old_conf = priv->dma_conf;
struct stmmac_dma_conf *dma_conf;
int ret;
@@ -4261,7 +4263,7 @@ static int stmmac_open(struct net_device *dev)
if (ret)
goto err_serdes;
- kfree(dma_conf);
+ kfree(old_conf);
/* We may have called phylink_speed_down before */
phylink_speed_up(priv->phylink);
@@ -4275,34 +4277,54 @@ static int stmmac_open(struct net_device *dev)
err_runtime_pm:
pm_runtime_put(priv->device);
err_dma_resources:
+ priv->dma_conf = old_conf;
free_dma_desc_resources(priv, dma_conf);
kfree(dma_conf);
return ret;
}
-static void __stmmac_release(struct net_device *dev)
+static void stmmac_stop_tx_queues(struct stmmac_priv *priv)
{
- struct stmmac_priv *priv = netdev_priv(dev);
u8 chan;
- /* Stop and disconnect the PHY */
- phylink_stop(priv->phylink);
+ netif_tx_disable(priv->dev);
- stmmac_disable_all_queues(priv);
+ /* A poll function can still arm a timer after napi_complete_done().
+ * Drain those poll tails and in-flight transmitters before cancelling
+ * the timers, so none can be rearmed after their final cancellation.
+ */
+ synchronize_net();
for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
- hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+ hrtimer_cancel(&priv->dma_conf->tx_queue[chan].txtimer);
+}
+
+/* Quiesce NAPI and transmit queues without releasing their resources. */
+static void stmmac_quiesce(struct stmmac_priv *priv)
+{
+ stmmac_disable_all_queues(priv);
+ stmmac_stop_tx_queues(priv);
+ /* A TX completion may have rearmed this after phylink stopped EEE. */
+ timer_delete_sync(&priv->eee_ctrl_timer);
+}
- netif_tx_disable(dev);
+static void __stmmac_release(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ phylink_stop(priv->phylink);
+ stmmac_quiesce(priv);
/* Free the IRQ lines */
stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+ /* TX error IRQs can restart a queue after the first quiescence. */
+ stmmac_stop_tx_queues(priv);
/* Stop TX/RX DMA and clear the descriptors */
stmmac_stop_all_dma(priv);
/* Release and free the Rx/Tx resources */
- free_dma_desc_resources(priv, &priv->dma_conf);
+ free_dma_desc_resources(priv, priv->dma_conf);
stmmac_release_ptp(priv);
@@ -4356,7 +4378,7 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb,
return false;
stmmac_set_tx_owner(priv, p);
- tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf->dma_tx_size);
return true;
}
@@ -4376,7 +4398,7 @@ static void stmmac_tso_allocator(struct stmmac_priv *priv, u32 *entry,
dma_addr_t des, int total_len,
bool last_segment, u32 queue)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
struct dma_desc *desc;
u32 buff_size;
int tmp_len;
@@ -4386,7 +4408,7 @@ static void stmmac_tso_allocator(struct stmmac_priv *priv, u32 *entry,
while (tmp_len > 0) {
dma_addr_t curr_addr;
- *entry = STMMAC_NEXT_ENTRY(*entry, priv->dma_conf.dma_tx_size);
+ *entry = STMMAC_NEXT_ENTRY(*entry, priv->dma_conf->dma_tx_size);
WARN_ON(tx_q->tx_skbuff[*entry]);
if (tx_q->tbs & STMMAC_TBS_AVAIL)
@@ -4410,7 +4432,7 @@ static void stmmac_tso_allocator(struct stmmac_priv *priv, u32 *entry,
static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
/* The own bit must be the latest setting done when prepare the
* descriptor and then barrier is needed to make sure that
@@ -4563,7 +4585,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
nfrags = skb_shinfo(skb)->nr_frags;
queue = skb_get_queue_mapping(skb);
- tx_q = &priv->dma_conf.tx_queue[queue];
+ tx_q = &priv->dma_conf->tx_queue[queue];
txq_stats = &priv->xstats.txq_stats[queue];
first_tx = tx_q->cur_tx;
@@ -4601,7 +4623,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
stmmac_set_mss(priv, mss_desc, mss);
tx_q->mss = mss;
tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx,
- priv->dma_conf.dma_tx_size);
+ priv->dma_conf->dma_tx_size);
WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]);
}
@@ -4672,7 +4694,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
/* Manage tx mitigation */
tx_packets = CIRC_CNT(tx_q->cur_tx + 1, first_tx,
- priv->dma_conf.dma_tx_size);
+ priv->dma_conf->dma_tx_size);
tx_q->tx_count_frames += tx_packets;
if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && priv->hwts_tx_en)
@@ -4704,7 +4726,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
* ndo_start_xmit will fill this descriptor the next time it's
* called and stmmac_tx_clean may clean up to this descriptor.
*/
- tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf->dma_tx_size);
if (unlikely(stmmac_tx_avail(priv, queue) <= (MAX_SKB_FRAGS + 1))) {
netif_dbg(priv, hw, priv->dev, "%s: stop transmitted packets\n",
@@ -4734,7 +4756,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
* segment.
*/
is_last_segment = CIRC_CNT(tx_q->cur_tx, first_entry,
- priv->dma_conf.dma_tx_size) == 1;
+ priv->dma_conf->dma_tx_size) == 1;
/* Complete the first descriptor before granting the DMA */
stmmac_prepare_tso_tx_desc(priv, first, 1, proto_hdr_len, 0, 1,
@@ -4772,13 +4794,13 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
for (;;) {
desc = stmmac_get_tx_desc(priv, tx_q, first_entry);
stmmac_release_tx_desc(priv, desc, priv->descriptor_mode);
- stmmac_free_tx_buffer(priv, &priv->dma_conf, queue,
+ stmmac_free_tx_buffer(priv, priv->dma_conf, queue,
first_entry);
if (first_entry == entry)
break;
first_entry = STMMAC_NEXT_ENTRY(first_entry,
- priv->dma_conf.dma_tx_size);
+ priv->dma_conf->dma_tx_size);
}
error:
dev_err(priv->device, "Tx dma map failed\n");
@@ -4863,7 +4885,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
return NETDEV_TX_BUSY;
}
- tx_q = &priv->dma_conf.tx_queue[queue];
+ tx_q = &priv->dma_conf->tx_queue[queue];
first_tx = tx_q->cur_tx;
/* Check if VLAN can be inserted by HW */
@@ -4939,7 +4961,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
unsigned int frag_size = skb_frag_size(frag);
bool last_segment = (i == (nfrags - 1));
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_tx_size);
WARN_ON(tx_q->tx_skbuff[entry]);
desc = stmmac_get_tx_desc(priv, tx_q, entry);
@@ -4969,7 +4991,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
* This approach takes care about the fragments: desc is the first
* element in case of no SG.
*/
- tx_packets = CIRC_CNT(entry + 1, first_tx, priv->dma_conf.dma_tx_size);
+ tx_packets = CIRC_CNT(entry + 1, first_tx, priv->dma_conf->dma_tx_size);
tx_q->tx_count_frames += tx_packets;
if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && priv->hwts_tx_en)
@@ -4997,7 +5019,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
* ndo_start_xmit will fill this descriptor the next time it's
* called and stmmac_tx_clean may clean up to this descriptor.
*/
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_tx_size);
tx_q->cur_tx = entry;
if (netif_msg_pktdata(priv)) {
@@ -5112,7 +5134,7 @@ static void stmmac_rx_vlan(struct net_device *dev, struct sk_buff *skb)
*/
static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
int dirty = stmmac_rx_dirty(priv, queue);
unsigned int entry = rx_q->dirty_rx;
gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN);
@@ -5163,7 +5185,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)
dma_wmb();
stmmac_set_rx_owner(priv, p, use_rx_wd);
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_rx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_rx_size);
}
rx_q->dirty_rx = entry;
stmmac_set_queue_rx_tail_ptr(priv, rx_q, queue, rx_q->dirty_rx);
@@ -5191,12 +5213,12 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv,
/* First descriptor, not last descriptor and not split header */
if (status & rx_not_ls)
- return priv->dma_conf.dma_buf_sz;
+ return priv->dma_conf->dma_buf_sz;
plen = stmmac_get_rx_frame_len(priv, p, coe);
/* First descriptor and last descriptor and not split header */
- return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen);
+ return min_t(unsigned int, priv->dma_conf->dma_buf_sz, plen);
}
static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv,
@@ -5226,7 +5248,7 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv,
/* Not GMAC4 and not last descriptor */
if (priv->plat->core_type != DWMAC_CORE_GMAC4 && (status & rx_not_ls))
- return priv->dma_conf.dma_buf_sz;
+ return priv->dma_conf->dma_buf_sz;
/* GMAC4 or last descriptor */
plen = stmmac_get_rx_frame_len(priv, p, coe);
@@ -5238,7 +5260,7 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
struct xdp_frame *xdpf, bool dma_map)
{
struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[queue];
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
bool csum = !priv->plat->tx_queues_cfg[queue].coe_unsupported;
unsigned int entry = tx_q->cur_tx;
enum stmmac_txbuf_type buf_type;
@@ -5304,7 +5326,7 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
stmmac_enable_dma_transmission(priv, priv->ioaddr, queue);
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_tx_size);
tx_q->cur_tx = entry;
return STMMAC_XDP_TX;
@@ -5495,7 +5517,7 @@ static void stmmac_dispatch_skb_zc(struct stmmac_priv *priv, u32 queue,
static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
{
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
unsigned int entry = rx_q->dirty_rx;
struct dma_desc *rx_desc = NULL;
bool ret = true;
@@ -5535,7 +5557,7 @@ static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
dma_wmb();
stmmac_set_rx_owner(priv, rx_desc, use_rx_wd);
- entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_rx_size);
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf->dma_rx_size);
}
if (rx_desc) {
@@ -5559,7 +5581,7 @@ static struct stmmac_xdp_buff *xsk_buff_to_stmmac_ctx(struct xdp_buff *xdp)
static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
{
struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue];
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
unsigned int count = 0, error = 0, len = 0;
int dirty = stmmac_rx_dirty(priv, queue);
unsigned int next_entry = rx_q->cur_rx;
@@ -5576,7 +5598,7 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
netdev_dbg(priv->dev, "%s: descriptor ring:\n", __func__);
desc_size = stmmac_get_rx_desc_size(priv);
- stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
+ stmmac_display_ring(priv, rx_head, priv->dma_conf->dma_rx_size, true,
rx_q->dma_rx_phy, desc_size);
}
while (count < limit) {
@@ -5620,7 +5642,7 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
/* Prefetch the next RX descriptor */
next_entry = STMMAC_NEXT_ENTRY(rx_q->cur_rx,
- priv->dma_conf.dma_rx_size);
+ priv->dma_conf->dma_rx_size);
if (unlikely(next_entry == rx_q->dirty_rx))
break;
@@ -5745,7 +5767,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
{
u32 rx_errors = 0, rx_dropped = 0, rx_bytes = 0, rx_packets = 0;
struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue];
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
struct stmmac_channel *ch = &priv->channel[queue];
unsigned int count = 0, error = 0, len = 0;
int status = 0, coe = priv->hw->rx_csum;
@@ -5759,7 +5781,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
int bufsz;
dma_dir = page_pool_get_dma_dir(rx_q->page_pool);
- bufsz = DIV_ROUND_UP(priv->dma_conf.dma_buf_sz, PAGE_SIZE) * PAGE_SIZE;
+ bufsz = DIV_ROUND_UP(priv->dma_conf->dma_buf_sz, PAGE_SIZE) * PAGE_SIZE;
if (netif_msg_rx_status(priv)) {
void *rx_head = stmmac_get_rx_desc(priv, rx_q, 0);
@@ -5767,7 +5789,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
netdev_dbg(priv->dev, "%s: descriptor ring:\n", __func__);
desc_size = stmmac_get_rx_desc_size(priv);
- stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
+ stmmac_display_ring(priv, rx_head, priv->dma_conf->dma_rx_size, true,
rx_q->dma_rx_phy, desc_size);
}
if (in_progress) {
@@ -5810,7 +5832,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
break;
next_entry = STMMAC_NEXT_ENTRY(rx_q->cur_rx,
- priv->dma_conf.dma_rx_size);
+ priv->dma_conf->dma_rx_size);
if (unlikely(next_entry == rx_q->dirty_rx))
break;
@@ -5946,7 +5968,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
buf1_len, dma_dir);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
buf->page, buf->page_offset, buf1_len,
- priv->dma_conf.dma_buf_sz);
+ priv->dma_conf->dma_buf_sz);
buf->page = NULL;
}
@@ -5955,7 +5977,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
buf2_len, dma_dir);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
buf->sec_page, 0, buf2_len,
- priv->dma_conf.dma_buf_sz);
+ priv->dma_conf->dma_buf_sz);
buf->sec_page = NULL;
}
@@ -6180,6 +6202,7 @@ static void stmmac_set_rx_mode(struct net_device *dev)
static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ struct stmmac_dma_conf *old_conf = priv->dma_conf;
int txfifosz = priv->plat->tx_fifo_size;
struct stmmac_dma_conf *dma_conf;
const int mtu = new_mtu;
@@ -6216,19 +6239,22 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
return PTR_ERR(dma_conf);
}
+ netif_device_detach(dev);
__stmmac_release(dev);
ret = __stmmac_open(dev, dma_conf);
if (ret) {
+ priv->dma_conf = old_conf;
free_dma_desc_resources(priv, dma_conf);
kfree(dma_conf);
netdev_err(priv->dev, "failed reopening the interface after MTU change\n");
return ret;
}
- kfree(dma_conf);
+ kfree(old_conf);
stmmac_set_rx_mode(dev);
+ netif_device_attach(dev);
}
WRITE_ONCE(dev->mtu, mtu);
@@ -6400,15 +6426,11 @@ static irqreturn_t stmmac_safety_interrupt(int irq, void *dev_id)
static irqreturn_t stmmac_msi_intr_tx(int irq, void *data)
{
- struct stmmac_tx_queue *tx_q = (struct stmmac_tx_queue *)data;
- struct stmmac_dma_conf *dma_conf;
- int chan = tx_q->queue_index;
- struct stmmac_priv *priv;
+ struct stmmac_channel *ch = data;
+ struct stmmac_priv *priv = ch->priv_data;
+ int chan = ch->index;
int status;
- dma_conf = container_of(tx_q, struct stmmac_dma_conf, tx_queue[chan]);
- priv = container_of(dma_conf, struct stmmac_priv, dma_conf);
-
/* Check if adapter is up */
if (test_bit(STMMAC_DOWN, &priv->state))
return IRQ_HANDLED;
@@ -6427,13 +6449,9 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data)
static irqreturn_t stmmac_msi_intr_rx(int irq, void *data)
{
- struct stmmac_rx_queue *rx_q = (struct stmmac_rx_queue *)data;
- struct stmmac_dma_conf *dma_conf;
- int chan = rx_q->queue_index;
- struct stmmac_priv *priv;
-
- dma_conf = container_of(rx_q, struct stmmac_dma_conf, rx_queue[chan]);
- priv = container_of(dma_conf, struct stmmac_priv, dma_conf);
+ struct stmmac_channel *ch = data;
+ struct stmmac_priv *priv = ch->priv_data;
+ int chan = ch->index;
/* Check if adapter is up */
if (test_bit(STMMAC_DOWN, &priv->state))
@@ -6588,45 +6606,48 @@ static int stmmac_rings_status_show(struct seq_file *seq, void *v)
{
struct net_device *dev = seq->private;
struct stmmac_priv *priv = netdev_priv(dev);
- u8 rx_count = priv->plat->rx_queues_to_use;
- u8 tx_count = priv->plat->tx_queues_to_use;
- u8 queue;
+ u8 rx_count, tx_count, queue;
+ rtnl_lock();
if ((dev->flags & IFF_UP) == 0)
- return 0;
+ goto out_unlock;
+ rx_count = priv->plat->rx_queues_to_use;
+ tx_count = priv->plat->tx_queues_to_use;
for (queue = 0; queue < rx_count; queue++) {
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
seq_printf(seq, "RX Queue %d:\n", queue);
if (priv->extend_desc) {
seq_printf(seq, "Extended descriptor ring:\n");
sysfs_display_ring((void *)rx_q->dma_erx,
- priv->dma_conf.dma_rx_size, 1, seq, rx_q->dma_rx_phy);
+ priv->dma_conf->dma_rx_size, 1, seq, rx_q->dma_rx_phy);
} else {
seq_printf(seq, "Descriptor ring:\n");
sysfs_display_ring((void *)rx_q->dma_rx,
- priv->dma_conf.dma_rx_size, 0, seq, rx_q->dma_rx_phy);
+ priv->dma_conf->dma_rx_size, 0, seq, rx_q->dma_rx_phy);
}
}
for (queue = 0; queue < tx_count; queue++) {
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
seq_printf(seq, "TX Queue %d:\n", queue);
if (priv->extend_desc) {
seq_printf(seq, "Extended descriptor ring:\n");
sysfs_display_ring((void *)tx_q->dma_etx,
- priv->dma_conf.dma_tx_size, 1, seq, tx_q->dma_tx_phy);
+ priv->dma_conf->dma_tx_size, 1, seq, tx_q->dma_tx_phy);
} else if (!(tx_q->tbs & STMMAC_TBS_AVAIL)) {
seq_printf(seq, "Descriptor ring:\n");
sysfs_display_ring((void *)tx_q->dma_tx,
- priv->dma_conf.dma_tx_size, 0, seq, tx_q->dma_tx_phy);
+ priv->dma_conf->dma_tx_size, 0, seq, tx_q->dma_tx_phy);
}
}
+out_unlock:
+ rtnl_unlock();
return 0;
}
DEFINE_SHOW_ATTRIBUTE(stmmac_rings_status);
@@ -7038,6 +7059,10 @@ static int stmmac_xdp_xmit(struct net_device *dev, int num_frames,
nq = netdev_get_tx_queue(priv->dev, queue);
__netif_tx_lock(nq, cpu);
+ if (unlikely(!netif_device_present(dev) || netif_tx_queue_stopped(nq))) {
+ __netif_tx_unlock(nq);
+ return -ENETDOWN;
+ }
/* Avoids TX time-out as we are sharing with slow path */
txq_trans_cond_update(nq);
@@ -7071,31 +7096,31 @@ void stmmac_disable_rx_queue(struct stmmac_priv *priv, u32 queue)
spin_unlock_irqrestore(&ch->lock, flags);
stmmac_stop_rx_dma(priv, queue);
- __free_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
+ __free_dma_rx_desc_resources(priv, priv->dma_conf, queue);
}
void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
struct stmmac_channel *ch = &priv->channel[queue];
unsigned long flags;
int ret;
- ret = __alloc_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
+ ret = __alloc_dma_rx_desc_resources(priv, priv->dma_conf, queue);
if (ret) {
netdev_err(priv->dev, "Failed to alloc RX desc.\n");
return;
}
- ret = __init_dma_rx_desc_rings(priv, &priv->dma_conf, queue, GFP_KERNEL);
+ ret = __init_dma_rx_desc_rings(priv, priv->dma_conf, queue, GFP_KERNEL);
if (ret) {
- __free_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
+ __free_dma_rx_desc_resources(priv, priv->dma_conf, queue);
netdev_err(priv->dev, "Failed to init RX desc.\n");
return;
}
stmmac_reset_rx_queue(priv, queue);
- stmmac_clear_rx_descriptors(priv, &priv->dma_conf, queue);
+ stmmac_clear_rx_descriptors(priv, priv->dma_conf, queue);
stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
rx_q->dma_rx_phy, queue);
@@ -7121,31 +7146,31 @@ void stmmac_disable_tx_queue(struct stmmac_priv *priv, u32 queue)
spin_unlock_irqrestore(&ch->lock, flags);
stmmac_stop_tx_dma(priv, queue);
- __free_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
+ __free_dma_tx_desc_resources(priv, priv->dma_conf, queue);
}
void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
struct stmmac_channel *ch = &priv->channel[queue];
unsigned long flags;
int ret;
- ret = __alloc_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
+ ret = __alloc_dma_tx_desc_resources(priv, priv->dma_conf, queue);
if (ret) {
netdev_err(priv->dev, "Failed to alloc TX desc.\n");
return;
}
- ret = __init_dma_tx_desc_rings(priv, &priv->dma_conf, queue);
+ ret = __init_dma_tx_desc_rings(priv, priv->dma_conf, queue);
if (ret) {
- __free_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
+ __free_dma_tx_desc_resources(priv, priv->dma_conf, queue);
netdev_err(priv->dev, "Failed to init TX desc.\n");
return;
}
stmmac_reset_tx_queue(priv, queue);
- stmmac_clear_tx_descriptors(priv, &priv->dma_conf, queue);
+ stmmac_clear_tx_descriptors(priv, priv->dma_conf, queue);
stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
tx_q->dma_tx_phy, queue);
@@ -7165,25 +7190,18 @@ void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue)
void stmmac_xdp_release(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
- u8 chan;
-
- /* Ensure tx function is not running */
- netif_tx_disable(dev);
- /* Disable NAPI process */
- stmmac_disable_all_queues(priv);
-
- for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
- hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+ stmmac_quiesce(priv);
/* Free the IRQ lines */
stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+ stmmac_stop_tx_queues(priv);
/* Stop TX/RX DMA channels */
stmmac_stop_all_dma(priv);
/* Release and free the Rx/Tx resources */
- free_dma_desc_resources(priv, &priv->dma_conf);
+ free_dma_desc_resources(priv, priv->dma_conf);
/* Disable the MAC Rx/Tx */
stmmac_mac_set(priv, priv->ioaddr, false);
@@ -7207,14 +7225,14 @@ int stmmac_xdp_open(struct net_device *dev)
u8 chan;
int ret;
- ret = alloc_dma_desc_resources(priv, &priv->dma_conf);
+ ret = alloc_dma_desc_resources(priv, priv->dma_conf);
if (ret < 0) {
netdev_err(dev, "%s: DMA descriptors allocation failed\n",
__func__);
goto dma_desc_error;
}
- ret = init_dma_desc_rings(dev, &priv->dma_conf, GFP_KERNEL);
+ ret = init_dma_desc_rings(dev, priv->dma_conf, GFP_KERNEL);
if (ret < 0) {
netdev_err(dev, "%s: DMA descriptors initialization failed\n",
__func__);
@@ -7234,7 +7252,7 @@ int stmmac_xdp_open(struct net_device *dev)
/* DMA RX Channel Configuration */
for (chan = 0; chan < rx_cnt; chan++) {
- rx_q = &priv->dma_conf.rx_queue[chan];
+ rx_q = &priv->dma_conf->rx_queue[chan];
stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
rx_q->dma_rx_phy, chan);
@@ -7249,7 +7267,7 @@ int stmmac_xdp_open(struct net_device *dev)
/* DMA TX Channel Configuration */
for (chan = 0; chan < tx_cnt; chan++) {
- tx_q = &priv->dma_conf.tx_queue[chan];
+ tx_q = &priv->dma_conf->tx_queue[chan];
stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
tx_q->dma_tx_phy, chan);
@@ -7279,10 +7297,10 @@ int stmmac_xdp_open(struct net_device *dev)
irq_error:
for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
- hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+ hrtimer_cancel(&priv->dma_conf->tx_queue[chan].txtimer);
init_error:
- free_dma_desc_resources(priv, &priv->dma_conf);
+ free_dma_desc_resources(priv, priv->dma_conf);
dma_desc_error:
return ret;
}
@@ -7290,8 +7308,6 @@ int stmmac_xdp_open(struct net_device *dev)
int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags)
{
struct stmmac_priv *priv = netdev_priv(dev);
- struct stmmac_rx_queue *rx_q;
- struct stmmac_tx_queue *tx_q;
struct stmmac_channel *ch;
if (test_bit(STMMAC_DOWN, &priv->state) ||
@@ -7305,11 +7321,9 @@ int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags)
queue >= priv->plat->tx_queues_to_use)
return -EINVAL;
- rx_q = &priv->dma_conf.rx_queue[queue];
- tx_q = &priv->dma_conf.tx_queue[queue];
ch = &priv->channel[queue];
- if (!rx_q->xsk_pool && !tx_q->xsk_pool)
+ if (!test_bit(queue, priv->af_xdp_zc_qps))
return -EINVAL;
if (!napi_if_scheduled_mark_missed(&ch->rxtx_napi)) {
@@ -7696,8 +7710,8 @@ int stmmac_reinit_ringparam(struct net_device *dev, u32 rx_size, u32 tx_size)
if (netif_running(dev))
stmmac_release(dev);
- priv->dma_conf.dma_rx_size = rx_size;
- priv->dma_conf.dma_tx_size = tx_size;
+ priv->dma_conf->dma_rx_size = rx_size;
+ priv->dma_conf->dma_tx_size = tx_size;
if (netif_running(dev))
ret = stmmac_open(dev);
@@ -7872,6 +7886,13 @@ struct plat_stmmacenet_data *stmmac_plat_dat_alloc(struct device *dev)
}
EXPORT_SYMBOL_GPL(stmmac_plat_dat_alloc);
+static void stmmac_free_dma_conf(void *data)
+{
+ struct stmmac_priv *priv = data;
+
+ kfree(priv->dma_conf);
+}
+
static int __stmmac_dvr_probe(struct device *device,
struct plat_stmmacenet_data *plat_dat,
struct stmmac_resources *res)
@@ -7896,6 +7917,13 @@ static int __stmmac_dvr_probe(struct device *device,
priv = netdev_priv(ndev);
priv->device = device;
priv->dev = ndev;
+ /* Keep ring sizes and per-queue settings even while the device is down. */
+ priv->dma_conf = kzalloc_obj(*priv->dma_conf);
+ if (!priv->dma_conf)
+ return -ENOMEM;
+ ret = devm_add_action_or_reset(device, stmmac_free_dma_conf, priv);
+ if (ret)
+ return ret;
for (i = 0; i < MTL_MAX_RX_QUEUES; i++)
u64_stats_init(&priv->xstats.rxq_stats[i].napi_syncp);
@@ -8258,7 +8286,6 @@ int stmmac_suspend(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct stmmac_priv *priv = netdev_priv(ndev);
- u8 chan;
if (!ndev || !netif_running(ndev))
goto suspend_bsp;
@@ -8267,10 +8294,7 @@ int stmmac_suspend(struct device *dev)
netif_device_detach(ndev);
- stmmac_disable_all_queues(priv);
-
- for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
- hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+ stmmac_quiesce(priv);
if (priv->eee_sw_timer_en) {
priv->tx_path_in_lpi_mode = false;
@@ -8310,7 +8334,7 @@ EXPORT_SYMBOL_GPL(stmmac_suspend);
static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
rx_q->cur_rx = 0;
rx_q->dirty_rx = 0;
@@ -8318,7 +8342,7 @@ static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
tx_q->cur_tx = 0;
tx_q->dirty_tx = 0;
@@ -8401,7 +8425,7 @@ int stmmac_resume(struct device *dev)
stmmac_reset_queues_param(priv);
stmmac_free_tx_skbufs(priv);
- stmmac_clear_descriptors(priv, &priv->dma_conf);
+ stmmac_clear_descriptors(priv, priv->dma_conf);
ret = stmmac_hw_setup(ndev);
if (ret < 0) {
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c
index c25dc9f89270..2511339e55e4 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c
@@ -887,8 +887,8 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv)
struct stmmac_channel *ch = &priv->channel[i];
u32 tail;
- tail = priv->dma_conf.rx_queue[i].dma_rx_phy +
- (priv->dma_conf.dma_rx_size * sizeof(struct dma_desc));
+ tail = priv->dma_conf->rx_queue[i].dma_rx_phy +
+ (priv->dma_conf->dma_rx_size * sizeof(struct dma_desc));
stmmac_set_rx_tail_ptr(priv, priv->ioaddr, tail, i);
stmmac_start_rx(priv, priv->ioaddr, i);
@@ -1787,7 +1787,7 @@ static int stmmac_test_arpoffload(struct stmmac_priv *priv)
static int __stmmac_test_jumbo(struct stmmac_priv *priv, u16 queue)
{
struct stmmac_packet_attrs attr = { };
- int size = priv->dma_conf.dma_buf_sz;
+ int size = priv->dma_conf->dma_buf_sz;
if (!dwmac_is_xmac(priv->plat->core_type))
size -= NET_IP_ALIGN;
@@ -1873,7 +1873,7 @@ static int stmmac_test_tbs(struct stmmac_priv *priv)
/* Find first TBS enabled Queue, if any */
for (i = 0; i < priv->plat->tx_queues_to_use; i++)
- if (priv->dma_conf.tx_queue[i].tbs & STMMAC_TBS_AVAIL)
+ if (priv->dma_conf->tx_queue[i].tbs & STMMAC_TBS_AVAIL)
break;
if (i >= priv->plat->tx_queues_to_use)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
index 42a00446e9b4..bb1dfe2702ac 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
@@ -1197,13 +1197,13 @@ static int tc_setup_etf(struct stmmac_priv *priv,
return -EOPNOTSUPP;
if (qopt->queue >= priv->plat->tx_queues_to_use)
return -EINVAL;
- if (!(priv->dma_conf.tx_queue[qopt->queue].tbs & STMMAC_TBS_AVAIL))
+ if (!(priv->dma_conf->tx_queue[qopt->queue].tbs & STMMAC_TBS_AVAIL))
return -EINVAL;
if (qopt->enable)
- priv->dma_conf.tx_queue[qopt->queue].tbs |= STMMAC_TBS_EN;
+ priv->dma_conf->tx_queue[qopt->queue].tbs |= STMMAC_TBS_EN;
else
- priv->dma_conf.tx_queue[qopt->queue].tbs &= ~STMMAC_TBS_EN;
+ priv->dma_conf->tx_queue[qopt->queue].tbs &= ~STMMAC_TBS_EN;
netdev_info(priv->dev, "%s ETF for Queue %d\n",
qopt->enable ? "enabled" : "disabled", qopt->queue);
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 09/15] net: stmmac: track datapath and power ownership across failed reopening
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (7 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 08/15] net: stmmac: keep DMA configurations at stable addresses James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 10/15] net: stmmac: use the tracked datapath restart for XSK pool changes James Hilliard
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Track allocated rings, IRQs and NAPI independently of IFF_UP and of
system sleep. Leave failed live reopening detached while the successful
ndo_open still owns its PHY attachment and runtime-PM reference;
ordinary down/up can recover without duplicate NAPI shutdown or PM puts.
Convert every live reopen consumer together: MTU, XDP program changes
and ethtool ring/channel changes. Restore old settings on error and
publish queue counts only after setup succeeds. XDP removal must release
its program even if rebuilding the non-XDP datapath fails. Use phylink
link replay for successful XDP rebuilding so the PHY is not stopped or
renegotiated.
Keep core sleep cleanup separate from platform and clock restoration.
Serialize noirq clock ownership and retry pending power transitions
before accessing MMIO. If restoration fails, block MAC, MDIO, PCS, IRQ
and PHC register accesses and finish only software cleanup. Check MAC
WoL viability before skipping an already completed core sleep sequence.
Close must reattach the netdev so ndo_open can retry power restoration.
Reject ethtool, timestamp, MAC-address, feature and qdisc installation
requests while registers remain inaccessible. Drain receive-filter work
under the address lock before power removal. VLAN and qdisc teardown
still clear their software state without touching unpowered registers.
Permit flower and u32 destruction while down or detached, clearing cached
rules without disabling already-stopped NAPI or accessing registers. TC
forgets deleted filters even when the driver returns an error, so rejecting
destruction would leave stale rules to be replayed on recovery. Keep
installation-only feature and RSS checks out of the destruction path.
Guard non-netdev-detach consumers, including TC, descriptor readback,
XDP transmission and reset work. Freeze deferred XSK teardown before
sleep through the prerequisite XSK change. Preserve PHY ownership on
failed ethtool reopen and stop a PHY temporarily resumed to supply the
MAC reset clock.
Fixes: 3470079687448 ("net: ethernet: stmicro: stmmac: permit MTU change with interface up")
Fixes: 6896c2449a18 ("net: stmmac: Check stmmac_hw_setup() in stmmac_resume()")
Fixes: ac746c8520d9 ("net: stmmac: enhance XDP ZC driver level switching performance")
Fixes: aa042f60e496 ("net: stmmac: Add support to Ethtool get/set ring parameters")
Fixes: 0366f7e06a6b ("net: stmmac: add ethtool support for get/set channels")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c | 5 +-
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 29 ++
.../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 11 +
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 557 ++++++++++++++++++---
drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c | 15 +
drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c | 3 +-
.../net/ethernet/stmicro/stmmac/stmmac_platform.c | 38 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 96 ++--
drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 6 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c | 39 +-
10 files changed, 640 insertions(+), 159 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
index 4ede4420c93b..64a55c72c15c 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
@@ -105,8 +105,9 @@ static int __maybe_unused tegra_mgbe_resume(struct device *dev)
}
err = stmmac_resume(dev);
- if (err < 0)
- clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+ /* Core resume failure retains the suspended datapath for retry or
+ * close. Keep its register interface powered until that cleanup.
+ */
return err;
}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 3365ed237953..2fa7b18eb9a4 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -258,6 +258,15 @@ struct stmmac_msi {
char int_name_tx_irq[MTL_MAX_TX_QUEUES][IFNAMSIZ + 18];
};
+enum stmmac_datapath_state {
+ /* No IRQs or DMA allocations owned by a successful open. */
+ STMMAC_DATAPATH_DOWN,
+ /* Resources allocated, NAPI enabled. */
+ STMMAC_DATAPATH_RUNNING,
+ /* Resources retained, NAPI and DMA stopped; also after failed resume. */
+ STMMAC_DATAPATH_SUSPENDED,
+};
+
struct stmmac_priv {
/* Frequently used values are kept adjacent for cache effect */
u32 tx_coal_frames[MTL_MAX_TX_QUEUES];
@@ -281,6 +290,18 @@ struct stmmac_priv {
struct mutex lock;
struct stmmac_dma_conf *dma_conf;
+ /* IRQ/DMA ownership and NAPI state, serialized by RTNL. */
+ enum stmmac_datapath_state datapath;
+ /* Core sleep sequence completed, independently of datapath ownership. */
+ bool hw_suspended;
+ /* System PM blocks MMIO until power restoration has completed. */
+ bool hw_unavailable;
+ bool bsp_suspended;
+ bool bus_clks_suspended;
+ bool ptp_clock_enabled;
+ bool ptp_clock_suspended;
+ /* Clock ownership can also change in noirq PM, without RTNL. */
+ struct mutex pm_mutex;
/* Generic channel for NAPI */
struct stmmac_channel channel[STMMAC_CH_MAX];
@@ -402,6 +423,7 @@ extern const struct dev_pm_ops stmmac_simple_pm_ops;
int stmmac_mdio_unregister(struct net_device *ndev);
int stmmac_mdio_register(struct net_device *ndev);
int stmmac_mdio_reset(struct mii_bus *mii);
+int stmmac_resume_clocks(struct stmmac_priv *priv);
void stmmac_mdio_lock(struct stmmac_priv *priv);
void stmmac_mdio_unlock(struct stmmac_priv *priv);
int stmmac_pcs_setup(struct net_device *ndev);
@@ -432,6 +454,13 @@ static inline bool stmmac_xdp_is_enabled(struct stmmac_priv *priv)
return !!priv->xdp_prog;
}
+/* RTNL serializes TC callbacks with datapath and power transitions. */
+static inline bool stmmac_tc_active(struct stmmac_priv *priv)
+{
+ return priv->datapath == STMMAC_DATAPATH_RUNNING &&
+ netif_device_present(priv->dev) && !priv->hw_unavailable;
+}
+
void stmmac_disable_rx_queue(struct stmmac_priv *priv, u32 queue);
void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue);
void stmmac_disable_tx_queue(struct stmmac_priv *priv, u32 queue);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
index 994350fcdbfa..6742401671ff 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
@@ -1090,7 +1090,18 @@ static void stmmac_get_mm_stats(struct net_device *ndev,
s->MACMergeHoldCount = mmc->mmc_tx_hold_req_cntr;
}
+static int stmmac_ethtool_begin(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ /* Close reattaches the netdev so open can retry power restoration.
+ * Presence alone does not make registers accessible after failed resume.
+ */
+ return priv->hw_unavailable ? -EHOSTDOWN : 0;
+}
+
static const struct ethtool_ops stmmac_ethtool_ops = {
+ .begin = stmmac_ethtool_begin,
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES,
.get_drvinfo = stmmac_ethtool_getdrvinfo,
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index c4ba97d57b47..22a65c1c1681 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -653,6 +653,9 @@ static int stmmac_hwtstamp_set(struct net_device *dev,
u32 ts_master_en = 0;
u32 ts_event_en = 0;
+ if (priv->hw_unavailable)
+ return -EHOSTDOWN;
+
if (!(priv->dma_cap.time_stamp || priv->adv_ts)) {
NL_SET_ERR_MSG_MOD(extack, "No support for HW time stamping");
priv->hwts_tx_en = 0;
@@ -946,12 +949,16 @@ static void stmmac_setup_ptp(struct stmmac_priv *priv)
priv->ptp_extts = 0;
priv->ptp_blocked = false;
+ mutex_lock(&priv->pm_mutex);
ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
- if (ret < 0)
+ priv->ptp_clock_enabled = !ret;
+ mutex_unlock(&priv->pm_mutex);
+ if (ret < 0) {
netdev_warn(priv->dev,
"failed to enable PTP reference clock: %pe\n",
ERR_PTR(ret));
-
+ return;
+ }
if (stmmac_init_timestamping(priv) == 0)
stmmac_ptp_register(priv);
}
@@ -959,7 +966,26 @@ static void stmmac_setup_ptp(struct stmmac_priv *priv)
static void stmmac_release_ptp(struct stmmac_priv *priv)
{
stmmac_ptp_unregister(priv);
- clk_disable_unprepare(priv->plat->clk_ptp_ref);
+ mutex_lock(&priv->pm_mutex);
+ if (priv->ptp_clock_enabled) {
+ clk_disable_unprepare(priv->plat->clk_ptp_ref);
+ priv->ptp_clock_enabled = false;
+ }
+ /* A later noirq resume must not reacquire a released reference. */
+ priv->ptp_clock_suspended = false;
+ mutex_unlock(&priv->pm_mutex);
+}
+
+/* ptp_mutex excludes configuration and crosstimestamp operations. The
+ * spinlock also excludes atomic clock reads while changing this gate.
+ */
+static void stmmac_block_ptp(struct stmmac_priv *priv, bool block)
+{
+ unsigned long flags;
+
+ write_lock_irqsave(&priv->ptp_lock, flags);
+ WRITE_ONCE(priv->ptp_blocked, block);
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
}
static void stmmac_legacy_serdes_power_down(struct stmmac_priv *priv)
@@ -1064,6 +1090,9 @@ static void stmmac_mac_link_down(struct phylink_config *config,
{
struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+ if (READ_ONCE(priv->hw_unavailable))
+ return;
+
stmmac_mac_set(priv, priv->ioaddr, false);
if (priv->dma_cap.eee)
stmmac_set_eee_pls(priv, priv->hw, false);
@@ -1188,10 +1217,12 @@ static void stmmac_mac_disable_tx_lpi(struct phylink_config *config)
netdev_dbg(priv->dev, "disable EEE\n");
priv->eee_sw_timer_en = false;
timer_delete_sync(&priv->eee_ctrl_timer);
- stmmac_set_lpi_mode(priv, priv->hw, STMMAC_LPI_DISABLE, false, 0);
priv->tx_path_in_lpi_mode = false;
- stmmac_set_eee_timer(priv, priv->hw, 0, STMMAC_DEFAULT_TWT_LS);
+ if (!READ_ONCE(priv->hw_unavailable)) {
+ stmmac_set_lpi_mode(priv, priv->hw, STMMAC_LPI_DISABLE, false, 0);
+ stmmac_set_eee_timer(priv, priv->hw, 0, STMMAC_DEFAULT_TWT_LS);
+ }
mutex_unlock(&priv->lock);
}
@@ -3781,10 +3812,6 @@ static int stmmac_hw_setup(struct net_device *dev)
stmmac_enable_tbs(priv, priv->ioaddr, enable, chan);
}
- /* Configure real RX and TX queues */
- netif_set_real_num_rx_queues(dev, priv->plat->rx_queues_to_use);
- netif_set_real_num_tx_queues(dev, priv->plat->tx_queues_to_use);
-
/* Start the ball rolling... */
stmmac_start_all_dma(priv);
@@ -4086,6 +4113,32 @@ static int stmmac_request_irq(struct net_device *dev)
return ret;
}
+/* Drain registered handlers without disabling lines shared by other devices. */
+static void stmmac_synchronize_irq(struct stmmac_priv *priv)
+{
+ struct stmmac_msi *msi = priv->msi;
+ int irq = priv->dev->irq;
+ u32 i;
+
+ synchronize_irq(irq);
+ if (priv->wol_irq > 0 && priv->wol_irq != irq)
+ synchronize_irq(priv->wol_irq);
+ if (priv->sfty_irq > 0 && priv->sfty_irq != irq)
+ synchronize_irq(priv->sfty_irq);
+ if (!msi)
+ return;
+ if (msi->sfty_ce_irq > 0 && msi->sfty_ce_irq != irq)
+ synchronize_irq(msi->sfty_ce_irq);
+ if (msi->sfty_ue_irq > 0 && msi->sfty_ue_irq != irq)
+ synchronize_irq(msi->sfty_ue_irq);
+ for (i = 0; i < priv->plat->rx_queues_to_use; i++)
+ if (msi->rx_irq[i] > 0)
+ synchronize_irq(msi->rx_irq[i]);
+ for (i = 0; i < priv->plat->tx_queues_to_use; i++)
+ if (msi->tx_irq[i] > 0)
+ synchronize_irq(msi->tx_irq[i]);
+}
+
/**
* stmmac_setup_dma_desc - Generate a dma_conf and allocate DMA queue
* @priv: driver private structure
@@ -4164,6 +4217,89 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
return ERR_PTR(ret);
}
+/* The freezer excludes pool teardown and userspace from noirq callbacks.
+ * Outside system sleep these flags are changed under RTNL.
+ */
+int stmmac_resume_clocks(struct stmmac_priv *priv)
+{
+ int ret = 0;
+
+ mutex_lock(&priv->pm_mutex);
+ if (priv->bus_clks_suspended) {
+ ret = pm_runtime_force_resume(priv->device);
+ if (ret)
+ goto out;
+ priv->bus_clks_suspended = false;
+ }
+ if (priv->ptp_clock_suspended) {
+ ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
+ if (ret)
+ goto out;
+ priv->ptp_clock_enabled = true;
+ priv->ptp_clock_suspended = false;
+ }
+out:
+ mutex_unlock(&priv->pm_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmmac_resume_clocks);
+
+static int stmmac_resume_power(struct stmmac_priv *priv, bool system_resume)
+{
+ bool pending = priv->bus_clks_suspended || priv->bsp_suspended ||
+ priv->ptp_clock_suspended;
+ int ret;
+
+ ret = stmmac_resume_clocks(priv);
+ if (ret)
+ return ret;
+ if (priv->bsp_suspended && priv->plat->resume) {
+ ret = priv->plat->resume(priv->device, priv->plat->bsp_priv);
+ if (ret)
+ return ret;
+ priv->bsp_suspended = false;
+ }
+ /* A wrapper which powers the device outside these callbacks must
+ * complete its own resume before core register access is possible.
+ */
+ if (system_resume || pending)
+ WRITE_ONCE(priv->hw_unavailable, false);
+ return priv->hw_unavailable ? -EHOSTDOWN : 0;
+}
+
+/* Finish core sleep state only after its power dependencies are restored. */
+static int stmmac_resume_hw(struct stmmac_priv *priv)
+{
+ int ret;
+
+ if (!priv->hw_suspended)
+ return 0;
+ if (priv->hw_unavailable)
+ return -EHOSTDOWN;
+
+ /* Use the state installed by suspend, not a subsequently changed WoL
+ * setting. Clear PMT even when a different device caused the wakeup.
+ */
+ if (priv->irq_wake) {
+ mutex_lock(&priv->lock);
+ stmmac_pmt(priv, priv->hw, 0);
+ mutex_unlock(&priv->lock);
+ priv->irq_wake = 0;
+ } else {
+ ret = pinctrl_pm_select_default_state(priv->device);
+ if (ret)
+ return ret;
+ if (priv->mii) {
+ ret = stmmac_mdio_reset(priv->mii);
+ if (ret)
+ return ret;
+ }
+ }
+ priv->hw_suspended = false;
+
+ return 0;
+}
+
/**
* __stmmac_open - open entry point of the driver
* @dev : pointer to the device structure.
@@ -4205,28 +4341,42 @@ static int __stmmac_open(struct net_device *dev,
stmmac_init_coalesce(priv);
- phylink_start(priv->phylink);
-
stmmac_vlan_restore(priv);
ret = stmmac_request_irq(dev);
if (ret)
goto irq_error;
+ /* Publish the topology only when no other fallible setup remains.
+ * The combined setter restores the old counts if an increase fails.
+ */
+ ret = netif_set_real_num_queues(dev, priv->plat->tx_queues_to_use,
+ priv->plat->rx_queues_to_use);
+ if (ret) {
+ stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+ goto irq_error;
+ }
+
stmmac_enable_all_queues(priv);
netif_tx_start_all_queues(priv->dev);
stmmac_enable_all_dma_irq(priv);
+ priv->datapath = STMMAC_DATAPATH_RUNNING;
+ phylink_start(priv->phylink);
return 0;
irq_error:
- phylink_stop(priv->phylink);
-
for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
hrtimer_cancel(&priv->dma_conf->tx_queue[chan].txtimer);
stmmac_release_ptp(priv);
init_error:
+ /* Undo phylink_prepare_resume() even if hardware setup failed before
+ * phylink_start(). Keep the PHY attachment and outer PM ownership.
+ */
+ phylink_stop(priv->phylink);
+ stmmac_stop_all_dma(priv);
+ stmmac_mac_set(priv, priv->ioaddr, false);
return ret;
}
@@ -4249,6 +4399,13 @@ static int stmmac_open(struct net_device *dev)
if (ret < 0)
goto err_dma_resources;
+ ret = stmmac_resume_power(priv, false);
+ if (ret)
+ goto err_runtime_pm;
+ ret = stmmac_resume_hw(priv);
+ if (ret)
+ goto err_runtime_pm;
+
ret = stmmac_init_phy(dev);
if (ret)
goto err_runtime_pm;
@@ -4312,23 +4469,36 @@ static void __stmmac_release(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ /* A failed MTU reopen has already released the data path. */
+ if (priv->datapath == STMMAC_DATAPATH_DOWN)
+ return;
+
phylink_stop(priv->phylink);
- stmmac_quiesce(priv);
+
+ /* Suspend retains the resources, but has already stopped activity. */
+ if (priv->datapath == STMMAC_DATAPATH_RUNNING)
+ stmmac_quiesce(priv);
+ priv->datapath = STMMAC_DATAPATH_DOWN;
/* Free the IRQ lines */
stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+
/* TX error IRQs can restart a queue after the first quiescence. */
stmmac_stop_tx_queues(priv);
- /* Stop TX/RX DMA and clear the descriptors */
- stmmac_stop_all_dma(priv);
+ /* Stop TX/RX DMA after draining IRQ handlers which can restart it. */
+ if (!priv->hw_unavailable) {
+ stmmac_stop_all_dma(priv);
+ /* Link resolution need not have reached mac_link_up() yet. */
+ stmmac_mac_set(priv, priv->ioaddr, false);
+ }
/* Release and free the Rx/Tx resources */
free_dma_desc_resources(priv, priv->dma_conf);
stmmac_release_ptp(priv);
- if (stmmac_fpe_supported(priv))
+ if (!priv->hw_unavailable && stmmac_fpe_supported(priv))
ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
}
@@ -4341,6 +4511,17 @@ static void __stmmac_release(struct net_device *dev)
static int stmmac_release(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ int ret;
+
+ /* Resume may have failed before restoring pins or disabling MAC wake.
+ * Complete that cleanup without restarting the link or the datapath.
+ * If it fails, keep hw_suspended set so a fresh open can retry it.
+ */
+ ret = stmmac_resume_power(priv, false);
+ if (!ret)
+ ret = stmmac_resume_hw(priv);
+ if (ret)
+ netdev_err(dev, "failed to restore hardware sleep state: %d\n", ret);
/* If the PHY or MAC has WoL enabled, then the PHY will not be
* suspended when phylink_stop() is called below. Set the PHY
@@ -4354,6 +4535,8 @@ static int stmmac_release(struct net_device *dev)
stmmac_legacy_serdes_power_down(priv);
phylink_disconnect_phy(priv->phylink);
pm_runtime_put(priv->device);
+ /* Allow a fresh open after a failed MTU reopen or resume. */
+ netif_device_attach(dev);
return 0;
}
@@ -6185,6 +6368,9 @@ static void stmmac_set_rx_mode(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ if (READ_ONCE(priv->hw_unavailable))
+ return;
+
stmmac_set_filter(priv, priv->hw, dev);
}
@@ -6247,6 +6433,11 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
priv->dma_conf = old_conf;
free_dma_desc_resources(priv, dma_conf);
kfree(dma_conf);
+ /*
+ * Keep the administrative state and PHY/PM ownership until
+ * ndo_stop(), but prevent use of the released data path.
+ */
+ netif_device_detach(dev);
netdev_err(priv->dev, "failed reopening the interface after MTU change\n");
return ret;
}
@@ -6290,6 +6481,9 @@ static int stmmac_set_features(struct net_device *netdev,
{
struct stmmac_priv *priv = netdev_priv(netdev);
+ if (priv->hw_unavailable)
+ return -EHOSTDOWN;
+
/* Keep the COE Type in case of csum is supporting */
if (features & NETIF_F_RXCSUM)
priv->hw->rx_csum = priv->plat->rx_coe;
@@ -6377,6 +6571,12 @@ static irqreturn_t stmmac_interrupt(int irq, void *dev_id)
struct net_device *dev = (struct net_device *)dev_id;
struct stmmac_priv *priv = netdev_priv(dev);
+ if (READ_ONCE(priv->hw_unavailable)) {
+ if (priv->irq_wake)
+ pm_wakeup_event(priv->device, 0);
+ return IRQ_NONE;
+ }
+
/* Check if adapter is up */
if (test_bit(STMMAC_DOWN, &priv->state))
return IRQ_HANDLED;
@@ -6399,6 +6599,12 @@ static irqreturn_t stmmac_mac_interrupt(int irq, void *dev_id)
struct net_device *dev = (struct net_device *)dev_id;
struct stmmac_priv *priv = netdev_priv(dev);
+ if (READ_ONCE(priv->hw_unavailable)) {
+ if (priv->irq_wake)
+ pm_wakeup_event(priv->device, 0);
+ return IRQ_NONE;
+ }
+
/* Check if adapter is up */
if (test_bit(STMMAC_DOWN, &priv->state))
return IRQ_HANDLED;
@@ -6414,6 +6620,9 @@ static irqreturn_t stmmac_safety_interrupt(int irq, void *dev_id)
struct net_device *dev = (struct net_device *)dev_id;
struct stmmac_priv *priv = netdev_priv(dev);
+ if (READ_ONCE(priv->hw_unavailable))
+ return IRQ_NONE;
+
/* Check if adapter is up */
if (test_bit(STMMAC_DOWN, &priv->state))
return IRQ_HANDLED;
@@ -6431,6 +6640,9 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data)
int chan = ch->index;
int status;
+ if (READ_ONCE(priv->hw_unavailable))
+ return IRQ_NONE;
+
/* Check if adapter is up */
if (test_bit(STMMAC_DOWN, &priv->state))
return IRQ_HANDLED;
@@ -6453,6 +6665,9 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data)
struct stmmac_priv *priv = ch->priv_data;
int chan = ch->index;
+ if (READ_ONCE(priv->hw_unavailable))
+ return IRQ_NONE;
+
/* Check if adapter is up */
if (test_bit(STMMAC_DOWN, &priv->state))
return IRQ_HANDLED;
@@ -6484,13 +6699,40 @@ static int stmmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
static int stmmac_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
void *cb_priv)
{
+ struct flow_cls_common_offload *common = type_data;
struct stmmac_priv *priv = cb_priv;
+ bool active = stmmac_tc_active(priv);
int ret = -EOPNOTSUPP;
+ bool destroy;
+
+ switch (type) {
+ case TC_SETUP_CLSU32:
+ destroy = ((struct tc_cls_u32_offload *)type_data)->command ==
+ TC_CLSU32_DELETE_KNODE;
+ break;
+ case TC_SETUP_CLSFLOWER:
+ destroy = ((struct flow_cls_offload *)type_data)->command ==
+ FLOW_CLS_DESTROY;
+ break;
+ default:
+ return ret;
+ }
- if (!tc_cls_can_offload_and_chain0(priv->dev, type_data))
+ if (common->chain_index) {
+ NL_SET_ERR_MSG(common->extack, "Driver supports only offload of chain 0");
+ return ret;
+ }
+ if (!destroy && !tc_can_offload_extack(priv->dev, common->extack))
return ret;
+ if (!destroy && !active)
+ return -ENETDOWN;
- __stmmac_disable_all_queues(priv);
+ /* TC discards deleted filters regardless of the callback result. Clear
+ * their cached state even while down or detached, without touching NAPI
+ * or inaccessible registers. Recovery must not replay deleted rules.
+ */
+ if (active)
+ __stmmac_disable_all_queues(priv);
switch (type) {
case TC_SETUP_CLSU32:
@@ -6503,7 +6745,8 @@ static int stmmac_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
break;
}
- stmmac_enable_all_queues(priv);
+ if (active)
+ stmmac_enable_all_queues(priv);
return ret;
}
@@ -6558,6 +6801,9 @@ static int stmmac_set_mac_address(struct net_device *ndev, void *addr)
struct stmmac_priv *priv = netdev_priv(ndev);
int ret = 0;
+ if (priv->hw_unavailable)
+ return -EHOSTDOWN;
+
ret = pm_runtime_resume_and_get(priv->device);
if (ret < 0)
return ret;
@@ -6609,8 +6855,9 @@ static int stmmac_rings_status_show(struct seq_file *seq, void *v)
u8 rx_count, tx_count, queue;
rtnl_lock();
- if ((dev->flags & IFF_UP) == 0)
+ if (priv->datapath == STMMAC_DATAPATH_DOWN)
goto out_unlock;
+
rx_count = priv->plat->rx_queues_to_use;
tx_count = priv->plat->tx_queues_to_use;
@@ -6924,7 +7171,7 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double)
hash = 0;
}
- if (!netif_running(priv->dev))
+ if (!netif_running(priv->dev) || priv->hw_unavailable)
return 0;
return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double);
@@ -6940,6 +7187,9 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid
bool is_double = false;
int ret;
+ if (priv->hw_unavailable)
+ return -EHOSTDOWN;
+
ret = pm_runtime_resume_and_get(priv->device);
if (ret < 0)
return ret;
@@ -6982,9 +7232,12 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi
bool is_double = false;
int ret;
- ret = pm_runtime_resume_and_get(priv->device);
- if (ret < 0)
- return ret;
+ /* Removal must update the cached filters even if power cannot return. */
+ if (!priv->hw_unavailable) {
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ return ret;
+ }
if (be16_to_cpu(proto) == ETH_P_8021AD)
is_double = true;
@@ -7009,7 +7262,8 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi
priv->num_double_vlans = num_double_vlans;
del_vlan_error:
- pm_runtime_put(priv->device);
+ if (!priv->hw_unavailable)
+ pm_runtime_put(priv->device);
return ret;
}
@@ -7029,6 +7283,18 @@ static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ if (bpf->command != XDP_SETUP_PROG &&
+ bpf->command != XDP_SETUP_XSK_POOL)
+ return -EOPNOTSUPP;
+
+ /*
+ * Pool removal must succeed even after a failed resume. Release the
+ * suspended rings before their pool or XDP buffer layout can change.
+ * Leave the interface detached until it is closed and reopened.
+ */
+ if (priv->datapath == STMMAC_DATAPATH_SUSPENDED)
+ __stmmac_release(dev);
+
switch (bpf->command) {
case XDP_SETUP_PROG:
return stmmac_xdp_set_prog(priv, bpf->prog, bpf->extack);
@@ -7191,7 +7457,13 @@ void stmmac_xdp_release(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ netif_device_detach(dev);
+ /* Pause MAC/PCS resolution, but keep the PHY and negotiation running.
+ * stmmac_xdp_open() completes this replay under the same RTNL lock.
+ */
+ phylink_replay_link_begin(priv->phylink);
stmmac_quiesce(priv);
+ priv->datapath = STMMAC_DATAPATH_DOWN;
/* Free the IRQ lines */
stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
@@ -7210,7 +7482,13 @@ void stmmac_xdp_release(struct net_device *dev)
* watchdogs during reset
*/
netif_trans_update(dev);
- netif_carrier_off(dev);
+
+ if (stmmac_fpe_supported(priv))
+ ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
+
+ /* Keep PTP across the immediately following stmmac_xdp_open(). That
+ * function releases it if reopening fails, before returning DOWN.
+ */
}
int stmmac_xdp_open(struct net_device *dev)
@@ -7289,19 +7567,25 @@ int stmmac_xdp_open(struct net_device *dev)
/* Enable NAPI process*/
stmmac_enable_all_queues(priv);
- netif_carrier_on(dev);
- netif_tx_start_all_queues(dev);
stmmac_enable_all_dma_irq(priv);
+ priv->datapath = STMMAC_DATAPATH_RUNNING;
+ phylink_replay_link_end(priv->phylink);
+ netif_device_attach(dev);
return 0;
irq_error:
- for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
- hrtimer_cancel(&priv->dma_conf->tx_queue[chan].txtimer);
+ stmmac_stop_tx_queues(priv);
+ stmmac_stop_all_dma(priv);
+ stmmac_mac_set(priv, priv->ioaddr, false);
init_error:
free_dma_desc_resources(priv, priv->dma_conf);
dma_desc_error:
+ /* STOPPED keeps replay_end() from reconfiguring a failed MAC. */
+ phylink_stop(priv->phylink);
+ phylink_replay_link_end(priv->phylink);
+ stmmac_release_ptp(priv);
return ret;
}
@@ -7424,6 +7708,9 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv)
netdev_err(priv->dev, "Reset adapter.\n");
rtnl_lock();
+ if (!netif_device_present(priv->dev))
+ goto out_unlock;
+
netif_trans_update(priv->dev);
while (test_and_set_bit(STMMAC_RESETING, &priv->state))
usleep_range(1000, 2000);
@@ -7433,6 +7720,7 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv)
dev_open(priv->dev, NULL);
clear_bit(STMMAC_DOWN, &priv->state);
clear_bit(STMMAC_RESETING, &priv->state);
+out_unlock:
rtnl_unlock();
}
@@ -7677,13 +7965,37 @@ static void stmmac_napi_del(struct net_device *dev)
}
}
-int stmmac_reinit_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt)
+/* Rebuild only the datapath. The administratively-up device still owns its
+ * PHY attachment and runtime-PM reference, even if this reopen fails.
+ */
+static int stmmac_reopen(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
- int ret = 0, i;
+ struct stmmac_dma_conf *old_conf = priv->dma_conf;
+ struct stmmac_dma_conf *dma_conf;
+ int ret;
- if (netif_running(dev))
- stmmac_release(dev);
+ dma_conf = stmmac_setup_dma_desc(priv, dev->mtu);
+ if (IS_ERR(dma_conf))
+ return PTR_ERR(dma_conf);
+
+ ret = __stmmac_open(dev, dma_conf);
+ if (ret) {
+ priv->dma_conf = old_conf;
+ free_dma_desc_resources(priv, dma_conf);
+ kfree(dma_conf);
+ return ret;
+ }
+
+ kfree(old_conf);
+ netif_device_attach(dev);
+ return 0;
+}
+
+static void stmmac_set_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int i;
stmmac_napi_del(dev);
@@ -7695,9 +8007,31 @@ int stmmac_reinit_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt)
rx_cnt);
stmmac_napi_add(dev);
+}
+
+int stmmac_reinit_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 old_rx = priv->plat->rx_queues_to_use;
+ u8 old_tx = priv->plat->tx_queues_to_use;
+ int ret = 0;
+
+ if (netif_running(dev)) {
+ if (!netif_device_present(dev))
+ return -ENETDOWN;
+ netif_device_detach(dev);
+ __stmmac_release(dev);
+ }
+
+ stmmac_set_queues(dev, rx_cnt, tx_cnt);
if (netif_running(dev))
- ret = stmmac_open(dev);
+ ret = stmmac_reopen(dev);
+ if (ret) {
+ stmmac_set_queues(dev, old_rx, old_tx);
+ netdev_err(dev, "failed reopening after channel change: %pe; interface remains detached\n",
+ ERR_PTR(ret));
+ }
return ret;
}
@@ -7705,16 +8039,28 @@ int stmmac_reinit_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt)
int stmmac_reinit_ringparam(struct net_device *dev, u32 rx_size, u32 tx_size)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ u32 old_rx = priv->dma_conf->dma_rx_size;
+ u32 old_tx = priv->dma_conf->dma_tx_size;
int ret = 0;
- if (netif_running(dev))
- stmmac_release(dev);
+ if (netif_running(dev)) {
+ if (!netif_device_present(dev))
+ return -ENETDOWN;
+ netif_device_detach(dev);
+ __stmmac_release(dev);
+ }
priv->dma_conf->dma_rx_size = rx_size;
priv->dma_conf->dma_tx_size = tx_size;
if (netif_running(dev))
- ret = stmmac_open(dev);
+ ret = stmmac_reopen(dev);
+ if (ret) {
+ priv->dma_conf->dma_rx_size = old_rx;
+ priv->dma_conf->dma_tx_size = old_tx;
+ netdev_err(dev, "failed reopening after ring change: %pe; interface remains detached\n",
+ ERR_PTR(ret));
+ }
return ret;
}
@@ -8131,6 +8477,7 @@ static int __stmmac_dvr_probe(struct device *device,
stmmac_napi_add(ndev);
mutex_init(&priv->lock);
+ mutex_init(&priv->pm_mutex);
rwlock_init(&priv->ptp_lock);
mutex_init(&priv->ptp_mutex);
@@ -8278,30 +8625,43 @@ EXPORT_SYMBOL_GPL(stmmac_dvr_remove);
/**
* stmmac_suspend - suspend callback
* @dev: device pointer
- * Description: this is the function to suspend the device and it is called
- * by the platform driver to stop the network queue, release the resources,
- * program the PMT register (for WoL), clean and release driver resources.
+ * Description: stop network activity and program hardware for system sleep,
+ * preserving any datapath resources still owned for resume or close.
*/
int stmmac_suspend(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct stmmac_priv *priv = netdev_priv(ndev);
+ bool accessible;
+ int ret = 0;
- if (!ndev || !netif_running(ndev))
+ rtnl_lock();
+ if (!netif_running(ndev))
+ goto suspend_bsp;
+
+ /* A failed datapath cannot provide a working MAC wake path. It may
+ * even have released its wake IRQ. Do not silently suspend without WoL.
+ */
+ if (priv->wolopts && priv->datapath != STMMAC_DATAPATH_RUNNING) {
+ netdev_err(ndev, "cannot suspend failed datapath with MAC WoL enabled\n");
+ rtnl_unlock();
+ return -EBUSY;
+ }
+ if (priv->hw_suspended)
goto suspend_bsp;
mutex_lock(&priv->lock);
netif_device_detach(ndev);
- stmmac_quiesce(priv);
+ if (priv->datapath == STMMAC_DATAPATH_RUNNING)
+ stmmac_quiesce(priv);
if (priv->eee_sw_timer_en) {
priv->tx_path_in_lpi_mode = false;
timer_delete_sync(&priv->eee_ctrl_timer);
}
- /* Stop TX/RX DMA */
stmmac_stop_all_dma(priv);
stmmac_legacy_serdes_power_down(priv);
@@ -8317,18 +8677,41 @@ int stmmac_suspend(struct device *dev)
mutex_unlock(&priv->lock);
- rtnl_lock();
phylink_suspend(priv->phylink, !!priv->wolopts);
- rtnl_unlock();
+ if (priv->datapath == STMMAC_DATAPATH_RUNNING)
+ priv->datapath = STMMAC_DATAPATH_SUSPENDED;
+ priv->hw_suspended = true;
if (stmmac_fpe_supported(priv))
ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
suspend_bsp:
- if (priv->plat->suspend)
- return priv->plat->suspend(dev, priv->plat->bsp_priv);
+ accessible = !priv->hw_unavailable;
+ mutex_lock(&priv->ptp_mutex);
+ stmmac_block_ptp(priv, true);
+ mutex_unlock(&priv->ptp_mutex);
+ /* Drain MDIO transactions before the platform can remove power. */
+ if (priv->mii)
+ mutex_lock(&priv->mii->mdio_lock);
+ /* ndo_set_rx_mode() can run without RTNL under the address lock. */
+ netif_addr_lock_bh(ndev);
+ WRITE_ONCE(priv->hw_unavailable, true);
+ netif_addr_unlock_bh(ndev);
+ if (priv->mii)
+ mutex_unlock(&priv->mii->mdio_lock);
+ if (priv->datapath == STMMAC_DATAPATH_SUSPENDED) {
+ stmmac_synchronize_irq(priv);
+ stmmac_stop_tx_queues(priv);
+ if (accessible)
+ stmmac_stop_all_dma(priv);
+ }
+ if (priv->plat->suspend && !priv->bsp_suspended) {
+ priv->bsp_suspended = true;
+ ret = priv->plat->suspend(dev, priv->plat->bsp_priv);
+ }
+ rtnl_unlock();
- return 0;
+ return ret;
}
EXPORT_SYMBOL_GPL(stmmac_suspend);
@@ -8380,41 +8763,39 @@ int stmmac_resume(struct device *dev)
struct stmmac_priv *priv = netdev_priv(ndev);
int ret;
- if (priv->plat->resume) {
- ret = priv->plat->resume(dev, priv->plat->bsp_priv);
- if (ret)
- return ret;
+ rtnl_lock();
+ ret = stmmac_resume_power(priv, true);
+ if (ret)
+ goto out_unlock;
+
+ if (!netif_running(ndev)) {
+ ret = 0;
+ goto out_unlock;
}
- if (!netif_running(ndev))
- return 0;
+ if (priv->hw_suspended) {
+ ret = stmmac_resume_hw(priv);
+ if (ret)
+ goto out_unlock;
- /* Power Down bit, into the PM register, is cleared
- * automatically as soon as a magic packet or a Wake-up frame
- * is received. Anyway, it's better to manually clear
- * this bit because it can generate problems while resuming
- * from another devices (e.g. serial console).
- */
- if (priv->wolopts) {
- mutex_lock(&priv->lock);
- stmmac_pmt(priv, priv->hw, 0);
- mutex_unlock(&priv->lock);
- priv->irq_wake = 0;
- } else {
- pinctrl_pm_select_default_state(priv->device);
- /* reset the phy so that it's ready */
- if (priv->mii)
- stmmac_mdio_reset(priv->mii);
+ /* Terminate PM speed control without restarting a datapath
+ * whose IRQs or rings were released before system sleep.
+ */
+ if (priv->datapath != STMMAC_DATAPATH_SUSPENDED)
+ phylink_stop(priv->phylink);
+ }
+
+ if (priv->datapath != STMMAC_DATAPATH_SUSPENDED) {
+ ret = 0;
+ goto out_unlock;
}
if (!(priv->plat->flags & STMMAC_FLAG_SERDES_UP_AFTER_PHY_LINKUP)) {
ret = stmmac_legacy_serdes_power_up(priv);
if (ret < 0)
- return ret;
+ goto out_unlock;
}
- rtnl_lock();
-
/* Prepare the PHY to resume, ensuring that its clocks which are
* necessary for the MAC DMA reset to complete are running
*/
@@ -8430,13 +8811,13 @@ int stmmac_resume(struct device *dev)
ret = stmmac_hw_setup(ndev);
if (ret < 0) {
netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
- stmmac_legacy_serdes_power_down(priv);
- mutex_unlock(&priv->lock);
- rtnl_unlock();
- return ret;
+ goto error_stop_dma;
}
stmmac_init_timestamping(priv);
+ mutex_lock(&priv->ptp_mutex);
+ stmmac_block_ptp(priv, false);
+ mutex_unlock(&priv->ptp_mutex);
stmmac_init_coalesce(priv);
phylink_rx_clk_stop_block(priv->phylink);
@@ -8455,11 +8836,25 @@ int stmmac_resume(struct device *dev)
* workqueue thread, which will race with initialisation.
*/
phylink_resume(priv->phylink);
- rtnl_unlock();
-
+ priv->datapath = STMMAC_DATAPATH_RUNNING;
netif_device_attach(ndev);
+ rtnl_unlock();
return 0;
+
+error_stop_dma:
+ stmmac_stop_all_dma(priv);
+ stmmac_mac_set(priv, priv->ioaddr, false);
+ stmmac_legacy_serdes_power_down(priv);
+ mutex_unlock(&priv->lock);
+ /*
+ * Keep the suspended data path detached. A later resume may retry, or
+ * ndo_stop() can release its resources without disabling NAPI again.
+ */
+out_unlock:
+ rtnl_unlock();
+
+ return ret;
}
EXPORT_SYMBOL_GPL(stmmac_resume);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
index 07d5eab05996..136f1f74af57 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
@@ -135,6 +135,9 @@ static int stmmac_xgmac2_mdio_read_c22(struct mii_bus *bus, int phyaddr,
struct stmmac_priv *priv = netdev_priv(bus->priv);
u32 addr;
+ if (READ_ONCE(priv->hw_unavailable))
+ return -EHOSTDOWN;
+
/* Until ver 2.20 XGMAC does not support C22 addr >= 4 */
if (priv->synopsys_id < DWXGMAC_CORE_2_20 &&
phyaddr > MII_XGMAC_MAX_C22ADDR)
@@ -151,6 +154,9 @@ static int stmmac_xgmac2_mdio_read_c45(struct mii_bus *bus, int phyaddr,
struct stmmac_priv *priv = netdev_priv(bus->priv);
u32 addr;
+ if (READ_ONCE(priv->hw_unavailable))
+ return -EHOSTDOWN;
+
stmmac_xgmac2_c45_format(priv, phyaddr, devad, phyreg, &addr);
return stmmac_xgmac2_mdio_read(priv, addr, MII_XGMAC_BUSY);
@@ -198,6 +204,9 @@ static int stmmac_xgmac2_mdio_write_c22(struct mii_bus *bus, int phyaddr,
struct stmmac_priv *priv = netdev_priv(bus->priv);
u32 addr;
+ if (READ_ONCE(priv->hw_unavailable))
+ return -EHOSTDOWN;
+
/* Until ver 2.20 XGMAC does not support C22 addr >= 4 */
if (priv->synopsys_id < DWXGMAC_CORE_2_20 &&
phyaddr > MII_XGMAC_MAX_C22ADDR)
@@ -215,6 +224,9 @@ static int stmmac_xgmac2_mdio_write_c45(struct mii_bus *bus, int phyaddr,
struct stmmac_priv *priv = netdev_priv(bus->priv);
u32 addr;
+ if (READ_ONCE(priv->hw_unavailable))
+ return -EHOSTDOWN;
+
stmmac_xgmac2_c45_format(priv, phyaddr, devad, phyreg, &addr);
return stmmac_xgmac2_mdio_write(priv, addr, MII_XGMAC_BUSY,
@@ -248,6 +260,9 @@ static int stmmac_mdio_access(struct stmmac_priv *priv, unsigned int pa,
u32 addr;
int ret;
+ if (READ_ONCE(priv->hw_unavailable))
+ return -EHOSTDOWN;
+
ret = pm_runtime_resume_and_get(priv->device);
if (ret < 0)
return ret;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c
index df37af5ab837..7de83fb8f06f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c
@@ -60,7 +60,8 @@ static void dwmac_integrated_pcs_disable(struct phylink_pcs *pcs)
{
struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
- stmmac_mac_irq_modify(spcs->priv, spcs->int_mask, 0);
+ if (!READ_ONCE(spcs->priv->hw_unavailable))
+ stmmac_mac_irq_modify(spcs->priv, spcs->int_mask, 0);
}
static void dwmac_integrated_pcs_get_state(struct phylink_pcs *pcs,
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
index 6128ed1bd521..0d914d69379e 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
@@ -957,14 +957,23 @@ static int __maybe_unused stmmac_pltfr_noirq_suspend(struct device *dev)
if (!netif_running(ndev))
return 0;
- if (!priv->wolopts) {
- /* Disable clock in case of PWM is off */
- clk_disable_unprepare(priv->plat->clk_ptp_ref);
+ mutex_lock(&priv->pm_mutex);
+ if (!priv->irq_wake) {
+ /* A detached datapath may already have released its PTP clock. */
+ if (priv->ptp_clock_enabled) {
+ clk_disable_unprepare(priv->plat->clk_ptp_ref);
+ priv->ptp_clock_enabled = false;
+ priv->ptp_clock_suspended = true;
+ }
+ priv->bus_clks_suspended = true;
ret = pm_runtime_force_suspend(dev);
- if (ret)
+ if (ret) {
+ mutex_unlock(&priv->pm_mutex);
return ret;
+ }
}
+ mutex_unlock(&priv->pm_mutex);
return 0;
}
@@ -973,27 +982,8 @@ static int __maybe_unused stmmac_pltfr_noirq_resume(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct stmmac_priv *priv = netdev_priv(ndev);
- int ret;
-
- if (!netif_running(ndev))
- return 0;
- if (!priv->wolopts) {
- /* enable the clk previously disabled */
- ret = pm_runtime_force_resume(dev);
- if (ret)
- return ret;
-
- ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
- if (ret < 0) {
- netdev_warn(priv->dev,
- "failed to enable PTP reference clock: %pe\n",
- ERR_PTR(ret));
- return ret;
- }
- }
-
- return 0;
+ return stmmac_resume_clocks(priv);
}
const struct dev_pm_ops stmmac_pltfr_pm_ops = {
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
index bb1dfe2702ac..9ded65750e89 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
@@ -11,6 +11,17 @@
#include "dwmac5.h"
#include "stmmac.h"
+static int tc_config_preemption(struct stmmac_priv *priv,
+ struct netlink_ext_ack *extack, u32 preemptible_tcs)
+{
+ /* Qdisc teardown must not access unpowered registers. */
+ if (priv->hw_unavailable)
+ return 0;
+
+ return stmmac_fpe_map_preemption_class(priv, priv->dev, extack,
+ preemptible_tcs);
+}
+
static void tc_fill_all_pass_entry(struct stmmac_tc_entry *entry)
{
memset(entry, 0, sizeof(*entry));
@@ -171,18 +182,16 @@ static int tc_fill_entry(struct stmmac_priv *priv,
static void tc_unfill_entry(struct stmmac_priv *priv,
struct tc_cls_u32_offload *cls)
{
- struct stmmac_tc_entry *entry;
+ struct stmmac_tc_entry *entry, *frag;
entry = tc_find_entry(priv, cls, false);
if (!entry)
return;
- entry->in_use = false;
- if (entry->frag_ptr) {
- entry = entry->frag_ptr;
- entry->is_frag = false;
- entry->in_use = false;
- }
+ frag = entry->frag_ptr;
+ if (frag)
+ memset(frag, 0, sizeof(*frag));
+ memset(entry, 0, sizeof(*entry));
}
static int tc_config_knode(struct stmmac_priv *priv,
@@ -212,6 +221,9 @@ static int tc_delete_knode(struct stmmac_priv *priv,
/* Set entry and fragments as not used */
tc_unfill_entry(priv, cls);
+ if (!stmmac_tc_active(priv))
+ return 0;
+
return stmmac_rxp_config(priv, priv->hw->pcsr, priv->tc_entries,
priv->tc_entries_max);
}
@@ -345,6 +357,8 @@ static int tc_setup_cbs(struct stmmac_priv *priv,
return -EINVAL;
if (!priv->dma_cap.av)
return -EOPNOTSUPP;
+ if (priv->hw_unavailable && qopt->enable)
+ return -EHOSTDOWN;
port_transmit_rate_kbps = qopt->idleslope - qopt->sendslope;
@@ -380,10 +394,12 @@ static int tc_setup_cbs(struct stmmac_priv *priv,
priv->plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_AVB;
} else if (!qopt->enable) {
- ret = stmmac_dma_qmode(priv, priv->ioaddr, queue,
- MTL_QUEUE_DCB);
- if (ret)
- return ret;
+ if (!priv->hw_unavailable) {
+ ret = stmmac_dma_qmode(priv, priv->ioaddr, queue,
+ MTL_QUEUE_DCB);
+ if (ret)
+ return ret;
+ }
priv->plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
return 0;
@@ -645,23 +661,21 @@ static int tc_del_flow(struct stmmac_priv *priv,
struct flow_cls_offload *cls)
{
struct stmmac_flow_entry *entry = tc_find_flow(priv, cls, false);
- int ret;
+ int ret = 0;
if (!entry || !entry->in_use)
return -ENOENT;
- if (entry->is_l4) {
- ret = stmmac_config_l4_filter(priv, priv->hw, entry->idx, false,
- false, false, false, 0);
- } else {
- ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, false,
- false, false, false, 0);
+ if (stmmac_tc_active(priv)) {
+ if (entry->is_l4)
+ ret = stmmac_config_l4_filter(priv, priv->hw, entry->idx,
+ false, false, false, false, 0);
+ else
+ ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx,
+ false, false, false, false, 0);
}
- entry->in_use = false;
- entry->cookie = 0;
- entry->is_l4 = false;
- entry->action = 0;
+ *entry = (struct stmmac_flow_entry) { .idx = entry->idx };
return ret;
}
@@ -744,7 +758,8 @@ static int tc_del_vlan_flow(struct stmmac_priv *priv,
if (!entry || !entry->in_use || entry->type != STMMAC_RFS_T_VLAN)
return -ENOENT;
- stmmac_rx_queue_prio(priv, priv->hw, 0, entry->tc);
+ if (stmmac_tc_active(priv))
+ stmmac_rx_queue_prio(priv, priv->hw, 0, entry->tc);
entry->in_use = false;
entry->cookie = 0;
@@ -840,13 +855,13 @@ static int tc_del_ethtype_flow(struct stmmac_priv *priv,
switch (entry->etype) {
case ETH_P_LLDP:
- stmmac_rx_queue_routing(priv, priv->hw,
- PACKET_DCBCPQ, 0);
+ if (stmmac_tc_active(priv))
+ stmmac_rx_queue_routing(priv, priv->hw, PACKET_DCBCPQ, 0);
priv->rfs_entries_cnt[STMMAC_RFS_T_LLDP]--;
break;
case ETH_P_1588:
- stmmac_rx_queue_routing(priv, priv->hw,
- PACKET_PTPQ, 0);
+ if (stmmac_tc_active(priv))
+ stmmac_rx_queue_routing(priv, priv->hw, PACKET_PTPQ, 0);
priv->rfs_entries_cnt[STMMAC_RFS_T_1588]--;
break;
default:
@@ -901,12 +916,12 @@ static int tc_setup_cls(struct stmmac_priv *priv,
{
int ret = 0;
- /* When RSS is enabled, the filtering will be bypassed */
- if (priv->rss.enable)
- return -EBUSY;
-
switch (cls->command) {
case FLOW_CLS_REPLACE:
+ /* When RSS is enabled, the filtering will be bypassed. */
+ if (priv->rss.enable)
+ return -EBUSY;
+
ret = tc_add_flow_cls(priv, cls);
break;
case FLOW_CLS_DESTROY:
@@ -1015,6 +1030,8 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
if (qopt->cmd == TAPRIO_CMD_DESTROY)
goto disable;
+ if (priv->hw_unavailable)
+ return -EHOSTDOWN;
if (qopt->num_entries > dep)
return -EINVAL;
@@ -1098,8 +1115,7 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
goto disable;
}
- ret = stmmac_fpe_map_preemption_class(priv, priv->dev, extack,
- qopt->mqprio.preemptible_tcs);
+ ret = tc_config_preemption(priv, extack, qopt->mqprio.preemptible_tcs);
if (ret)
goto disable;
@@ -1109,8 +1125,9 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
if (priv->est) {
mutex_lock(&priv->est_lock);
priv->est->enable = false;
- stmmac_est_configure(priv, priv, priv->est,
- priv->plat->clk_ptp_rate);
+ if (!priv->hw_unavailable)
+ stmmac_est_configure(priv, priv, priv->est,
+ priv->plat->clk_ptp_rate);
/* Reset taprio status */
for (i = 0; i < priv->plat->tx_queues_to_use; i++) {
priv->xstats.max_sdu_txq_drop[i] = 0;
@@ -1120,7 +1137,7 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
mutex_unlock(&priv->est_lock);
}
- err = stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0);
+ err = tc_config_preemption(priv, extack, 0);
return qopt->cmd == TAPRIO_CMD_DESTROY ? err : ret;
}
@@ -1269,7 +1286,7 @@ static int stmmac_reset_tc_mqprio(struct net_device *ndev,
netdev_reset_tc(ndev);
netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use);
- return stmmac_fpe_map_preemption_class(priv, ndev, extack, 0);
+ return tc_config_preemption(priv, extack, 0);
}
static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
@@ -1286,6 +1303,8 @@ static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
if (!qopt->num_tc)
return stmmac_reset_tc_mqprio(ndev, extack);
+ if (priv->hw_unavailable)
+ return -EHOSTDOWN;
if (qopt->num_tc > ARRAY_SIZE(tc_to_txq))
return -EINVAL;
@@ -1315,8 +1334,7 @@ static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
if (err)
goto error_reset_tc;
- err = stmmac_fpe_map_preemption_class(priv, ndev, extack,
- mqprio->preemptible_tcs);
+ err = tc_config_preemption(priv, extack, mqprio->preemptible_tcs);
if (err)
goto error_reset_num_tx_queues;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c
index e24efe3bfedb..006882c14a8d 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c
@@ -114,6 +114,8 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev,
struct mac_device_info *hw,
__be16 proto, u16 vid)
{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ bool update_hw = netif_running(dev) && !priv->hw_unavailable;
int i, ret = 0;
/* Single Rx VLAN Filter */
@@ -121,7 +123,7 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev,
if ((hw->vlan_filter[0] & VLAN_TAG_VID) == vid) {
hw->vlan_filter[0] = 0;
- if (netif_running(dev))
+ if (update_hw)
vlan_write_single(dev, 0);
}
return 0;
@@ -132,7 +134,7 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev,
if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VEN) &&
((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) == vid)) {
- if (netif_running(dev)) {
+ if (update_hw) {
ret = vlan_write_filter(dev, hw, i, 0);
if (ret)
return ret;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
index d7e4db7224b0..7ecb7addd2ea 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
@@ -31,7 +31,8 @@ static int stmmac_xdp_enable_pool(struct stmmac_priv *priv,
return err;
}
- need_update = netif_running(priv->dev) && stmmac_xdp_is_enabled(priv);
+ need_update = priv->datapath == STMMAC_DATAPATH_RUNNING &&
+ stmmac_xdp_is_enabled(priv);
if (need_update) {
napi_disable(&ch->rx_napi);
@@ -69,7 +70,8 @@ static int stmmac_xdp_disable_pool(struct stmmac_priv *priv, u16 queue)
if (!pool)
return -EINVAL;
- need_update = netif_running(priv->dev) && stmmac_xdp_is_enabled(priv);
+ need_update = priv->datapath == STMMAC_DATAPATH_RUNNING &&
+ stmmac_xdp_is_enabled(priv);
if (need_update) {
napi_disable(&ch->rxtx_napi);
@@ -106,8 +108,9 @@ int stmmac_xdp_set_prog(struct stmmac_priv *priv, struct bpf_prog *prog,
struct bpf_prog *old_prog;
bool need_update;
bool if_running;
+ int ret;
- if_running = netif_running(dev);
+ if_running = priv->datapath == STMMAC_DATAPATH_RUNNING;
if (prog && dev->mtu > ETH_DATA_LEN) {
/* For now, the driver doesn't support XDP functionality with
@@ -117,25 +120,41 @@ int stmmac_xdp_set_prog(struct stmmac_priv *priv, struct bpf_prog *prog,
return -EOPNOTSUPP;
}
- if (!prog)
- xdp_features_clear_redirect_target(dev);
-
need_update = !!priv->xdp_prog != !!prog;
if (if_running && need_update)
stmmac_xdp_release(dev);
old_prog = xchg(&priv->xdp_prog, prog);
- if (old_prog)
- bpf_prog_put(old_prog);
/* Disable RX SPH for XDP operation */
priv->sph_active = priv->sph_capable && !stmmac_xdp_is_enabled(priv);
- if (if_running && need_update)
- stmmac_xdp_open(dev);
+ if (if_running && need_update) {
+ ret = stmmac_xdp_open(dev);
+ if (ret) {
+ netdev_err(dev, "failed reopening after XDP change: %pe; interface remains detached\n",
+ ERR_PTR(ret));
+ if (prog) {
+ /* The core retains the old program on error and drops
+ * the reference it passed for the proposed program.
+ */
+ xchg(&priv->xdp_prog, old_prog);
+ priv->sph_active = priv->sph_capable && !old_prog;
+ return ret;
+ }
+ /* Uninstalling a BPF link must release its program even
+ * if the non-XDP datapath cannot be restarted.
+ */
+ }
+ }
+
+ if (old_prog)
+ bpf_prog_put(old_prog);
if (prog)
xdp_features_set_redirect_target(dev, false);
+ else
+ xdp_features_clear_redirect_target(dev);
return 0;
}
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 10/15] net: stmmac: use the tracked datapath restart for XSK pool changes
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (8 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 09/15] net: stmmac: track datapath and power ownership across failed reopening James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 11/15] net: stmmac: restore TC offloads before restarting DMA James Hilliard
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Replace the void per-queue enable helpers with the tracked XDP restart.
Pause all queues and MAC link resolution while the pool bitmap and rings
change, leaving the PHY running through phylink replay.
Unwind a failed pool attachment without leaving NAPI over missing
buffers. Pool removal must complete even if ordinary-ring rebuilding
fails, after retiring all references to the departing pool. Preserve TBS
configuration and return a detached interface to the ordinary down/up
recovery path.
Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 5 -
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 116 +++-------------------
drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c | 44 ++++----
3 files changed, 32 insertions(+), 133 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 2fa7b18eb9a4..b65f47db3c2e 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -461,11 +461,6 @@ static inline bool stmmac_tc_active(struct stmmac_priv *priv)
netif_device_present(priv->dev) && !priv->hw_unavailable;
}
-void stmmac_disable_rx_queue(struct stmmac_priv *priv, u32 queue);
-void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue);
-void stmmac_disable_tx_queue(struct stmmac_priv *priv, u32 queue);
-void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue);
-int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags);
struct timespec64 stmmac_calc_tas_basetime(ktime_t old_base_time,
ktime_t current_time,
u64 cycle_time);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 22a65c1c1681..618e01772ab8 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -2268,6 +2268,7 @@ static void __free_dma_tx_desc_resources(struct stmmac_priv *priv,
tx_q->dma_etx = NULL;
tx_q->dma_entx = NULL;
tx_q->dma_tx = NULL;
+ tx_q->xsk_pool = NULL;
}
static void free_dma_tx_desc_resources(struct stmmac_priv *priv,
@@ -2756,6 +2757,12 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
bool work_done = true;
u32 tx_set_ic_bit = 0;
+ /* Nothing can be submitted while the link is down. Let NAPI complete;
+ * userspace can retry ndo_xsk_wakeup() once carrier has returned.
+ */
+ if (!netif_carrier_ok(priv->dev))
+ return true;
+
/* Avoids TX time-out as we are sharing with slow path */
txq_trans_cond_update(nq);
@@ -2770,8 +2777,7 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
/* We are sharing with slow path and stop XSK TX desc submission when
* available TX ring is less than threshold.
*/
- if (unlikely(stmmac_tx_avail(priv, queue) < STMMAC_TX_XSK_AVAIL) ||
- !netif_carrier_ok(priv->dev)) {
+ if (unlikely(stmmac_tx_avail(priv, queue) < STMMAC_TX_XSK_AVAIL)) {
work_done = false;
break;
}
@@ -7352,107 +7358,6 @@ static int stmmac_xdp_xmit(struct net_device *dev, int num_frames,
return nxmit;
}
-void stmmac_disable_rx_queue(struct stmmac_priv *priv, u32 queue)
-{
- struct stmmac_channel *ch = &priv->channel[queue];
- unsigned long flags;
-
- spin_lock_irqsave(&ch->lock, flags);
- stmmac_disable_dma_irq(priv, priv->ioaddr, queue, 1, 0);
- spin_unlock_irqrestore(&ch->lock, flags);
-
- stmmac_stop_rx_dma(priv, queue);
- __free_dma_rx_desc_resources(priv, priv->dma_conf, queue);
-}
-
-void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue)
-{
- struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[queue];
- struct stmmac_channel *ch = &priv->channel[queue];
- unsigned long flags;
- int ret;
-
- ret = __alloc_dma_rx_desc_resources(priv, priv->dma_conf, queue);
- if (ret) {
- netdev_err(priv->dev, "Failed to alloc RX desc.\n");
- return;
- }
-
- ret = __init_dma_rx_desc_rings(priv, priv->dma_conf, queue, GFP_KERNEL);
- if (ret) {
- __free_dma_rx_desc_resources(priv, priv->dma_conf, queue);
- netdev_err(priv->dev, "Failed to init RX desc.\n");
- return;
- }
-
- stmmac_reset_rx_queue(priv, queue);
- stmmac_clear_rx_descriptors(priv, priv->dma_conf, queue);
-
- stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
- rx_q->dma_rx_phy, queue);
-
- stmmac_set_queue_rx_tail_ptr(priv, rx_q, queue, rx_q->buf_alloc_num);
-
- stmmac_set_queue_rx_buf_size(priv, rx_q, queue);
-
- stmmac_start_rx_dma(priv, queue);
-
- spin_lock_irqsave(&ch->lock, flags);
- stmmac_enable_dma_irq(priv, priv->ioaddr, queue, 1, 0);
- spin_unlock_irqrestore(&ch->lock, flags);
-}
-
-void stmmac_disable_tx_queue(struct stmmac_priv *priv, u32 queue)
-{
- struct stmmac_channel *ch = &priv->channel[queue];
- unsigned long flags;
-
- spin_lock_irqsave(&ch->lock, flags);
- stmmac_disable_dma_irq(priv, priv->ioaddr, queue, 0, 1);
- spin_unlock_irqrestore(&ch->lock, flags);
-
- stmmac_stop_tx_dma(priv, queue);
- __free_dma_tx_desc_resources(priv, priv->dma_conf, queue);
-}
-
-void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue)
-{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[queue];
- struct stmmac_channel *ch = &priv->channel[queue];
- unsigned long flags;
- int ret;
-
- ret = __alloc_dma_tx_desc_resources(priv, priv->dma_conf, queue);
- if (ret) {
- netdev_err(priv->dev, "Failed to alloc TX desc.\n");
- return;
- }
-
- ret = __init_dma_tx_desc_rings(priv, priv->dma_conf, queue);
- if (ret) {
- __free_dma_tx_desc_resources(priv, priv->dma_conf, queue);
- netdev_err(priv->dev, "Failed to init TX desc.\n");
- return;
- }
-
- stmmac_reset_tx_queue(priv, queue);
- stmmac_clear_tx_descriptors(priv, priv->dma_conf, queue);
-
- stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
- tx_q->dma_tx_phy, queue);
-
- if (tx_q->tbs & STMMAC_TBS_AVAIL)
- stmmac_enable_tbs(priv, priv->ioaddr, 1, queue);
-
- stmmac_set_queue_tx_tail_ptr(priv, tx_q, queue, 0);
-
- stmmac_start_tx_dma(priv, queue);
-
- spin_lock_irqsave(&ch->lock, flags);
- stmmac_enable_dma_irq(priv, priv->ioaddr, queue, 0, 1);
- spin_unlock_irqrestore(&ch->lock, flags);
-}
-
void stmmac_xdp_release(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
@@ -7552,6 +7457,9 @@ int stmmac_xdp_open(struct net_device *dev)
stmmac_set_queue_tx_tail_ptr(priv, tx_q, chan, 0);
+ if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ stmmac_enable_tbs(priv, priv->ioaddr, 1, chan);
+
hrtimer_setup(&tx_q->txtimer, stmmac_tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
}
@@ -7589,7 +7497,7 @@ int stmmac_xdp_open(struct net_device *dev)
return ret;
}
-int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags)
+static int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags)
{
struct stmmac_priv *priv = netdev_priv(dev);
struct stmmac_channel *ch;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
index 7ecb7addd2ea..907ac49a1b76 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
@@ -9,7 +9,6 @@
static int stmmac_xdp_enable_pool(struct stmmac_priv *priv,
struct xsk_buff_pool *pool, u16 queue)
{
- struct stmmac_channel *ch = &priv->channel[queue];
bool need_update;
u32 frame_size;
int err;
@@ -34,23 +33,23 @@ static int stmmac_xdp_enable_pool(struct stmmac_priv *priv,
need_update = priv->datapath == STMMAC_DATAPATH_RUNNING &&
stmmac_xdp_is_enabled(priv);
- if (need_update) {
- napi_disable(&ch->rx_napi);
- napi_disable(&ch->tx_napi);
- stmmac_disable_rx_queue(priv, queue);
- stmmac_disable_tx_queue(priv, queue);
- }
+ if (need_update)
+ stmmac_xdp_release(priv->dev);
set_bit(queue, priv->af_xdp_zc_qps);
if (need_update) {
- stmmac_enable_rx_queue(priv, queue);
- stmmac_enable_tx_queue(priv, queue);
- napi_enable(&ch->rxtx_napi);
-
- err = stmmac_xsk_wakeup(priv->dev, queue, XDP_WAKEUP_RX);
- if (err)
+ err = stmmac_xdp_open(priv->dev);
+ if (err) {
+ clear_bit(queue, priv->af_xdp_zc_qps);
+ xsk_pool_dma_unmap(pool, STMMAC_RX_DMA_ATTR);
+ netdev_err(priv->dev, "failed reopening after XSK pool attach: %pe; interface remains detached\n",
+ ERR_PTR(err));
return err;
+ }
+
+ /* The pool is installed even if link resolution is still pending. */
+ napi_schedule(&priv->channel[queue].rxtx_napi);
}
return 0;
@@ -58,9 +57,9 @@ static int stmmac_xdp_enable_pool(struct stmmac_priv *priv,
static int stmmac_xdp_disable_pool(struct stmmac_priv *priv, u16 queue)
{
- struct stmmac_channel *ch = &priv->channel[queue];
struct xsk_buff_pool *pool;
bool need_update;
+ int err;
if (queue >= priv->plat->rx_queues_to_use ||
queue >= priv->plat->tx_queues_to_use)
@@ -73,24 +72,21 @@ static int stmmac_xdp_disable_pool(struct stmmac_priv *priv, u16 queue)
need_update = priv->datapath == STMMAC_DATAPATH_RUNNING &&
stmmac_xdp_is_enabled(priv);
- if (need_update) {
- napi_disable(&ch->rxtx_napi);
- stmmac_disable_rx_queue(priv, queue);
- stmmac_disable_tx_queue(priv, queue);
- synchronize_rcu();
- }
+ if (need_update)
+ stmmac_xdp_release(priv->dev);
xsk_pool_dma_unmap(pool, STMMAC_RX_DMA_ATTR);
clear_bit(queue, priv->af_xdp_zc_qps);
if (need_update) {
- stmmac_enable_rx_queue(priv, queue);
- stmmac_enable_tx_queue(priv, queue);
- napi_enable(&ch->rx_napi);
- napi_enable(&ch->tx_napi);
+ err = stmmac_xdp_open(priv->dev);
+ if (err)
+ netdev_err(priv->dev, "failed reopening after XSK pool removal: %pe; interface remains detached\n",
+ ERR_PTR(err));
}
+ /* Socket teardown must be able to unmap and free the removed pool. */
return 0;
}
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 11/15] net: stmmac: restore TC offloads before restarting DMA
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (9 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 10/15] net: stmmac: use the tracked datapath restart for XSK pool changes James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 12/15] xsk: allow drivers to retain DMA mappings independently of pools James Hilliard
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
A DMA software reset loses MAC filters and the MTL gate schedule, not
just the ring addresses. Restore those offloads during ordinary hardware
setup and resume. The later live-XDP reset fallback and retained-ring
MTU transaction will use the same restoration.
Keep the parsed L3/L4 rule rather than only its cookie. Program from
that saved definition both when installing a rule and after reset. Do
not publish a partially programmed replacement; restore the previous
slot when installation fails. Keep runtime VLAN priorities in the
existing queue configuration and replay EtherType steering and the
configured preemption/TC mapping. Preserve the additional fragment size.
Restore EST after the timestamp counter is usable, advancing its saved
base time by whole cycles when necessary. Delay DMA start until filters,
timestamp state and the gate schedule have been restored. Propagate a
replay error through the existing rollback or detached recovery path.
Build and validate TAPRIO replacements separately, including the PHC time
read, and publish the saved schedule only after hardware setup succeeds.
Keep the previous schedule on rejection and attempt to restore it after
a programming error. A rejected first install must not leave an enabled
zero-cycle cache for PHC adjustment or reset replay. Serialize schedule
publication with those consumers under the PTP mutex.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 9 +
drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c | 1 +
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 25 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 286 +++++++++++++++-------
4 files changed, 226 insertions(+), 95 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index b65f47db3c2e..6abb0d1d4095 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -151,6 +151,9 @@ struct stmmac_fpe_cfg {
struct ethtool_mmsv mmsv;
const struct stmmac_fpe_reg *reg;
u32 fpe_csr; /* MAC_FPE_CTRL_STS reg cache */
+ u32 preemptible_tcs;
+ u32 add_frag_size;
+ bool mapping_configured;
};
struct stmmac_tc_entry {
@@ -194,6 +197,10 @@ struct stmmac_flow_entry {
unsigned long cookie;
unsigned long action;
u8 ip_proto;
+ u32 ip4_src;
+ u32 ip4_dst;
+ u16 port_src;
+ u16 port_dst;
int in_use;
int idx;
int is_l4;
@@ -433,6 +440,8 @@ void stmmac_set_ethtool_ops(struct net_device *netdev);
void stmmac_ptp_register(struct stmmac_priv *priv);
void stmmac_ptp_unregister(struct stmmac_priv *priv);
int stmmac_ptp_restore(struct stmmac_priv *priv);
+int stmmac_tc_restore_filters(struct stmmac_priv *priv);
+int stmmac_tc_restore_est(struct stmmac_priv *priv);
int stmmac_xdp_open(struct net_device *dev);
void stmmac_xdp_release(struct net_device *dev);
int stmmac_get_phy_intf_sel(phy_interface_t interface);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
index c889204a7aa5..067ea1f5134b 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
@@ -195,6 +195,7 @@ void stmmac_fpe_set_add_frag_size(struct stmmac_priv *priv, u32 add_frag_size)
value = readl(ioaddr + reg->mtl_fpe_reg);
writel(u32_replace_bits(value, add_frag_size, FPE_MTL_ADD_FRAG_SZ),
ioaddr + reg->mtl_fpe_reg);
+ priv->fpe_cfg.add_frag_size = add_frag_size;
}
#define ALG_ERR_MSG "TX algorithm SP is not suitable for one-to-many mapping"
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 618e01772ab8..edcb9a025385 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -3756,6 +3756,11 @@ static int stmmac_hw_setup(struct net_device *dev)
if (ret)
return ret;
}
+ ret = stmmac_tc_restore_filters(priv);
+ if (ret)
+ return ret;
+ if (stmmac_fpe_supported(priv))
+ stmmac_fpe_set_add_frag_size(priv, priv->fpe_cfg.add_frag_size);
/* Initialize Safety Features */
stmmac_safety_feat_configuration(priv);
@@ -3818,9 +3823,6 @@ static int stmmac_hw_setup(struct net_device *dev)
stmmac_enable_tbs(priv, priv->ioaddr, enable, chan);
}
- /* Start the ball rolling... */
- stmmac_start_all_dma(priv);
-
phylink_rx_clk_stop_block(priv->phylink);
stmmac_set_hw_vlan_mode(priv, priv->hw);
phylink_rx_clk_stop_unblock(priv->phylink);
@@ -4348,6 +4350,14 @@ static int __stmmac_open(struct net_device *dev,
stmmac_init_coalesce(priv);
stmmac_vlan_restore(priv);
+ mutex_lock(&priv->ptp_mutex);
+ ret = stmmac_tc_restore_est(priv);
+ mutex_unlock(&priv->ptp_mutex);
+ if (ret)
+ goto irq_error;
+
+ /* All reset-sensitive offloads must be installed before DMA runs. */
+ stmmac_start_all_dma(priv);
ret = stmmac_request_irq(dev);
if (ret)
@@ -8722,10 +8732,14 @@ int stmmac_resume(struct device *dev)
goto error_stop_dma;
}
- stmmac_init_timestamping(priv);
mutex_lock(&priv->ptp_mutex);
- stmmac_block_ptp(priv, false);
+ stmmac_init_timestamping(priv);
+ ret = stmmac_tc_restore_est(priv);
+ if (!ret)
+ stmmac_block_ptp(priv, false);
mutex_unlock(&priv->ptp_mutex);
+ if (ret)
+ goto error_stop_dma;
stmmac_init_coalesce(priv);
phylink_rx_clk_stop_block(priv->phylink);
@@ -8734,6 +8748,7 @@ int stmmac_resume(struct device *dev)
stmmac_vlan_restore(priv);
+ stmmac_start_all_dma(priv);
stmmac_enable_all_queues(priv);
stmmac_enable_all_dma_irq(priv);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
index 9ded65750e89..de254919f6ab 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
@@ -14,12 +14,17 @@
static int tc_config_preemption(struct stmmac_priv *priv,
struct netlink_ext_ack *extack, u32 preemptible_tcs)
{
- /* Qdisc teardown must not access unpowered registers. */
- if (priv->hw_unavailable)
- return 0;
+ int ret = 0;
- return stmmac_fpe_map_preemption_class(priv, priv->dev, extack,
- preemptible_tcs);
+ /* Qdisc teardown still clears the saved mapping after failed resume. */
+ if (!priv->hw_unavailable)
+ ret = stmmac_fpe_map_preemption_class(priv, priv->dev, extack,
+ preemptible_tcs);
+ if (!ret) {
+ priv->fpe_cfg.preemptible_tcs = preemptible_tcs;
+ priv->fpe_cfg.mapping_configured = true;
+ }
+ return ret;
}
static void tc_fill_all_pass_entry(struct stmmac_tc_entry *entry)
@@ -519,31 +524,15 @@ static int tc_add_ip4_flow(struct stmmac_priv *priv,
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector;
- bool inv = entry->action & STMMAC_FLOW_ACTION_DROP;
struct flow_match_ipv4_addrs match;
- u32 hw_match;
- int ret;
/* Nothing to do here */
if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_IPV4_ADDRS))
return -EINVAL;
flow_rule_match_ipv4_addrs(rule, &match);
- hw_match = ntohl(match.key->src) & ntohl(match.mask->src);
- if (hw_match) {
- ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, true,
- false, true, inv, hw_match);
- if (ret)
- return ret;
- }
-
- hw_match = ntohl(match.key->dst) & ntohl(match.mask->dst);
- if (hw_match) {
- ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, true,
- false, false, inv, hw_match);
- if (ret)
- return ret;
- }
+ entry->ip4_src = ntohl(match.key->src) & ntohl(match.mask->src);
+ entry->ip4_dst = ntohl(match.key->dst) & ntohl(match.mask->dst);
return 0;
}
@@ -554,11 +543,7 @@ static int tc_add_ports_flow(struct stmmac_priv *priv,
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector;
- bool inv = entry->action & STMMAC_FLOW_ACTION_DROP;
struct flow_match_ports match;
- u32 hw_match;
- bool is_udp;
- int ret;
/* Nothing to do here */
if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_PORTS))
@@ -566,10 +551,7 @@ static int tc_add_ports_flow(struct stmmac_priv *priv,
switch (entry->ip_proto) {
case IPPROTO_TCP:
- is_udp = false;
- break;
case IPPROTO_UDP:
- is_udp = true;
break;
default:
return -EINVAL;
@@ -577,23 +559,46 @@ static int tc_add_ports_flow(struct stmmac_priv *priv,
flow_rule_match_ports(rule, &match);
- hw_match = ntohs(match.key->src) & ntohs(match.mask->src);
- if (hw_match) {
- ret = stmmac_config_l4_filter(priv, priv->hw, entry->idx, true,
- is_udp, true, inv, hw_match);
+ entry->port_src = ntohs(match.key->src) & ntohs(match.mask->src);
+ entry->port_dst = ntohs(match.key->dst) & ntohs(match.mask->dst);
+
+ entry->is_l4 = true;
+ return 0;
+}
+
+static int tc_config_flow(struct stmmac_priv *priv,
+ const struct stmmac_flow_entry *entry)
+{
+ bool inv = entry->action & STMMAC_FLOW_ACTION_DROP;
+ bool udp = entry->ip_proto == IPPROTO_UDP;
+ int ret;
+
+ /* Clear the whole slot, including matches removed by a replacement. */
+ ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, false,
+ false, false, false, 0);
+ if (ret || !entry->in_use)
+ return ret;
+ if (entry->ip4_src) {
+ ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, true,
+ false, true, inv, entry->ip4_src);
if (ret)
return ret;
}
-
- hw_match = ntohs(match.key->dst) & ntohs(match.mask->dst);
- if (hw_match) {
+ if (entry->ip4_dst) {
+ ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, true,
+ false, false, inv, entry->ip4_dst);
+ if (ret)
+ return ret;
+ }
+ if (entry->port_src) {
ret = stmmac_config_l4_filter(priv, priv->hw, entry->idx, true,
- is_udp, false, inv, hw_match);
+ udp, true, inv, entry->port_src);
if (ret)
return ret;
}
-
- entry->is_l4 = true;
+ if (entry->port_dst)
+ return stmmac_config_l4_filter(priv, priv->hw, entry->idx, true,
+ udp, false, inv, entry->port_dst);
return 0;
}
@@ -629,6 +634,7 @@ static int tc_add_flow(struct stmmac_priv *priv,
{
struct stmmac_flow_entry *entry = tc_find_flow(priv, cls, false);
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct stmmac_flow_entry new = {};
int i, ret;
if (!entry) {
@@ -637,23 +643,33 @@ static int tc_add_flow(struct stmmac_priv *priv,
return -ENOENT;
}
- ret = tc_parse_flow_actions(priv, &rule->action, entry,
+ new.idx = entry->idx;
+ ret = tc_parse_flow_actions(priv, &rule->action, &new,
cls->common.extack);
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(tc_flow_parsers); i++) {
- ret = tc_flow_parsers[i].fn(priv, cls, entry);
+ ret = tc_flow_parsers[i].fn(priv, cls, &new);
if (!ret)
- entry->in_use = true;
+ new.in_use = true;
else if (ret == -EOPNOTSUPP)
return ret;
}
- if (!entry->in_use)
+ if (!new.in_use)
return -EINVAL;
- entry->cookie = cls->cookie;
+ ret = tc_config_flow(priv, &new);
+ if (ret) {
+ /* Do not publish a rule that was only partially programmed. */
+ if (tc_config_flow(priv, entry))
+ netdev_err(priv->dev, "failed to restore flower filter %d\n",
+ entry->idx);
+ return ret;
+ }
+ new.cookie = cls->cookie;
+ *entry = new;
return 0;
}
@@ -739,6 +755,8 @@ static int tc_add_vlan_flow(struct stmmac_priv *priv,
prio = BIT(match.key->vlan_priority);
stmmac_rx_queue_prio(priv, priv->hw, prio, tc);
+ priv->plat->rx_queues_cfg[tc].prio = prio;
+ priv->plat->rx_queues_cfg[tc].use_prio = true;
entry->in_use = true;
entry->cookie = cls->cookie;
@@ -760,6 +778,8 @@ static int tc_del_vlan_flow(struct stmmac_priv *priv,
if (stmmac_tc_active(priv))
stmmac_rx_queue_prio(priv, priv->hw, 0, entry->tc);
+ priv->plat->rx_queues_cfg[entry->tc].prio = 0;
+ priv->plat->rx_queues_cfg[entry->tc].use_prio = true;
entry->in_use = false;
entry->cookie = 0;
@@ -934,6 +954,45 @@ static int tc_setup_cls(struct stmmac_priv *priv,
return ret;
}
+int stmmac_tc_restore_filters(struct stmmac_priv *priv)
+{
+ int i, ret;
+
+ for (i = 0; i < priv->flow_entries_max; i++) {
+ struct stmmac_flow_entry *entry = &priv->flow_entries[i];
+
+ if (!entry->in_use)
+ continue;
+ ret = tc_config_flow(priv, entry);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < priv->rfs_entries_total; i++) {
+ struct stmmac_rfs_entry *entry = &priv->rfs_entries[i];
+
+ if (!entry->in_use)
+ continue;
+ switch (entry->type) {
+ /* VLAN priorities are replayed by stmmac_mtl_configuration(). */
+ case STMMAC_RFS_T_VLAN:
+ break;
+ case STMMAC_RFS_T_LLDP:
+ stmmac_rx_queue_routing(priv, priv->hw, PACKET_DCBCPQ,
+ entry->tc);
+ break;
+ case STMMAC_RFS_T_1588:
+ stmmac_rx_queue_routing(priv, priv->hw, PACKET_PTPQ,
+ entry->tc);
+ break;
+ }
+ }
+ /* The XGMAC callback also restores the runtime TC-to-queue mapping. */
+ if (priv->fpe_cfg.mapping_configured)
+ return tc_config_preemption(priv, NULL,
+ priv->fpe_cfg.preemptible_tcs);
+ return 0;
+}
+
struct timespec64 stmmac_calc_tas_basetime(ktime_t old_base_time,
ktime_t current_time,
u64 cycle_time)
@@ -957,7 +1016,37 @@ struct timespec64 stmmac_calc_tas_basetime(ktime_t old_base_time,
return time;
}
-static void tc_taprio_map_maxsdu_txq(struct stmmac_priv *priv,
+/* Called after timestamp setup and before DMA starts, with ptp_mutex held.
+ * The PHC can still be blocked while reset state is being restored.
+ */
+int stmmac_tc_restore_est(struct stmmac_priv *priv)
+{
+ struct stmmac_est *est = priv->est;
+ struct timespec64 base;
+ unsigned long flags;
+ u64 now, cycle;
+ int ret;
+
+ if (!est || !est->enable)
+ return 0;
+ mutex_lock(&priv->est_lock);
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ ret = stmmac_get_systime(priv, priv->ptpaddr, &now);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+ if (ret)
+ goto out;
+ cycle = (u64)est->ctr[1] * NSEC_PER_SEC + est->ctr[0];
+ base = stmmac_calc_tas_basetime(ktime_set(est->btr_reserve[1],
+ est->btr_reserve[0]), now, cycle);
+ est->btr[0] = base.tv_nsec;
+ est->btr[1] = base.tv_sec;
+ ret = stmmac_est_configure(priv, priv, est, priv->plat->clk_ptp_rate);
+out:
+ mutex_unlock(&priv->est_lock);
+ return ret;
+}
+
+static void tc_taprio_map_maxsdu_txq(struct stmmac_est *est,
struct tc_taprio_qopt_offload *qopt)
{
u32 num_tc = qopt->mqprio.qopt.num_tc;
@@ -974,7 +1063,7 @@ static void tc_taprio_map_maxsdu_txq(struct stmmac_priv *priv,
count = qopt->mqprio.qopt.count[i];
for (j = offset; j < offset + count; j++)
- priv->est->max_sdu[j] = qopt->max_sdu[i] + ETH_HLEN - ETH_TLEN;
+ est->max_sdu[j] = qopt->max_sdu[i] + ETH_HLEN - ETH_TLEN;
}
}
@@ -984,8 +1073,9 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
u32 size, wid = priv->dma_cap.estwid, dep = priv->dma_cap.estdep;
struct netlink_ext_ack *extack = qopt->mqprio.extack;
struct timespec64 time, current_time, qopt_time;
+ struct stmmac_est *est;
ktime_t current_time_ns;
- int err, i, ret = 0;
+ int i, ret;
u64 ctr;
if (qopt->base_time < 0)
@@ -1040,34 +1130,27 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
if (qopt->cycle_time_extension >= BIT(wid + 7))
return -ERANGE;
- if (!priv->est) {
- priv->est = devm_kzalloc(priv->device, sizeof(*priv->est),
- GFP_KERNEL);
- if (!priv->est)
- return -ENOMEM;
-
- mutex_init(&priv->est_lock);
- } else {
- mutex_lock(&priv->est_lock);
- memset(priv->est, 0, sizeof(*priv->est));
- mutex_unlock(&priv->est_lock);
- }
+ /* Build the replacement without changing the installed schedule. An
+ * entry rejected below must not leave an enabled, zero-cycle cache for
+ * PHC adjustment or reset replay to consume.
+ */
+ est = kzalloc_obj(*est);
+ if (!est)
+ return -ENOMEM;
size = qopt->num_entries;
-
- mutex_lock(&priv->est_lock);
- priv->est->gcl_size = size;
- priv->est->enable = qopt->cmd == TAPRIO_CMD_REPLACE;
- mutex_unlock(&priv->est_lock);
+ est->gcl_size = size;
+ est->enable = true;
for (i = 0; i < size; i++) {
s64 delta_ns = qopt->entries[i].interval;
u32 gates = qopt->entries[i].gate_mask;
- if (delta_ns > GENMASK(wid - 1, 0))
- return -ERANGE;
- if (gates > GENMASK(31 - wid, 0))
- return -ERANGE;
+ if (delta_ns > GENMASK(wid - 1, 0) ||
+ gates > GENMASK(31 - wid, 0)) {
+ ret = -ERANGE;
+ goto free_est;
+ }
switch (qopt->entries[i].command) {
case TC_TAPRIO_CMD_SET_GATES:
@@ -1079,47 +1162,69 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
gates &= ~BIT(0);
break;
default:
- return -EOPNOTSUPP;
+ ret = -EOPNOTSUPP;
+ goto free_est;
}
- priv->est->gcl[i] = delta_ns | (gates << wid);
+ est->gcl[i] = delta_ns | (gates << wid);
}
- mutex_lock(&priv->est_lock);
/* Adjust for real system time */
- priv->ptp_clock_ops.gettime64(&priv->ptp_clock_ops, ¤t_time);
+ ret = priv->ptp_clock_ops.gettime64(&priv->ptp_clock_ops, ¤t_time);
+ if (ret)
+ goto free_est;
current_time_ns = timespec64_to_ktime(current_time);
time = stmmac_calc_tas_basetime(qopt->base_time, current_time_ns,
qopt->cycle_time);
- priv->est->btr[0] = (u32)time.tv_nsec;
- priv->est->btr[1] = (u32)time.tv_sec;
+ est->btr[0] = (u32)time.tv_nsec;
+ est->btr[1] = (u32)time.tv_sec;
qopt_time = ktime_to_timespec64(qopt->base_time);
- priv->est->btr_reserve[0] = (u32)qopt_time.tv_nsec;
- priv->est->btr_reserve[1] = (u32)qopt_time.tv_sec;
+ est->btr_reserve[0] = (u32)qopt_time.tv_nsec;
+ est->btr_reserve[1] = (u32)qopt_time.tv_sec;
ctr = qopt->cycle_time;
- priv->est->ctr[0] = do_div(ctr, NSEC_PER_SEC);
- priv->est->ctr[1] = (u32)ctr;
+ est->ctr[0] = do_div(ctr, NSEC_PER_SEC);
+ est->ctr[1] = (u32)ctr;
- priv->est->ter = qopt->cycle_time_extension;
+ est->ter = qopt->cycle_time_extension;
- tc_taprio_map_maxsdu_txq(priv, qopt);
+ tc_taprio_map_maxsdu_txq(est, qopt);
- ret = stmmac_est_configure(priv, priv, priv->est,
- priv->plat->clk_ptp_rate);
- mutex_unlock(&priv->est_lock);
+ if (!priv->est) {
+ priv->est = devm_kzalloc(priv->device, sizeof(*priv->est),
+ GFP_KERNEL);
+ if (!priv->est) {
+ ret = -ENOMEM;
+ goto free_est;
+ }
+ mutex_init(&priv->est_lock);
+ }
+
+ mutex_lock(&priv->est_lock);
+ ret = stmmac_est_configure(priv, priv, est, priv->plat->clk_ptp_rate);
if (ret) {
netdev_err(priv->dev, "failed to configure EST\n");
- goto disable;
+ goto restore;
}
ret = tc_config_preemption(priv, extack, qopt->mqprio.preemptible_tcs);
if (ret)
- goto disable;
+ goto restore;
- return 0;
+ *priv->est = *est;
+ mutex_unlock(&priv->est_lock);
+free_est:
+ kfree(est);
+ return ret;
+
+restore:
+ /* A failed hardware update must not publish the rejected schedule. */
+ if (stmmac_est_configure(priv, priv, priv->est, priv->plat->clk_ptp_rate))
+ netdev_err(priv->dev, "failed to restore EST\n");
+ mutex_unlock(&priv->est_lock);
+ goto free_est;
disable:
if (priv->est) {
@@ -1137,9 +1242,7 @@ static int tc_taprio_configure(struct stmmac_priv *priv,
mutex_unlock(&priv->est_lock);
}
- err = tc_config_preemption(priv, extack, 0);
-
- return qopt->cmd == TAPRIO_CMD_DESTROY ? err : ret;
+ return tc_config_preemption(priv, extack, 0);
}
static void tc_taprio_stats(struct stmmac_priv *priv,
@@ -1180,7 +1283,10 @@ static int tc_setup_taprio(struct stmmac_priv *priv,
switch (qopt->cmd) {
case TAPRIO_CMD_REPLACE:
case TAPRIO_CMD_DESTROY:
+ /* Serialize cache publication with PHC adjustment and reset replay. */
+ mutex_lock(&priv->ptp_mutex);
err = tc_taprio_configure(priv, qopt);
+ mutex_unlock(&priv->ptp_mutex);
break;
case TAPRIO_CMD_STATS:
tc_taprio_stats(priv, qopt);
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 12/15] xsk: allow drivers to retain DMA mappings independently of pools
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (10 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 11/15] net: stmmac: restore TC offloads before restarting DMA James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 13/15] net: stmmac: retain DMA memory until hardware shutdown completes James Hilliard
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
An unsuccessful device shutdown does not make its DMA memory safe to
unmap. AF_XDP pool removal must nevertheless complete: the last socket
release invokes the driver detach callback and then destroys the pool,
regardless of the callback's return value.
Provide an independent reference to the existing DMA mapping and its
UMEM pages. A driver can take it while installing rings and release it
after DMA has actually stopped. It does not retain pool metadata, fill
or completion rings, or the pool users reference which triggers
teardown.
Keep the DMA device and the mapping's netdev lookup key allocated,
without taking a netdev usage reference that would prevent unregister.
Save the mapping attributes and unmap before dropping the last retained
UMEM reference. Mapping reference operations are serialized by RTNL,
like the existing mapping list operations.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
include/net/xdp_sock_drv.h | 23 ++++++++++++++++++++++
include/net/xsk_buff_pool.h | 4 ++++
net/xdp/xsk_buff_pool.c | 48 +++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h
index d94aeb506379..819410797db3 100644
--- a/include/net/xdp_sock_drv.h
+++ b/include/net/xdp_sock_drv.h
@@ -95,6 +95,20 @@ static inline void xsk_pool_dma_unmap(struct xsk_buff_pool *pool,
xp_dma_unmap(pool, attrs);
}
+/* RTNL must be held. Keep DMA mappings and pinned pages independently of
+ * the socket/pool lifetime, for rings whose DMA shutdown can fail.
+ * This does not keep pool metadata alive or postpone the detach callback.
+ */
+static inline struct xsk_dma_map *xsk_pool_dma_get(struct xsk_buff_pool *pool)
+{
+ return xp_dma_get(pool);
+}
+
+static inline void xsk_pool_dma_put(struct xsk_dma_map *dma_map)
+{
+ xp_dma_put(dma_map);
+}
+
static inline int xsk_pool_dma_map(struct xsk_buff_pool *pool,
struct device *dev, unsigned long attrs)
{
@@ -432,6 +446,15 @@ static inline void xsk_pool_dma_unmap(struct xsk_buff_pool *pool,
{
}
+static inline struct xsk_dma_map *xsk_pool_dma_get(struct xsk_buff_pool *pool)
+{
+ return NULL;
+}
+
+static inline void xsk_pool_dma_put(struct xsk_dma_map *dma_map)
+{
+}
+
static inline int xsk_pool_dma_map(struct xsk_buff_pool *pool,
struct device *dev, unsigned long attrs)
{
diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h
index a7df573784fd..25a09b098703 100644
--- a/include/net/xsk_buff_pool.h
+++ b/include/net/xsk_buff_pool.h
@@ -38,6 +38,8 @@ struct xsk_dma_map {
dma_addr_t *dma_pages;
struct device *dev;
struct net_device *netdev;
+ struct xdp_umem *umem;
+ unsigned long attrs;
refcount_t users;
struct list_head list; /* Protected by the RTNL_LOCK */
u32 dma_pages_cnt;
@@ -143,6 +145,8 @@ void xp_fill_cb(struct xsk_buff_pool *pool, struct xsk_cb_desc *desc);
int xp_dma_map(struct xsk_buff_pool *pool, struct device *dev,
unsigned long attrs, struct page **pages, u32 nr_pages);
void xp_dma_unmap(struct xsk_buff_pool *pool, unsigned long attrs);
+struct xsk_dma_map *xp_dma_get(struct xsk_buff_pool *pool);
+void xp_dma_put(struct xsk_dma_map *dma_map);
struct xdp_buff *xp_alloc(struct xsk_buff_pool *pool);
u32 xp_alloc_batch(struct xsk_buff_pool *pool, struct xdp_buff **xdp, u32 max);
bool xp_can_alloc(struct xsk_buff_pool *pool, u32 count);
diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c
index c58f56f24a9c..8f95eddc91fc 100644
--- a/net/xdp/xsk_buff_pool.c
+++ b/net/xdp/xsk_buff_pool.c
@@ -360,7 +360,8 @@ static struct xsk_dma_map *xp_find_dma_map(struct xsk_buff_pool *pool)
}
static struct xsk_dma_map *xp_create_dma_map(struct device *dev, struct net_device *netdev,
- u32 nr_pages, struct xdp_umem *umem)
+ u32 nr_pages, struct xdp_umem *umem,
+ unsigned long attrs)
{
struct xsk_dma_map *dma_map;
@@ -376,6 +377,8 @@ static struct xsk_dma_map *xp_create_dma_map(struct device *dev, struct net_devi
dma_map->netdev = netdev;
dma_map->dev = dev;
+ dma_map->umem = umem;
+ dma_map->attrs = attrs;
dma_map->dma_pages_cnt = nr_pages;
refcount_set(&dma_map->users, 1);
list_add(&dma_map->list, &umem->xsk_dma_list);
@@ -430,6 +433,47 @@ void xp_dma_unmap(struct xsk_buff_pool *pool, unsigned long attrs)
}
EXPORT_SYMBOL(xp_dma_unmap);
+struct xsk_dma_map *xp_dma_get(struct xsk_buff_pool *pool)
+{
+ struct xsk_dma_map *dma_map;
+
+ ASSERT_RTNL();
+ if (!pool->dma_pages)
+ return NULL;
+ dma_map = xp_find_dma_map(pool);
+ if (WARN_ON_ONCE(!dma_map))
+ return NULL;
+
+ refcount_inc(&dma_map->users);
+ xdp_get_umem(dma_map->umem);
+ get_device(dma_map->dev);
+ /* Keep the mapping's lookup key alive without preventing unregister. */
+ get_device(&dma_map->netdev->dev);
+ return dma_map;
+}
+EXPORT_SYMBOL_GPL(xp_dma_get);
+
+void xp_dma_put(struct xsk_dma_map *dma_map)
+{
+ struct net_device *netdev;
+ struct xdp_umem *umem;
+ struct device *dev;
+
+ ASSERT_RTNL();
+ if (!dma_map)
+ return;
+ dev = dma_map->dev;
+ netdev = dma_map->netdev;
+ umem = dma_map->umem;
+ if (refcount_dec_and_test(&dma_map->users))
+ __xp_dma_unmap(dma_map, dma_map->attrs);
+ /* Unmap before the final reference can unpin the UMEM pages. */
+ xdp_put_umem(umem, false);
+ put_device(&netdev->dev);
+ put_device(dev);
+}
+EXPORT_SYMBOL_GPL(xp_dma_put);
+
static void xp_check_dma_contiguity(struct xsk_dma_map *dma_map)
{
u32 i;
@@ -487,7 +531,7 @@ int xp_dma_map(struct xsk_buff_pool *pool, struct device *dev,
return 0;
}
- dma_map = xp_create_dma_map(dev, pool->netdev, nr_pages, pool->umem);
+ dma_map = xp_create_dma_map(dev, pool->netdev, nr_pages, pool->umem, attrs);
if (!dma_map)
return -ENOMEM;
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 13/15] net: stmmac: retain DMA memory until hardware shutdown completes
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (11 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 12/15] xsk: allow drivers to retain DMA mappings independently of pools James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 14/15] net: stmmac: prepare device-local DMA interrupt quiescence James Hilliard
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Clearing the DMA start bits requests a stop but need not complete an
in-flight frame or descriptor writeback. Ordinary release, live XDP/XSK
replacement and late open failures currently free the rings and buffers
immediately afterwards. An XSK socket can then unmap and unpin the UMEM
while hardware still has its addresses.
Wait for stopped process states on the legacy and Allwinner DMA engines.
For GMAC4 configurations represented by DSR0, also require its bus-busy
bits to clear. Fall back to a completed global reset if the idle wait
times out or the integration has no supported idle indication, including
XGMAC and GMAC4 configurations with more than three channels. Prepare
the PHY receive clock for that reset and restore PHC configuration
before a live XDP restart which required it. As with MTU reset,
continuous PHC time is not preserved on this fallback.
Track configurations exposed to DMA separately from software datapath
ownership. If both idle and reset fail, keep the rings, DMA mappings and
backing memory until a subsequent successful reset. Do not overwrite
retained buffers in an XDP reopen or change their ring/channel geometry.
Record failed open replacements too, including ones which are no longer
the active configuration pointer. A successful down/up reset retires
them.
Take independent XSK DMA/UMEM references when initializing RX rings. On
failed shutdown, return only software buffer metadata to the departing
pool and remove every driver pool pointer before socket teardown
proceeds. The mapping references keep the actual memory pinned after the
pool is gone. A later successful reset releases these references after
freeing the descriptors and buffers that used them.
Defer hard TX error recovery to process context instead of rewriting a
ring in the IRQ handler immediately after clearing ST. Likewise, move
resume-time TX cleanup and descriptor rebuilding after the reset
succeeds. Do not let queued recovery work reopen an administratively
closed device.
There is no generic, guaranteed isolation mechanism across all stmmac
integrations. If hardware still cannot stop or reset at removal,
deliberately retain the DMA allocations and report the quarantine rather
than expose recycled memory to DMA. Such an unrecoverable device can
therefore retain memory, including pinned UMEM, until reboot.
Rebuild retained RX descriptors after reset with buffer addresses and
chain links written before ownership. GMAC4 and XGMAC secondary-address
programming overwrites des3, so publishing OWN first would lose it.
Fixes: ac746c8520d9 ("net: stmmac: enhance XDP ZC driver level switching performance")
Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 17 +
.../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 1 +
drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c | 1 +
drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 2 +
drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h | 6 +
drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c | 18 +
drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 2 +
drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 20 +
drivers/net/ethernet/stmicro/stmmac/hwif.h | 4 +
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 11 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 427 ++++++++++++++++-----
11 files changed, 415 insertions(+), 94 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
index 48c52eb96233..422d3df21ed9 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
@@ -163,6 +163,7 @@ static const struct emac_variant emac_variant_h6 = {
#define EMAC_TX_CUR_DESC 0xB4
#define EMAC_TX_CUR_BUF 0xB8
#define EMAC_RX_DMA_STA 0xC0
+#define EMAC_DMA_STATE_MASK GENMASK(2, 0)
#define EMAC_RX_CUR_DESC 0xC4
#define EMAC_RX_CUR_BUF 0xC8
@@ -425,6 +426,21 @@ static void sun8i_dwmac_dma_stop_rx(struct stmmac_priv *priv,
writel(v, ioaddr + EMAC_RX_CTL1);
}
+static int sun8i_dwmac_dma_wait_idle(struct stmmac_priv *priv,
+ void __iomem *ioaddr)
+{
+ u32 value;
+ int ret;
+
+ /* STOP (0) follows the frame transfer and descriptor close states. */
+ ret = readl_poll_timeout(ioaddr + EMAC_TX_DMA_STA, value,
+ !(value & EMAC_DMA_STATE_MASK), 100, 100000);
+ if (ret)
+ return ret;
+ return readl_poll_timeout(ioaddr + EMAC_RX_DMA_STA, value,
+ !(value & EMAC_DMA_STATE_MASK), 100, 100000);
+}
+
static int sun8i_dwmac_dma_interrupt(struct stmmac_priv *priv,
void __iomem *ioaddr,
struct stmmac_extra_stats *x, u32 chan,
@@ -553,6 +569,7 @@ static void sun8i_dwmac_dma_operation_mode_tx(struct stmmac_priv *priv,
static const struct stmmac_dma_ops sun8i_dwmac_dma_ops = {
.reset = sun8i_dwmac_dma_reset,
+ .wait_idle = sun8i_dwmac_dma_wait_idle,
.init = sun8i_dwmac_dma_init,
.init_rx_chan = sun8i_dwmac_dma_init_rx,
.init_tx_chan = sun8i_dwmac_dma_init_tx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
index 3ac7a7949529..4cb7e6c16bdd 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
@@ -252,6 +252,7 @@ static void dwmac1000_rx_watchdog(struct stmmac_priv *priv,
const struct stmmac_dma_ops dwmac1000_dma_ops = {
.reset = dwmac_dma_reset,
+ .wait_idle = dwmac_dma_wait_idle,
.init_chan = dwmac1000_dma_init_channel,
.init_rx_chan = dwmac1000_dma_init_rx,
.init_tx_chan = dwmac1000_dma_init_tx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
index 12b2bf2d739a..5ffd3c1471c4 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
@@ -108,6 +108,7 @@ static void dwmac100_dma_diagnostic_fr(struct stmmac_extra_stats *x,
const struct stmmac_dma_ops dwmac100_dma_ops = {
.reset = dwmac_dma_reset,
+ .wait_idle = dwmac_dma_wait_idle,
.init = dwmac100_dma_init,
.init_rx_chan = dwmac100_dma_init_rx,
.init_tx_chan = dwmac100_dma_init_tx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
index 23ffe1adcd0d..fb0a4e156a7b 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
@@ -568,6 +568,7 @@ static int dwmac4_enable_tbs(struct stmmac_priv *priv, void __iomem *ioaddr,
const struct stmmac_dma_ops dwmac4_dma_ops = {
.reset = dwmac4_dma_reset,
+ .wait_idle = dwmac4_dma_wait_idle,
.init = dwmac4_dma_init,
.init_chan = dwmac4_dma_init_channel,
.deinit_chan = dwmac4_dma_deinit_channel,
@@ -598,6 +599,7 @@ const struct stmmac_dma_ops dwmac4_dma_ops = {
const struct stmmac_dma_ops dwmac410_dma_ops = {
.reset = dwmac4_dma_reset,
+ .wait_idle = dwmac4_dma_wait_idle,
.init = dwmac4_dma_init,
.init_chan = dwmac410_dma_init_channel,
.deinit_chan = dwmac410_dma_deinit_channel,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
index 43b036d4e95b..9352107204eb 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
@@ -10,6 +10,12 @@
#ifndef __DWMAC4_DMA_H__
#define __DWMAC4_DMA_H__
+int dwmac4_dma_wait_idle(struct stmmac_priv *priv, void __iomem *ioaddr);
+
+#define DMA_DEBUG_STATUS0 0x0000100c
+#define DMA_DEBUG_BUS_BUSY GENMASK(1, 0)
+#define DMA_DEBUG_CH_STATE(ch) (GENMASK(15, 8) << ((ch) * 8))
+
/* Define the max channel number used for tx (also rx).
* dwmac4 accepts up to 8 channels for TX (and also 8 channels for RX
*/
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
index a0249715fafa..9af0565a9bca 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
@@ -26,6 +26,24 @@ int dwmac4_dma_reset(void __iomem *ioaddr)
10000, 1000000);
}
+int dwmac4_dma_wait_idle(struct stmmac_priv *priv, void __iomem *ioaddr)
+{
+ u32 channels = max(priv->plat->rx_queues_to_use,
+ priv->plat->tx_queues_to_use);
+ u32 mask = DMA_DEBUG_BUS_BUSY;
+ u32 value, chan;
+
+ /* DSR0 describes channels 0..2 and outstanding AXI transactions.
+ * Other debug-register layouts require a successful reset instead.
+ */
+ if (channels > 3)
+ return -EOPNOTSUPP;
+ for (chan = 0; chan < channels; chan++)
+ mask |= DMA_DEBUG_CH_STATE(chan);
+ return readl_poll_timeout(ioaddr + DMA_DEBUG_STATUS0, value,
+ !(value & mask), 100, 100000);
+}
+
void dwmac4_set_rx_tail_ptr(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 tail_ptr, u32 chan)
{
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
index e1c37ac2c99d..970495bccfd2 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
@@ -11,6 +11,8 @@
#ifndef __DWMAC_DMA_H__
#define __DWMAC_DMA_H__
+int dwmac_dma_wait_idle(struct stmmac_priv *priv, void __iomem *ioaddr);
+
/* DMA CRS Control and Status Register Mapping */
#define DMA_BUS_MODE 0x00001000 /* Bus Mode */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
index a0383f9486c2..bb907db8fca1 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
@@ -27,6 +27,26 @@ int dwmac_dma_reset(void __iomem *ioaddr)
10000, 200000);
}
+int dwmac_dma_wait_idle(struct stmmac_priv *priv, void __iomem *ioaddr)
+{
+ u32 channels = max(priv->plat->rx_queues_to_use,
+ priv->plat->tx_queues_to_use);
+ u32 value, chan;
+ int ret;
+
+ /* CSR5 process states, not the latched process-stopped interrupts.
+ * Stopped is reached after the outstanding descriptor writeback.
+ */
+ for (chan = 0; chan < channels; chan++) {
+ ret = readl_poll_timeout(ioaddr + DMA_CHAN_STATUS(chan), value,
+ !(value & (DMA_STATUS_TS_MASK | DMA_STATUS_RS_MASK)),
+ 100, 100000);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
/* CSR1 enables the transmit DMA to check for new descriptor */
void dwmac_enable_dma_transmission(void __iomem *ioaddr, u32 chan)
{
diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h
index 857f7562c6c6..3bc4d955ef5f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/hwif.h
+++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h
@@ -205,6 +205,8 @@ struct stmmac_dma_ops {
u32 chan);
void (*stop_rx)(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan);
+ /* Called after stopping every channel; must also drain bus accesses. */
+ int (*wait_idle)(struct stmmac_priv *priv, void __iomem *ioaddr);
int (*dma_interrupt)(struct stmmac_priv *priv, void __iomem *ioaddr,
struct stmmac_extra_stats *x, u32 chan, u32 dir);
/* If supported then get the optional core features */
@@ -269,6 +271,8 @@ struct stmmac_dma_ops {
stmmac_do_void_callback(__priv, dma, start_rx, __priv, __args)
#define stmmac_stop_rx(__priv, __args...) \
stmmac_do_void_callback(__priv, dma, stop_rx, __priv, __args)
+#define stmmac_dma_wait_idle(__priv, __args...) \
+ stmmac_do_callback(__priv, dma, wait_idle, __priv, __args)
#define stmmac_dma_interrupt_status(__priv, __args...) \
stmmac_do_callback(__priv, dma, dma_interrupt, __priv, __args)
#define stmmac_get_hw_feature(__priv, __args...) \
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 6abb0d1d4095..e15bab9accce 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -120,6 +120,7 @@ struct stmmac_rx_queue {
u32 queue_index;
struct xdp_rxq_info xdp_rxq;
struct xsk_buff_pool *xsk_pool;
+ struct xsk_dma_map *xsk_dma;
struct page_pool *page_pool;
struct stmmac_rx_buffer *buf_pool;
struct stmmac_priv *priv_data;
@@ -223,6 +224,10 @@ struct stmmac_rfs_entry {
};
struct stmmac_dma_conf {
+ /* RTNL: all configurations exposed to DMA survive until stop/reset. */
+ struct list_head list;
+ bool dma_owned;
+ bool retired;
unsigned int dma_buf_sz;
/* RX Queue */
@@ -266,11 +271,11 @@ struct stmmac_msi {
};
enum stmmac_datapath_state {
- /* No IRQs or DMA allocations owned by a successful open. */
+ /* No IRQs or enabled NAPI; failed DMA shutdown may retain memory. */
STMMAC_DATAPATH_DOWN,
/* Resources allocated, NAPI enabled. */
STMMAC_DATAPATH_RUNNING,
- /* Resources retained, NAPI and DMA stopped; also after failed resume. */
+ /* Resources retained, NAPI disabled, DMA stop requested. */
STMMAC_DATAPATH_SUSPENDED,
};
@@ -297,6 +302,8 @@ struct stmmac_priv {
struct mutex lock;
struct stmmac_dma_conf *dma_conf;
+ struct list_head dma_confs;
+ bool dma_reset_needed;
/* IRQ/DMA ownership and NAPI state, serialized by RTNL. */
enum stmmac_datapath_state datapath;
/* Core sleep sequence completed, independently of datapath ownership. */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index edcb9a025385..4d70645c4a4f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -988,6 +988,24 @@ static void stmmac_block_ptp(struct stmmac_priv *priv, bool block)
write_unlock_irqrestore(&priv->ptp_lock, flags);
}
+static int stmmac_restore_timestamping(struct stmmac_priv *priv)
+{
+ int ret;
+
+ if (priv->plat->ptp_clk_freq_config)
+ priv->plat->ptp_clk_freq_config(priv);
+ if (!(priv->dma_cap.time_stamp || priv->dma_cap.atime_stamp) ||
+ !priv->plat->clk_ptp_rate)
+ return 0;
+
+ ret = stmmac_init_tstamp_counter(priv, priv->systime_flags);
+ if (ret)
+ return ret;
+ if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY)
+ stmmac_hwtstamp_correct_latency(priv, priv);
+ return stmmac_ptp_restore(priv);
+}
+
static void stmmac_legacy_serdes_power_down(struct stmmac_priv *priv)
{
if (priv->plat->serdes_powerdown && priv->legacy_serdes_is_powered)
@@ -1665,24 +1683,10 @@ static void stmmac_clear_descriptors(struct stmmac_priv *priv,
stmmac_clear_tx_descriptors(priv, dma_conf, queue);
}
-/**
- * stmmac_init_rx_buffers - init the RX descriptor buffer.
- * @priv: driver private structure
- * @dma_conf: structure to take the dma data
- * @p: descriptor pointer
- * @i: descriptor index
- * @flags: gfp flag
- * @queue: RX queue index
- * Description: this function is called to allocate a receive buffer, perform
- * the DMA mapping and init the descriptor.
- */
-static int stmmac_init_rx_buffers(struct stmmac_priv *priv,
- struct stmmac_dma_conf *dma_conf,
- struct dma_desc *p,
- int i, gfp_t flags, u32 queue)
+static int stmmac_alloc_rx_buffer(struct stmmac_priv *priv,
+ struct stmmac_rx_queue *rx_q,
+ struct stmmac_rx_buffer *buf)
{
- struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
- struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN);
if (priv->dma_cap.host_dma_width <= 32)
@@ -1699,19 +1703,49 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv,
buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp);
if (!buf->sec_page)
return -ENOMEM;
-
buf->sec_addr = page_pool_get_dma_addr(buf->sec_page);
- stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true);
- } else {
- buf->sec_page = NULL;
- stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false);
}
+ return 0;
+}
+
+static void stmmac_init_rx_buffer_desc(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ struct dma_desc *p,
+ struct stmmac_rx_buffer *buf)
+{
+ if (buf->sec_page)
+ buf->sec_addr = page_pool_get_dma_addr(buf->sec_page);
+ stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, !!buf->sec_page);
buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset;
stmmac_set_desc_addr(priv, p, buf->addr);
if (dma_conf->dma_buf_sz == BUF_SIZE_16KiB)
stmmac_init_desc3(priv, p);
+}
+
+/**
+ * stmmac_init_rx_buffers - allocate a receive buffer and init its descriptor
+ * @priv: driver private structure
+ * @dma_conf: structure to take the dma data
+ * @p: descriptor pointer
+ * @i: descriptor index
+ * @flags: gfp flag
+ * @queue: RX queue index
+ */
+static int stmmac_init_rx_buffers(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ struct dma_desc *p,
+ int i, gfp_t flags, u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
+ int ret;
+
+ ret = stmmac_alloc_rx_buffer(priv, rx_q, buf);
+ if (ret)
+ return ret;
+ stmmac_init_rx_buffer_desc(priv, dma_conf, p, buf);
return 0;
}
@@ -1923,6 +1957,9 @@ static int __init_dma_rx_desc_rings(struct stmmac_priv *priv,
rx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue);
if (rx_q->xsk_pool) {
+ rx_q->xsk_dma = xsk_pool_dma_get(rx_q->xsk_pool);
+ if (!rx_q->xsk_dma)
+ return -EINVAL;
ret = xdp_rxq_info_reg_mem_model(&rx_q->xdp_rxq,
MEM_TYPE_XSK_BUFF_POOL, NULL);
if (ret)
@@ -2168,6 +2205,41 @@ static void stmmac_free_tx_skbufs(struct stmmac_priv *priv)
dma_free_tx_skbufs(priv, priv->dma_conf, queue);
}
+/* Only after a successful DMA reset, and with all RX buffers prepared. */
+static void stmmac_reinit_dma_desc(struct stmmac_priv *priv)
+{
+ struct stmmac_dma_conf *dma_conf = priv->dma_conf;
+ u32 queue, i;
+
+ stmmac_free_tx_skbufs(priv);
+ stmmac_reset_queues_param(priv);
+ init_dma_tx_desc_rings(priv->dev, dma_conf);
+
+ for (queue = 0; queue < priv->plat->rx_queues_to_use; queue++) {
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+
+ if (rx_q->state_saved)
+ dev_kfree_skb_any(rx_q->state.skb);
+ rx_q->state.skb = NULL;
+ rx_q->state_saved = 0;
+ rx_q->rx_count_frames = 0;
+ rx_q->buf_alloc_num = dma_conf->dma_rx_size;
+
+ for (i = 0; i < dma_conf->dma_rx_size; i++)
+ stmmac_init_rx_buffer_desc(priv, dma_conf,
+ stmmac_get_rx_desc(priv, rx_q, i),
+ &rx_q->buf_pool[i]);
+
+ if (priv->descriptor_mode == STMMAC_CHAIN_MODE)
+ stmmac_mode_init(priv, stmmac_get_rx_desc(priv, rx_q, 0),
+ rx_q->dma_rx_phy, dma_conf->dma_rx_size,
+ priv->extend_desc);
+ }
+
+ /* Address programming can overwrite OWN in GMAC4/XGMAC descriptors. */
+ stmmac_clear_descriptors(priv, dma_conf);
+}
+
/**
* __free_dma_rx_desc_resources - free RX dma desc resources (per queue)
* @priv: private structure
@@ -2214,6 +2286,9 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
kfree(rx_q->buf_pool);
if (rx_q->page_pool)
page_pool_destroy(rx_q->page_pool);
+ if (rx_q->xsk_dma)
+ xsk_pool_dma_put(rx_q->xsk_dma);
+ rx_q->xsk_dma = NULL;
rx_q->buf_pool = NULL;
rx_q->page_pool = NULL;
rx_q->dma_erx = NULL;
@@ -2223,11 +2298,10 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
static void free_dma_rx_desc_resources(struct stmmac_priv *priv,
struct stmmac_dma_conf *dma_conf)
{
- u8 rx_count = priv->plat->rx_queues_to_use;
u8 queue;
/* Free RX queue resources */
- for (queue = 0; queue < rx_count; queue++)
+ for (queue = 0; queue < MTL_MAX_RX_QUEUES; queue++)
__free_dma_rx_desc_resources(priv, dma_conf, queue);
}
@@ -2274,11 +2348,10 @@ static void __free_dma_tx_desc_resources(struct stmmac_priv *priv,
static void free_dma_tx_desc_resources(struct stmmac_priv *priv,
struct stmmac_dma_conf *dma_conf)
{
- u8 tx_count = priv->plat->tx_queues_to_use;
u8 queue;
/* Free TX queue resources */
- for (queue = 0; queue < tx_count; queue++)
+ for (queue = 0; queue < MTL_MAX_TX_QUEUES; queue++)
__free_dma_tx_desc_resources(priv, dma_conf, queue);
}
@@ -2481,14 +2554,36 @@ static int alloc_dma_desc_resources(struct stmmac_priv *priv,
return ret;
}
-/**
- * free_dma_desc_resources - free dma desc resources
- * @priv: private structure
- * @dma_conf: structure to take the dma data
- */
+static void stmmac_detach_xsk_buffers(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u32 queue;
+
+ /* Socket teardown cannot retain the pool itself. Drop software-only
+ * references, but keep each xsk_dma reference: hardware can still reach
+ * the mapped UMEM pages even after the pool and its heads are freed.
+ */
+ for (queue = 0; queue < MTL_MAX_RX_QUEUES; queue++) {
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+
+ if (!rx_q->xsk_pool)
+ continue;
+ dma_free_rx_xskbufs(priv, dma_conf, queue);
+ xsk_pool_set_rxq_info(rx_q->xsk_pool, NULL);
+ rx_q->xsk_pool = NULL;
+ }
+ for (queue = 0; queue < MTL_MAX_TX_QUEUES; queue++)
+ dma_conf->tx_queue[queue].xsk_pool = NULL;
+}
+
static void free_dma_desc_resources(struct stmmac_priv *priv,
struct stmmac_dma_conf *dma_conf)
{
+ if (dma_conf->dma_owned) {
+ stmmac_detach_xsk_buffers(priv, dma_conf);
+ return;
+ }
+
/* Release the DMA TX socket buffers */
free_dma_tx_desc_resources(priv, dma_conf);
@@ -2498,6 +2593,42 @@ static void free_dma_desc_resources(struct stmmac_priv *priv,
free_dma_rx_desc_resources(priv, dma_conf);
}
+static void stmmac_put_dma_conf(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ free_dma_desc_resources(priv, dma_conf);
+ if (dma_conf->dma_owned) {
+ dma_conf->retired = true;
+ return;
+ }
+ list_del(&dma_conf->list);
+ kfree(dma_conf);
+}
+
+/* A successful global reset is also the retirement fence for configurations
+ * retained by a previous failed close, open, or MTU rollback.
+ */
+static void stmmac_dma_reset_complete(struct stmmac_priv *priv)
+{
+ struct stmmac_dma_conf *dma_conf, *next;
+
+ list_for_each_entry_safe(dma_conf, next, &priv->dma_confs, list) {
+ dma_conf->dma_owned = false;
+ if (dma_conf->retired)
+ stmmac_put_dma_conf(priv, dma_conf);
+ }
+}
+
+static bool stmmac_dma_busy(struct stmmac_priv *priv)
+{
+ struct stmmac_dma_conf *dma_conf;
+
+ list_for_each_entry(dma_conf, &priv->dma_confs, list)
+ if (dma_conf->dma_owned)
+ return true;
+ return false;
+}
+
/**
* stmmac_mac_enable_rx_queues - Enable MAC rx queues
* @priv: driver private structure
@@ -2524,6 +2655,7 @@ static void stmmac_mac_enable_rx_queues(struct stmmac_priv *priv)
*/
static void stmmac_start_rx_dma(struct stmmac_priv *priv, u32 chan)
{
+ priv->dma_conf->dma_owned = true;
netdev_dbg(priv->dev, "DMA RX processes started in channel %d\n", chan);
stmmac_start_rx(priv, priv->ioaddr, chan);
}
@@ -2537,6 +2669,7 @@ static void stmmac_start_rx_dma(struct stmmac_priv *priv, u32 chan)
*/
static void stmmac_start_tx_dma(struct stmmac_priv *priv, u32 chan)
{
+ priv->dma_conf->dma_owned = true;
netdev_dbg(priv->dev, "DMA TX processes started in channel %d\n", chan);
stmmac_start_tx(priv, priv->ioaddr, chan);
}
@@ -3072,25 +3205,17 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
* stmmac_tx_err - to manage the tx error
* @priv: driver private structure
* @chan: channel index
- * Description: it cleans the descriptors and restarts the transmission
- * in case of transmission errors.
+ * Description: stop submissions and request process-context DMA recovery.
*/
static void stmmac_tx_err(struct stmmac_priv *priv, u32 chan)
{
- struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[chan];
-
netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, chan));
-
stmmac_stop_tx_dma(priv, chan);
- dma_free_tx_skbufs(priv, priv->dma_conf, chan);
- stmmac_clear_tx_descriptors(priv, priv->dma_conf, chan);
- stmmac_reset_tx_queue(priv, chan);
- stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
- tx_q->dma_tx_phy, chan);
- stmmac_start_tx_dma(priv, chan);
-
priv->xstats.tx_errors++;
- netif_tx_wake_queue(netdev_get_tx_queue(priv->dev, chan));
+ /* Recovery must wait for DMA before freeing or rewriting descriptors.
+ * Use the process-context reset path, not teardown in hard IRQ context.
+ */
+ stmmac_global_err(priv);
}
/**
@@ -3326,12 +3451,13 @@ static int stmmac_prereset_configure(struct stmmac_priv *priv)
/**
* stmmac_init_dma_engine - DMA init.
* @priv: driver private structure
+ * @reinit: rebuild the retained rings after a successful reset
* Description:
* It inits the DMA invoking the specific MAC/GMAC callback.
* Some DMA parameters can be passed from the platform;
* in case of these are not passed a default is kept for the MAC or GMAC.
*/
-static int stmmac_init_dma_engine(struct stmmac_priv *priv)
+static int stmmac_init_dma_engine(struct stmmac_priv *priv, bool reinit)
{
u8 rx_channels_count = priv->plat->rx_queues_to_use;
u8 tx_channels_count = priv->plat->tx_queues_to_use;
@@ -3350,6 +3476,19 @@ static int stmmac_init_dma_engine(struct stmmac_priv *priv)
netdev_err(priv->dev, "Failed to reset the dma\n");
return ret;
}
+ stmmac_dma_reset_complete(priv);
+ priv->dma_reset_needed = false;
+
+ if (reinit) {
+ stmmac_reinit_dma_desc(priv);
+ } else if (priv->datapath == STMMAC_DATAPATH_SUSPENDED) {
+ /* Suspend only requested a stop. Do not modify its descriptors
+ * or release pending TX buffers until this reset has completed.
+ */
+ stmmac_reset_queues_param(priv);
+ stmmac_free_tx_skbufs(priv);
+ stmmac_clear_descriptors(priv, priv->dma_conf);
+ }
/* DMA Configuration */
stmmac_dma_init(priv, priv->ioaddr, priv->plat->dma_cfg);
@@ -3700,6 +3839,8 @@ static bool stmmac_tso_channel_permitted(struct stmmac_priv *priv,
/**
* stmmac_hw_setup - setup mac in a usable state.
* @dev : pointer to the device structure.
+ * @reinit: rebuild retained descriptor rings after the DMA reset
+ * @keep_ptp: restore the registered PHC's configuration before starting DMA
* Description:
* this is the main function to setup the HW in a usable state because the
* dma engine is reset, the core registers are configured (e.g. AXI,
@@ -3709,7 +3850,7 @@ static bool stmmac_tso_channel_permitted(struct stmmac_priv *priv,
* 0 on success and an appropriate (-)ve integer as defined in errno.h
* file on failure.
*/
-static int stmmac_hw_setup(struct net_device *dev)
+static int stmmac_hw_setup(struct net_device *dev, bool reinit, bool keep_ptp)
{
struct stmmac_priv *priv = netdev_priv(dev);
u8 rx_cnt = priv->plat->rx_queues_to_use;
@@ -3731,7 +3872,7 @@ static int stmmac_hw_setup(struct net_device *dev)
phylink_rx_clk_stop_block(priv->phylink);
/* DMA initialization and SW reset */
- ret = stmmac_init_dma_engine(priv);
+ ret = stmmac_init_dma_engine(priv, reinit);
if (ret < 0) {
phylink_rx_clk_stop_unblock(priv->phylink);
netdev_err(priv->dev, "%s: DMA engine initialization failed\n",
@@ -3823,6 +3964,15 @@ static int stmmac_hw_setup(struct net_device *dev)
stmmac_enable_tbs(priv, priv->ioaddr, enable, chan);
}
+ if (keep_ptp) {
+ ret = stmmac_restore_timestamping(priv);
+ if (ret)
+ return ret;
+ ret = stmmac_tc_restore_est(priv);
+ if (ret)
+ return ret;
+ }
+
phylink_rx_clk_stop_block(priv->phylink);
stmmac_set_hw_vlan_mode(priv, priv->hw);
phylink_rx_clk_stop_unblock(priv->phylink);
@@ -4169,6 +4319,7 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
__func__);
return ERR_PTR(-ENOMEM);
}
+ list_add_tail(&dma_conf->list, &priv->dma_confs);
len = mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN;
@@ -4219,9 +4370,8 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
return dma_conf;
init_error:
- free_dma_desc_resources(priv, dma_conf);
alloc_error:
- kfree(dma_conf);
+ stmmac_put_dma_conf(priv, dma_conf);
return ERR_PTR(ret);
}
@@ -4308,6 +4458,57 @@ static int stmmac_resume_hw(struct stmmac_priv *priv)
return 0;
}
+/* NAPI, transmitters and IRQ handlers have already been drained. Clearing
+ * ST/SR only requests a stop: the current frame may still access memory.
+ * Keep the configuration DMA-owned unless hardware acknowledges idle/reset.
+ */
+static void stmmac_drain_dma(struct stmmac_priv *priv)
+{
+ struct stmmac_dma_conf *dma_conf;
+ int ret;
+
+ if (priv->hw_unavailable)
+ return;
+ stmmac_stop_all_dma(priv);
+ stmmac_mac_set(priv, priv->ioaddr, false);
+ if (!stmmac_dma_busy(priv))
+ return;
+
+ /* A failed replacement may have programmed a different topology. Only
+ * a global reset can acknowledge all of those retired configurations.
+ */
+ list_for_each_entry(dma_conf, &priv->dma_confs, list)
+ if (dma_conf != priv->dma_conf && dma_conf->dma_owned)
+ goto reset;
+
+ ret = stmmac_dma_wait_idle(priv, priv->ioaddr);
+ if (!ret) {
+ priv->dma_conf->dma_owned = false;
+ return;
+ }
+
+ /* Some integrations do not expose a usable idle indication. Reset is
+ * also the fallback after a stop timeout. It needs the PHY RX clock,
+ * even though phylink has already stopped link resolution.
+ */
+reset:
+ phylink_prepare_resume(priv->phylink);
+ mutex_lock(&priv->ptp_mutex);
+ stmmac_block_ptp(priv, true);
+ priv->dma_reset_needed = true;
+ phylink_rx_clk_stop_block(priv->phylink);
+ ret = stmmac_prereset_configure(priv);
+ if (!ret)
+ ret = stmmac_reset(priv);
+ phylink_rx_clk_stop_unblock(priv->phylink);
+ if (!ret)
+ stmmac_dma_reset_complete(priv);
+ else
+ netdev_err(priv->dev, "DMA shutdown failed: %pe; retaining DMA memory\n",
+ ERR_PTR(ret));
+ mutex_unlock(&priv->ptp_mutex);
+}
+
/**
* __stmmac_open - open entry point of the driver
* @dev : pointer to the device structure.
@@ -4339,7 +4540,7 @@ static int __stmmac_open(struct net_device *dev,
stmmac_reset_queues_param(priv);
- ret = stmmac_hw_setup(dev);
+ ret = stmmac_hw_setup(dev, false, false);
if (ret < 0) {
netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
goto init_error;
@@ -4391,8 +4592,9 @@ static int __stmmac_open(struct net_device *dev,
* phylink_start(). Keep the PHY attachment and outer PM ownership.
*/
phylink_stop(priv->phylink);
- stmmac_stop_all_dma(priv);
- stmmac_mac_set(priv, priv->ioaddr, false);
+ stmmac_drain_dma(priv);
+ /* Reset fallback may have powered the stopped PHY up for its clock. */
+ phylink_stop(priv->phylink);
return ret;
}
@@ -4436,7 +4638,7 @@ static int stmmac_open(struct net_device *dev)
if (ret)
goto err_serdes;
- kfree(old_conf);
+ stmmac_put_dma_conf(priv, old_conf);
/* We may have called phylink_speed_down before */
phylink_speed_up(priv->phylink);
@@ -4451,8 +4653,7 @@ static int stmmac_open(struct net_device *dev)
pm_runtime_put(priv->device);
err_dma_resources:
priv->dma_conf = old_conf;
- free_dma_desc_resources(priv, dma_conf);
- kfree(dma_conf);
+ stmmac_put_dma_conf(priv, dma_conf);
return ret;
}
@@ -4499,23 +4700,19 @@ static void __stmmac_release(struct net_device *dev)
/* Free the IRQ lines */
stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
- /* TX error IRQs can restart a queue after the first quiescence. */
+ /* Drain any final IRQ-triggered network activity before DMA shutdown. */
stmmac_stop_tx_queues(priv);
+ if (!priv->hw_unavailable && stmmac_fpe_supported(priv))
+ ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
- /* Stop TX/RX DMA after draining IRQ handlers which can restart it. */
- if (!priv->hw_unavailable) {
- stmmac_stop_all_dma(priv);
- /* Link resolution need not have reached mac_link_up() yet. */
- stmmac_mac_set(priv, priv->ioaddr, false);
- }
+ /* Only confirmed hardware shutdown permits releasing DMA memory. */
+ stmmac_drain_dma(priv);
+ phylink_stop(priv->phylink);
/* Release and free the Rx/Tx resources */
free_dma_desc_resources(priv, priv->dma_conf);
stmmac_release_ptp(priv);
-
- if (!priv->hw_unavailable && stmmac_fpe_supported(priv))
- ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
}
/**
@@ -6447,8 +6644,7 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
ret = __stmmac_open(dev, dma_conf);
if (ret) {
priv->dma_conf = old_conf;
- free_dma_desc_resources(priv, dma_conf);
- kfree(dma_conf);
+ stmmac_put_dma_conf(priv, dma_conf);
/*
* Keep the administrative state and PHY/PM ownership until
* ndo_stop(), but prevent use of the released data path.
@@ -6458,7 +6654,7 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
return ret;
}
- kfree(old_conf);
+ stmmac_put_dma_conf(priv, old_conf);
stmmac_set_rx_mode(dev);
netif_device_attach(dev);
@@ -7383,24 +7579,19 @@ void stmmac_xdp_release(struct net_device *dev)
/* Free the IRQ lines */
stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
stmmac_stop_tx_queues(priv);
+ if (stmmac_fpe_supported(priv))
+ ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
- /* Stop TX/RX DMA channels */
- stmmac_stop_all_dma(priv);
+ stmmac_drain_dma(priv);
/* Release and free the Rx/Tx resources */
free_dma_desc_resources(priv, priv->dma_conf);
- /* Disable the MAC Rx/Tx */
- stmmac_mac_set(priv, priv->ioaddr, false);
-
/* set trans_start so we don't get spurious
* watchdogs during reset
*/
netif_trans_update(dev);
- if (stmmac_fpe_supported(priv))
- ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
-
/* Keep PTP across the immediately following stmmac_xdp_open(). That
* function releases it if reopening fails, before returning DOWN.
*/
@@ -7418,6 +7609,14 @@ int stmmac_xdp_open(struct net_device *dev)
u8 chan;
int ret;
+ /* The old rings cannot be overwritten after a failed shutdown. Pool
+ * removal still completes, with their mappings held independently.
+ */
+ if (stmmac_dma_busy(priv)) {
+ ret = -EBUSY;
+ goto dma_desc_error;
+ }
+
ret = alloc_dma_desc_resources(priv, priv->dma_conf);
if (ret < 0) {
netdev_err(dev, "%s: DMA descriptors allocation failed\n",
@@ -7433,6 +7632,21 @@ int stmmac_xdp_open(struct net_device *dev)
}
stmmac_reset_queues_param(priv);
+ if (priv->dma_reset_needed) {
+ phylink_prepare_resume(priv->phylink);
+ mutex_lock(&priv->ptp_mutex);
+ ret = stmmac_hw_setup(dev, false, true);
+ if (!ret)
+ stmmac_block_ptp(priv, false);
+ mutex_unlock(&priv->ptp_mutex);
+ if (ret) {
+ stmmac_drain_dma(priv);
+ goto init_error;
+ }
+ stmmac_set_rx_mode(dev);
+ stmmac_vlan_restore(priv);
+ goto setup_timers;
+ }
/* DMA CSR Channel configuration */
for (chan = 0; chan < dma_csr_ch; chan++) {
@@ -7469,15 +7683,17 @@ int stmmac_xdp_open(struct net_device *dev)
if (tx_q->tbs & STMMAC_TBS_AVAIL)
stmmac_enable_tbs(priv, priv->ioaddr, 1, chan);
-
- hrtimer_setup(&tx_q->txtimer, stmmac_tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
}
/* Enable the MAC Rx/Tx */
stmmac_mac_set(priv, priv->ioaddr, true);
- /* Start Rx & Tx DMA Channels */
+setup_timers:
+ /* The reset path has also restored filters, PTP and the EST schedule. */
stmmac_start_all_dma(priv);
+ for (chan = 0; chan < tx_cnt; chan++)
+ hrtimer_setup(&priv->dma_conf->tx_queue[chan].txtimer,
+ stmmac_tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ret = stmmac_request_irq(dev);
if (ret)
@@ -7494,8 +7710,7 @@ int stmmac_xdp_open(struct net_device *dev)
irq_error:
stmmac_stop_tx_queues(priv);
- stmmac_stop_all_dma(priv);
- stmmac_mac_set(priv, priv->ioaddr, false);
+ stmmac_drain_dma(priv);
init_error:
free_dma_desc_resources(priv, priv->dma_conf);
@@ -7626,7 +7841,7 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv)
netdev_err(priv->dev, "Reset adapter.\n");
rtnl_lock();
- if (!netif_device_present(priv->dev))
+ if (!netif_device_present(priv->dev) || !netif_running(priv->dev))
goto out_unlock;
netif_trans_update(priv->dev);
@@ -7900,12 +8115,11 @@ static int stmmac_reopen(struct net_device *dev)
ret = __stmmac_open(dev, dma_conf);
if (ret) {
priv->dma_conf = old_conf;
- free_dma_desc_resources(priv, dma_conf);
- kfree(dma_conf);
+ stmmac_put_dma_conf(priv, dma_conf);
return ret;
}
- kfree(old_conf);
+ stmmac_put_dma_conf(priv, old_conf);
netif_device_attach(dev);
return 0;
}
@@ -7940,6 +8154,8 @@ int stmmac_reinit_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt)
netif_device_detach(dev);
__stmmac_release(dev);
}
+ if (stmmac_dma_busy(priv))
+ return -EBUSY;
stmmac_set_queues(dev, rx_cnt, tx_cnt);
@@ -7967,6 +8183,8 @@ int stmmac_reinit_ringparam(struct net_device *dev, u32 rx_size, u32 tx_size)
netif_device_detach(dev);
__stmmac_release(dev);
}
+ if (stmmac_dma_busy(priv))
+ return -EBUSY;
priv->dma_conf->dma_rx_size = rx_size;
priv->dma_conf->dma_tx_size = tx_size;
@@ -8153,8 +8371,20 @@ EXPORT_SYMBOL_GPL(stmmac_plat_dat_alloc);
static void stmmac_free_dma_conf(void *data)
{
struct stmmac_priv *priv = data;
+ struct stmmac_dma_conf *dma_conf, *next;
- kfree(priv->dma_conf);
+ list_for_each_entry_safe(dma_conf, next, &priv->dma_confs, list) {
+ list_del(&dma_conf->list);
+ /* A permanently unresponsive device must not DMA into recycled
+ * memory, even on unbind. There is no generic isolation mechanism
+ * for all stmmac integrations. Deliberately retain these allocations.
+ */
+ if (dma_conf->dma_owned) {
+ dev_err(priv->device, "DMA still active on removal; DMA memory quarantined\n");
+ continue;
+ }
+ kfree(dma_conf);
+ }
}
static int __stmmac_dvr_probe(struct device *device,
@@ -8181,10 +8411,12 @@ static int __stmmac_dvr_probe(struct device *device,
priv = netdev_priv(ndev);
priv->device = device;
priv->dev = ndev;
+ INIT_LIST_HEAD(&priv->dma_confs);
/* Keep ring sizes and per-queue settings even while the device is down. */
priv->dma_conf = kzalloc_obj(*priv->dma_conf);
if (!priv->dma_conf)
return -ENOMEM;
+ list_add_tail(&priv->dma_conf->list, &priv->dma_confs);
ret = devm_add_action_or_reset(device, stmmac_free_dma_conf, priv);
if (ret)
return ret;
@@ -8508,12 +8740,28 @@ void stmmac_dvr_remove(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct stmmac_priv *priv = netdev_priv(ndev);
+ struct stmmac_dma_conf *dma_conf;
+ u32 queue;
netdev_info(priv->dev, "%s: removing driver", __func__);
pm_runtime_get_sync(dev);
unregister_netdev(ndev);
+ rtnl_lock();
+ /* A failed ndo_open has no matching ndo_stop. Its retained resources
+ * still need retirement, or software-only disconnection on timeout.
+ */
+ list_for_each_entry(dma_conf, &priv->dma_confs, list) {
+ free_dma_desc_resources(priv, dma_conf);
+ for (queue = 0; queue < MTL_MAX_RX_QUEUES; queue++) {
+ struct xdp_rxq_info *rxq = &dma_conf->rx_queue[queue].xdp_rxq;
+
+ if (xdp_rxq_info_is_reg(rxq))
+ xdp_rxq_info_unreg(rxq);
+ }
+ }
+ rtnl_unlock();
#ifdef CONFIG_DEBUG_FS
stmmac_exit_fs(ndev);
@@ -8721,12 +8969,7 @@ int stmmac_resume(struct device *dev)
mutex_lock(&priv->lock);
- stmmac_reset_queues_param(priv);
-
- stmmac_free_tx_skbufs(priv);
- stmmac_clear_descriptors(priv, priv->dma_conf);
-
- ret = stmmac_hw_setup(ndev);
+ ret = stmmac_hw_setup(ndev, false, false);
if (ret < 0) {
netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
goto error_stop_dma;
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 14/15] net: stmmac: prepare device-local DMA interrupt quiescence
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (12 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 13/15] net: stmmac: retain DMA memory until hardware shutdown completes James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 15:49 ` [PATCH net v4 15/15] net: stmmac: retain DMA resources across MTU changes James Hilliard
2026-09-26 16:00 ` [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures Maxime Chevallier
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Add DMA interrupt-mask accessors for the supported cores and a
per-channel gate protected by the channel lock. A handler invoked on a
shared IRQ can then mask newly enabled device sources without
acknowledging pending events or touching rings being replaced.
The retained-ring MTU transaction will save and restore these masks and
synchronize the registered handlers. No interrupt-controller line needs
to be disabled, so other devices sharing the IRQ remain serviceable.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 11 ++++++++
.../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 1 +
drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c | 1 +
drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 2 ++
drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h | 2 ++
drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c | 11 ++++++++
drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 2 ++
drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 10 ++++++++
drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 11 ++++++++
drivers/net/ethernet/stmicro/stmmac/hwif.h | 5 ++++
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 ++
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 30 ++++++++++++++++------
12 files changed, 80 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
index 422d3df21ed9..31bebb2a9bab 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
@@ -374,6 +374,16 @@ static void sun8i_dwmac_disable_dma_irq(struct stmmac_priv *priv,
writel(value, ioaddr + EMAC_INT_EN);
}
+static u32 sun8i_dwmac_set_dma_irq_mask(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan, u32 mask)
+{
+ u32 old_mask = readl(ioaddr + EMAC_INT_EN);
+
+ writel(mask, ioaddr + EMAC_INT_EN);
+ readl(ioaddr + EMAC_INT_EN);
+ return old_mask;
+}
+
static void sun8i_dwmac_dma_start_tx(struct stmmac_priv *priv,
void __iomem *ioaddr, u32 chan)
{
@@ -579,6 +589,7 @@ static const struct stmmac_dma_ops sun8i_dwmac_dma_ops = {
.enable_dma_transmission = sun8i_dwmac_enable_dma_transmission,
.enable_dma_irq = sun8i_dwmac_enable_dma_irq,
.disable_dma_irq = sun8i_dwmac_disable_dma_irq,
+ .set_irq_mask = sun8i_dwmac_set_dma_irq_mask,
.start_tx = sun8i_dwmac_dma_start_tx,
.stop_tx = sun8i_dwmac_dma_stop_tx,
.start_rx = sun8i_dwmac_dma_start_rx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
index 4cb7e6c16bdd..2285eac69071 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
@@ -264,6 +264,7 @@ const struct stmmac_dma_ops dwmac1000_dma_ops = {
.enable_dma_reception = dwmac_enable_dma_reception,
.enable_dma_irq = dwmac_enable_dma_irq,
.disable_dma_irq = dwmac_disable_dma_irq,
+ .set_irq_mask = dwmac_set_dma_irq_mask,
.start_tx = dwmac_dma_start_tx,
.stop_tx = dwmac_dma_stop_tx,
.start_rx = dwmac_dma_start_rx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
index 5ffd3c1471c4..41579d10af3c 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
@@ -118,6 +118,7 @@ const struct stmmac_dma_ops dwmac100_dma_ops = {
.enable_dma_transmission = dwmac_enable_dma_transmission,
.enable_dma_irq = dwmac_enable_dma_irq,
.disable_dma_irq = dwmac_disable_dma_irq,
+ .set_irq_mask = dwmac_set_dma_irq_mask,
.start_tx = dwmac_dma_start_tx,
.stop_tx = dwmac_dma_stop_tx,
.start_rx = dwmac_dma_start_rx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
index fb0a4e156a7b..86b23ce39d6b 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
@@ -580,6 +580,7 @@ const struct stmmac_dma_ops dwmac4_dma_ops = {
.dma_tx_mode = dwmac4_dma_tx_chan_op_mode,
.enable_dma_irq = dwmac4_enable_dma_irq,
.disable_dma_irq = dwmac4_disable_dma_irq,
+ .set_irq_mask = dwmac4_set_dma_irq_mask,
.start_tx = dwmac4_dma_start_tx,
.stop_tx = dwmac4_dma_stop_tx,
.start_rx = dwmac4_dma_start_rx,
@@ -611,6 +612,7 @@ const struct stmmac_dma_ops dwmac410_dma_ops = {
.dma_tx_mode = dwmac4_dma_tx_chan_op_mode,
.enable_dma_irq = dwmac4_enable_dma_irq,
.disable_dma_irq = dwmac4_disable_dma_irq,
+ .set_irq_mask = dwmac4_set_dma_irq_mask,
.start_tx = dwmac4_dma_start_tx,
.stop_tx = dwmac4_dma_stop_tx,
.start_rx = dwmac4_dma_start_rx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
index 9352107204eb..edccc09f0b03 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
@@ -184,6 +184,8 @@ void dwmac4_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan, bool rx, bool tx);
void dwmac4_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan, bool rx, bool tx);
+u32 dwmac4_set_dma_irq_mask(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, u32 mask);
void dwmac4_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan);
void dwmac4_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
index 9af0565a9bca..477bdb081c52 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
@@ -155,6 +155,17 @@ void dwmac4_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
writel(value, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
}
+u32 dwmac4_set_dma_irq_mask(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, u32 mask)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 old_mask = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+
+ writel(mask, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+ readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+ return old_mask;
+}
+
int dwmac4_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr,
struct stmmac_extra_stats *x, u32 chan, u32 dir)
{
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
index 970495bccfd2..4726801253f5 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
@@ -150,6 +150,8 @@ void dwmac_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan, bool rx, bool tx);
void dwmac_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan, bool rx, bool tx);
+u32 dwmac_set_dma_irq_mask(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, u32 mask);
void dwmac_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan);
void dwmac_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
index bb907db8fca1..88d904ed4685 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
@@ -84,6 +84,16 @@ void dwmac_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
writel(value, ioaddr + DMA_CHAN_INTR_ENA(chan));
}
+u32 dwmac_set_dma_irq_mask(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, u32 mask)
+{
+ u32 old_mask = readl(ioaddr + DMA_CHAN_INTR_ENA(chan));
+
+ writel(mask, ioaddr + DMA_CHAN_INTR_ENA(chan));
+ readl(ioaddr + DMA_CHAN_INTR_ENA(chan));
+ return old_mask;
+}
+
void dwmac_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan)
{
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
index ff83858ebc1f..30915d3f5230 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
@@ -256,6 +256,16 @@ static void dwxgmac2_disable_dma_irq(struct stmmac_priv *priv,
writel(value, ioaddr + XGMAC_DMA_CH_INT_EN(chan));
}
+static u32 dwxgmac2_set_dma_irq_mask(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan, u32 mask)
+{
+ u32 old_mask = readl(ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+
+ writel(mask, ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+ readl(ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+ return old_mask;
+}
+
static void dwxgmac2_dma_start_tx(struct stmmac_priv *priv,
void __iomem *ioaddr, u32 chan)
{
@@ -604,6 +614,7 @@ const struct stmmac_dma_ops dwxgmac210_dma_ops = {
.dma_tx_mode = dwxgmac2_dma_tx_mode,
.enable_dma_irq = dwxgmac2_enable_dma_irq,
.disable_dma_irq = dwxgmac2_disable_dma_irq,
+ .set_irq_mask = dwxgmac2_set_dma_irq_mask,
.start_tx = dwxgmac2_dma_start_tx,
.stop_tx = dwxgmac2_dma_stop_tx,
.start_rx = dwxgmac2_dma_start_rx,
diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h
index 3bc4d955ef5f..e1630e84e91d 100644
--- a/drivers/net/ethernet/stmicro/stmmac/hwif.h
+++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h
@@ -197,6 +197,9 @@ struct stmmac_dma_ops {
u32 chan, bool rx, bool tx);
void (*disable_dma_irq)(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan, bool rx, bool tx);
+ /* Replace and flush the full interrupt enable mask; return the old mask. */
+ u32 (*set_irq_mask)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, u32 mask);
void (*start_tx)(struct stmmac_priv *priv, void __iomem *ioaddr,
u32 chan);
void (*stop_tx)(struct stmmac_priv *priv, void __iomem *ioaddr,
@@ -263,6 +266,8 @@ struct stmmac_dma_ops {
stmmac_do_void_callback(__priv, dma, enable_dma_irq, __priv, __args)
#define stmmac_disable_dma_irq(__priv, __args...) \
stmmac_do_void_callback(__priv, dma, disable_dma_irq, __priv, __args)
+#define stmmac_set_dma_irq_mask(__priv, __args...) \
+ stmmac_do_callback(__priv, dma, set_irq_mask, __priv, __args)
#define stmmac_start_tx(__priv, __args...) \
stmmac_do_void_callback(__priv, dma, start_tx, __priv, __args)
#define stmmac_stop_tx(__priv, __args...) \
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index e15bab9accce..9e711acb81fb 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -146,6 +146,8 @@ struct stmmac_channel {
struct stmmac_priv *priv_data;
spinlock_t lock;
u32 index;
+ /* Protected by lock; IRQ handlers must not access the DMA rings. */
+ bool irq_quiesced;
};
struct stmmac_fpe_cfg {
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 4d70645c4a4f..08049c05ffc4 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -3267,14 +3267,30 @@ static bool stmmac_safety_feat_interrupt(struct stmmac_priv *priv)
static int stmmac_napi_check(struct stmmac_priv *priv, u32 chan, u32 dir)
{
- int status = stmmac_dma_interrupt_status(priv, priv->ioaddr,
- &priv->xstats, chan, dir);
- struct stmmac_rx_queue *rx_q = &priv->dma_conf->rx_queue[chan];
- struct stmmac_tx_queue *tx_q = &priv->dma_conf->tx_queue[chan];
struct stmmac_channel *ch = &priv->channel[chan];
+ struct stmmac_rx_queue *rx_q;
+ struct stmmac_tx_queue *tx_q;
struct napi_struct *rx_napi;
struct napi_struct *tx_napi;
unsigned long flags;
+ int status;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ if (unlikely(ch->irq_quiesced)) {
+ /* A shared IRQ may still invoke us, and DMA initialization can
+ * restore interrupt enables. Mask them again without acknowledging
+ * pending events or accessing the configuration being replaced.
+ */
+ stmmac_set_dma_irq_mask(priv, priv->ioaddr, chan, 0);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ return 0;
+ }
+ spin_unlock_irqrestore(&ch->lock, flags);
+
+ status = stmmac_dma_interrupt_status(priv, priv->ioaddr,
+ &priv->xstats, chan, dir);
+ rx_q = &priv->dma_conf->rx_queue[chan];
+ tx_q = &priv->dma_conf->tx_queue[chan];
rx_napi = rx_q->xsk_pool ? &ch->rxtx_napi : &ch->rx_napi;
tx_napi = tx_q->xsk_pool ? &ch->rxtx_napi : &ch->tx_napi;
@@ -3995,8 +4011,7 @@ static void stmmac_free_irq(struct net_device *dev,
for (j = irq_idx - 1; msi && j >= 0; j--) {
if (msi->tx_irq[j] > 0) {
irq_set_affinity_hint(msi->tx_irq[j], NULL);
- free_irq(msi->tx_irq[j],
- &priv->channel[j]);
+ free_irq(msi->tx_irq[j], &priv->channel[j]);
}
}
irq_idx = priv->plat->rx_queues_to_use;
@@ -4005,8 +4020,7 @@ static void stmmac_free_irq(struct net_device *dev,
for (j = irq_idx - 1; msi && j >= 0; j--) {
if (msi->rx_irq[j] > 0) {
irq_set_affinity_hint(msi->rx_irq[j], NULL);
- free_irq(msi->rx_irq[j],
- &priv->channel[j]);
+ free_irq(msi->rx_irq[j], &priv->channel[j]);
}
}
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH net v4 15/15] net: stmmac: retain DMA resources across MTU changes
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (13 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 14/15] net: stmmac: prepare device-local DMA interrupt quiescence James Hilliard
@ 2026-09-26 15:49 ` James Hilliard
2026-09-26 16:00 ` [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures Maxime Chevallier
15 siblings, 0 replies; 17+ messages in thread
From: James Hilliard @ 2026-09-26 15:49 UTC (permalink / raw)
To: Russell King, Andrew Lunn, Heiner Kallweit, David S. Miller,
Jakub Kicinski, Paolo Abeni, Joakim Zhang, Russell King (Oracle),
Maxime Chevallier, Andrew Lunn, Maxime Coquelin,
Alexandre Torgue, Christian Marangi, Tiezhu Yang, Huacai Chen,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Serge Semin, Suraj Jaiswal,
Richard Cochran, Joao Pinto, Vladimir Oltean, Ong Boon Leong,
Voon Weifeng, Song, Yoong Siang, Linus Walleij,
Martin Blumenstingl, Magnus Karlsson, Maciej Fijalkowski,
Simon Horman, Björn Töpel, Thierry Reding,
Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, James Hilliard, linux-tegra,
linux-sunxi
Prepare the replacement configuration before quiescing the old datapath.
Retain its rings and IRQ registrations until setup succeeds so rollback
needs no new allocations or IRQ requests. Fill RX buffer holes before
reset without altering active descriptors; rebuild the retained rings
only after a successful reset.
Mask the device DMA interrupt sources, gate shared-IRQ handlers and
drain all registered handlers before the final transmitter and timer
cancellation. Restore saved interrupt masks only after the selected
rings and NAPI are ready. This leaves interrupt-controller lines
available to unrelated devices.
Program receive limits using the prospective MTU and restore the old MTU
before rollback. Reapply PHC and TC state before starting DMA,
preserving the PHC registration and packet timestamp filters. Continuous
PHC time across the reset is not preserved.
If rollback fails, leave the administratively-up interface detached in a
distinct HALTED state: rings retained, NAPI disabled and IRQ
registrations released. Close or a later down/up can finish cleanup and
recovery without freeing IRQs twice. Retain potentially active DMA
memory until hardware shutdown is confirmed.
Fixes: 3470079687448 ("net: ethernet: stmicro: stmmac: permit MTU change with interface up")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 +
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 216 +++++++++++++++++-----
2 files changed, 173 insertions(+), 45 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 9e711acb81fb..e2b9b5b387c7 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -279,6 +279,8 @@ enum stmmac_datapath_state {
STMMAC_DATAPATH_RUNNING,
/* Resources retained, NAPI disabled, DMA stop requested. */
STMMAC_DATAPATH_SUSPENDED,
+ /* Failed MTU rollback: rings retained, but no IRQs or running NAPI. */
+ STMMAC_DATAPATH_HALTED,
};
struct stmmac_priv {
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 08049c05ffc4..8964f52c26ba 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -977,7 +977,7 @@ static void stmmac_release_ptp(struct stmmac_priv *priv)
}
/* ptp_mutex excludes configuration and crosstimestamp operations. The
- * spinlock also excludes atomic clock reads while changing this gate.
+ * spinlock also excludes atomic gettime callers while changing this gate.
*/
static void stmmac_block_ptp(struct stmmac_priv *priv, bool block)
{
@@ -2205,6 +2205,30 @@ static void stmmac_free_tx_skbufs(struct stmmac_priv *priv)
dma_free_tx_skbufs(priv, priv->dma_conf, queue);
}
+/* NAPI is stopped, but DMA may still be using the old rings. Fill holes in
+ * the software buffer array without changing any descriptors. If allocation
+ * fails, the old rings can continue unchanged. Otherwise rollback after a
+ * reset will not need to allocate buffers.
+ */
+static int stmmac_prepare_rx_buffers(struct stmmac_priv *priv)
+{
+ struct stmmac_dma_conf *dma_conf = priv->dma_conf;
+ u32 queue, i;
+ int ret;
+
+ for (queue = 0; queue < priv->plat->rx_queues_to_use; queue++) {
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+
+ for (i = 0; i < dma_conf->dma_rx_size; i++) {
+ ret = stmmac_alloc_rx_buffer(priv, rx_q, &rx_q->buf_pool[i]);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
/* Only after a successful DMA reset, and with all RX buffers prepared. */
static void stmmac_reinit_dma_desc(struct stmmac_priv *priv)
{
@@ -4311,14 +4335,38 @@ static void stmmac_synchronize_irq(struct stmmac_priv *priv)
synchronize_irq(msi->tx_irq[i]);
}
+/* Keep the IRQ registrations, but prevent DMA handlers from using the rings.
+ * The caller drains handlers after quiescing every channel and restores their
+ * masks only once the active DMA configuration is ready again.
+ */
+static void stmmac_set_dma_irq_state(struct stmmac_priv *priv, bool enable,
+ u32 *irq_mask)
+{
+ u32 channels = max(priv->plat->rx_queues_to_use,
+ priv->plat->tx_queues_to_use);
+ u32 chan;
+
+ for (chan = 0; chan < channels; chan++) {
+ struct stmmac_channel *ch = &priv->channel[chan];
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ ch->irq_quiesced = !enable;
+ if (enable)
+ stmmac_set_dma_irq_mask(priv, priv->ioaddr, chan,
+ irq_mask[chan]);
+ else
+ irq_mask[chan] = stmmac_set_dma_irq_mask(priv, priv->ioaddr,
+ chan, 0);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+}
+
/**
- * stmmac_setup_dma_desc - Generate a dma_conf and allocate DMA queue
- * @priv: driver private structure
- * @mtu: MTU to setup the dma queue and buf with
- * Description: Allocate and generate a dma_conf based on the provided MTU.
- * Allocate the Tx/Rx DMA queue and init them.
- * Return value:
- * the dma_conf allocated struct on success and an appropriate ERR_PTR on failure.
+ * stmmac_setup_dma_desc - allocate and initialize a DMA configuration
+ * @priv: driver private structure
+ * @mtu: MTU to size the receive buffers for
+ * Return: the allocated configuration, or an ERR_PTR on failure
*/
static struct stmmac_dma_conf *
stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
@@ -4544,7 +4592,7 @@ static int __stmmac_open(struct net_device *dev,
priv->dma_conf = dma_conf;
/* The PHY is suspended when the interface is reopened without
- * disconnecting the PHY, e.g. on MTU change. IEEE 802.3 allows PHYs
+ * disconnecting the PHY, e.g. on an ethtool change. IEEE 802.3 allows PHYs
* to stop their receive clock while powered down, but the DMA
* software reset in stmmac_hw_setup() requires a running receive
* clock, and phylink_start() below resumes the PHY only after the
@@ -4699,20 +4747,22 @@ static void stmmac_quiesce(struct stmmac_priv *priv)
static void __stmmac_release(struct net_device *dev)
{
struct stmmac_priv *priv = netdev_priv(dev);
+ enum stmmac_datapath_state state = priv->datapath;
- /* A failed MTU reopen has already released the data path. */
+ /* There may be no resources left after detached XDP reconfiguration. */
if (priv->datapath == STMMAC_DATAPATH_DOWN)
return;
phylink_stop(priv->phylink);
- /* Suspend retains the resources, but has already stopped activity. */
+ /* SUSPENDED and HALTED retain rings with NAPI already disabled. */
if (priv->datapath == STMMAC_DATAPATH_RUNNING)
stmmac_quiesce(priv);
priv->datapath = STMMAC_DATAPATH_DOWN;
/* Free the IRQ lines */
- stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+ if (state != STMMAC_DATAPATH_HALTED)
+ stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
/* Drain any final IRQ-triggered network activity before DMA shutdown. */
stmmac_stop_tx_queues(priv);
@@ -6601,6 +6651,109 @@ static void stmmac_set_rx_mode(struct net_device *dev)
stmmac_set_filter(priv, priv->hw, dev);
}
+static int stmmac_reconfigure_mtu(struct net_device *dev, int mtu)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ struct stmmac_dma_conf *old_conf = priv->dma_conf;
+ struct stmmac_dma_conf *new_conf;
+ int old_mtu = dev->mtu;
+ int ret, restore_ret;
+ u32 irq_mask[STMMAC_CH_MAX];
+ u32 chan;
+
+ new_conf = stmmac_setup_dma_desc(priv, mtu);
+ if (IS_ERR(new_conf))
+ return PTR_ERR(new_conf);
+
+ mutex_lock(&priv->ptp_mutex);
+ stmmac_block_ptp(priv, true);
+ netif_device_detach(dev);
+ phylink_stop(priv->phylink);
+ stmmac_quiesce(priv);
+ if (stmmac_fpe_supported(priv))
+ ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
+
+ /* Drain handlers before the final TX stop and configuration swap,
+ * and keep the registrations for rollback.
+ */
+ stmmac_set_dma_irq_state(priv, false, irq_mask);
+ stmmac_synchronize_irq(priv);
+ stmmac_stop_tx_queues(priv);
+
+ ret = stmmac_prepare_rx_buffers(priv);
+ if (ret)
+ goto restart;
+
+ stmmac_stop_all_dma(priv);
+ phylink_prepare_resume(priv->phylink);
+
+ /* MAC receive limits must be programmed for the prospective MTU. */
+ WRITE_ONCE(dev->mtu, mtu);
+ priv->dma_conf = new_conf;
+ stmmac_reset_queues_param(priv);
+ ret = stmmac_hw_setup(dev, false, true);
+ if (ret) {
+ stmmac_stop_all_dma(priv);
+ stmmac_mac_set(priv, priv->ioaddr, false);
+ priv->dma_conf = old_conf;
+ WRITE_ONCE(dev->mtu, old_mtu);
+
+ /* Reuse the retained rings. Reinitialize them only after reset
+ * has completed, not merely after clearing the DMA enable bits.
+ */
+ restore_ret = stmmac_hw_setup(dev, true, true);
+ if (restore_ret) {
+ stmmac_stop_all_dma(priv);
+ stmmac_mac_set(priv, priv->ioaddr, false);
+ /* Setup may have restored DMA interrupt enables. */
+ stmmac_set_dma_irq_state(priv, false, irq_mask);
+ stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+ stmmac_stop_tx_queues(priv);
+ stmmac_stop_all_dma(priv);
+ memset(irq_mask, 0, sizeof(irq_mask));
+ stmmac_set_dma_irq_state(priv, true, irq_mask);
+ priv->datapath = STMMAC_DATAPATH_HALTED;
+ netdev_err(dev, "MTU rollback failed: %pe; interface remains detached\n",
+ ERR_PTR(restore_ret));
+ goto free_new;
+ }
+ } else {
+ /* Hardware setup completed its reset before using the new rings.
+ * The old DMA allocations can now be released safely.
+ */
+ stmmac_put_dma_conf(priv, old_conf);
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
+ hrtimer_setup(&new_conf->tx_queue[chan].txtimer,
+ stmmac_tx_timer, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+ }
+
+ stmmac_set_rx_mode(dev);
+ stmmac_vlan_restore(priv);
+ stmmac_start_all_dma(priv);
+
+restart:
+ stmmac_block_ptp(priv, false);
+ mutex_unlock(&priv->ptp_mutex);
+ stmmac_enable_all_queues(priv);
+ stmmac_set_dma_irq_state(priv, true, irq_mask);
+ stmmac_enable_all_dma_irq(priv);
+ phylink_start(priv->phylink);
+ netif_device_attach(dev);
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
+ stmmac_tx_timer_arm(priv, chan);
+ if (!ret)
+ return 0;
+ goto free_conf;
+
+free_new:
+ /* Failed rollback leaves the registered PHC inaccessible as well. */
+ mutex_unlock(&priv->ptp_mutex);
+free_conf:
+ stmmac_put_dma_conf(priv, new_conf);
+ return ret;
+}
+
/**
* stmmac_change_mtu - entry point to change MTU size for the device.
* @dev : device pointer.
@@ -6615,9 +6768,7 @@ static void stmmac_set_rx_mode(struct net_device *dev)
static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
{
struct stmmac_priv *priv = netdev_priv(dev);
- struct stmmac_dma_conf *old_conf = priv->dma_conf;
int txfifosz = priv->plat->tx_fifo_size;
- struct stmmac_dma_conf *dma_conf;
const int mtu = new_mtu;
int ret;
@@ -6643,35 +6794,9 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
*/
if (netif_running(dev) &&
(dev->mtu > ETH_DATA_LEN || mtu > ETH_DATA_LEN)) {
- netdev_dbg(priv->dev, "restarting interface to change its MTU\n");
- /* Try to allocate the new DMA conf with the new mtu */
- dma_conf = stmmac_setup_dma_desc(priv, mtu);
- if (IS_ERR(dma_conf)) {
- netdev_err(priv->dev, "failed allocating new dma conf for new MTU %d\n",
- mtu);
- return PTR_ERR(dma_conf);
- }
-
- netif_device_detach(dev);
- __stmmac_release(dev);
-
- ret = __stmmac_open(dev, dma_conf);
- if (ret) {
- priv->dma_conf = old_conf;
- stmmac_put_dma_conf(priv, dma_conf);
- /*
- * Keep the administrative state and PHY/PM ownership until
- * ndo_stop(), but prevent use of the released data path.
- */
- netif_device_detach(dev);
- netdev_err(priv->dev, "failed reopening the interface after MTU change\n");
+ ret = stmmac_reconfigure_mtu(dev, mtu);
+ if (ret)
return ret;
- }
-
- stmmac_put_dma_conf(priv, old_conf);
-
- stmmac_set_rx_mode(dev);
- netif_device_attach(dev);
}
WRITE_ONCE(dev->mtu, mtu);
@@ -7514,11 +7639,12 @@ static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf)
return -EOPNOTSUPP;
/*
- * Pool removal must succeed even after a failed resume. Release the
- * suspended rings before their pool or XDP buffer layout can change.
+ * Pool removal must succeed after failed resume or MTU rollback. Release
+ * retained rings before their pool or XDP buffer layout can change.
* Leave the interface detached until it is closed and reopened.
*/
- if (priv->datapath == STMMAC_DATAPATH_SUSPENDED)
+ if (priv->datapath == STMMAC_DATAPATH_SUSPENDED ||
+ priv->datapath == STMMAC_DATAPATH_HALTED)
__stmmac_release(dev);
switch (bpf->command) {
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures
2026-09-26 15:48 [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures James Hilliard
` (14 preceding siblings ...)
2026-09-26 15:49 ` [PATCH net v4 15/15] net: stmmac: retain DMA resources across MTU changes James Hilliard
@ 2026-09-26 16:00 ` Maxime Chevallier
15 siblings, 0 replies; 17+ messages in thread
From: Maxime Chevallier @ 2026-09-26 16:00 UTC (permalink / raw)
To: James Hilliard, Russell King, Andrew Lunn, Heiner Kallweit,
David S. Miller, Jakub Kicinski, Paolo Abeni, Joakim Zhang,
Russell King (Oracle),
Andrew Lunn, Maxime Coquelin, Alexandre Torgue,
Christian Marangi, Tiezhu Yang, Huacai Chen, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Serge Semin, Suraj Jaiswal, Richard Cochran,
Joao Pinto, Vladimir Oltean, Ong Boon Leong, Voon Weifeng, Song,
Yoong Siang, Linus Walleij, Martin Blumenstingl, Magnus Karlsson,
Maciej Fijalkowski, Simon Horman, Björn Töpel,
Thierry Reding, Jonathan Hunter, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Eric Dumazet
Cc: Richard Genoud, Alastair D'Silva, Maxime Ripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, ZhaoJinming,
Lorenzo Bianconi, Ding Hui, linux-tegra, linux-sunxi
Hello James,
On 9/26/26 17:48, James Hilliard wrote:
> Keep the stmmac datapath coherent after failed MTU changes or hardware
> resume without changing the interface's administrative state. Retain the
> working MTU configuration for rollback, and allow ordinary down/up recovery
> when hardware cannot be restored.
>
> The fifteen patches put independent fixes and lifetime prerequisites ahead
> of the retained-ring MTU transaction:
>
> 1. Correct the WoL/safety IRQ unwind order.
> 2. Reuse the MDIO reset GPIO across repeated resets.
> 3. Allow phylink_stop() to finish a suspended instance.
> 4. Freeze deferred AF_XDP pool teardown before device system suspend.
> 5. Serialize PHC changes and cache reset-sensitive timestamp settings.
> 6. Keep normal-size MTU changes running, including supported AF_XDP MTUs.
> 7. Unwind partial DMA allocations and fix saved RX skb ownership.
> 8. Keep DMA configurations and IRQ contexts at stable addresses, and
> drain timer producers before cancelling their timers.
> 9. Track datapath and power ownership across MTU, XDP and ethtool reopen
> failures, including noirq clock handling and unpowered-access guards.
> 10. Use the tracked restart for XSK pool changes.
> 11. Restore TC filters, steering, preemption mapping and EST before DMA.
> 12. Allow independent references to AF_XDP DMA mappings and UMEM pages.
> 13. Retain DMA memory until hardware confirms shutdown or reset.
> 14. Prepare device-local interrupt masking and DMA-handler quiescence.
> 15. Retain rings and IRQ registrations across jumbo MTU replacement.
Please look at the mailing list, several of these patches are already being
worked on and have gone through multiple iterations and reviews.
Thanks :)
Maxime
^ permalink raw reply [flat|nested] 17+ messages in thread