mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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

      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®