net/ncsi: Introduce ncsi_stop_dev()
[cascardo/linux.git] / net / ncsi / ncsi-manage.c
index ef017b8..5e509e5 100644 (file)
@@ -132,6 +132,7 @@ static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down)
        struct ncsi_dev *nd = &ndp->ndev;
        struct ncsi_package *np;
        struct ncsi_channel *nc;
+       unsigned long flags;
 
        nd->state = ncsi_dev_state_functional;
        if (force_down) {
@@ -142,14 +143,21 @@ static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down)
        nd->link_up = 0;
        NCSI_FOR_EACH_PACKAGE(ndp, np) {
                NCSI_FOR_EACH_CHANNEL(np, nc) {
+                       spin_lock_irqsave(&nc->lock, flags);
+
                        if (!list_empty(&nc->link) ||
-                           nc->state != NCSI_CHANNEL_ACTIVE)
+                           nc->state != NCSI_CHANNEL_ACTIVE) {
+                               spin_unlock_irqrestore(&nc->lock, flags);
                                continue;
+                       }
 
                        if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) {
+                               spin_unlock_irqrestore(&nc->lock, flags);
                                nd->link_up = 1;
                                goto report;
                        }
+
+                       spin_unlock_irqrestore(&nc->lock, flags);
                }
        }
 
@@ -163,43 +171,55 @@ static void ncsi_channel_monitor(unsigned long data)
        struct ncsi_package *np = nc->package;
        struct ncsi_dev_priv *ndp = np->ndp;
        struct ncsi_cmd_arg nca;
-       bool enabled;
-       unsigned int timeout;
+       bool enabled, chained;
+       unsigned int monitor_state;
        unsigned long flags;
-       int ret;
+       int state, ret;
 
        spin_lock_irqsave(&nc->lock, flags);
-       timeout = nc->timeout;
-       enabled = nc->enabled;
+       state = nc->state;
+       chained = !list_empty(&nc->link);
+       enabled = nc->monitor.enabled;
+       monitor_state = nc->monitor.state;
        spin_unlock_irqrestore(&nc->lock, flags);
 
-       if (!enabled || !list_empty(&nc->link))
+       if (!enabled || chained)
                return;
-       if (nc->state != NCSI_CHANNEL_INACTIVE &&
-           nc->state != NCSI_CHANNEL_ACTIVE)
+       if (state != NCSI_CHANNEL_INACTIVE &&
+           state != NCSI_CHANNEL_ACTIVE)
                return;
 
-       if (!(timeout % 2)) {
+       switch (monitor_state) {
+       case NCSI_CHANNEL_MONITOR_START:
+       case NCSI_CHANNEL_MONITOR_RETRY:
                nca.ndp = ndp;
                nca.package = np->id;
                nca.channel = nc->id;
                nca.type = NCSI_PKT_CMD_GLS;
-               nca.driven = false;
+               nca.req_flags = 0;
                ret = ncsi_xmit_cmd(&nca);
                if (ret) {
                        netdev_err(ndp->ndev.dev, "Error %d sending GLS\n",
                                   ret);
                        return;
                }
-       }
 
-       if (timeout + 1 >= 3) {
+               break;
+       case NCSI_CHANNEL_MONITOR_WAIT ... NCSI_CHANNEL_MONITOR_WAIT_MAX:
+               break;
+       default:
                if (!(ndp->flags & NCSI_DEV_HWA) &&
-                   nc->state == NCSI_CHANNEL_ACTIVE)
+                   state == NCSI_CHANNEL_ACTIVE) {
                        ncsi_report_link(ndp, true);
+                       ndp->flags |= NCSI_DEV_RESHUFFLE;
+               }
+
+               spin_lock_irqsave(&nc->lock, flags);
+               nc->state = NCSI_CHANNEL_INVISIBLE;
+               spin_unlock_irqrestore(&nc->lock, flags);
 
                spin_lock_irqsave(&ndp->lock, flags);
-               xchg(&nc->state, NCSI_CHANNEL_INACTIVE);
+               nc->state = NCSI_CHANNEL_INACTIVE;
                list_add_tail_rcu(&nc->link, &ndp->channel_queue);
                spin_unlock_irqrestore(&ndp->lock, flags);
                ncsi_process_next_channel(ndp);
@@ -207,10 +227,9 @@ static void ncsi_channel_monitor(unsigned long data)
        }
 
        spin_lock_irqsave(&nc->lock, flags);
