From: netdev-bot+sashiko@kernel.org
To: linmag7@gmail.com
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, netdev@vger.kernel.org,
linux-parisc@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-alpha@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH v2] net: tulip: use mod_timer() in t21142_lnk_change()
Date: Thu, 10 Sep 2026 06:54:45 +0000 [thread overview]
Message-ID: <178902328547.219967.7621030268252739442@kernel.org> (raw)
In-Reply-To: <20260905225454.439466-1-linmag7@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:
Critical: 0 · High: 1 (1) · Medium: 0 · Low: 1
- [High] Incomplete fix: the illegal hardirq timer_delete_sync() on the
non-TIMER_IRQSAFE media timer is still reachable from the exact call…
- [Low] Stale synchronization comment left behind by the conversion.
Pre-existing issues:
- [High] t21142_lnk_change() re-arms tp->timer from hardirq
(drivers/net/ethernet/dec/tulip/21142.c:219 and :227) with no…
--
net: tulip: use mod_timer() in t21142_lnk_change()
This replaces the timer_delete_sync() + expires + add_timer() sequences in
t21142_lnk_change() with a single mod_timer() call, since the function runs
in hardirq context and tp->timer is not TIMER_IRQSAFE. The mod_timer() is
placed before t21142_start_nway() so a pending timer cannot fire while the
NWay state is being reprogrammed. The comment in tulip_interrupt() is
updated to point at pnic2_lnk_change() instead.
> diff --git a/drivers/net/ethernet/dec/tulip/21142.c b/drivers/net/ethernet/dec/tulip/21142.c
> index 76767dec216da..950abf4a8c14a 100644
> --- a/drivers/net/ethernet/dec/tulip/21142.c
> +++ b/drivers/net/ethernet/dec/tulip/21142.c
> @@ -216,20 +216,16 @@ void t21142_lnk_change(struct net_device *dev, int csr5)
> (csr12 & 2) == 2) ||
> (tp->nway && (csr5 & (TPLnkFail)))) {
> /* Link blew? Maybe restart NWay. */
> - timer_delete_sync(&tp->timer);
> + mod_timer(&tp->timer, RUN_AT(3 * HZ));
> t21142_start_nway(dev);
> - tp->timer.expires = RUN_AT(3*HZ);
> - add_timer(&tp->timer);
[Severity: High]
This isn't a bug introduced by this patch, but can this hardirq re-arm make
the media timer pending again after teardown already cancelled it?
tulip_down() in tulip_core.c does the sync-cancel while chip interrupts and
the registered IRQ are still live:
cancel_work_sync(&tp->media_work);
napi_disable(&tp->napi);
timer_delete_sync(&tp->timer);
spin_lock_irqsave(&tp->lock, flags);
/* Disable interrupts by clearing the interrupt mask. */
iowrite32(0x00000000, ioaddr + CSR7);
...
timer_setup(&tp->timer, tulip_tbl[tp->chip_id].media_timer, 0);
free_irq() only happens later, in tulip_close(). A TPLnkFail/TPLnkPass
interrupt taken in that window reaches:
tulip_interrupt() -> tp->link_change() -> t21142_lnk_change()
-> mod_timer(&tp->timer, RUN_AT(3 * HZ));
and the hook is deliberately called outside tp->lock, so this can run on
another CPU while tulip_down() holds the lock. tulip_down() then calls
timer_setup() on a timer that is pending again, which re-initialises
timer->entry while the node is still linked into base->vectors[idx]. Can
that corrupt the timer base's hlist, so a later detach_timer() ->
__hlist_del() writes through a NULL pprev from the timer softirq?
If the entry instead stays armed, tulip_remove_one() only calls
unregister_netdev() with no final timer_delete_sync()/timer_shutdown_sync(),
so tulip_timer() would dereference tp->dev after the netdev and the embedded
tulip_private are gone.
The old code had the same shape (sync-delete then add_timer() from hardirq),
so this is not new, but the conversion makes the post-teardown re-arm silent
where add_timer() would at least have tripped WARN_ON_ONCE(timer_pending()).
Would it make sense to mask CSR7 and free the IRQ before the timer sync (or
switch tulip_down() to timer_shutdown_sync()), or to guard the hardirq
re-arm?
There is a symmetric case on the way up: tulip_up() enables CSR7 interrupts
before the unlocked pair
tp->timer.expires = RUN_AT(next_tick);
add_timer(&tp->timer);
so a concurrent hardirq mod_timer() races that plain store, and add_timer()
can then hit its pending warning and leave the wrong deadline.
[Severity: Low]
With both add_timer() calls here converted, does the comment in
t21142_media_task() still describe an existing call site?
drivers/net/ethernet/dec/tulip/21142.c:t21142_media_task() {
...
/* mod_timer synchronizes us with potential add_timer calls
* from interrupts.
*/
mod_timer(&tp->timer, RUN_AT(next_tick));
}
tulip_media_task() in timer.c carries the identical wording. tulip_tbl[]
gives t21142_media_task to DC21142/43 and tulip_media_task to DC21140,
AX88140, I21145, DM910X and RS7112; of those only DC21142/43 and I21145 have
HAS_NWAY, so tp->link_change is t21142_lnk_change, whose two add_timer()
calls this patch removed. The remaining add_timer(&tp->timer) is in
tulip_up(), which is process context, and the interrupt-path re-arms in
interrupt.c already use mod_timer().
The analogous comment in tulip_interrupt() was updated in this patch, so
should these two say mod_timer calls from interrupts as well?
> } else if (dev->if_port == 3 || dev->if_port == 5) {
> if (tulip_debug > 1)
> dev_info(&dev->dev, "21143 %s link beat %s\n",
> medianame[dev->if_port],
> (csr12 & 2) ? "failed" : "good");
> if ((csr12 & 2) && ! tp->medialock) {
> - timer_delete_sync(&tp->timer);
> + mod_timer(&tp->timer, RUN_AT(3 * HZ));
> t21142_start_nway(dev);
> - tp->timer.expires = RUN_AT(3*HZ);
> - add_timer(&tp->timer);
> } else if (dev->if_port == 5)
> iowrite32(csr14 & ~0x080, ioaddr + CSR14);
> } else if (dev->if_port == 0 || dev->if_port == 4) {
> diff --git a/drivers/net/ethernet/dec/tulip/interrupt.c b/drivers/net/ethernet/dec/tulip/interrupt.c
> index 0a12cb9b3ba78..6ed4b68ad86c2 100644
> --- a/drivers/net/ethernet/dec/tulip/interrupt.c
> +++ b/drivers/net/ethernet/dec/tulip/interrupt.c
> @@ -698,9 +698,8 @@ irqreturn_t tulip_interrupt(int irq, void *dev_instance)
> dev->stats.rx_errors++;
> tulip_start_rxtx(tp);
> }
> - /*
> - * NB: t21142_lnk_change() does a timer_delete_sync(), so be careful
> - * if this call is ever done under the spinlock
> + /* NB: pnic2_lnk_change() does a timer_delete_sync(), so be careful
> + * if this call is ever done under the spinlock.
> */
> if (csr5 & (TPLnkPass | TPLnkFail | 0x08000000)) {
> if (tp->link_change)
[Severity: High]
Since the same hazard is still reachable through this exact hook, would it
be better to convert pnic2_lnk_change() in the same patch rather than
document it in the comment?
tulip_init_one() installs both handlers into the one hook:
drivers/net/ethernet/dec/tulip/tulip_core.c:tulip_init_one() {
...
if (tp->chip_id == PNIC2)
tp->link_change = pnic2_lnk_change;
else if (tp->flags & HAS_NWAY)
tp->link_change = t21142_lnk_change;
...
timer_setup(&tp->timer, tulip_tbl[tp->chip_id].media_timer, 0);
}
The timer is set up with flags 0, i.e. not TIMER_IRQSAFE, and
pnic2_lnk_change() still has three copies of the old sequence:
drivers/net/ethernet/dec/tulip/pnic2.c:pnic2_lnk_change() {
...
timer_delete_sync(&tp->timer);
pnic2_start_nway(dev);
tp->timer.expires = RUN_AT(3*HZ);
add_timer(&tp->timer);
...
}
On a PNIC-II board a link-state interrupt therefore produces the same
WARN_ON(in_hardirq() && !(timer->flags & TIMER_IRQSAFE)) splat this patch
fixes for the 21142.
Beyond the warning, __timer_delete_sync() then spins:
kernel/time/timer.c:__timer_delete_sync() {
do {
ret = __try_to_del_timer_sync(timer, shutdown);
if (unlikely(ret < 0)) {
del_timer_wait_running(timer);
cpu_relax();
}
} while (ret < 0);
}
del_timer_wait_running() is a no-op without CONFIG_PREEMPT_RT, so if the
tulip hardirq lands on the CPU whose timer softirq is currently in
pnic2_timer(), can that callback ever complete, or does the CPU spin
indefinitely?
The commit message says:
"pnic2_lnk_change() still calls timer_delete_sync() from the same hardirq
path, but its timer callback re-arms the timer directly with mod_timer(),
so fixing that path requires separate consideration of the
callback/reschedule race."
pnic2_timer() re-arming with mod_timer() looks like the same situation as
t21142_media_task()/tulip_media_task(), whose overwrite race the commit
message already accepts for the converted sites. What makes the PNIC2 case
different?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260905225454.439466-1-linmag7%40gmail.com
prev parent reply other threads:[~2026-09-10 6:54 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-05 22:53 Magnus Lindholm
2026-09-10 6:54 ` netdev-bot+sashiko [this message]
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=178902328547.219967.7621030268252739442@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=linmag7@gmail.com \
--cc=linux-alpha@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-parisc@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
/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 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®