From: netdev-bot+sashiko@kernel.org
To: maimon.sagi@gmail.com
Cc: radhey.shyam.pandey@amd.com, netdev@vger.kernel.org,
andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, michal.simek@amd.com,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next] net: axienet: add a TX timeout handler to recover a lost DMA interrupt
Date: Thu, 17 Sep 2026 06:58:30 +0000 [thread overview]
Message-ID: <178962831034.22033.349240265456808212@kernel.org> (raw)
In-Reply-To: <20260915065534.102249-1-maimon.sagi@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 1 (1) · Medium: 0 · Low: 0
- [High] The new .ndo_tx_timeout handler (axienet_tx_timeout(),
drivers/net/ethernet/xilinx/xilinx_axienet_main.c:1907-1913) only…
Pre-existing issues:
- [High] axienet_dma_err_handler()
(drivers/net/ethernet/xilinx/xilinx_axienet_main.c:2727-2810) tears…
--
net: axienet: add a TX timeout handler to recover a lost DMA interrupt
This adds an .ndo_tx_timeout handler to axienet_netdev_ops that queues
lp->dma_err_task, so the netdev watchdog can reach the existing DMA reset
path without depending on a DMA interrupt. watchdog_timeo is set to 5 * HZ
in the legacy DMA branch of axienet_probe().
> diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> index 782f903d318f3..b5927e979c341 100644
> --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
[ ... ]
> @@ -1884,6 +1888,30 @@ axienet_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
> } while (read_seqcount_retry(&lp->hw_stats_seqcount, start));
> }
>
> +/**
> + * axienet_tx_timeout - Driver TX timeout callback
> + * @ndev: Pointer to net_device structure
> + * @txqueue: Index of the transmit queue that stalled
> + *
> + * Called by the netdev watchdog when a transmit queue has made no progress for
> + * @ndev->watchdog_timeo. axienet_dma_err_handler() is the driver's only reset
> + * path, and it is otherwise scheduled solely from axienet_tx_irq() and
> + * axienet_rx_irq() - so a completion interrupt that is never delivered leaves
> + * the queue stopped with descriptors unreclaimed and no way back short of
> + * unloading the driver. Schedule the reset from here as well, so a lost
> + * interrupt is recoverable.
> + *
> + * This runs from a timer, so it only queues the work; the reset itself happens
> + * in process context in axienet_dma_err_handler().
> + */
> +static void axienet_tx_timeout(struct net_device *ndev, unsigned int txqueue)
> +{
> + struct axienet_local *lp = netdev_priv(ndev);
> +
> + netdev_err(ndev, "TX queue %u stalled, resetting DMA\n", txqueue);
> + schedule_work(&lp->dma_err_task);
> +}
[Severity: High]
Does the queue actually get restarted after this reset?
dev_watchdog() only calls ndo_tx_timeout for a queue that is already
stopped:
net/sched/sch_generic.c:dev_watchdog() {
...
if (!netif_xmit_stopped(txq))
continue;
...
dev->netdev_ops->ndo_tx_timeout(dev, i);
}
In the scenario the changelog describes (lost TX completion, descriptors
unreclaimed) the stop bit is __QUEUE_STATE_DRV_XOFF, set here:
drivers/net/ethernet/xilinx/xilinx_axienet_main.c:axienet_start_xmit() {
...
if (axienet_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) {
netif_stop_queue(ndev);
...
}
axienet_dma_err_handler() then resets DMA, zeroes every TX BD, sets
lp->tx_bd_ci = lp->tx_bd_tail = 0, re-enables NAPI and returns. The only
queue-state clearing it does is netdev_reset_queue(), which touches
STACK_XOFF only, and nothing at all without CONFIG_BQL:
include/linux/netdevice.h:netdev_tx_reset_queue() {
#ifdef CONFIG_BQL
clear_bit(__QUEUE_STATE_STACK_XOFF, &q->state);
dql_reset(&q->dql);
#endif
}
There is no netif_wake_queue()/netif_tx_wake_all_queues() and no
netif_trans_update() anywhere in axienet_dma_err_handler(). The remaining
wake site in axienet_tx_poll() is gated on packets:
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) {
...
if (!axienet_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1))
netif_wake_queue(ndev);
}
}
Can axienet_free_tx_chain() ever return non-zero after the handler cleared
cur_p->status for every descriptor? And the wake in axienet_start_xmit()
cannot run while the queue is stopped.
If DRV_XOFF survives the reset, and txq->trans_start is never refreshed,
doesn't dev_watchdog()'s time_after(jiffies, trans_start + watchdog_timeo)
stay true, so the timeout path re-fires every 5 seconds, re-running
napi_disable() / axienet_dma_stop() / __axienet_device_reset() /
axienet_dma_start() and printing netdev_crit + netdev_err indefinitely
while the interface still cannot transmit?
The driver's other reset path does refresh the timestamp:
drivers/net/ethernet/xilinx/xilinx_axienet_main.c:axienet_device_reset() {
...
netif_trans_update(ndev);
}
Would adding netif_trans_update() plus a wake of the TX queue at the tail
of axienet_dma_err_handler() be needed for the recovery claimed in the
changelog ("the watchdog fires and the interface resumes passing traffic")
to hold for the DRV_XOFF case rather than only for a BQL-only stop?
[Severity: High]
This is a pre-existing issue and not introduced by this patch, but the new
trigger makes it much easier to reach: axienet_dma_err_handler() tears down
the TX ring with no exclusion against axienet_start_xmit().
The worker calls napi_disable() (which only excludes axienet_tx_poll()),
axienet_dma_stop(), then re-enables transmission for a BQL-only stop before
it starts freeing descriptors:
drivers/net/ethernet/xilinx/xilinx_axienet_main.c:axienet_dma_err_handler() {
axienet_dma_stop(lp);
netdev_reset_queue(ndev); /* clears __QUEUE_STATE_STACK_XOFF */
for (i = 0; i < lp->tx_bd_num; i++) {
cur_p = &lp->tx_bd_v[i];
if (cur_p->cntrl) {
dma_addr_t addr = desc_get_phys_addr(lp, cur_p);
dma_unmap_single(lp->dev, addr, ..., DMA_TO_DEVICE);
}
if (cur_p->skb)
dev_kfree_skb_irq(cur_p->skb);
...
lp->tx_bd_ci = 0;
lp->tx_bd_tail = 0;
axienet_dma_start(lp);
}
It never takes the per-queue _xmit_lock and never calls
netif_tx_disable()/netif_device_detach(), and the core unfreezes the queues
as soon as ndo_tx_timeout() returns, long before the work runs:
net/sched/sch_generic.c:dev_watchdog() {
netif_freeze_queues(dev);
dev->netdev_ops->ndo_tx_timeout(dev, i);
netif_unfreeze_queues(dev);
}
So while the teardown loop is running, can a transmit on another CPU enter
drivers/net/ethernet/xilinx/xilinx_axienet_main.c:axienet_start_xmit() {
...
WRITE_ONCE(lp->tx_bd_tail, new_tail_ptr);
netdev_sent_queue(ndev, skb->len);
/* Start the transfer */
axienet_dma_out_addr(lp, XAXIDMA_TX_TDESC_OFFSET, tail_p);
}
and write cur_p->cntrl/phys/skb into a descriptor the worker is about to
unmap and clear? The outcomes look like an skb freed by the worker and
then re-referenced, a DMA mapping released while a fresh buffer is being
programmed into the same descriptor, and lp->tx_bd_tail/tx_bd_ci rewound to
0 out of step with the hardware tail pointer written by axienet_start_xmit().
Since this new .ndo_tx_timeout makes the reset a routine event (and, per the
comment above, potentially every 5 seconds), should the reset path be made
to exclude the xmit path, for example with netif_tx_disable() around the
descriptor teardown?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915065534.102249-1-maimon.sagi%40gmail.com
next prev parent reply other threads:[~2026-09-17 6:58 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-15 6:55 [PATCH net-next] net: axienet: add a TX timeout handler to recover a lost DMA interrupt Sagi Maimon
2026-09-15 16:15 ` Andrew Lunn
2026-09-16 3:56 ` Sagi Maimon
2026-09-17 6:58 ` netdev-bot+sashiko [this message]
2026-09-17 12:20 ` Sagi Maimon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178962831034.22033.349240265456808212@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maimon.sagi@gmail.com \
--cc=michal.simek@amd.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=radhey.shyam.pandey@amd.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.