* [PATCH net v2] net: axienet: bound TX completion cleanup by the NAPI budget
@ 2026-09-17 11:56 Sagi Maimon
2026-09-21 11:57 ` netdev-bot+sashiko
0 siblings, 1 reply; 2+ messages in thread
From: Sagi Maimon @ 2026-09-17 11:56 UTC (permalink / raw)
To: Radhey Shyam Pandey, netdev
Cc: Robert Hancock, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Michal Simek, linux-arm-kernel,
linux-kernel, Sagi Maimon
axienet_tx_poll() passes lp->tx_bd_num to axienet_free_tx_chain() as
@nr_bds, and @budget is only forwarded to napi_consume_skb() as its
bulk-free hint. Nothing limits the cleanup loop to the NAPI budget, so
the number of packets returned is bounded by the TX ring size rather
than by the budget, and the poll can report more work than it was
given:
eth0: NAPI poll function axienet_tx_poll+0x0/0x180 [xilinx_emac]
returned 96, exceeding its budget of 64.
Returning more than the budget breaks the NAPI contract. It also makes
the "packets < budget" test in axienet_tx_poll() false, so
napi_complete_done() is skipped and TX completion interrupts are not
re-enabled on that pass. NAPI reschedules the poll, so this recovers,
but the accounting is wrong either way.
In steady state fewer descriptors complete per poll than the budget
allows, which is why this is rarely observed. Triggering it needs more
than @budget completions outstanding at once - for example when TX
completion interrupts have not been taken for a while and a full ring is
reclaimed in one go.
Stop the loop once the budget is spent. cur_p->skb is only set on a
packet's last descriptor, so breaking there never leaves a packet
half-freed.
A zero budget must not be treated as a spent budget. netpoll calls
napi->poll() with a budget of 0 to reclaim the TX path only, and
axienet_tx_poll() forwards it unchanged with @force false; testing
packets >= budget alone would break out before examining a single
descriptor, leaving skbs unfreed and lp->tx_bd_ci unchanged. Since
cur_p->cntrl is cleared only here, axienet_check_tx_bd_space() would
keep reporting the ring full and netconsole output could stall. Zero
therefore means no limit, which also covers the @force callers that
clean up after a DMA mapping failure with a budget of 0.
Fixes: 9e2bc267e780 ("net: axienet: Use NAPI for TX completion path")
Signed-off-by: Sagi Maimon <maimon.sagi@gmail.com>
---
v2:
- Do not treat a zero budget as a spent budget. netpoll polls with a
budget of 0 to reclaim the TX path only, and v1 broke out of the loop
before examining any descriptor, so nothing was reclaimed and the ring
could stay full. Reported by the Sashiko AI reviewer.
- Update the @budget kernel-doc: it bounds the cleanup when @force is
false, and zero means no limit. It previously read "use 0 when not
called from NAPI poll", which after v1 would have meant "reclaim
nothing".
- Compile-tested only; the board this was found on is not available to
me at the moment. v1 was verified on hardware with the napi:napi_poll
tracepoint (8424 polls, max work 64 against a budget of 64).
v1: https://lore.kernel.org/netdev/20260914114821.55503-1-maimon.sagi@gmail.com/
drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
index 782f903d318f..f643453261f1 100644
--- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
+++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
@@ -772,7 +772,9 @@ static int axienet_device_reset(struct net_device *ndev)
* @force: Whether to clean descriptors even if not complete
* @sizep: Pointer to a u32 accumulating the total byte count of
* completed packets (using skb->len). Ignored if NULL.
- * @budget: NAPI budget (use 0 when not called from NAPI poll)
+ * @budget: NAPI budget. When @force is false, cleanup stops after this
+ * many completed packets. Zero means no limit, as used by the
+ * netpoll TX reclaim and by callers outside NAPI poll.
*
* Would either be called after a successful transmit operation, or after
* there was an error when setting up the chain.
@@ -788,6 +790,15 @@ static int axienet_free_tx_chain(struct axienet_local *lp, u32 first_bd,
dma_addr_t phys;
for (i = 0; i < nr_bds; i++) {
+ /* A NAPI poll must not return more than its budget. Stop on a
+ * packet boundary once it is spent - cur_p->skb is only set on
+ * a packet's last descriptor, so no packet is left half-freed.
+ * A zero budget means no limit: netpoll polls with a budget of
+ * 0 to reclaim the TX path only, and must still clean the ring.
+ */
+ if (!force && budget && packets >= budget)
+ break;
+
cur_p = &lp->tx_bd_v[(first_bd + i) % lp->tx_bd_num];
status = cur_p->status;
--
2.47.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH net v2] net: axienet: bound TX completion cleanup by the NAPI budget
2026-09-17 11:56 [PATCH net v2] net: axienet: bound TX completion cleanup by the NAPI budget Sagi Maimon
@ 2026-09-21 11:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 2+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 11:57 UTC (permalink / raw)
To: maimon.sagi
Cc: radhey.shyam.pandey, netdev, robert.hancock, andrew+netdev,
davem, edumazet, kuba, pabeni, michal.simek, linux-arm-kernel,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 (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] Incomplete fix: the new budget guard in axienet_free_tx_chain()
is disabled when @budget == 0 (`if (!force && budget && packets >=…
Pre-existing issues:
- [Medium] Pre-existing (not introduced or claimed to be fixed by this
patch): axienet_dma_bd_release()…
--
net: axienet: bound TX completion cleanup by the NAPI budget
The patch adds a budget check to axienet_free_tx_chain() so the TX
completion loop stops on a packet boundary once the NAPI budget is spent,
and treats a budget of zero as "no limit" so the netpoll TX reclaim and the
@force callers still drain the ring. The kernel-doc for @budget is updated
to match.
> diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> index 1722b7038f340..039885814a5d2 100644
> --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> @@ -772,7 +772,9 @@ static int axienet_device_reset(struct net_device *ndev)
> * @force: Whether to clean descriptors even if not complete
> * @sizep: Pointer to a u32 accumulating the total byte count of
> * completed packets (using skb->len). Ignored if NULL.
> - * @budget: NAPI budget (use 0 when not called from NAPI poll)
> + * @budget: NAPI budget. When @force is false, cleanup stops after this
> + * many completed packets. Zero means no limit, as used by the
> + * netpoll TX reclaim and by callers outside NAPI poll.
> *
> * Would either be called after a successful transmit operation, or after
> * there was an error when setting up the chain.
> @@ -788,6 +790,15 @@ static int axienet_free_tx_chain(struct axienet_local *lp, u32 first_bd,
> dma_addr_t phys;
>
> for (i = 0; i < nr_bds; i++) {
> + /* A NAPI poll must not return more than its budget. Stop on a
> + * packet boundary once it is spent - cur_p->skb is only set on
> + * a packet's last descriptor, so no packet is left half-freed.
> + * A zero budget means no limit: netpoll polls with a budget of
> + * 0 to reclaim the TX path only, and must still clean the ring.
> + */
> + if (!force && budget && packets >= budget)
> + break;
> +
[Severity: High]
With the "budget &&" term, is the case the commit message is about still
reachable on the netpoll path?
For a budget of 0 the guard never fires, so axienet_free_tx_chain() walks up
to lp->tx_bd_num descriptors and increments packets for every completed
descriptor with a non-NULL cur_p->skb. axienet_tx_poll() then returns that
count verbatim:
drivers/net/ethernet/xilinx/xilinx_axienet_main.c:axienet_tx_poll() {
packets = axienet_free_tx_chain(lp, lp->tx_bd_ci, lp->tx_bd_num, false,
&size, budget);
...
if (packets < budget && napi_complete_done(napi, packets)) {
...
return packets;
}
netpoll is the caller that enforces the invariant:
net/core/netpoll.c:poll_one_napi() {
/* We explicitly pass the polling call a budget of 0 to
* indicate that we are clearing the Tx path only.
*/
work = napi->poll(napi, 0);
WARN_ONCE(work, "%pS exceeded budget in poll\n", napi->poll);
...
}
lp->napi_tx is on dev->napi_list (netif_napi_add(ndev, &lp->napi_tx,
axienet_tx_poll) in axienet_probe()), so netpoll_poll_dev()->poll_napi()->
poll_one_napi() reaches axienet_tx_poll() with budget 0. If netconsole or
kgdboe is active on the interface and at least one TX descriptor has
XAXIDMA_BD_STS_COMPLETE_MASK set with cur_p->skb != NULL, doesn't that poll
return non-zero work for a budget of 0 and trip the WARN_ONCE?
The "packets < budget" test is also false for budget 0, so
napi_complete_done() is skipped on that pass, which is the secondary symptom
the changelog describes.
Would it work better to keep the unlimited reclaim for budget 0 (it is
already bounded by lp->tx_bd_num) but have axienet_tx_poll() report
"budget ? packets : 0", while still doing the byte/packet accounting?
The new kernel-doc says zero means "no limit, as used by the netpoll TX
reclaim". netpoll asks for two things: drain the TX path, and report zero
work. Could the kernel-doc and the in-loop comment mention both?
One more nit on the changelog:
"Zero therefore means no limit, which also covers the @force callers that
clean up after a DMA mapping failure with a budget of 0."
The guard is already gated on !force, so the @force callers are unaffected by
the value of @budget here.
[Severity: Medium]
This isn't a bug introduced by this patch, but while looking at the TX
reclaim path: does axienet_dma_bd_release() leak the outstanding TX skbs and
their streaming DMA mappings?
The RX side walks the ring and cleans up:
drivers/net/ethernet/xilinx/xilinx_axienet_main.c:axienet_dma_bd_release() {
for (i = 0; i < lp->rx_bd_num; i++) {
...
dev_kfree_skb(lp->rx_bd_v[i].skb);
...
dma_unmap_single(lp->dev, phys, lp->max_frm_size, DMA_FROM_DEVICE);
}
...
/* If we end up here, tx_bd_v must have been DMA allocated. */
dma_free_coherent(lp->dev,
sizeof(*lp->tx_bd_v) * lp->tx_bd_num,
lp->tx_bd_v,
lp->tx_bd_p);
}
The TX ring is freed with no equivalent pass over lp->tx_bd_v, so descriptors
between lp->tx_bd_ci and lp->tx_bd_tail keep their cur_p->skb and their
dma_map_single() mapping.
axienet_stop() disables TX reclamation before the release, so nothing can run
afterwards:
napi_disable(&lp->napi_tx);
...
axienet_dma_stop(lp);
cancel_work_sync(&lp->dma_err_task);
free_irq(lp->tx_irq, ndev);
free_irq(lp->rx_irq, ndev);
axienet_dma_bd_release(ndev);
So an "ip link set down" or a driver unbind with frames in flight would lose
up to lp->tx_bd_num skbs and mappings. The budget cap added here can leave
completed-but-unreclaimed descriptors around for a later poll, which slightly
widens the window, but the leak is independent of this change.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917115657.20697-1-maimon.sagi%40gmail.com
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-21 11:57 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-17 11:56 [PATCH net v2] net: axienet: bound TX completion cleanup by the NAPI budget Sagi Maimon
2026-09-21 11:57 ` netdev-bot+sashiko
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®