-       nc->timeout = timeout + 1;
-       nc->enabled = true;
+       nc->monitor.state++;
        spin_unlock_irqrestore(&nc->lock, flags);
-       mod_timer(&nc->timer, jiffies + HZ * (1 << (nc->timeout / 2)));
+       mod_timer(&nc->monitor.timer, jiffies + HZ);
 }
 
 void ncsi_start_channel_monitor(struct ncsi_channel *nc)
@@ -218,12 +237,12 @@ void ncsi_start_channel_monitor(struct ncsi_channel *nc)
        unsigned long flags;
 
        spin_lock_irqsave(&nc->lock, flags);
-       WARN_ON_ONCE(nc->enabled);
-       nc->timeout = 0;
-       nc->enabled = true;
+       WARN_ON_ONCE(nc->monitor.enabled);
+       nc->monitor.enabled = true;
+       nc->monitor.state = NCSI_CHANNEL_MONITOR_START;
        spin_unlock_irqrestore(&nc->lock, flags);
 
-       mod_timer(&nc->timer, jiffies + HZ * (1 << (nc->timeout / 2)));
+       mod_timer(&nc->monitor.timer, jiffies + HZ);
 }
 
 void ncsi_stop_channel_monitor(struct ncsi_channel *nc)
@@ -231,14 +250,14 @@ void ncsi_stop_channel_monitor(struct ncsi_channel *nc)
        unsigned long flags;
 
        spin_lock_irqsave(&nc->lock, flags);
-       if (!nc->enabled) {
+       if (!nc->monitor.enabled) {
                spin_unlock_irqrestore(&nc->lock, flags);
                return;
        }
-       nc->enabled = false;
+       nc->monitor.enabled = false;
        spin_unlock_irqrestore(&nc->lock, flags);
 
-       del_timer_sync(&nc->timer);
+       del_timer_sync(&nc->monitor.timer);
 }
 
 struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np,
@@ -267,8 +286,9 @@ struct ncsi_channel *ncsi_add_channel(struct ncsi_package *np, unsigned char id)
        nc->id = id;
        nc->package = np;
        nc->state = NCSI_CHANNEL_INACTIVE;
-       nc->enabled = false;
-       setup_timer(&nc->timer, ncsi_channel_monitor, (unsigned long)nc);
+       nc->monitor.enabled = false;
+       setup_timer(&nc->monitor.timer,
+                   ncsi_channel_monitor, (unsigned long)nc);
        spin_lock_init(&nc->lock);
        INIT_LIST_HEAD(&nc->link);
        for (index = 0; index < NCSI_CAP_MAX; index++)
