* [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures
@ 2026-09-26 15:48 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
` (15 more replies)
0 siblings, 16 replies; 29+ 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
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.
The ownership transition and its XDP, ethtool and PM consumers land
together, so resource cleanup does not precede the power-access guards it
requires. The larger DMA shutdown change precedes MTU rollback, rather
than leaving early retained-ring commits with unsafe teardown paths.
The ownership states remain distinct: DOWN owns no IRQs or enabled NAPI;
RUNNING has active queues; SUSPENDED retains rings and IRQs; HALTED retains
rings but has released IRQs after failed MTU rollback. Collapsing the last
two states would make close free IRQs which it no longer owns. DMA memory
ownership is tracked separately: a failed shutdown can retain allocations
even in DOWN until hardware acknowledges a successful reset.
Jumbo rollback allocates no new buffers or IRQ registrations. RX holes are
filled before reset without changing the old descriptors; descriptors are
rebuilt or retired only after reset succeeds. If rollback also fails, the
interface remains detached until down/up recovery. This cannot guarantee
recovery from indefinitely failing hardware.
Failed interfaces still receive hardware/PHY sleep handling. A failed
datapath with MAC WoL configured rejects suspend with -EBUSY because its
wake path is unusable; PHY-only WoL is not rejected. Timestamp filters and
PHC registration survive an MTU change. Replay the platform PTP clock selector,
frequency correction, PEROUT and EXTTS settings after reset; continuous PHC
time across the reset is not preserved.
Live XDP and ethtool failures leave the administratively-up interface
detached for ordinary down/up recovery. Program attachment failures return
an error without consuming the proposed program. BPF-link and XSK-pool
removal still complete if their datapath restart fails, so teardown can
release the program or pool safely. AF_XDP pool reconfiguration now pauses
all queues and replays the MAC link while rebuilding the rings. Successful
reconfiguration leaves the PHY running rather than powering it down and
restarting autonegotiation. All queues and other sockets are still paused
while their rings are rebuilt. A failed restart stops phylink without
reconfiguring the failed MAC, then permits ordinary down/up recovery.
Close and live XDP/XSK release now require stopped DMA process states
or a completed global reset before releasing DMA memory. Unsupported
idle-register layouts, including XGMAC and GMAC4 with more than three
channels, use the reset fallback. Replay cached L3/L4 filters, EtherType
steering, VLAN priority, preemption/TC mapping, PTP settings and EST before
starting DMA. Replay failure enters the existing rollback or detached
recovery path. Reset does not preserve continuous PHC time.
If shutdown and reset both fail, retain rings, mappings and backing memory.
AF_XDP pool teardown still completes, with independent references keeping
its DMA mapping and UMEM pages alive. A later successful down/up reset
reclaims them. There is no generic isolation guarantee for permanently
unresponsive hardware: removal reports and retains unsafe DMA allocations,
which may therefore remain pinned until reboot.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes in v4:
- Reorder prerequisites ahead of their consumers. Separate the independent
MDIO GPIO fix and PHC/IRQ preparation, and combine datapath ownership with
the XDP, ethtool and PM paths which must observe it. Keep MTU rollback last.
- Replay cached TC filters, steering, preemption mapping and EST after reset,
with DMA held stopped until replay succeeds. Preserve the previous cached
rule on installation failure and advance EST base time by whole cycles.
- Validate TAPRIO replacements without modifying the installed schedule;
reject failed PHC reads and retain a valid cache after setup failure.
- Keep control operations from accessing unpowered registers after close
reattaches the netdev. Permit software-only VLAN, qdisc and TC filter
removal, and preserve open-time power recovery. Remove cached flower,
steering and u32 rules while down or detached so reset cannot replay
filters which TC has already deleted.
- Use phylink link replay for live XDP/XSK updates, leaving the PHY running
on success and balancing replay with phylink_stop() on failed restart.
- Require a hardware idle acknowledgement or completed reset before DMA
memory retirement on close, XDP/XSK replacement and failed open. Retain
failed configurations for recovery, and defer IRQ TX recovery and resume
descriptor rebuilding until shutdown/reset is confirmed.
- Add independent AF_XDP mapping/UMEM references so pool teardown can
complete without unmapping DMA-visible memory after a shutdown failure.
Document reset fallback and permanent-failure memory retention.
- Keep shared IRQ controller lines enabled during MTU replacement. Mask DMA
sources at the device, protect DMA-handler quiescence with the channel
lock, and drain registered handlers with synchronize_irq().
- Preserve pending DMA status while quiesced, including when hardware setup
restores interrupt enables. Restore saved masks on restart and leave DMA
sources masked when failed rollback releases the driver's IRQ handlers.
- Reuse the final TX queue and timer drain after synchronizing IRQ handlers.
- Restore saved RX skb ownership immediately when taking partial receive
state, including a budget-one completion and a DMA-owned next descriptor.
- Cancel the EEE software timer after draining NAPI, and make GSO feature
checks use stable platform TBS capabilities rather than replaceable rings.
- Reuse the MDIO reset GPIO acquired at probe and reject TC queue work when
the datapath is not running, except for software-only filter deletion.
- Let carrier-down XSK TX polls complete instead of continuously repolling,
and remove the unused external XSK wakeup declaration.
- Stop a PHY powered by prepare_resume() after failed ethtool setup. Publish
core queue counts transactionally only after the fallible open work.
- Initialize rollback RX addresses before publishing descriptor ownership.
Restore the platform PTP clock selection, frequency adjustment, PEROUT and
EXTTS settings; serialize PHC/devlink access across reset and reject access
to a failed retained clock.
- Freeze deferred XSK pool teardown before device PM. Track platform power,
pending clock restoration and actual PTP clock ownership separately from
the datapath. Avoid unpowered MAC/MDIO accesses on failed close-time
recovery and balance clocks even after a noirq resume failure.
- Check unusable MAC WoL before the pending-sleep shortcut, and update
comments for HALTED ownership and the ethtool reopen path.
- Link to v3: https://patch.msgid.link/20260924-submit-stmmac-reset-fixes-v1-v3-0-c031e3f3a282@gmail.com
Changes in v3:
- Snapshot debugfs queue counts under RTNL with the descriptor ownership
check, so a reader blocked behind channel shrink uses the new topology.
- Complete pending core sleep state during close and retry restoration on
open after runtime-PM acquisition. Use the installed wake state rather
than mutable WoL settings, and retain the pending marker if restoration
fails.
- Put the independent IRQ unwind fix first, ahead of the ownership changes.
- Correct the WoL/safety IRQ unwind order, fixing a repeated safety IRQ
free and a leaked WoL handler.
- Account for live XDP release/reopen in datapath ownership. Stop DMA on
late IRQ failure, propagate program-attach errors without losing program
references, and allow BPF-link removal to complete after restart failure.
- Replace the void AF_XDP queue-enable helpers with the tracked XDP restart.
Unmap a removed pool only after freeing its rings, undo failed pool
attachment, and preserve TBS configuration while rebuilding the channels.
- Keep PHY/PM ownership across ethtool ring/channel changes, restoring the
old configuration after failed reopening and leaving the interface
detached for down/up recovery without a second runtime-PM put.
Explicitly disable the MAC during release even when link resolution has
not reached mac_link_up().
- Split the MTU work into normal-size MTU handling, allocation unwind,
stable DMA/IRQ ownership (including timer lifetime), and the retained-ring
transaction. Keep the ownership change and its open/error-path adaptations
in the same patch.
- Restore the PHY advertisement reduced by phylink_suspend() when stopping
a suspended instance, without restarting or reconfiguring the MAC, in
response to Andrew Lunn.
- Keep suspend-owned PHY speed control separate from explicit driver
speed-down requests, so close-time power saving does not overwrite the
original advertisement and remains effective until the matching speed-up.
- Suspend a stopped PHY powered back up by phylink_prepare_resume() when
stopping after a failed MAC resume, without repeating phy_stop().
- Add a separate MTU transaction patch retaining old DMA resources and IRQ
registrations until replacement succeeds, following Andrew Lunn's request
to keep the old setup for failure recovery.
- Keep DMA configuration objects at stable addresses and use persistent
channel objects for per-queue interrupt contexts.
- Drain transmitters and post-completion NAPI poll tails before cancelling
TX timers.
- Avoid resetting normal-size MTU changes, including all supported AF_XDP
MTUs, and stop rebinding live pools through temporary RXQ objects.
- Program the new MAC receive limit before activation and restore the old
MTU on failure; refill old RX buffer holes before reset so rollback does
not require allocation.
- Preserve coalescing, TBS state, PHC registration and timestamp filters;
leave a failed rollback detached with old rings retained for close.
- Harden partial-allocation unwind.
- Separate core sleep completion from datapath ownership so failed MTU and
resume paths still receive hardware PM handling, without disabling NAPI
twice or restarting released queues. Address the skipped-suspend issue
reported by Sashiko; reject an unusable MAC WoL path with -EBUSY.
- Balance platform noirq PTP clock handling against resources still owned
after detached XDP cleanup.
- Link to v2: https://patch.msgid.link/20260922-submit-stmmac-reset-fixes-v1-v2-0-d92e609be996@gmail.com
Changes in v2:
- Drop forced netif_close() and retain the interface's administrative state,
as requested by Maxime Chevallier.
- Separate queue quiescence from resource release and use explicit datapath
state instead of a napi_disabled argument to the release helper.
- Handle repeated suspend/resume after failure, preserve PHY/PM ownership
until ordinary close, and allow a fresh down/up recovery.
- Check XDP/AF_XDP cleanup, descriptor readback and asynchronous reset work
while the netdev is administratively up but unavailable.
- Explain the generic phylink suspend-to-stop transition and why restarting
phylink after a failed MAC resume is not a valid substitute, in response
to Andrew Lunn.
- Combine the two stmmac error-path fixes so every user of the new state
has consistent resource and NAPI lifetime handling in one patch.
- Link to v1: https://patch.msgid.link/20260921-submit-stmmac-reset-fixes-v1-v1-0-87a4e431ee00@gmail.com
To: Maxime Chevallier <maxime.chevallier@bootlin.com>
To: Andrew Lunn <andrew+netdev@lunn.ch>
To: "David S. Miller" <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Maxime Coquelin <mcoquelin.stm32@gmail.com>
To: Alexandre Torgue <alexandre.torgue@foss.st.com>
To: Serge Semin <fancer.lancer@gmail.com>
To: Suraj Jaiswal <quic_jsuraj@quicinc.com>
To: Russell King <linux@armlinux.org.uk>
To: Andrew Lunn <andrew@lunn.ch>
To: Heiner Kallweit <hkallweit1@gmail.com>
To: Joakim Zhang <qiangqing.zhang@nxp.com>
To: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
To: Alexei Starovoitov <ast@kernel.org>
To: Daniel Borkmann <daniel@iogearbox.net>
To: Jesper Dangaard Brouer <hawk@kernel.org>
To: John Fastabend <john.fastabend@gmail.com>
To: Stanislav Fomichev <sdf@fomichev.me>
To: Richard Cochran <richardcochran@gmail.com>
To: Christian Marangi <ansuelsmth@gmail.com>
To: Tiezhu Yang <yangtiezhu@loongson.cn>
To: Huacai Chen <chenhuacai@kernel.org>
To: Joao Pinto <Joao.Pinto@synopsys.com>
To: Vladimir Oltean <vladimir.oltean@nxp.com>
To: Ong Boon Leong <boon.leong.ong@intel.com>
To: Voon Weifeng <weifeng.voon@intel.com>
To: "Song, Yoong Siang" <yoong.siang.song@intel.com>
To: Linus Walleij <linusw@kernel.org>
To: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
To: Magnus Karlsson <magnus.karlsson@intel.com>
To: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
To: Simon Horman <horms@kernel.org>
To: Björn Töpel <bjorn@kernel.org>
To: Thierry Reding <thierry.reding@kernel.org>
To: Jonathan Hunter <jonathanh@nvidia.com>
To: Chen-Yu Tsai <wens@kernel.org>
To: Jernej Skrabec <jernej.skrabec@gmail.com>
To: Samuel Holland <samuel@sholland.org>
Cc: netdev@vger.kernel.org
Cc: linux-stm32@st-md-mailman.stormreply.com
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: bpf@vger.kernel.org
Cc: linux-tegra@vger.kernel.org
Cc: linux-sunxi@lists.linux.dev
---
James Hilliard (15):
net: stmmac: unwind the WoL IRQ after a safety IRQ request failure
net: stmmac: reuse the MDIO reset GPIO on resume
net: phylink: allow stopping a suspended instance
xsk: freeze deferred pool teardown during system sleep
net: stmmac: serialize and retain PHC configuration across reset
net: stmmac: leave the datapath running for normal-size MTU changes
net: stmmac: unwind partially allocated DMA configurations
net: stmmac: keep DMA configurations at stable addresses
net: stmmac: track datapath and power ownership across failed reopening
net: stmmac: use the tracked datapath restart for XSK pool changes
net: stmmac: restore TC offloads before restarting DMA
xsk: allow drivers to retain DMA mappings independently of pools
net: stmmac: retain DMA memory until hardware shutdown completes
net: stmmac: prepare device-local DMA interrupt quiescence
net: stmmac: retain DMA resources across MTU changes
drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 6 +-
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 28 +
drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c | 5 +-
.../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 2 +
drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c | 2 +
drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 4 +
drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h | 8 +
drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c | 29 +
drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 4 +
drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 30 +
drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 11 +
drivers/net/ethernet/stmicro/stmmac/hwif.h | 9 +
drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 4 +-
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 64 +-
.../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 15 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c | 1 +
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 1720 ++++++++++++++------
drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c | 33 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c | 3 +-
.../net/ethernet/stmicro/stmmac/stmmac_platform.c | 38 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c | 131 +-
.../net/ethernet/stmicro/stmmac/stmmac_selftests.c | 8 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 376 +++--
drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 6 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c | 83 +-
drivers/net/phy/phylink.c | 51 +-
include/net/xdp_sock_drv.h | 23 +
include/net/xsk_buff_pool.h | 4 +
net/xdp/xsk_buff_pool.c | 53 +-
29 files changed, 2049 insertions(+), 702 deletions(-)
---
base-commit: a7bfaba4823e3c165bb2004c74eff7c096672bc7
change-id: 20260921-submit-stmmac-reset-fixes-v1-7c98b92d29a9
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
^ permalink raw reply [flat|nested] 29+ messages in thread
* [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; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:48 ` [PATCH net v4 03/15] net: phylink: allow stopping a suspended instance James Hilliard
` (13 subsequent siblings)
15 siblings, 1 reply; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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; 29+ 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] 29+ 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-30 4:51 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:52 ` netdev-bot+sashiko
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, 1 reply; 29+ 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] 29+ 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-30 4:52 ` netdev-bot+sashiko
2026-09-26 16:00 ` [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures Maxime Chevallier
15 siblings, 1 reply; 29+ 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] 29+ 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; 29+ 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] 29+ messages in thread
* Re: [PATCH net v4 02/15] net: stmmac: reuse the MDIO reset GPIO on resume
2026-09-26 15:48 ` [PATCH net v4 02/15] net: stmmac: reuse the MDIO reset GPIO on resume James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] Once the cached descriptor is used, stmmac_mdio_reset() really
does pulse the snps,reset PHY reset line on every system resume…
- [Low] The commit message explains the mechanism (the second exclusive
request of snps,reset returns -EBUSY) but not what users see.
--
net: stmmac: reuse the MDIO reset GPIO on resume
MDIO bus registration acquires the snps,reset GPIO. This commit caches
that descriptor in priv->mdio_reset_gpio, so later calls to
stmmac_mdio_reset(), such as the one on resume, reuse it. Without the
cache, those calls request the exclusive line again and fail with
-EBUSY.
> 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.
[Severity: Low]
This isn't a bug, but could the commit message describe the effect that
users see?
stmmac_resume() ignores the return value of stmmac_mdio_reset(). Before
this patch, on OF boards with a snps,reset GPIO, every resume without
MAC WoL silently skipped two things: the PHY reset pulse and the
STE101P dummy MDC write:
writel(0, priv->ioaddr + mii_address);
Nothing was logged when this happened.
The message also doesn't say that this change turns the PHY hard reset
on resume back on. That reset has not run since 7c86f20d15b7. Without a
symptom or behaviour change in the message, stable backporters
following the Fixes: tag have nothing to match against bug reports.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
> index afe98ff5bdcb0..07d5eab05996b 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;
> + }
[Severity: High]
With the descriptor cached, stmmac_mdio_reset() now really pulses the
snps,reset line on every system resume where MAC WoL is off. Does
anything re-initialise the PHY after that?
Before this patch, the second devm_gpiod_get_optional() on the
exclusive line failed in gpiod_request_commit() with -EBUSY. The
resume path ignores that error:
stmmac_resume()
} else {
pinctrl_pm_select_default_state(priv->device);
/* reset the phy so that it's ready */
if (priv->mii)
stmmac_mdio_reset(priv->mii);
}
So no PHY hard reset has run on resume since 7c86f20d15b7. Before that
commit the reset did run, but mdio_bus_phy_resume()->phy_init_hw()
followed it.
Today stmmac_phylink_setup() sets config->mac_managed_pm = true, and
phylink copies that into phydev->mac_managed_pm. As a result,
mdio_bus_phy_resume() returns early:
mdio_bus_phy_resume()
if (phydev->mac_managed_pm)
return 0;
...
ret = phy_init_hw(phydev);
The MAC-managed path only runs phylink_prepare_resume()->phy_resume()
and phylink_resume()->phylink_start()->phy_start()->__phy_resume().
Neither of these replays config_init() or config_intr().
Wouldn't that leave the PHY at its power-on or strap defaults while
phylib believes it is still configured? The config_init() settings
would be lost: RGMII delays or skews, clock-out, LEDs, vendor quirks,
and the autonomous EEE disable.
For an interrupt-driven PHY, the reset also clears the interrupt
enable register. phy_start() restarts aneg, sees link down and moves
to PHY_NOLINK, and it does not poll after that. Could link-up then
never be reported, so the interface stays down until the admin runs a
down/up cycle?
PHY-based WoL seems to be affected too. phylink_ethtool_set_wol()
removes the modes the PHY handles from the MAC mask:
wolopts &= ~w.wolopts;
With PHY WoL, priv->wolopts is therefore 0, so resume takes the reset
branch. Won't the reset pulse erase the PHY's WoL enables, so the next
suspend is not armed for wake?
Resume now also blocks for the snps,reset-delays-us msleep() calls.
When those delays are 0, the DMA reset in stmmac_hw_setup() needs the
PHY RX clock, and it runs right after reset deassert.
This also looks unresolved at the end of the series. After "net:
stmmac: track datapath and power ownership across failed reopening",
stmmac_resume_hw() still calls stmmac_mdio_reset(). stmmac_resume()
still only calls phylink_prepare_resume() and phylink_resume() after
it, and no phy_init_hw() call was added in stmmac or phylink.
This patch carries a Fixes: tag, so it will reach stable. Should the
resume path either re-initialise the PHY after the reset, or skip the
reset pulse on resume?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 03/15] net: phylink: allow stopping a suspended instance
2026-09-26 15:48 ` [PATCH net v4 03/15] net: phylink: allow stopping a suspended instance James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] phylink_stop() now returns early whenever
PHYLINK_DISABLE_STOPPED is set (drivers/net/phy/phylink.c:2536-2543).
- [Medium] The new MAC-WoL completion in phylink_stop()
(phylink.c:2557-2565) calls phylink_link_down() only when…
- [Low] The only Fixes: tag is f97493657c63 ("net: phylink: add
suspend/resume support", 2021).
--
net: phylink: allow stopping a suspended instance
This patch lets phylink_stop() shut down an instance that is still
suspended after a driver resume failed, without going through
phylink_resume(). When the instance is already stopped, it no longer
repeats PHY/SFP/PCS shutdown. It also completes the link-down that MAC WoL
deferred, and it tracks and undoes the PHY speed-down done at suspend time.
> Fixes: f97493657c63 ("net: phylink: add suspend/resume support")
[Severity: Low]
Is this Fixes: tag complete? Several of the things this patch fixes were
added after f97493657c63.
phylink_phy_pm_speed_ctrl() and the phylink_speed_down() call in
phylink_suspend() came from dc1a2a9ce5b2c8. The new suspend_speed_down
tracking and phylink_restore_suspend_speed() fix that commit.
The new MAC WoL block in phylink_stop() depends on suspend_link_up
handling, which came from 4c8925cb9db158.
The commit message gives phylink_prepare_resume() as the reason for the
new phy_suspend() call, and phylink_prepare_resume() came from
367f1854d442b3.
Should there be at least one more Fixes: tag, for dc1a2a9ce5b2c8? With
only f97493657c63, stable tooling may try to backport this to trees that
lack the state and functions the patch modifies.
> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index 1bbcf46c83566..b7d30ca340316 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
[ ... ]
> @@ -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;
> + }
[Severity: Medium]
Is PHYLINK_DISABLE_STOPPED specific enough to identify a suspended
instance? phylink_create() also sets it:
drivers/net/phy/phylink.c:phylink_create() {
...
__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
...
}
Every completed phylink_stop() also leaves it set. So this branch also
runs for an instance that was never started and for a second, unbalanced
phylink_stop(). In both cases it calls phy_suspend() without any warning.
Before this patch, a second phylink_stop() reached phy_stop() with the PHY
in PHY_HALTED, which hit:
WARN(1, "called from state %s\n", phy_state_to_str(phydev->state));
Is it intended that phylink users lose this warning for unbalanced
start/stop?
The new kernel-doc says the PHY is suspended again only "If
phylink_prepare_resume() powered up an already stopped PHY". The code does
it for any stopped instance whose PHY is not suspended. That includes a
PHY powered up by phy_attach_direct() on an instance that was never
started.
A later patch in this series ("net: stmmac: track datapath and power
ownership across failed reopening") already relies on this broader
behaviour. Its __stmmac_open() calls phylink_stop() twice on an instance
that phylink_start() never started.
Should the commit message and kernel-doc describe this wider contract?
The early return also skips the new MAC WoL cleanup further down. If
phylink_suspend(pl, true) runs on an instance that is already stopped,
both bits end up set:
phylink_suspend(pl, true)
__set_bit(PHYLINK_DISABLE_MAC_WOL, ...)
phylink_stop(pl)
STOPPED already set -> phy_suspend(); return;
phylink_start(pl)
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED)
MAC_WOL is still set, so the resolver never runs
Can the link still come up after that phylink_start()? At this commit,
stmmac seems able to reach this sequence:
- an MTU change reopen fails, leaving the interface up with phylink
stopped
- a MAC WoL system suspend
- a failed resume
- close, then open
> +
> 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);
> + }
[Severity: Medium]
Does this still complete the deferred link-down if phylink_suspend() was
called twice with no phylink_resume() in between?
phylink_suspend() recomputes the flag on every MAC WoL suspend:
drivers/net/phy/phylink.c:phylink_suspend() {
...
pl->suspend_link_up = phylink_link_is_up(pl);
if (pl->suspend_link_up) {
...
if (pl->netdev)
netif_carrier_off(pl->netdev);
pl->old_link_state = false;
}
...
}
The first suspend turns the carrier off and clears old_link_state, but
does not call mac_link_down(). While PHYLINK_DISABLE_MAC_WOL is set, the
resolver cannot turn the carrier back on.
Now suppose resume fails before phylink_resume() and the device stays
running. The next system suspend calls phylink_suspend(pl, true) again.
stmmac_suspend() at this commit does this, because it only checks
netif_running(). ucc_geth_suspend() does it after ucc_geth_init_mac()
fails in ucc_geth_resume().
That second call sets suspend_link_up to false, although mac_link_down()
has still not been called.
A later close reaches this block and skips phylink_link_down(), so neither
mac_link_down() nor phylink_deactivate_lpi() runs. PHYLINK_DISABLE_MAC_WOL
is still cleared.
Wouldn't that leave the earlier mac_link_up() without a matching
mac_link_down(), with pl->mac_enable_tx_lpi still true? The next
phylink_start() could then call mac_link_up() again with no link-down in
between.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 04/15] xsk: freeze deferred pool teardown during system sleep
2026-09-26 15:48 ` [PATCH net v4 04/15] xsk: freeze deferred pool teardown during system sleep James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] Resume can hang forever when a netdev is unregistered from a
device PM callback while an xsk pool release is waiting on the frozen…
--
xsk: freeze deferred pool teardown during system sleep
This patch moves the deferred xsk buffer pool release from system_wq to
system_freezable_wq. The goal is to stop ndo_bpf() from being called
while the device is suspended.
> diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c
> index 9d2d94f1fb759..c58f56f24a9c7 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);
[Severity: High]
Can this hang system resume if the same netdev is unregistered from a PM
callback while this work is still queued on the frozen workqueue?
This work is now the only path to the dev_put() that balances the
dev_hold() in xp_assign_dev():
xp_release_deferred()
xp_clear_dev()
dev_put(netdev);
The final put can arrive after the workqueues are frozen:
- xsk_release() has already taken the socket off net->xdp.list with
sk_del_node_init_rcu().
- xsk_destruct() -> xp_put_pool() runs from the SOCK_RCU_FREE callback
once sk_wmem_alloc drains. That drain can happen when pending copy-mode
TX skbs are freed via xsk_destruct_skb() -> sock_wfree(). Two ways this
can happen are a driver freeing TX skbs in its suspend callback and a
qdisc reset during unregister.
If this final put happens after freeze_workqueues_begin(), the work does
not run until thaw_workqueues(). For suspend-to-RAM that is
suspend_finish() -> suspend_thaw_processes(), which runs after
dpm_resume_end().
Now suppose a resume callback unregisters that same netdev:
usb_resume()
unbind_marked_interfaces()
usb_forced_unbind_intf()
usbnet_disconnect()
unregister_netdev()
netdev_run_todo()
netdev_wait_allrefs_any()
This path is taken when usb_resume_interface() sets needs_binding on a
reset-resume and the driver has no .reset_resume. Several usbnet drivers
lack one, for example dm9601, rndis_host, cdc_eem, cdc_subset and aqc111.
netdev_wait_allrefs_any() loops with no timeout:
- Its NETDEV_UNREGISTER rebroadcasts reach xsk_notifier(), but that only
walks net->xdp.list, so it cannot find the released socket's pool.
- Its rcu_barrier() does not run frozen work.
The resume thread never reaches thaw_workqueues(), so the netdev
reference is never dropped. With system_wq the work would have run and
called dev_put().
Copy-mode pools also take the freezable path, even though
xp_disable_drv_zc() only calls ndo_bpf() when pool->umem->zc is set.
Would it work to defer only the driver ndo_bpf() teardown to the
freezable queue, and never the dev_put()? Another option might be to let
xsk_notifier() handle NETDEV_UNREGISTER for pools whose release is still
pending, for example by tracking them per netdev.
Separately, the commit message says:
newly queued destruction waits until device resume
Is this accurate? As far as I can tell, the work waits until process
thaw, which comes after every device resume and complete callback has
finished.
> return true;
> }
>
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 05/15] net: stmmac: serialize and retain PHC configuration across reset
2026-09-26 15:49 ` [PATCH net v4 05/15] net: stmmac: serialize and retain PHC configuration across reset James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 7 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 4 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] While ptp_blocked is set, stmmac_common_interrupt() skips
stmmac_timestamp_interrupt() completely.
- [Medium] System resume re-initializes the PTP block but never replays
or clears the new retained PHC state.
- [Medium] The commit says it serializes timestamp writers and devlink
timestamp-mode updates with ptp_mutex.
- [Medium] While ptp_blocked is set, stmmac_get_time() now returns -EBUSY
and writes a zero timespec.
- [Medium] stmmac_dl_ts_coarse_set() now runs under ptp_mutex/ptp_lock,
but it still calls stmmac_update_subsecond_increment().
- [Low] __stmmac_enable() caches the PEROUT start and period exactly as
userspace supplied them in priv->pps[] whenever flex_pps_config…
- [Low] When an EXTTS request's ATSFC FIFO-clear poll times out,
__stmmac_enable() and dwmac1000_ptp_enable() have already written the…
--
net: stmmac: serialize and retain PHC configuration across reset
This commit caches the PHC frequency correction, PEROUT requests and EXTTS
selection in stmmac_priv, so that a new stmmac_ptp_restore() helper can
replay them after a MAC reset. It adds ptp_mutex and a ptp_blocked gate to
serialize PHC writers with resets, and stmmac_get_time() now returns
-EBUSY while the gate is set.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 4f3d452c35036..8b0bc7c46df93 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;
> +
[Severity: Medium]
Does the retained state stay consistent across system resume?
These caches are only cleared in stmmac_setup_ptp(). At the end of the
series, stmmac_resume() calls stmmac_hw_setup(ndev, false, false), which
resets the MAC, and then does:
stmmac_resume() {
...
mutex_lock(&priv->ptp_mutex);
stmmac_init_timestamping(priv);
ret = stmmac_tc_restore_est(priv);
if (!ret)
stmmac_block_ptp(priv, false);
...
}
That programs the default addend and loses PEROUT/EXTTS in hardware.
Resume does not call stmmac_ptp_restore(), and it does not clear
ptp_scaled_ppm, ptp_perout, ptp_extts or pps[].
So after resume the hardware has no frequency correction and no periodic
outputs, but the cache still says they are active. A later jumbo MTU
change or XDP reset fallback then goes through stmmac_ptp_restore(). That
re-applies the old frequency correction and restarts PEROUT/EXTTS without
any request from userspace.
Should resume either replay the cache or reset it?
[ ... ]
> @@ -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);
[Severity: High]
Can this cause an interrupt storm while ptp_blocked is set?
timestamp_interrupt() in stmmac_hwtstamp.c is the only place that reads
the timestamp status register, and that read is what clears TSIS:
ts_status = readl(priv->ioaddr + GMAC_TIMESTAMP_STATUS);
dwmac4_irq_status() does not touch that register, and GMAC_INT_TSIE is
part of GMAC_INT_DEFAULT_ENABLE.
At the end of the series, stmmac_reconfigure_mtu() sets ptp_blocked while
the MAC IRQ is still registered and enabled. Only the DMA channel sources
are masked. stmmac_ptp_restore() also re-arms EXTTS while the gate is
still closed.
Suppose an aux snapshot event (for example a 1 Hz external PPS on an
EXTTS input) or a PPS target time event arrives in that window. TSIS
then stays asserted. With a level-triggered MAC interrupt,
stmmac_interrupt() keeps returning IRQ_HANDLED, so the spurious IRQ
detector never steps in.
If that IRQ fires on the CPU running the MTU transaction, can the reset
thread ever get to stmmac_block_ptp(priv, false)?
Would it help to read and discard the status, or to mask TSIE, while
blocked?
[ ... ]
> @@ -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;
[Severity: Medium]
The commit message says:
Serialize timestamp writers and devlink timestamp-mode updates with a
mutex
Are all of the timestamp writers covered? Two paths write the same state
without taking ptp_mutex or ptp_lock:
stmmac_hwtstamp_set()
stmmac_setup_ptp()->stmmac_init_timestamping()->stmmac_init_tstamp_counter()
The shared state is systime_flags, sub_second_inc, default_addend and
the PTP_TCR/SSIR/addend registers.
stmmac_dl_ts_coarse_set() runs under the devlink instance lock, not RTNL.
stmmac_hwtstamp_set() runs under RTNL and does:
priv->systime_flags = STMMAC_HWTS_ACTIVE;
if (!priv->tsfupdt_coarse)
priv->systime_flags |= PTP_TCR_TSCFUPDT;
...
stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags);
Suppose hwtstamp_set writes TCR with the old TSCFUPDT after devlink has
already computed SSINC and the addend for the new mode. Wouldn't the PHC
then run in one update mode with an increment computed for the other?
The same interleaving looks possible with stmmac_open(), because the
devlink param is registered at probe. This is still the case at the end
of the series.
> @@ -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);
[Severity: Medium]
Should the retained state be re-applied here?
stmmac_update_subsecond_increment() rewrites SSINC and programs the
uncorrected addend:
priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate);
stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend);
After this the hardware has lost the frequency correction, but
priv->ptp_scaled_ppm still records it.
Running flex PPS outputs keep INTERVAL/WIDTH values that were computed in
units of the old sub_second_inc. Their real period and width therefore
change, while pps[] and ptp_perout still describe the requested period.
The next reset replays the cache, which abruptly changes frequency and PPS
timing again. This path already holds ptp_mutex. Could the replay done in
stmmac_ptp_restore() also be applied here? This is unchanged at the end of
the series.
[ ... ]
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
> index 3bfcc9760dce7..bc6fe0c87e01a 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
[ ... ]
> @@ -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;
> }
[Severity: Medium]
What happens to in-kernel callers that ignore the gettime64 return value?
While blocked, this returns -EBUSY and also writes a zero timespec.
At this commit, tc_taprio_configure() ignores the result:
priv->ptp_clock_ops.gettime64(&priv->ptp_clock_ops, ¤t_time);
It would then compute the EST base time from 0. A later commit in the
series, "net: stmmac: restore TC offloads before restarting DMA", fixes
that caller: it checks the return value and runs under ptp_mutex.
The virtual clock path still looks affected at the end of the series.
stmmac has no getcycles64, so the core falls back to
ptp_getcycles64()->gettime64, and ptp_vclock_read() drops the error:
ptp->info->getcycles64(ptp->info, &ts);
return timespec64_to_ns(&ts);
The vclock uses a CYCLECOUNTER_MASK(32) cyclecounter. A refresh or read
that lands in a blocked window records a delta of
(0 - cycle_last) & 0xffffffff, and the next real read adds another one.
Can the vclock then jump by about 2^32 ns (4.29 s)?
The window also covers a case where the PHC is never reset:
stmmac_reconfigure_mtu() jumps to its restart label when
stmmac_prepare_rx_buffers() fails. Before this series the PHC was
unregistered across an MTU reopen, so vclocks could not see this state.
[ ... ]
> +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;
> + }
> + }
[Severity: Low]
Can ptp_extts get out of sync with the hardware when the ATSFC poll
times out? In __stmmac_enable(), the new ATSEN state and
STMMAC_FLAG_EXT_SNAPSHOT_EN are written before the poll that can fail:
writel(acr_value, ptpaddr + PTP_ACR);
mutex_unlock(&priv->aux_ts_lock);
/* wait for auxts fifo clear to finish */
ret = readl_poll_timeout(ptpaddr + PTP_ACR, acr_value,
dwmac1000_ptp_enable() works the same way. It also calls
dwmac1000_timestamp_interrupt_cfg(priv, on) whatever the poll result.
Nothing is rolled back, but ptp_extts is only updated when ret == 0.
If a disable fails, the hardware is off while ptp_extts still holds
BIT(index). The next stmmac_ptp_restore() then re-enables an EXTTS
channel that the user asked to turn off. If an enable fails, the opposite
happens: the hardware keeps capturing, but replay won't re-arm the
channel.
> + 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)
> +{
[ ... ]
> + 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);
[Severity: Low]
Are the cached PEROUT values checked before they are replayed here?
Whenever flex_pps_config accepts a request, __stmmac_enable() stores start
and period in priv->pps[] exactly as userspace supplied them.
A negative period such as sec=-1, nsec=999999999 wraps to a huge u64 in
the dwmac5/xgmac flex_pps_config, which accepts it. A negative start.sec
skips the fixup for start times in the past, because target_ns wraps.
On replay, timespec64_to_ns(&cfg.period) is negative and becomes
cycle_time for stmmac_calc_tas_basetime(). That function calls
div64_s64() with a negative divisor and does signed multiplications that
can overflow, so the resulting start time is meaningless. With
period == -1 and current - start == S64_MIN, the division would trap on
x86.
Would it make sense to reject non-positive or non-normalized periods and
negative starts before caching them?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 07/15] net: stmmac: unwind partially allocated DMA configurations
2026-09-26 15:49 ` [PATCH net v4 07/15] net: stmmac: unwind partially allocated DMA configurations James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 (2) · Medium: 1 (1) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] At this commit, __init_dma_rx_desc_rings() returns
xdp_rxq_info_reg_mem_model() failures, and…
- [Medium] The stmmac_rx() hunk fixes a use-after-free and double-free
that already exists in the RX NAPI hot path, but the commit message…
Pre-existing issues:
- [High] At this commit, __init_dma_rx_desc_rings() calls
xsk_pool_set_rxq_info(pool, &rx_q->xdp_rxq) on the temporary dma_conf…
- [High] At this commit, stmmac_xdp_enable_pool() and
stmmac_xdp_disable_pool() disable only the selected queue's NAPI.
- [Medium] stmmac_suspend() does not clear saved partial-RX state, and
neither does stmmac_resume().
--
net: stmmac: unwind partially allocated DMA configurations
The RX/TX DMA allocation failure paths now free only the buffer arrays and
coherent memory that were actually allocated. RX resources are also freed
when TX allocation fails. RXQ memory model registration errors are now
propagated, and teardown clears XSK RXQ bindings, saved partial packets
and released pointers. stmmac_rx() now takes ownership of the saved
partial RX state when the poll starts.
> 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")
[Severity: Medium]
Is this paragraph describing a use-after-free that already exists in
stmmac_rx()? As written, it only mentions the new teardown cleanup.
Before this patch, the saved state was restored only under:
if (!count && rx_q->state_saved) {
skb = rx_q->state.skb;
...
} else {
rx_q->state_saved = false;
...
}
Suppose stmmac_rx() runs with limit == 1 and the saved partial frame
completes in that call:
- The skb goes to napi_gro_receive() or dev_kfree_skb().
- count becomes 1, and the loop exits before the else branch can clear
state_saved.
- The trailing test is false:
if (status & rx_not_ls || skb) {
So state_saved stays true, and state.skb still points at the skb that was
already delivered or freed. The next poll restores it and passes it to
skb_add_rx_frag(), napi_gro_receive() or dev_kfree_skb().
A budget of 1 looks reachable without privileges. The EPIOCSPARAMS
handler in ep_eventpoll_ioctl() requires CAP_NET_ADMIN only when
busy_poll_budget > NAPI_POLL_WEIGHT.
The saved-state logic seems to come from ec222003bd94 ("net: stmmac:
Prepare to add Split Header support"), not from 71fedb0198cb.
Could the stmmac_rx() change go in its own patch? Its commit message
could describe the use-after-free, with a Fixes tag pointing at
ec222003bd94.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index bdebc5bfa2b8b..1cc68c1cf37dc 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -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);
[Severity: High]
This isn't a bug introduced by this patch, but this binds the pool to
the xdp_rxq of whichever dma_conf is being initialized. On open, that is
the temporary conf built by stmmac_setup_dma_desc(). __stmmac_open()
then copies it:
memcpy(&priv->dma_conf, dma_conf, sizeof(*dma_conf));
and stmmac_open() frees the temporary:
kfree(dma_conf);
Does that leave the XSK pool's xdp.rxq pointing into freed memory? ZC
receive paths could then dereference it, for example stmmac_xdp_xmit_back()
reading xdp->rxq->mem.type, or xsk_rcv_check().
The clearing this patch adds at teardown does not cover the open path.
This dates back to ba39b344e924. The next patch in the series, "net:
stmmac: keep DMA configurations at stable addresses", appears to fix it
by storing the allocated conf pointer in priv instead of copying it.
> } 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;
[Severity: High]
With these errors now returned, what happens on the XSK pool
enable/disable path? stmmac_enable_rx_queue() returns void:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_enable_rx_queue() {
...
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_xdp_enable_pool() and stmmac_xdp_disable_pool() then continue
anyway:
stmmac_enable_rx_queue(priv, queue);
stmmac_enable_tx_queue(priv, queue);
napi_enable(&ch->rxtx_napi);
__free_dma_rx_desc_resources() now sets buf_pool, page_pool, dma_rx and
dma_erx to NULL. Can the re-enabled NAPI poll reach stmmac_rx() or
stmmac_rx_zc() and dereference those NULL pointers? In the enable path,
stmmac_xsk_wakeup() also kicks the poll.
This path could already crash on allocation failure before this patch.
The patch adds mem-model registration failure as another trigger.
A later patch in the series, "net: stmmac: use the tracked datapath
restart for XSK pool changes", replaces these void helpers with
stmmac_xdp_release()/stmmac_xdp_open() and checks the error. So this is
fixed by the end of the series, but it is still exposed at this commit.
[ ... ]
> @@ -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;
[Severity: High]
This is a pre-existing issue, but stmmac_xdp_enable_pool() and
stmmac_xdp_disable_pool() disable only the selected channel's NAPI.
stmmac_disable_tx_queue() then frees the ring:
stmmac_stop_tx_dma(priv, queue);
__free_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
At that point only RTNL and ch->lock are held. Other TX producers take
only the netdev TX queue lock:
- stmmac_xmit()
- stmmac_xdp_xmit()
- XDP_TX from other RX queues, which pick a TX queue by CPU
Can these still touch tx_skbuff_dma[], dma_tx and cur_tx while the ring
is being freed?
Before this patch that would be a use-after-free. With the new NULL
assignments it becomes a NULL dereference instead.
In the final state of the series, stmmac_xdp_release() detaches the
netdev and quiesces all queues before freeing. ndo_xdp_xmit also checks
netif_device_present() under the TX lock.
> }
[ ... ]
> @@ -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;
> + }
[Severity: Medium]
This is a pre-existing issue, but does anything clear the saved
partial-RX state across suspend/resume? stmmac_resume() resets the ring
and DMA:
stmmac_reset_queues_param(priv);
stmmac_free_tx_skbufs(priv);
stmmac_clear_descriptors(priv, &priv->dma_conf);
ret = stmmac_hw_setup(ndev);
rx_q->state_saved, state.skb, state.len and state.error are left as
they were, and stmmac_suspend() does not clear them either.
If suspend happened mid-frame, the first poll after resume would restore
the stale skb and len here. It would then treat the first new frame at
descriptor 0 as a continuation and append that frame's buffers as frags.
With split header active, the stale len is also passed to
stmmac_rx_buf1_len() and stmmac_rx_buf2_len(). buf1_len would be 0
because len is non-zero. In stmmac_rx_buf2_len(), plen - len could
underflow if the new frame is shorter than the stale len.
The state is only cleared in stmmac_reinit_dma_desc() and
__free_dma_rx_desc_resources(). The SUSPENDED branch of
stmmac_init_dma_engine() later in the series does not clear it either.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 08/15] net: stmmac: keep DMA configurations at stable addresses
2026-09-26 15:49 ` [PATCH net v4 08/15] net: stmmac: keep DMA configurations at stable addresses James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 (1) · Medium: 2 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] stmmac_change_mtu() now calls netif_device_detach(dev) before
__stmmac_release()/__stmmac_open().
- [Medium] stmmac_xsk_wakeup() now decides only on test_bit(queue,
priv->af_xdp_zc_qps).
Pre-existing issues:
- [High] At this commit, when __stmmac_open() fails after
stmmac_hw_setup() (for example in stmmac_request_irq()), the irq_error…
--
net: stmmac: keep DMA configurations at stable addresses
This commit changes priv->dma_conf from an embedded struct into a pointer
to the allocated configuration, so XDP rxq metadata and AF_XDP pool
references keep pointing at valid storage. Per-queue MSI handlers now use
the persistent channel objects as their context, and new quiesce helpers
drain transmitters and NAPI before the TX timers are cancelled. The MTU
reopen path is also wrapped in netif_device_detach()/netif_device_attach().
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 1cc68c1cf37dc..c4ba97d57b47e 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -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:
[ ... ]
> @@ -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;
> }
[Severity: High]
This is a pre-existing issue that this patch did not introduce, but can
the DMA engine still be using these rings when they are freed?
Say __stmmac_open() fails after stmmac_hw_setup(), for example in
stmmac_request_irq(). The irq_error path only does this:
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);
The MAC is still enabled at that point. The RX/TX DMA channels that
stmmac_hw_setup()->stmmac_start_all_dma() started are also still
running.
stmmac_open() then calls free_dma_desc_resources() and kfree() on
dma_conf here. stmmac_change_mtu() does the same on its failure path, and
stmmac_xdp_open() follows the same pattern.
Could the hardware keep reading or writing freed descriptor memory and
page_pool buffers?
A later commit in this series, "net: stmmac: retain DMA memory until
hardware shutdown completes", appears to fix this. It makes the
__stmmac_open() and stmmac_xdp_open() failure paths call
stmmac_drain_dma(), and it keeps configurations owned by DMA until a
confirmed reset.
[ ... ]
> @@ -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);
> }
[Severity: Medium]
Does the interface stay detached for good if __stmmac_open() fails here?
netif_device_detach() runs unconditionally. netif_device_attach() is only
reached on the success path. When stmmac_hw_setup() or
stmmac_request_irq() fails, stmmac_change_mtu() returns with
__LINK_STATE_PRESENT still cleared.
At this point in the series, neither stmmac_release() nor stmmac_open()
calls netif_device_attach(). __dev_open() refuses to open a device that
is not present, so a later "ip link set up" looks like it would fail with
-ENODEV until the driver is rebound or the system goes through
suspend/resume.
A later commit in the series, "net: stmmac: track datapath and power
ownership across failed reopening", adds netif_device_attach() to
stmmac_release() ("Allow a fresh open after a failed MTU reopen or
resume") and reworks this path, so the end of the series looks fine.
Should the failure branch here also reattach, so the series bisects
cleanly?
[ ... ]
> @@ -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;
[Severity: Medium]
Can this new check schedule rxtx_napi on a queue whose rings were never
set up?
In stmmac_xdp_enable_pool() the bit is set before the queue enables,
which return void:
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);
stmmac_enable_rx_queue() and stmmac_enable_tx_queue() just log and return
if allocation fails.
If both fail, the old check (!rx_q->xsk_pool && !tx_q->xsk_pool) returned
-EINVAL. The new check passes because the bit is set, so rxtx_napi gets
scheduled. stmmac_napi_poll_rxtx()->stmmac_rx_zc() would then run on
unallocated rings with rx_q->xsk_pool == NULL.
A later commit in the series, "net: stmmac: use the tracked datapath
restart for XSK pool changes", replaces the void per-queue helpers with
stmmac_xdp_release()/stmmac_xdp_open(). On open failure it clears the bit
and returns an error.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 09/15] net: stmmac: track datapath and power ownership across failed reopening
2026-09-26 15:49 ` [PATCH net v4 09/15] net: stmmac: track datapath and power ownership across failed reopening James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider.
Critical: 0 · High: 4 · Medium: 4 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] Platforms that have a resume hook but no suspend hook no longer
run the resume hook on system resume.
- [High] The new 'retry pending power transitions' logic misuses
pm_runtime_force_resume(), which only does its work once.
- [High] While hw_unavailable is set, stmmac_interrupt() and
stmmac_mac_interrupt() (and the safety and MSI rx/tx handlers) return…
- [High] With MAC WoL enabled, stmmac_suspend() calls phylink_suspend(pl,
true), which only sets PHYLINK_DISABLE_MAC_WOL and does not call…
- [Medium] A failed system resume leaves the PHY powered after it was
resumed only to supply the MAC reset clock.
- [Medium] stmmac_bpf() calls __stmmac_release() on a retained SUSPENDED
datapath (and, at the end of the series, HALTED) for every…
- [Medium] On platforms without plat->suspend (so bsp_suspended is never
set), a resume where only re-enabling the optional PTP reference clock…
- [Medium] stmmac_suspend() sets priv->bsp_suspended = true before
calling priv->plat->suspend(), and does not clear it if the callback…
- [Low] Submission scope and stable backportability: one commit carries
five Fixes: tags and many independent behaviour changes, and it…
--
net: stmmac: track datapath and power ownership across failed reopening
This commit tracks datapath state (down, running, suspended) separately
from IFF_UP. On failure, the MTU, XDP and ethtool reopen paths now leave
the device detached instead of tearing it down. It also splits core sleep
cleanup from platform and clock restoration, and adds an hw_unavailable
gate on MMIO, MDIO, PHC and IRQ access while power is not restored.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index c4ba97d57b47e..22a65c1c16810 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -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);
> +}
[Severity: Medium]
Can clearing ptp_clock_suspended here stop the interface from opening
until the next system sleep?
Take a platform with no plat->suspend, so bsp_suspended is never set.
Suppose noirq resume restores the bus clocks but
clk_prepare_enable(clk_ptp_ref) fails. ptp_clock_suspended stays true.
stmmac_resume()->stmmac_resume_power(priv, true) fails the same way and
returns before hw_unavailable is cleared.
If the interface is then closed,
stmmac_release()->__stmmac_release()->stmmac_release_ptp() clears
ptp_clock_suspended here. On the next open,
stmmac_open()->stmmac_resume_power(priv, false) evaluates:
bool pending = priv->bus_clks_suspended || priv->bsp_suspended ||
priv->ptp_clock_suspended;
as false. hw_unavailable is never cleared, so every open attempt returns
-EHOSTDOWN.
stmmac_setup_ptp() treats a PTP reference clock failure as only a
warning. Should this optional clock be able to block the whole MAC?
[ ... ]
> @@ -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;
> + }
[Severity: High]
Is pm_runtime_force_resume() safe to retry like this?
In drivers/base/power/runtime.c it clears needs_force_resume and calls
pm_runtime_enable() on both success and failure:
if (!dev->power.needs_force_resume && (!dev_pm_smart_suspend(dev) ||
pm_runtime_status_suspended(dev)))
goto out;
...
out:
dev->power.needs_force_resume = false;
pm_runtime_enable(dev);
return ret;
Suppose the noirq call fails, for example when stmmac_runtime_resume()
fails a clock enable. bus_clks_suspended stays true. The retry from
stmmac_resume(), stmmac_open() or stmmac_release() then skips the
callback, does an unbalanced pm_runtime_enable(), and returns 0.
stmmac_resume_clocks() then clears bus_clks_suspended and
stmmac_resume_power() clears hw_unavailable. After that,
stmmac_resume_hw() and stmmac_hw_setup() access MMIO while the bus
clocks are still off.
stmmac_pltfr_noirq_suspend() has the same problem. It sets the flag
before the call and does not roll it back if the call fails:
priv->bus_clks_suspended = true;
ret = pm_runtime_force_suspend(dev);
if (ret) {
mutex_unlock(&priv->pm_mutex);
return ret;
}
The later retry would then hit the same unbalanced enable and false
success.
[ ... ]
> +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;
> + }
[Severity: High]
Does this skip plat->resume() on platforms that have a resume hook but
no suspend hook?
stmmac_suspend() sets bsp_suspended only when plat->suspend is non-NULL:
if (priv->plat->suspend && !priv->bsp_suspended) {
priv->bsp_suspended = true;
Before this patch, stmmac_resume() called plat->resume() unconditionally.
dwmac-mediatek sets plat->resume = mediatek_dwmac_init and has no suspend
hook. stmmac_pltfr_probe() does:
if (!plat->suspend && plat->exit)
plat->suspend = stmmac_plat_suspend;
if (!plat->resume && plat->init)
plat->resume = stmmac_plat_resume;
So glue drivers with init but no exit (dwmac-thead, dwmac-socfpga,
dwmac-loongson1) get only a resume hook.
On those boards, PHY interface selection, delays and syscon setup would
not be reprogrammed after sleep. stmmac_resume_power(priv, true) still
clears hw_unavailable, and the core goes on into stmmac_hw_setup().
The commit message doesn't mention this change. Was it intended?
[ ... ]
> @@ -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;
> + }
> +
[Severity: High]
Can this cause an interrupt storm on a MAC WoL wake?
stmmac_suspend() sets hw_unavailable after PMT has been armed, and only
stmmac_resume() clears it. With irq_wake set,
stmmac_pltfr_noirq_suspend() keeps the clocks on, so the registers are
still accessible in this window.
The PMT status is cleared by reading it in the host IRQ status path,
which this early return now skips. A magic packet can arrive after
stmmac_suspend(), or the wake IRQ can be replayed after
resume_device_irqs() but before stmmac_resume(). Either way, a
level-triggered MAC/WoL line would stay asserted.
Each pass calls pm_wakeup_event() and returns IRQ_NONE, until
note_interrupt() disables the line as "nobody cared". After that the
interface gets no interrupts following resume.
stmmac_mac_interrupt() has the same early return. When irq_wake keeps the
MAC clocked, would it be better to service the PMT status here?
[ ... ]
> @@ -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);
> +
[Severity: Medium]
Should this release happen only for pool removal, as the comment says?
As written, it runs for every XDP_SETUP_PROG and XDP_SETUP_XSK_POOL
request before any validation. That includes program installs, swaps
from one program to another, and requests that are later rejected by
stmmac_xdp_set_prog() (jumbo MTU) or stmmac_xdp_enable_pool() (bad queue,
small frame size).
Once datapath is DOWN, a later stmmac_resume() takes this path:
if (priv->datapath != STMMAC_DATAPATH_SUSPENDED) {
ret = 0;
goto out_unlock;
}
It returns 0 with the netdev still detached. The "A later resume may
retry" behaviour described in the resume error path is lost, even when
the XDP request itself failed.
[ ... ]
> @@ -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);
[Severity: High]
With MAC WoL enabled, what happens to a polled PHY once this flag is set?
phylink_suspend(priv->phylink, true) only sets PHYLINK_DISABLE_MAC_WOL
and does not call phy_stop(). stmmac sets mac_managed_pm, so
mdio_bus_phy_suspend() does not stop the PHY state machine either.
>From here until stmmac_resume() clears the flag, stmmac_mdio_access()
and the xgmac2 c22/c45 accessors return -EHOSTDOWN. The PHY state machine
work runs on a workqueue that is not freezable. A poll in this window
would get -EHOSTDOWN from phy_read_status(), and phylib would then WARN,
move the PHY to PHY_ERROR and stop polling.
On resume, the irq_wake branch in stmmac_resume_hw() only clears PMT, and
phylink_resume() only clears the disable bit. Is anything left that
restarts the PHY?
Before this patch MDIO stayed usable in this window, because the clocks
are kept on for irq_wake.
> + 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();
[Severity: Medium]
What happens to bsp_suspended if plat->suspend() fails?
It stays true. The PM core won't call ->resume for a device whose
->suspend failed. Even so, stmmac_open() or
stmmac_release()->stmmac_resume_power() will call plat->resume() for a
platform suspend that never completed.
If no open or close happens first, the next system suspend skips
plat->suspend() because of the !priv->bsp_suspended check, while
stmmac_resume() still calls plat->resume().
For hooks such as the dwmac-intel or PCI glue (pci_enable_device, clock
enables), would this leave the platform enable counts unbalanced? Should
bsp_suspended be set only when the callback succeeds?
[ ... ]
> @@ -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;
> }
[ ... ]
> @@ -8455,11 +8836,25 @@ int stmmac_resume(struct device *dev)
[ ... ]
> +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;
> }
[Severity: Medium]
Does this leave the PHY powered after a failed resume?
stmmac sets mac_requires_rxc. So the earlier phylink_prepare_resume() in
stmmac_resume() calls phy_resume() on a PHY that the non-WoL
phylink_suspend() had stopped.
If stmmac_hw_setup() then fails, this path does not call phylink_stop().
With the earlier "allow stopping a suspended instance" change in the
series, phylink_stop() would call phy_suspend() here.
__stmmac_open() does this unwind in its init_error path ("Undo
phylink_prepare_resume() even if hardware setup failed"). Should the
resume failure path do the same?
Otherwise the PHY stays powered, and the link partner may see link up
while the datapath is detached, until close or the next suspend.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 10/15] net: stmmac: use the tracked datapath restart for XSK pool changes
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-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 3 (2) · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The all-queue restart now reports AF_XDP TX completions a second
time for buffers that were already returned on another, still-attached…
- [Medium] In stmmac_xdp_xmit_zc()
(drivers/net/ethernet/stmicro/stmmac/stmmac_main.c), the patch changes…
- [Medium] stmmac_xdp_enable_pool() and stmmac_xdp_disable_pool() in
drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c now restart the whole…
- [Medium] Because pool attach and detach now restart every queue,
pending AF_XDP TX work on an unrelated zero-copy queue can be left…
- [Low] stmmac_xdp_xmit_zc() now returns true at entry when carrier is
down, even if XSK TX descriptors are still pending in the user TX ring.
Pre-existing issues:
- [Medium] At this commit, stmmac_xdp_release() calls
stmmac_stop_all_dma(), which only clears the SR/ST bits and does not…
- [Medium] stmmac_set_queue_rx_buf_size() picks the XSK frame size only
when `rx_q->xsk_pool && rx_q->buf_alloc_num`.
--
net: stmmac: use the tracked datapath restart for XSK pool changes
This commit drops the per-queue XSK pool enable/disable helpers. When a
pool is attached or removed while XDP is running, it now does a full
stmmac_xdp_release()/stmmac_xdp_open() restart instead. It also makes
stmmac_xdp_xmit_zc() return early while carrier is down, restores TBS in
stmmac_xdp_open(), and unwinds a failed reopen.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 22a65c1c16810..618e01772ab86 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -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;
> +
[Severity: Medium]
Can AF_XDP zero-copy TX stall after a link flap with this change?
Before this patch, the carrier check inside the loop set work_done = false.
stmmac_tx_clean() then returned budget, and rxtx_napi kept polling until
carrier came back. With the early return, stmmac_tx_clean() takes the other
branch:
stmmac_tx_clean() {
...
work_done = stmmac_xdp_xmit_zc(priv, queue, STMMAC_XSK_TX_BUDGET_MAX);
if (work_done)
xmits = budget - 1;
else
xmits = budget;
...
}
NAPI then completes. Nothing seems to reschedule rxtx_napi on link up,
because stmmac_mac_link_up() doesn't call napi_schedule(). A kick made
during the outage also fails:
stmmac_xsk_wakeup() {
...
if (test_bit(STMMAC_DOWN, &priv->state) ||
!netif_carrier_ok(priv->dev))
return -ENETDOWN;
...
}
Descriptors queued during the outage stay unsent until userspace kicks
again after carrier returns, or until RX traffic raises an IRQ on that
channel. An application blocked in poll() with no RX on the channel would
not recover.
This applies to every carrier-down period, not only pool changes, and the
commit message doesn't mention it. Would it be better as a separate patch
that describes the new behaviour?
[Severity: Low]
This isn't a bug, but a true return can now also mean "carrier down,
nothing drained". The comment in stmmac_tx_clean() still says:
/* For XSK TX, we try to send as many as possible.
* If XSK work done (XSK TX desc empty and budget still
* available), return "budget - 1" to reenable TX IRQ.
The trailing comment in stmmac_xdp_xmit_zc() also still says true is only
returned when budget remains and the XSK TX desc peek is empty. Should
both comments be updated?
[ ... ]
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
> index 7ecb7addd2ea7..907ac49a1b764 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
[ ... ]
> @@ -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);
[Severity: Medium]
Is a full device restart intended on every AF_XDP bind and unbind?
stmmac_xdp_release() does the following:
- detaches the netdev;
- calls phylink_replay_link_begin(), which runs phylink_link_down() and
netif_carrier_off();
- frees all IRQs, stops all DMA channels and frees every RX/TX ring.
stmmac_xdp_open() then rebuilds everything, and phylink_replay_link_end()
turns carrier back on.
Before this patch, only the target queue was restarted. Now each bind or
unbind appears to cause a carrier off/on transition: RTM_NEWLINK events,
operstate changes, a carrier_down_count increment, and possible bonding or
team failover. It also drops in-flight traffic on all unrelated queues.
The operation can now fail for reasons that have nothing to do with the
target queue, such as re-requesting IRQs or allocating rings for other
queues. On failure, the whole interface stays detached, with phylink
stopped and PTP released.
In the attach path, if the socket didn't force XDP_ZEROCOPY,
xp_assign_dev() falls back to copy mode. bind() can then succeed while the
interface is dead.
The commit message says "Pause all queues and MAC link resolution". It
doesn't mention the carrier flap or the effect on other traffic. Could that
be spelled out?
>
> 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);
[Severity: Medium]
What happens to pending XSK TX work on other zero-copy queues after this
restart?
Take a queue A that already has an active pool, with its NAPI polling
because work_done == false (for example, the TX ring was nearly full).
stmmac_xdp_release() disables A's NAPI, cancels the TX hrtimers and frees
A's TX ring.
stmmac_xdp_open() re-enables NAPI via stmmac_enable_all_queues(), but
napi_enable() doesn't schedule a poll. netif_device_attach() only wakes the
netdev TX queues.
Only channel[queue].rxtx_napi for the newly attached queue is scheduled
here, and stmmac_xdp_disable_pool() schedules nothing. Queue A is left with
no pending completions, no armed timer and no NAPI poll. Its XSK TX work
stays stalled until userspace kicks again or RX traffic arrives on
channel A.
xsk_poll() only calls xsk_wakeup() on entry, so a TX-only application
already blocked in poll() for POLLOUT would not be woken. Should every
queue with an XSK pool be rescheduled after a successful reopen?
[Severity: Medium]
This is a pre-existing issue, but now that this napi_schedule() drives the
initial ZC refill, can RBSZ end up programmed with the wrong size?
stmmac_set_queue_rx_buf_size() only uses the XSK frame size when buffers
have already been allocated:
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;
__init_dma_rx_desc_rings() ignores the return value of
stmmac_alloc_rx_buffers_zc(). If the fill ring is still empty at bind time,
buf_alloc_num is 0 and stmmac_xdp_open() programs dma_buf_sz. The later
stmmac_rx_refill_zc() only advances the tail pointer and never reprograms
RBSZ.
The programmed size would only exceed the real buffer size for XSK frame
sizes from 1522 to 1535 bytes. The removed stmmac_enable_rx_queue() used
the same ordering.
[ ... ]
> @@ -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);
[Severity: High]
Could this restart report TX completions a second time to a pool that
stays attached on another queue?
When stmmac_tx_clean() completes an XSK TX descriptor, it counts it but
leaves buf_type set:
if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XSK_TX)
tx_q->xsk_frames_done++;
The slot stays STMMAC_TXBUF_T_XSK_TX until an skb transmit reuses it.
A pool change on queue B now goes through stmmac_xdp_release() ->
free_dma_desc_resources() for every queue. For queue A,
dma_free_tx_skbufs() walks all dma_tx_size entries:
for (i = 0; i < dma_conf->dma_tx_size; i++)
stmmac_free_tx_buffer(priv, dma_conf, queue, i);
if (tx_q->xsk_pool && tx_q->xsk_frames_done) {
xsk_tx_completed(tx_q->xsk_pool, tx_q->xsk_frames_done);
stmmac_free_tx_buffer() counts every stale XSK_TX slot again.
xsk_tx_completed() ends in xskq_prod_submit_n(), which advances the
producer without checking cached_prod:
static inline void xskq_prod_submit_n(struct xsk_queue *q, u32 nb_entries)
{
__xskq_prod_submit(q, q->ring->producer + nb_entries);
}
Userspace would then see stale addresses in queue A's completion ring as
newly completed, and later completions would be misaligned. The
application could then reuse UMEM frames the kernel still owns. The old
per-queue restart never touched queue A's ring.
Should buf_type be reset on completion, or should teardown count only the
dirty_tx..cur_tx range?
>
> xsk_pool_dma_unmap(pool, STMMAC_RX_DMA_ATTR);
[Severity: Medium]
This is a pre-existing issue, but is DMA guaranteed to be idle before the
rings are freed and the pool is unmapped here?
At this commit, stmmac_xdp_release() does:
stmmac_stop_all_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);
dwmac4_dma_stop_rx() and dwmac4_dma_stop_tx() only clear the SR/ST bits;
they don't wait for the stopped state. An in-flight frame or descriptor
write-back could therefore land in freed rings or unmapped UMEM.
The removed per-queue helpers used the same stop-then-free pattern, and
their synchronize_rcu() never drained DMA.
A later patch in this series appears to address this: "net: stmmac: retain
DMA memory until hardware shutdown completes" adds stmmac_drain_dma()
before free_dma_desc_resources() in stmmac_xdp_release().
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 11/15] net: stmmac: restore TC offloads before restarting DMA
2026-09-26 15:49 ` [PATCH net v4 11/15] net: stmmac: restore TC offloads before restarting DMA James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 3 (1) · Low: 1 (2)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The patch saves VLAN-priority steering in
priv->plat->rx_queues_cfg[tc] and relies on stmmac_mtl_configuration()…
- [High] On XGMAC, the runtime TC-to-queue mapping that this patch
restores is corrupted again before DMA starts.
- [Medium] The patch fixes a real divide-by-zero crash but does not say
so and has no Fixes: tag.
- [Medium] When tc_taprio_configure() fails after programming the new
schedule (stmmac_est_configure() or tc_config_preemption() fails), the…
- [Medium] stmmac_tc_restore_filters() walks
priv->flow_entries[0..flow_entries_max) and…
- [Low] EST replay and DMA start go ahead even when timestamp setup
failed.
Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but
tc_config_flow() programs port_src and then port_dst into the same…
- [Low] This problem wasn't introduced by this patch, but a protocol-only
flower rule (e.g. IPv4/UDP DROP with no address or port keys) is…
- [Low] This problem wasn't introduced by this patch, but
dwmac4_config_l3_filter()/dwmac4_config_l4_filter() (and the XGMAC…
--
net: stmmac: restore TC offloads before restarting DMA
This patch saves L3/L4 flower rules, VLAN priority steering, EtherType
steering, the FPE preemption mapping and the EST schedule so they can be
replayed after a DMA software reset on open and resume. DMA start is
delayed until those offloads have been restored. TAPRIO replacement now
builds the new schedule separately and publishes it only after hardware
programming succeeds.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 618e01772ab86..edcb9a025385b 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -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;
[Severity: Low]
The commit message says "Restore EST after the timestamp counter is
usable". The comment above stmmac_tc_restore_est() also says it is called
after timestamp setup. Does either caller actually enforce that?
In stmmac_resume(), the return value of stmmac_init_timestamping() is
ignored before stmmac_tc_restore_est() runs. Also,
stmmac_init_tstamp_counter() throws away the result of
stmmac_init_systime(), so a TSINIT timeout is reported as success.
In __stmmac_open(), stmmac_setup_ptp() returns void. It returns early if
clk_prepare_enable() fails for clk_ptp_ref, but stmmac_tc_restore_est()
and stmmac_start_all_dma() still run afterwards.
stmmac_get_systime() cannot fail when the callback exists. In these cases,
won't the replay compute a base time from a counter that was never
initialized, arm EST, and report success?
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> index 9ded65750e89f..de254919f6ab7 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
[ ... ]
> @@ -577,23 +559,46 @@ static int tc_add_ports_flow(struct stmmac_priv *priv,
[ ... ]
> +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);
[Severity: Low]
This isn't a bug introduced by this patch, but dwmac4_config_l3_filter()
and dwmac4_config_l4_filter() do an unlocked read-modify-write of
MAC_PACKET_FILTER to set IPFE. The XGMAC equivalents do the same:
value = readl(ioaddr + GMAC_PACKET_FILTER);
value |= GMAC_PACKET_FILTER_IPFE;
writel(value, ioaddr + GMAC_PACKET_FILTER);
tc_add_flow() holds only RTNL. The rx-mode set_filter path writes the same
register under the address lock.
Could a concurrent rx-mode update lose the IPFE bit? This clear-slot call
adds one more read-modify-write per add or replace, but the race itself
is unchanged.
> + if (ret || !entry->in_use)
> + 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;
> }
[Severity: Medium]
This is a pre-existing issue, but on dwmac4, is the source port match
still there after port_src and then port_dst are programmed into the same
slot?
Every call to dwmac4_config_l4_filter() clears both enables and rewrites
the whole GMAC_L4_ADDR register:
value &= ~(GMAC_L4SPM0 | GMAC_L4SPIM0);
value &= ~(GMAC_L4DPM0 | GMAC_L4DPIM0);
...
writel(value, ioaddr + GMAC_L4_ADDR(filter_no));
As a result, a flower rule with both src and dst ports seems to match only
the dst port. That happens at install time and, with this patch, on every
reset replay too. The old tc_add_ports_flow() used the same call sequence.
[ ... ]
> @@ -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);
[Severity: Low]
This isn't a bug introduced by this patch, but what happens with a
protocol-only rule, for example an IPv4/UDP drop with no address or port
keys?
tc_add_basic_flow() records only ip_proto and returns 0, which sets
new.in_use. The IPv4 and ports parsers return -EINVAL, and that is ignored
here.
tc_config_flow() then clears the slot, enables no match and returns 0.
The cookie is published and the rule is reported as offloaded, but it has
no effect in hardware. The baseline had the same false success.
[ ... ]
> @@ -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;
[Severity: High]
Can tc go past the end of rx_queues_cfg[] here?
rx_queues_cfg[] has MTL_MAX_RX_QUEUES (8) entries, and tx_queues_cfg[]
follows it in struct plat_stmmacenet_data. tc comes from
tc_classid_to_hwtc(), which only guarantees tc < netdev_get_num_tc().
taprio in txtime-assist mode allows overlapping TXQ ranges and can set up
to TC_MAX_QUEUE (16) traffic classes. A rule with "vlan_prio N hw_tc 8..15
skip_sw" would then write into tx_queues_cfg[] (CBS slopes, weights,
prio). tc_del_vlan_flow() uses entry->tc as the index in the same way.
Before this patch, tc_add_vlan_flow() only called stmmac_rx_queue_prio()
and did not write to the array.
Separately, does the saved copy match what the hardware holds?
dwmac4_rx_queue_priority() and dwxgmac2_rx_queue_prio() OR the new bit
into the target queue's PSRQ field and clear it from the other queues.
The saved value, however, is overwritten:
priv->plat->rx_queues_cfg[tc].prio = prio;
With two VLAN priority rules on the same tc, both bits are set in
hardware, but only the last one would be replayed after a reset.
In tc_del_vlan_flow(), setting prio = 0 for the whole tc also drops any
other VLAN rule still installed on that tc. It also permanently removes
the board or DT priority for that queue.
A rule can also move priority p from DT queue r to queue q. In that case
rx_queues_cfg[r].prio still contains p. stmmac_mac_config_rx_queues_prio()
replays queues in ascending order. When r > q, wouldn't replaying r take
p back from q?
In addition, stmmac_mtl_configuration() only replays priorities for
queues below rx_queues_to_use, and only when rx_queues_to_use > 1:
if (rx_queues_count > 1)
stmmac_mac_config_rx_queues_prio(priv);
stmmac_tc_restore_filters() skips STMMAC_RFS_T_VLAN on the assumption
that stmmac_mtl_configuration() replays it. After open or resume, won't
the RX VLAN priority steering then differ from the installed flower
rules?
> @@ -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;
[Severity: Medium]
Can priv->flow_entries be NULL here while flow_entries_max is non-zero?
tc_init() sets flow_entries_max from dma_cap->l3l4fnum before calling
devm_kcalloc(). If the allocation fails, it returns -ENOMEM with the
count still set. tc_rfs_init() does the same with rfs_entries_total and
rfs_entries.
__stmmac_dvr_probe() ignores the error:
ret = stmmac_tc_init(priv, priv);
if (!ret) {
ndev->hw_features |= NETIF_F_HW_TC;
}
Before this patch, these tables were only reached through flower
offload, which needs NETIF_F_HW_TC. Now stmmac_hw_setup() calls
stmmac_tc_restore_filters() on every open and resume. Wouldn't that
dereference NULL on every open and resume?
The RX parser replay nearby is already guarded with if (priv->tc_entries).
> + ret = tc_config_flow(priv, entry);
> + if (ret)
> + return ret;
> + }
[ ... ]
> + /* 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;
> +}
[Severity: High]
On XGMAC, is this mapping still intact when DMA starts?
dwxgmac3_fpe_map_preemption_class() writes Q2TCMAP = tc using
u32_replace_bits(). However, stmmac_hw_setup() calls
stmmac_dma_operation_mode() after stmmac_tc_restore_filters(), and
dwxgmac2_dma_tx_mode() ORs in the channel without clearing the field:
/* Use static TC to Queue mapping */
value |= FIELD_PREP(XGMAC_Q2TCMAP, channel);
That would leave Q2TCMAP = tc | channel. For example, with mqprio 2@0 2@2,
queue 1 would move from TC 0 to TC 1, and queues 2 and 3 would move from
TC 1 to TC 3, which does not exist.
Should the replay run after stmmac_dma_operation_mode(), or should
dwxgmac2_dma_tx_mode() clear the field first?
[ ... ]
> @@ -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;
[Severity: Medium]
This change fixes a divide-by-zero in the baseline, but the commit
message only says:
"A rejected first install must not leave an enabled zero-cycle cache
for PHC adjustment or reset replay."
In the baseline, every TAPRIO_CMD_REPLACE cleared priv->est with memset()
and set enable before the gate list was validated. An entry with an
out-of-range interval (for example more than 65535 ns with wid=16)
returned -ERANGE and left enable set with ctr[] still zero.
taprio_change() then keeps the previous qdisc and does not send DESTROY.
The next PHC step then reaches this code in stmmac_adjust_time():
cycle_time = (u64)priv->est->ctr[1] * NSEC_PER_SEC + priv->est->ctr[0];
time = stmmac_calc_tas_basetime(basetime, current_time_ns, cycle_time);
That ends up calling div64_s64() with a zero divisor. It affects any
rejected replace, not only the first install.
Could this fix be split into its own patch with a Fixes: tag and a commit
message that describes the crash, so it can go to stable without the rest
of the series?
[ ... ]
> + *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;
[Severity: Medium]
Is priv->est->btr[] still safe to write back here?
It was computed when the old schedule was installed, or at the last PHC
adjust or reset replay. It is not rebased against the current PHC time
here. stmmac_tc_restore_est() and stmmac_adjust_time() both recompute it
from btr_reserve[] with stmmac_calc_tas_basetime().
est_configure() writes cfg->btr[] directly to EST_BTR_LOW/HIGH and sets
EEST | SSWL. The EST block only accepts a past BTR for a limited number
of cycles (EST_GMAC5_BTRL allows 15). Beyond that it raises BTRE and does
not switch lists.
User space can reach this path. One example is when
dwmac5_fpe_map_preemption_class() rejects the SP algorithm with
one-to-many TC mapping after EST has already been programmed.
In that case, could the hardware keep running the rejected schedule while
priv->est and the returned error say the old schedule is still active?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 13/15] net: stmmac: retain DMA memory until hardware shutdown completes
2026-09-26 15:49 ` [PATCH net v4 13/15] net: stmmac: retain DMA memory until hardware shutdown completes James Hilliard
@ 2026-09-30 4:51 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:51 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 3 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] Resume puts retained RX descriptors back into service after the
reset without rewriting their buffer addresses.
- [High] After a failed close, the retained ring set can stay attached to
priv->dma_conf with all its allocations but with dma_owned cleared, so…
- [Medium] TX error recovery can now be lost for good.
- [Medium] On a failed DMA shutdown, stmmac_detach_xsk_buffers() sets
every dma_conf->tx_queue[q].xsk_pool to NULL but never calls…
- [Medium] Once a config is left dma_owned by a failed drain, removal
never retries the stop or reset before stmmac_free_dma_conf()…
--
net: stmmac: retain DMA memory until hardware shutdown completes
Before rings and buffers are released on close, XDP/XSK replacement or a
failed open, this patch waits for the DMA engines to report idle. If
they do not, it falls back to a completed global reset. Configurations
the hardware may still reach are tracked on a list and kept until a
later successful reset, and XSK DMA references are held independently.
TX hard error recovery moves to the process-context reset path, and
resume-time descriptor cleanup moves after the reset.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index edcb9a025385b..4d70645c4a4f8 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> +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;
> +}
[Severity: Medium]
Are the zero-copy XSK TX frames still in the retained TX ring ever
completed after a failed shutdown? Each one reserved a completion queue
slot in xsk_tx_peek_desc() through xskq_prod_reserve_addr().
After a later successful reset, the retained config is freed through
free_dma_desc_resources() -> dma_free_tx_skbufs(). There,
stmmac_free_tx_buffer() counts the STMMAC_TXBUF_T_XSK_TX entries in
xsk_frames_done, but the completion is gated on the pool pointer:
if (tx_q->xsk_pool && tx_q->xsk_frames_done) {
xsk_tx_completed(tx_q->xsk_pool, tx_q->xsk_frames_done);
tx_q->xsk_pool was already cleared here, so those frames are dropped.
The socket can stay bound across ifdown/ifup, an XDP program swap, or a
pool change on another queue. In that case the next open attaches the
same pool, which still holds k reserved but unsubmitted CQ slots.
xskq_prod_submit_n() publishes entries in reservation order. Wouldn't
every later completion then report an address k frames behind, with
those k CQ slots and UMEM frames lost for good?
[ ... ]
> @@ -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);
> }
[Severity: Medium]
Now that the in-IRQ restart is gone, TX recovery depends only on
stmmac_global_err() queueing the reset work. Can that request be lost?
stmmac_service_event_schedule() queues the work only when
STMMAC_SERVICE_SCHED is not already set:
if (!test_bit(STMMAC_DOWN, &priv->state) &&
!test_and_set_bit(STMMAC_SERVICE_SCHED, &priv->state))
queue_work(priv->wq, &priv->service_task);
stmmac_reset_subtask() clears STMMAC_DOWN right after dev_open() has
re-requested the IRQs and restarted DMA. STMMAC_SERVICE_SCHED is only
cleared once the subtask returns:
stmmac_reset_subtask()
dev_open(priv->dev, NULL);
clear_bit(STMMAC_DOWN, &priv->state);
clear_bit(STMMAC_RESETING, &priv->state);
...
stmmac_service_task()
stmmac_reset_subtask(priv);
clear_bit(STMMAC_SERVICE_SCHED, &priv->state);
Suppose a TX hard error lands in that window. It stops the queue and the
TX DMA channel, turns the carrier off and sets STMMAC_RESET_REQUESTED,
but no work gets queued. The worker also never re-checks
STMMAC_RESET_REQUESTED.
With the carrier off, dev_watchdog() won't call ndo_tx_timeout either.
Does the channel then stay stopped until an administrative down/up or a
link change?
[ ... ]
> @@ -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);
> + }
[Severity: High]
The commit message says:
"Rebuild retained RX descriptors after reset with buffer addresses and
chain links written before ownership."
and:
"Likewise, move resume-time TX cleanup and descriptor rebuilding after
the reset succeeds."
That rebuild is stmmac_reinit_dma_desc(), which only runs when reinit is
true. No caller in this patch passes true.
A later patch in the series ("net: stmmac: retain DMA resources across
MTU changes") passes it only for MTU rollback. At the end of the series,
stmmac_resume() still does:
ret = stmmac_hw_setup(ndev, false, false);
So resume takes the STMMAC_DATAPATH_SUSPENDED branch above, which only
calls stmmac_clear_descriptors(). On GMAC4 that goes through
dwmac4_rd_init_rx_desc() -> dwmac4_set_rx_owner(). On XGMAC it goes
through dwxgmac2_init_rx_desc() -> dwxgmac2_set_rx_owner(). Both only OR
flags into des3:
p->des3 |= cpu_to_le32(flags);
stmmac_suspend() disables NAPI in stmmac_quiesce() before
stmmac_stop_all_dma(). Frames the DMA completes in between stay in
writeback format: des0 holds VLAN tags or zero, and des1/des2 hold
status. Entries whose refill failed (buf->page == NULL) also stay in
writeback format.
After stmmac_start_all_dma() on resume, would the RX DMA take those
writeback words as buffer addresses and write received data there?
[ ... ]
> @@ -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;
[Severity: High]
Can priv->dma_conf still hold all of its allocations while dma_owned is
false, so that this check doesn't catch it? One sequence that seems to
get there:
1. ndo_stop -> __stmmac_release() -> stmmac_drain_dma(): the idle wait
and stmmac_reset() both fail. Config A stays dma_owned.
free_dma_desc_resources(A) only calls stmmac_detach_xsk_buffers(), so
A keeps its descriptors, buf_pool, page_pool and tx_skbuff /
tx_skbuff_dma arrays. A is not marked retired, because only
stmmac_put_dma_conf() sets that.
2. ndo_open: stmmac_open() allocates config B. Then
__stmmac_open(B) -> stmmac_hw_setup() -> stmmac_init_dma_engine()
resets successfully, and stmmac_dma_reset_complete() sets
A->dma_owned = false without freeing A.
3. A later step of the open fails, for example stmmac_rxp_config(),
stmmac_tc_restore_filters(), stmmac_tc_restore_est(),
stmmac_request_irq() or netif_set_real_num_queues().
err_dma_resources restores priv->dma_conf = A and frees B.
4. With the interface down, ethtool -G reaches
stmmac_reinit_ringparam(). stmmac_dma_busy() returns false, and
A->dma_rx_size and A->dma_tx_size are overwritten.
5. On the next successful open, stmmac_put_dma_conf(A) ->
free_dma_desc_resources(A) frees A using the new sizes.
If the new sizes are larger, dma_free_rx_skbufs() and
dma_free_tx_skbufs() loop up to the new size. Would they then write
buf->page = NULL and tx_skbuff_dma[i].buf = 0 past the ends of buf_pool
and tx_skbuff_dma, and unmap or free pointers read from beyond them?
Would dma_free_coherent() also be called with a size that doesn't match
the original allocation?
[ ... ]
> @@ -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();
[Severity: Medium]
If an earlier ndo_stop left a config dma_owned, is the stop or reset
ever retried before stmmac_free_dma_conf() quarantines it?
With the datapath already DOWN, __stmmac_release() returns early when
unregister_netdev() calls ndo_stop:
if (priv->datapath == STMMAC_DATAPATH_DOWN)
return;
This loop then only calls free_dma_desc_resources(). For owned configs
that is just the software XSK detach; neither stmmac_drain_dma() nor
stmmac_reset() is called.
Further down, stmmac_dvr_remove() asserts the IP reset:
if (priv->plat->stmmac_rst)
reset_control_assert(priv->plat->stmmac_rst);
dma_owned is never cleared, though, so the devm action
stmmac_free_dma_conf() skips kfree() for those configs.
After one transient stop/reset timeout followed by an unbind, wouldn't
these leak until reboot?
- the coherent rings
- page_pool pages and TX skbs
- the xsk_dma references, with their UMEM pin and the device and
netdev references taken by xp_dma_get()
The commit message describes quarantine as happening "If hardware still
cannot stop or reset at removal". Does removal ever actually test that?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 14/15] net: stmmac: prepare device-local DMA interrupt quiescence
2026-09-26 15:49 ` [PATCH net v4 14/15] net: stmmac: prepare device-local DMA interrupt quiescence James Hilliard
@ 2026-09-30 4:52 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:52 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The patch includes an unrelated whitespace-only reflow in
stmmac_free_irq() (drivers/net/ethernet/stmicro/stmmac/stmmac_main.c).
--
net: stmmac: prepare device-local DMA interrupt quiescence
This commit adds DMA interrupt-mask accessors (set_irq_mask) for the
supported stmmac cores, plus a per-channel irq_quiesced gate that the
channel lock protects. When the gate is set, stmmac_napi_check() masks
the channel's DMA interrupt sources again. It does this without
acknowledging pending events and without touching the rings that are
being replaced.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 4d70645c4a4f8..08049c05ffc48 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -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]);
[Severity: Low]
This isn't a bug, but these two hunks in stmmac_free_irq() only join the
tx_irq and rx_irq free_irq() calls onto one line each. Nothing else
changes.
The commit message describes the DMA interrupt-mask accessors and the
irq_quiesced gate. It doesn't mention the IRQ free path, and this reflow
has no connection to interrupt quiescence.
Could the reflow be dropped from this patch, or sent as a separate
cleanup patch?
> }
> }
>
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH net v4 15/15] net: stmmac: retain DMA resources across MTU changes
2026-09-26 15:49 ` [PATCH net v4 15/15] net: stmmac: retain DMA resources across MTU changes James Hilliard
@ 2026-09-30 4:52 ` netdev-bot+sashiko
0 siblings, 0 replies; 29+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 4:52 UTC (permalink / raw)
To: james.hilliard1
Cc: linux, andrew, hkallweit1, davem, kuba, pabeni, qiangqing.zhang,
rmk+kernel, maxime.chevallier, andrew+netdev, mcoquelin.stm32,
alexandre.torgue, ansuelsmth, yangtiezhu, chenhuacai, ast,
daniel, hawk, john.fastabend, sdf, fancer.lancer, quic_jsuraj,
richardcochran, Joao.Pinto, vladimir.oltean, boon.leong.ong,
weifeng.voon, yoong.siang.song, linusw, martin.blumenstingl,
magnus.karlsson, maciej.fijalkowski, horms, bjorn,
thierry.reding, jonathanh, wens, jernej.skrabec, samuel,
edumazet, richard.genoud, alastair, mripard, netdev,
linux-kernel, linux-stm32, linux-arm-kernel, bpf, zhaojinming,
lorenzo.bianconi, dinghui1111, linux-tegra, linux-sunxi
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 4 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] stmmac_reconfigure_mtu()
(drivers/net/ethernet/stmicro/stmmac/stmmac_main.c) calls…
- [Medium] stmmac_reconfigure_mtu() takes priv->ptp_mutex
(stmmac_main.c:6668) before it calls phylink_stop().
- [Medium] stmmac_reconfigure_mtu() masks only the per-channel DMA
interrupt enables (stmmac_set_dma_irq_state -> set_irq_mask).
- [Medium] Before the first stmmac_hw_setup(), stmmac_reconfigure_mtu()
calls phylink_prepare_resume().
--
net: stmmac: retain DMA resources across MTU changes
This patch replaces the release/reopen sequence for live jumbo MTU
changes with a new function, stmmac_reconfigure_mtu(). The new function
keeps the old rings and IRQ registrations so a failed setup can be rolled
back. If the rollback also fails, the interface is left detached in a new
STMMAC_DATAPATH_HALTED state that close or a later down/up can clean up.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 08049c05ffc48..8964f52c26bae 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -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)
> +{
[ ... ]
> + mutex_lock(&priv->ptp_mutex);
> + stmmac_block_ptp(priv, true);
> + netif_device_detach(dev);
> + phylink_stop(priv->phylink);
[Severity: Medium]
Can holding ptp_mutex across phylink_stop() create a lock ordering cycle?
phylink_stop()->phylink_run_resolve_and_disable() does:
queue_work(system_power_efficient_wq, &pl->resolve);
flush_work(&pl->resolve);
so lockdep records a ptp_mutex -> resolve work dependency.
On link down, the resolve worker runs
phylink_resolve()->phylink_link_down()->phylink_deactivate_lpi()->
stmmac_mac_disable_tx_lpi(), and that takes priv->lock. With MAC WoL,
phylink_stop() also calls phylink_link_down() directly under state_mutex.
stmmac_resume() takes the two locks in the opposite order:
mutex_lock(&priv->lock);
...
mutex_lock(&priv->ptp_mutex);
Together these form ptp_mutex -> resolve work -> priv->lock -> ptp_mutex.
Both the MTU path and the resume path hold RTNL today, so this may not
deadlock in practice. The resolve worker does not hold RTNL, though.
Once an MTU change, an LPI link-down and a resume have all run, won't
lockdep report a circular dependency? Before this patch, nothing called
phylink_stop() with ptp_mutex held.
Could ptp_mutex be taken after phylink_stop() and stmmac_quiesce()
instead?
> + 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);
[Severity: Medium]
Only the per-channel DMA interrupt enables are masked here. What happens
to the MAC core interrupt sources? On dwmac4, dwmac4_core_init() writes
GMAC_INT_DEFAULT_ENABLE (PMT, LPI and TSIE) to GMAC_INT_EN, and none of
those are masked.
While ptp_blocked is set, stmmac_common_interrupt() skips the timestamp
acknowledgement:
if (!READ_ONCE(priv->ptp_blocked))
stmmac_timestamp_interrupt(priv, priv);
The only place that clears the TS interrupt is timestamp_interrupt(),
which reads GMAC_TIMESTAMP_STATUS.
Suppose an extts snapshot or a PPS target time event arrives between
stmmac_block_ptp(priv, true) and the unblock at the restart label. Can a
level-triggered line then keep firing, with stmmac_interrupt() returning
IRQ_HANDLED every time? That window includes the sleeping
phylink_stop()/flush_work() and synchronize_net(). It also covers the
time after stmmac_ptp_restore() re-arms extts/PPS inside
stmmac_hw_setup().
On a single-CPU system, could this stop the thread that performs the
reset from ever running?
The failed rollback branch further down has a related problem:
stmmac_set_dma_irq_state(priv, false, irq_mask);
stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
...
priv->datapath = STMMAC_DATAPATH_HALTED;
The failed stmmac_hw_setup() has already run stmmac_core_init(), which
re-enables the MAC interrupts, before its later fallible steps
(stmmac_rxp_config(), stmmac_tc_restore_filters(),
stmmac_restore_timestamping(), stmmac_tc_restore_est()).
stmmac_mac_set(false) only clears RE/TE.
stmmac_request_irq_single() registers dev->irq with IRQF_SHARED. If the
MAC raises LPI, TS, PMT or safety status after the handler is freed,
could the shared line storm until the IRQ core disables it? That would
break the unrelated devices that the commit message says this design
keeps working. The old failed-reopen path behaved similarly for this
second case, but the ptp_blocked window above is new.
> + 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);
[Severity: Medium]
Does passing keep_ptp=true here still work if the PTP reference clock
was never enabled at open?
stmmac_setup_ptp() only warns when the clock fails to enable:
ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
priv->ptp_clock_enabled = !ret;
...
if (ret < 0) {
netdev_warn(...);
return;
}
However, stmmac_restore_timestamping() checks only dma_cap and
clk_ptp_rate before it programs the counter. It does not look at
priv->ptp_clock_enabled:
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);
Further down,
stmmac_hw_setup()->stmmac_restore_timestamping()->stmmac_ptp_restore()->
config_addend() polls TSADDREG for up to 100ms:
return readl_poll_timeout_atomic(ioaddr + PTP_TCR, value,
!(value & PTP_TCR_TSADDREG),
10, 100000);
On hardware where this handshake cannot complete while the reference
clock is gated, the rollback stmmac_hw_setup(dev, true, true) would fail
the same way. Wouldn't that leave the interface in
STMMAC_DATAPATH_HALTED?
Before this patch, the MTU path went through __stmmac_open(). That called
stmmac_hw_setup() with keep_ptp=false and then stmmac_setup_ptp(), which
tolerates this failure. Should stmmac_restore_timestamping() also check
priv->ptp_clock_enabled?
> + 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;
[Severity: Medium]
Does anything undo the earlier phylink_prepare_resume() on this path?
On mac_requires_rxc platforms, phylink_prepare_resume() calls
phy_resume() on the PHY that phylink_stop() has just suspended. The
HALTED branch never calls phylink_stop() or phy_suspend(). The PHY stays
powered and can present link to the partner while the interface is
detached. That lasts until close, when __stmmac_release() calls
phylink_stop().
The init_error path in __stmmac_open() does undo it:
init_error:
phylink_stop(priv->phylink);
stmmac_drain_dma(priv);
phylink_stop(priv->phylink);
Should the HALTED branch also call phylink_stop() before it returns?
> + }
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926-submit-stmmac-reset-fixes-v1-v4-0-ec1c0250b3c9%40gmail.com
^ permalink raw reply [flat|nested] 29+ messages in thread
end of thread, other threads:[~2026-09-30 4:52 UTC | newest]
Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:48 ` [PATCH net v4 03/15] net: phylink: allow stopping a suspended instance James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:48 ` [PATCH net v4 04/15] xsk: freeze deferred pool teardown during system sleep James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:49 ` [PATCH net v4 05/15] net: stmmac: serialize and retain PHC configuration across reset James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
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 ` [PATCH net v4 07/15] net: stmmac: unwind partially allocated DMA configurations James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:49 ` [PATCH net v4 08/15] net: stmmac: keep DMA configurations at stable addresses James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:49 ` [PATCH net v4 09/15] net: stmmac: track datapath and power ownership across failed reopening James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
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-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:49 ` [PATCH net v4 11/15] net: stmmac: restore TC offloads before restarting DMA James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
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 ` [PATCH net v4 13/15] net: stmmac: retain DMA memory until hardware shutdown completes James Hilliard
2026-09-30 4:51 ` netdev-bot+sashiko
2026-09-26 15:49 ` [PATCH net v4 14/15] net: stmmac: prepare device-local DMA interrupt quiescence James Hilliard
2026-09-30 4:52 ` netdev-bot+sashiko
2026-09-26 15:49 ` [PATCH net v4 15/15] net: stmmac: retain DMA resources across MTU changes James Hilliard
2026-09-30 4:52 ` netdev-bot+sashiko
2026-09-26 16:00 ` [PATCH net v4 00/15] net: stmmac: preserve datapath state across MTU and resume failures Maxime Chevallier
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®