net/ncsi: Reset channel state in ncsi_start_dev()
authorSamuel Mendoza-Jonas <sam@mendozajonas.com>
Fri, 16 Nov 2018 04:51:58 +0000 (15:51 +1100)
committerDavid S. Miller <davem@davemloft.net>
Sun, 18 Nov 2018 05:09:49 +0000 (21:09 -0800)
When the NCSI driver is stopped with ncsi_stop_dev() the channel
monitors are stopped and the state set to "inactive". However the
channels are still configured and active from the perspective of the
network controller. We should suspend each active channel but in the
context of ncsi_stop_dev() the transmit queue has been or is about to be
stopped so we won't have time to do so.

Instead when ncsi_start_dev() is called if the NCSI topology has already
been probed then call ncsi_reset_dev() to suspend any channels that were
previously active. This resets the network controller to a known state,
provides an up to date view of channel link state, and makes sure that
mode flags such as NCSI_MODE_TX_ENABLE are properly reset.

In addition to ncsi_start_dev() use ncsi_reset_dev() in ncsi-netlink.c
to update the channel configuration more cleanly.

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ncsi/internal.h
net/ncsi/ncsi-manage.c
net/ncsi/ncsi-netlink.c

index ec65778c41f3e4a3af39c7ef7febcc8a5e2077d2..bda51cb179fe58fb81c7d9a6ddded2a84f4ce08b 100644 (file)
@@ -287,6 +287,7 @@ struct ncsi_dev_priv {
 #define NCSI_DEV_PROBED                1            /* Finalized NCSI topology    */
 #define NCSI_DEV_HWA           2            /* Enabled HW arbitration     */
 #define NCSI_DEV_RESHUFFLE     4
+#define NCSI_DEV_RESET         8            /* Reset state of NC          */
        unsigned int        gma_flag;        /* OEM GMA flag               */
        spinlock_t          lock;            /* Protect the NCSI device    */
 #if IS_ENABLED(CONFIG_IPV6)
@@ -342,6 +343,7 @@ extern spinlock_t ncsi_dev_lock;
        list_for_each_entry_rcu(nc, &np->channels, node)
 
 /* Resources */
+int ncsi_reset_dev(struct ncsi_dev *nd);
 void ncsi_start_channel_monitor(struct ncsi_channel *nc);
 void ncsi_stop_channel_monitor(struct ncsi_channel *nc);
 struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np,
index b9de5b78c4e981d9cd6e0f8de44ab01556f21ca4..c814ce9bb3de47f513777f4be0388a380a34ca77 100644 (file)
@@ -550,8 +550,10 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp)
                spin_lock_irqsave(&nc->lock, flags);
                nc->state = NCSI_CHANNEL_INACTIVE;
                spin_unlock_irqrestore(&nc->lock, flags);
