]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
net/ncsi: Avoid channel_monitor hrtimer deadlock
authorMilton Miller <miltonm@us.ibm.com>
Mon, 29 Mar 2021 15:20:39 +0000 (10:20 -0500)
committerDavid S. Miller <davem@davemloft.net>
Tue, 30 Mar 2021 20:16:23 +0000 (13:16 -0700)
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: 0795fb2021f0 ("net/ncsi: Stop monitor if channel times out or is inactive")
Signed-off-by: Milton Miller <miltonm@us.ibm.com>
Signed-off-by: Eddie James <eajames@linux.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ncsi/ncsi-manage.c

index a9cb355324d1ab5a4d33870c13f2a178ec793a81..ffff8da707b8c1c9d8f771072ce39f906cb67a69 100644 (file)
@@ -105,13 +105,20 @@ static void ncsi_channel_monitor(struct timer_list *t)
        monitor_state = nc->monitor.state;
        spin_unlock_irqrestore(&nc->lock, flags);
 
-       if (!enabled || chained) {
-               ncsi_stop_channel_monitor(nc);
-               return;
-       }
+       if (!enabled)
+               return;         /* expected race disabling timer */
+       if (WARN_ON_ONCE(chained))
+               goto bad_state;
+
        if (state != NCSI_CHANNEL_INACTIVE &&
            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;
        }
 
@@ -136,10 +143,9 @@ static void ncsi_channel_monitor(struct timer_list *t)
                ncsi_report_link(ndp, true);
                ndp->flags |= NCSI_DEV_RESHUFFLE;
 
-               ncsi_stop_channel_monitor(nc);
-
                ncm = &nc->modes[NCSI_MODE_LINK];
                spin_lock_irqsave(&nc->lock, flags);
+               nc->monitor.enabled = false;
                nc->state = NCSI_CHANNEL_INVISIBLE;
                ncm->data[2] &= ~0x1;
                spin_unlock_irqrestore(&nc->lock, flags);