s390/cio: fix premature wakeup during chp configure
authorSebastian Ott <sebott@linux.vnet.ibm.com>
Tue, 19 Jul 2016 08:53:35 +0000 (10:53 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Sun, 31 Jul 2016 09:27:57 +0000 (05:27 -0400)
We store requests for channel path configure operations in an array but
maintain an additional cfg_busy variable (indicating if we have requests
stored in said array). When 2 tasks request a channel path configure
operation cfg_busy could be set to 0 even if we still have unprocessed
requests. This would lead to the second task being woken up although its
request was not processed yet.

Fix that by getting rid of cfg_busy and use the chp_cfg_task array
in the wake up condition.

Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/cio/chp.c

index c602211..46be25c 100644 (file)
@@ -38,7 +38,6 @@ enum cfg_task_t {
 /* Map for pending configure tasks. */
 static enum cfg_task_t chp_cfg_task[__MAX_CSSID + 1][__MAX_CHPID + 1];
 static DEFINE_SPINLOCK(cfg_lock);
-static int cfg_busy;
 
 /* Map for channel-path status. */
 static struct sclp_chp_info chp_info;
@@ -666,6 +665,20 @@ static void cfg_set_task(struct chp_id chpid, enum cfg_task_t cfg)
        chp_cfg_task[chpid.cssid][chpid.id] = cfg;
 }
 
+/* Fetch the first configure task. Set chpid accordingly. */
+static enum cfg_task_t chp_cfg_fetch_task(struct chp_id *chpid)
+{
+       enum cfg_task_t t = cfg_none;
+
+       chp_id_for_each(chpid) {
+               t = cfg_get_task(*chpid);
+               if (t != cfg_none)
+                       break;
+       }
+
+       return t;
+}
+
 /* Perform one configure/deconfigure request. Reschedule work function until
  * last request. */
 static void cfg_func(struct work_struct *work)
@@ -675,14 +688,7 @@ static void cfg_func(struct work_struct *work)
        int rc;
 
        spin_lock(&cfg_lock);
-       t = cfg_none;
-       chp_id_for_each(&chpid) {
-               t = cfg_get_task(chpid);
-               if (t != cfg_none) {
-                       cfg_set_task(chpid, cfg_none);
-                       break;
-               }
-       }
+       t = chp_cfg_fetch_task(&chpid);
        spin_unlock(&cfg_lock);
 
        switch (t) {
@@ -709,12 +715,13 @@ static void cfg_func(struct work_struct *work)
        case cfg_none:
                /* Get updated information after last change. */
                info_update();
-               spin_lock(&cfg_lock);
-               cfg_busy = 0;
-               spin_unlock(&cfg_lock);
                wake_up_interruptible(&cfg_wait_queue);
                return;
        }
+       spin_lock(&cfg_lock);
+       if (t == cfg_get_task(chpid))
+               cfg_set_task(chpid, cfg_none);
+       spin_unlock(&cfg_lock);
        schedule_work(&cfg_work);
 }
 
@@ -731,7 +738,6 @@ void chp_cfg_schedule(struct chp_id chpid, int configure)
                      configure);
        spin_lock(&cfg_lock);
        cfg_set_task(chpid, configure ? cfg_configure : cfg_deconfigure);
-       cfg_busy = 1;
        spin_unlock(&cfg_lock);
        schedule_work(&cfg_work);
 }
@@ -752,9 +758,21 @@ void chp_cfg_cancel_deconfigure(struct chp_id chpid)
        spin_unlock(&cfg_lock);
 }
 
+static bool cfg_idle(void)
+{
+       struct chp_id chpid;
+       enum cfg_task_t t;
+
+       spin_lock(&cfg_lock);
+       t = chp_cfg_fetch_task(&chpid);
+       spin_unlock(&cfg_lock);
+
+       return t == cfg_none;
+}
+
 static int cfg_wait_idle(void)
 {
-       if (wait_event_interruptible(cfg_wait_queue, !cfg_busy))
+       if (wait_event_interruptible(cfg_wait_queue, cfg_idle()))
                return -ERESTARTSYS;
        return 0;
 }