-               ncsi_process_next_channel(ndp);
-
+               if (ndp->flags & NCSI_DEV_RESET)
+                       ncsi_reset_dev(nd);
+               else
+                       ncsi_process_next_channel(ndp);
                break;
        default:
                netdev_warn(nd->dev, "Wrong NCSI state 0x%x in suspend\n",
@@ -898,6 +900,16 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
                netdev_dbg(ndp->ndev.dev, "NCSI: channel %u config done\n",
                           nc->id);
                spin_lock_irqsave(&nc->lock, flags);
+               nc->state = NCSI_CHANNEL_ACTIVE;
+
+               if (ndp->flags & NCSI_DEV_RESET) {
+                       /* A reset event happened during config, start it now */
+                       nc->reconfigure_needed = false;
+                       spin_unlock_irqrestore(&nc->lock, flags);
+                       ncsi_reset_dev(nd);
+                       break;
+               }
+
                if (nc->reconfigure_needed) {
                        /* This channel's configuration has been updated
                         * part-way during the config state - start the
@@ -916,7 +928,6 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
                        break;
                }
 
-               nc->state = NCSI_CHANNEL_ACTIVE;
                if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) {
                        hot_nc = nc;
                } else {
@@ -1554,7 +1565,7 @@ int ncsi_start_dev(struct ncsi_dev *nd)
                return 0;
        }
 
-       return ncsi_choose_active_channel(ndp);
+       return ncsi_reset_dev(nd);
 }
 EXPORT_SYMBOL_GPL(ncsi_start_dev);
 
@@ -1567,7 +1578,10 @@ void ncsi_stop_dev(struct ncsi_dev *nd)
        int old_state;
        unsigned long flags;
 
-       /* Stop the channel monitor and reset channel's state */
+       /* Stop the channel monitor on any active channels. Don't reset the
+        * channel state so we know which were active when ncsi_start_dev()
+        * is next called.
+        */
        NCSI_FOR_EACH_PACKAGE(ndp, np) {
                NCSI_FOR_EACH_CHANNEL(np, nc) {
                        ncsi_stop_channel_monitor(nc);
@@ -1575,7 +1589,6 @@ void ncsi_stop_dev(struct ncsi_dev *nd)
                        spin_lock_irqsave(&nc->lock, flags);
                        chained = !list_empty(&nc->link);
                        old_state = nc->state;
-                       nc->state = NCSI_CHANNEL_INACTIVE;
                        spin_unlock_irqrestore(&nc->lock, flags);
 
                        WARN_ON_ONCE(chained ||
@@ -1588,6 +1601,92 @@ void ncsi_stop_dev(struct ncsi_dev *nd)
 }
 EXPORT_SYMBOL_GPL(ncsi_stop_dev);
 
+int ncsi_reset_dev(struct ncsi_dev *nd)
+{
+       struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+       struct ncsi_channel *nc, *active, *tmp;
+       struct ncsi_package *np;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ndp->lock, flags);
+
+       if (!(ndp->flags & NCSI_DEV_RESET)) {
+               /* Haven't been called yet, check states */
+               switch (nd->state & ncsi_dev_state_major) {
+               case ncsi_dev_state_registered:
+               case ncsi_dev_state_probe:
+                       /* Not even probed yet - do nothing */
+                       spin_unlock_irqrestore(&ndp->lock, flags);
+                       return 0;
+               case ncsi_dev_state_suspend:
+               case ncsi_dev_state_config:
+                       /* Wait for the channel to finish its suspend/config
+                        * operation; once it finishes it will check for
+                        * NCSI_DEV_RESET and reset the state.
+                        */
+                       ndp->flags |= NCSI_DEV_RESET;
+                       spin_unlock_irqrestore(&ndp->lock, flags);
+                       return 0;
+               }
+       } else {
+               switch (nd->state) {
+               case ncsi_dev_state_suspend_done:
+               case ncsi_dev_state_config_done:
+               case ncsi_dev_state_functional:
+                       /* Ok */
+                       break;
+               default:
+                       /* Current reset operation happening */
+                       spin_unlock_irqrestore(&ndp->lock, flags);
+                       return 0;
+               }
+       }
+
+       if (!list_empty(&ndp->channel_queue)) {
+               /* Clear any channel queue we may have interrupted */
+               list_for_each_entry_safe(nc, tmp, &ndp->channel_queue, link)
+                       list_del_init(&nc->link);
+       }
+       spin_unlock_irqrestore(&ndp->lock, flags);
+
+       active = NULL;
+       NCSI_FOR_EACH_PACKAGE(ndp, np) {
+               NCSI_FOR_EACH_CHANNEL(np, nc) {
+                       spin_lock_irqsave(&nc->lock, flags);
+
+                       if (nc->state == NCSI_CHANNEL_ACTIVE) {
+                               active = nc;
+                               nc->state = NCSI_CHANNEL_INVISIBLE;
+                               spin_unlock_irqrestore(&nc->lock, flags);
+                               ncsi_stop_channel_monitor(nc);
+                               break;
+                       }
+
+                       spin_unlock_irqrestore(&nc->lock, flags);
+               }
+               if (active)
+                       break;
+       }
+
+       if (!active) {
+               /* Done */
+               spin_lock_irqsave(&ndp->lock, flags);
+               ndp->flags &= ~NCSI_DEV_RESET;
+               spin_unlock_irqrestore(&ndp->lock, flags);
+               return ncsi_choose_active_channel(ndp);
+       }
+
+       spin_lock_irqsave(&ndp->lock, flags);
+       ndp->flags |= NCSI_DEV_RESET;
+       ndp->active_channel = active;
+       ndp->active_package = active->package;
+       spin_unlock_irqrestore(&ndp->lock, flags);
+
+       nd->state = ncsi_dev_state_suspend;
+       schedule_work(&ndp->work);
+       return 0;
+}
+
 void ncsi_unregister_dev(struct ncsi_dev *nd)
 {
        struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
index 33314381b4f5875757b73c80bc86765d6d84e184..cde48fe43dbafbaffbb52d9ee96c1b71fdb94994 100644 (file)
@@ -330,9 +330,9 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info)
                    package_id, channel_id,
                    channel_id == NCSI_RESERVED_CHANNEL ? " (any)" : "");
 
-       /* Bounce the NCSI channel to set changes */
-       ncsi_stop_dev(&ndp->ndev);
-       ncsi_start_dev(&ndp->ndev);
+       /* Update channel configuration */
+       if (!(ndp->flags & NCSI_DEV_RESET))
+               ncsi_reset_dev(&ndp->ndev);
 
        return 0;
 }
@@ -360,9 +360,9 @@ static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info)
        spin_unlock_irqrestore(&ndp->lock, flags);
        netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n");
 
-       /* Bounce the NCSI channel to set changes */
-       ncsi_stop_dev(&ndp->ndev);
-       ncsi_start_dev(&ndp->ndev);
+       /* Update channel configuration */
+       if (!(ndp->flags & NCSI_DEV_RESET))
+               ncsi_reset_dev(&ndp->ndev);
 
        return 0;
 }