@@ -405,7 +425,8 @@ void ncsi_find_package_and_channel(struct ncsi_dev_priv *ndp,
  * be same. Otherwise, the bogus response might be replied. So
  * the available IDs are allocated in round-robin fashion.
  */
-struct ncsi_request *ncsi_alloc_request(struct ncsi_dev_priv *ndp, bool driven)
+struct ncsi_request *ncsi_alloc_request(struct ncsi_dev_priv *ndp,
+                                       unsigned int req_flags)
 {
        struct ncsi_request *nr = NULL;
        int i, limit = ARRAY_SIZE(ndp->requests);
@@ -413,30 +434,31 @@ struct ncsi_request *ncsi_alloc_request(struct ncsi_dev_priv *ndp, bool driven)
 
        /* Check if there is one available request until the ceiling */
        spin_lock_irqsave(&ndp->lock, flags);
-       for (i = ndp->request_id; !nr && i < limit; i++) {
+       for (i = ndp->request_id; i < limit; i++) {
                if (ndp->requests[i].used)
                        continue;
 
                nr = &ndp->requests[i];
                nr->used = true;
-               nr->driven = driven;
-               if (++ndp->request_id >= limit)
-                       ndp->request_id = 0;
+               nr->flags = req_flags;
+               ndp->request_id = i + 1;
+               goto found;
        }
 
        /* Fail back to check from the starting cursor */
-       for (i = 0; !nr && i < ndp->request_id; i++) {
+       for (i = NCSI_REQ_START_IDX; i < ndp->request_id; i++) {
                if (ndp->requests[i].used)
                        continue;
 
                nr = &ndp->requests[i];
                nr->used = true;
-               nr->driven = driven;
-               if (++ndp->request_id >= limit)
-                       ndp->request_id = 0;
+               nr->flags = req_flags;
+               ndp->request_id = i + 1;
+               goto found;
        }
-       spin_unlock_irqrestore(&ndp->lock, flags);
 
+found:
+       spin_unlock_irqrestore(&ndp->lock, flags);
        return nr;
 }
 
@@ -458,7 +480,7 @@ void ncsi_free_request(struct ncsi_request *nr)
        nr->cmd = NULL;
        nr->rsp = NULL;
        nr->used = false;
-       driven = nr->driven;
+       driven = !!(nr->flags & NCSI_REQ_FLAG_EVENT_DRIVEN);
        spin_unlock_irqrestore(&ndp->lock, flags);
 
        if (driven && cmd && --ndp->pending_req_num == 0)
@@ -508,10 +530,11 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp)
        struct ncsi_package *np = ndp->active_package;
        struct ncsi_channel *nc = ndp->active_channel;
        struct ncsi_cmd_arg nca;
+       unsigned long flags;
        int ret;
 
        nca.ndp = ndp;
-       nca.driven = true;
+       nca.req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN;
        switch (nd->state) {
        case ncsi_dev_state_suspend:
                nd->state = ncsi_dev_state_suspend_select;
@@ -527,7 +550,7 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp)
                nca.package = np->id;
                if (nd->state == ncsi_dev_state_suspend_select) {
                        nca.type = NCSI_PKT_CMD_SP;
-                       nca.channel = 0x1f;
+                       nca.channel = NCSI_RESERVED_CHANNEL;
                        if (ndp->flags & NCSI_DEV_HWA)
                                nca.bytes[0] = 0;
                        else
@@ -544,7 +567,7 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp)
                        nd->state = ncsi_dev_state_suspend_deselect;
                } else if (nd->state == ncsi_dev_state_suspend_deselect) {
                        nca.type = NCSI_PKT_CMD_DP;
-                       nca.channel = 0x1f;
+                       nca.channel = NCSI_RESERVED_CHANNEL;
                        nd->state = ncsi_dev_state_suspend_done;
                }
 
@@ -556,7 +579,9 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp)
 
                break;
        case ncsi_dev_state_suspend_done:
-               xchg(&nc->state, NCSI_CHANNEL_INACTIVE);
+               spin_lock_irqsave(&nc->lock, flags);
+               nc->state = NCSI_CHANNEL_INACTIVE;
+               spin_unlock_irqrestore(&nc->lock, flags);
                ncsi_process_next_channel(ndp);
 
                break;
@@ -574,10 +599,11 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
        struct ncsi_channel *nc = ndp->active_channel;
        struct ncsi_cmd_arg nca;
        unsigned char index;
+       unsigned long flags;
        int ret;
 
        nca.ndp = ndp;
-       nca.driven = true;
+       nca.req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN;
        switch (nd->state) {
        case ncsi_dev_state_config:
        case ncsi_dev_state_config_sp:
@@ -590,7 +616,7 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
                else
                        nca.bytes[0] = 1;
                nca.package = np->id;
-               nca.channel = 0x1f;
+               nca.channel = NCSI_RESERVED_CHANNEL;
                ret = ncsi_xmit_cmd(&nca);
                if (ret)
                        goto error;
@@ -675,10 +701,12 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp)
                        goto error;
                break;
        case ncsi_dev_state_config_done:
