Commit 03cb4d05 authored by Milton Miller's avatar Milton Miller Committed by David S. Miller

net/ncsi: Avoid channel_monitor hrtimer deadlock

Calling ncsi_stop_channel_monitor from channel_monitor is a guaranteed
deadlock on SMP because stop calls del_timer_sync on the timer that
invoked channel_monitor as its timer function.

Recognise the inherent race of marking the monitor disabled before
deleting the timer by just returning if enable was cleared.  After
a timeout (the default case -- reset to START when response received)
just mark the monitor.enabled false.

If the channel has an entry on the channel_queue list, or if the
state is not ACTIVE or INACTIVE, then warn and mark the timer stopped
and don't restart, as the locking is broken somehow.

Fixes: 0795fb20 ("net/ncsi: Stop monitor if channel times out or is inactive")
Signed-off-by: default avatarMilton Miller <miltonm@us.ibm.com>
Signed-off-by: default avatarEddie James <eajames@linux.ibm.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 6e5a03bc
...@@ -105,13 +105,20 @@ static void ncsi_channel_monitor(struct timer_list *t) ...@@ -105,13 +105,20 @@ static void ncsi_channel_monitor(struct timer_list *t)
monitor_state = nc->monitor.state; monitor_state = nc->monitor.state;
spin_unlock_irqrestore(&nc->lock, flags); spin_unlock_irqrestore(&nc->lock, flags);
if (!enabled || chained) { if (!enabled)
ncsi_stop_channel_monitor(nc); return; /* expected race disabling timer */
return; if (WARN_ON_ONCE(chained))
} goto bad_state;
if (state != NCSI_CHANNEL_INACTIVE && if (state != NCSI_CHANNEL_INACTIVE &&
state != NCSI_CHANNEL_ACTIVE) { state != NCSI_CHANNEL_ACTIVE) {
ncsi_stop_channel_monitor(nc); bad_state:
netdev_warn(ndp->ndev.dev,
"Bad NCSI monitor state channel %d 0x%x %s queue\n",
nc->id, state, chained ? "on" : "off");
spin_lock_irqsave(&nc->lock, flags);
nc->monitor.enabled = false;
spin_unlock_irqrestore(&nc->lock, flags);
return; return;
} }
...@@ -136,10 +143,9 @@ static void ncsi_channel_monitor(struct timer_list *t) ...@@ -136,10 +143,9 @@ static void ncsi_channel_monitor(struct timer_list *t)
ncsi_report_link(ndp, true); ncsi_report_link(ndp, true);
ndp->flags |= NCSI_DEV_RESHUFFLE; ndp->flags |= NCSI_DEV_RESHUFFLE;
ncsi_stop_channel_monitor(nc);
ncm = &nc->modes[NCSI_MODE_LINK]; ncm = &nc->modes[NCSI_MODE_LINK];
spin_lock_irqsave(&nc->lock, flags); spin_lock_irqsave(&nc->lock, flags);
nc->monitor.enabled = false;
nc->state = NCSI_CHANNEL_INVISIBLE; nc->state = NCSI_CHANNEL_INVISIBLE;
ncm->data[2] &= ~0x1; ncm->data[2] &= ~0x1;
spin_unlock_irqrestore(&nc->lock, flags); spin_unlock_irqrestore(&nc->lock, flags);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment