From 4475aeb8b77db34dea96b09900ba0cb245b6fb42 Mon Sep 17 00:00:00 2001
From: Sebastian Ott <sebott@linux.vnet.ibm.com>
Date: Tue, 19 Jul 2016 10:53:35 +0200
Subject: [PATCH] s390/cio: fix premature wakeup during chp configure

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 | 46 +++++++++++++++++++++++++++++-------------
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c
index c602211ab94e..46be25c7461e 100644
--- a/drivers/s390/cio/chp.c
+++ b/drivers/s390/cio/chp.c
@@ -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;
 }
-- 
2.30.2