+               spin_lock_irqsave(&nc->lock, flags);
                if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1)
-                       xchg(&nc->state, NCSI_CHANNEL_ACTIVE);
+                       nc->state = NCSI_CHANNEL_ACTIVE;
                else
-                       xchg(&nc->state, NCSI_CHANNEL_INACTIVE);
+                       nc->state = NCSI_CHANNEL_INACTIVE;
+               spin_unlock_irqrestore(&nc->lock, flags);
 
                ncsi_start_channel_monitor(nc);
                ncsi_process_next_channel(ndp);
@@ -707,18 +735,25 @@ static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp)
        found = NULL;
        NCSI_FOR_EACH_PACKAGE(ndp, np) {
                NCSI_FOR_EACH_CHANNEL(np, nc) {
+                       spin_lock_irqsave(&nc->lock, flags);
+
                        if (!list_empty(&nc->link) ||
-                           nc->state != NCSI_CHANNEL_INACTIVE)
+                           nc->state != NCSI_CHANNEL_INACTIVE) {
+                               spin_unlock_irqrestore(&nc->lock, flags);
                                continue;
+                       }
 
                        if (!found)
                                found = nc;
 
                        ncm = &nc->modes[NCSI_MODE_LINK];
                        if (ncm->data[2] & 0x1) {
+                               spin_unlock_irqrestore(&nc->lock, flags);
                                found = nc;
                                goto out;
                        }
+
+                       spin_unlock_irqrestore(&nc->lock, flags);
                }
        }
 
@@ -797,7 +832,7 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp)
        int ret;
 
        nca.ndp = ndp;
-       nca.driven = true;
+       nca.req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN;
        switch (nd->state) {
        case ncsi_dev_state_probe:
                nd->state = ncsi_dev_state_probe_deselect;
@@ -807,7 +842,7 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp)
 
                /* Deselect all possible packages */
                nca.type = NCSI_PKT_CMD_DP;
-               nca.channel = 0x1f;
+               nca.channel = NCSI_RESERVED_CHANNEL;
                for (index = 0; index < 8; index++) {
                        nca.package = index;
                        ret = ncsi_xmit_cmd(&nca);
@@ -823,7 +858,7 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp)
                /* Select all possible packages */
                nca.type = NCSI_PKT_CMD_SP;
                nca.bytes[0] = 1;
-               nca.channel = 0x1f;
+               nca.channel = NCSI_RESERVED_CHANNEL;
                for (index = 0; index < 8; index++) {
                        nca.package = index;
                        ret = ncsi_xmit_cmd(&nca);
@@ -876,7 +911,7 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp)
                nca.type = NCSI_PKT_CMD_SP;
                nca.bytes[0] = 1;
                nca.package = ndp->active_package->id;
-               nca.channel = 0x1f;
+               nca.channel = NCSI_RESERVED_CHANNEL;
                ret = ncsi_xmit_cmd(&nca);
                if (ret)
                        goto error;
@@ -884,12 +919,12 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp)
                nd->state = ncsi_dev_state_probe_cis;
                break;
        case ncsi_dev_state_probe_cis:
-               ndp->pending_req_num = 32;
+               ndp->pending_req_num = NCSI_RESERVED_CHANNEL;
 
                /* Clear initial state */
                nca.type = NCSI_PKT_CMD_CIS;
                nca.package = ndp->active_package->id;
-               for (index = 0; index < 0x20; index++) {
+               for (index = 0; index < NCSI_RESERVED_CHANNEL; index++) {
                        nca.channel = index;
                        ret = ncsi_xmit_cmd(&nca);
                        if (ret)
@@ -933,7 +968,7 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp)
                /* Deselect the active package */
                nca.type = NCSI_PKT_CMD_DP;
                nca.package = ndp->active_package->id;
-               nca.channel = 0x1f;
+               nca.channel = NCSI_RESERVED_CHANNEL;
                ret = ncsi_xmit_cmd(&nca);
                if (ret)
                        goto error;
@@ -987,11 +1022,14 @@ int ncsi_process_next_channel(struct ncsi_dev_priv *ndp)
                goto out;
        }
 
-       old_state = xchg(&nc->state, NCSI_CHANNEL_INVISIBLE);
        list_del_init(&nc->link);
-
        spin_unlock_irqrestore(&ndp->lock, flags);
 
+       spin_lock_irqsave(&nc->lock, flags);
+       old_state = nc->state;
+       nc->state = NCSI_CHANNEL_INVISIBLE;
+       spin_unlock_irqrestore(&nc->lock, flags);
+
        ndp->active_channel = nc;
        ndp->active_package = nc->package;
 
@@ -1006,7 +1044,7 @@ int ncsi_process_next_channel(struct ncsi_dev_priv *ndp)
                break;
        default:
                netdev_err(ndp->ndev.dev, "Invalid state 0x%x on %d:%d\n",
-                          nc->state, nc->package->id, nc->id);
+                          old_state, nc->package->id, nc->id);
                ncsi_report_link(ndp, false);
                return -EINVAL;
        }
@@ -1070,7 +1108,7 @@ static int ncsi_inet6addr_event(struct notifier_block *this,
                return NOTIFY_OK;
 
        nca.ndp = ndp;
-       nca.driven = false;
+       nca.req_flags = 0;
        nca.package = np->id;
        nca.channel = nc->id;
        nca.dwords[0] = nc->caps[NCSI_CAP_MC].cap;
@@ -1118,7 +1156,7 @@ struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
        /* Initialize private NCSI device */
        spin_lock_init(&ndp->lock);
        INIT_LIST_HEAD(&ndp->packages);
-       ndp->request_id = 0;
+       ndp->request_id = NCSI_REQ_START_IDX;
        for (i = 0; i < ARRAY_SIZE(ndp->requests); i++) {
                ndp->requests[i].id = i;
                ndp->requests[i].ndp = ndp;
@@ -1149,9 +1187,7 @@ EXPORT_SYMBOL_GPL(ncsi_register_dev);
 int ncsi_start_dev(struct ncsi_dev *nd)
 {
        struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
-       struct ncsi_package *np;
-       struct ncsi_channel *nc;
-       int old_state, ret;
+       int ret;
 
        if (nd->state != ncsi_dev_state_registered &&
            nd->state != ncsi_dev_state_functional)
@@ -1163,15 +1199,6 @@ int ncsi_start_dev(struct ncsi_dev *nd)
                return 0;
        }
 
-       /* Reset channel's state and start over */
-       NCSI_FOR_EACH_PACKAGE(ndp, np) {
-               NCSI_FOR_EACH_CHANNEL(np, nc) {
-                       old_state = xchg(&nc->state, NCSI_CHANNEL_INACTIVE);
-                       WARN_ON_ONCE(!list_empty(&nc->link) ||
-                                    old_state == NCSI_CHANNEL_INVISIBLE);
-               }
-       }
-
        if (ndp->flags & NCSI_DEV_HWA)
                ret = ncsi_enable_hwa(ndp);
        else
@@ -1181,6 +1208,35 @@ int ncsi_start_dev(struct ncsi_dev *nd)
 }
 EXPORT_SYMBOL_GPL(ncsi_start_dev);
 
+void ncsi_stop_dev(struct ncsi_dev *nd)
+{
+       struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+       struct ncsi_package *np;
+       struct ncsi_channel *nc;
+       bool chained;
+       int old_state;
+       unsigned long flags;
+
+       /* Stop the channel monitor and reset channel's state */
+       NCSI_FOR_EACH_PACKAGE(ndp, np) {
+               NCSI_FOR_EACH_CHANNEL(np, nc) {
+                       ncsi_stop_channel_monitor(nc);
+
+                       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 ||
+                                    old_state == NCSI_CHANNEL_INVISIBLE);
+               }
+       }
+
+       ncsi_report_link(ndp, true);
+}
+EXPORT_SYMBOL_GPL(ncsi_stop_dev);
+
 void ncsi_unregister_dev(struct ncsi_dev *nd)
 {
        struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);