[S390] zfcpdump: Implement async sdias event processing

There are two different ways to do the "store data in absolute
storage" SCLP event processing, synchronous and asynchrounous.
This patch adds the asynchronous variant.

Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/drivers/s390/char/sclp_sdias.c b/drivers/s390/char/sclp_sdias.c
index fa733ec..69e6c50 100644
--- a/drivers/s390/char/sclp_sdias.c
+++ b/drivers/s390/char/sclp_sdias.c
@@ -8,6 +8,7 @@
 #define KMSG_COMPONENT "sclp_sdias"
 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
 
+#include <linux/completion.h>
 #include <linux/sched.h>
 #include <asm/sclp.h>
 #include <asm/debug.h>
@@ -62,15 +63,29 @@
 } __attribute__((packed));
 
 static struct sdias_sccb sccb __attribute__((aligned(4096)));
+static struct sdias_evbuf sdias_evbuf;
 
-static int sclp_req_done;
-static wait_queue_head_t sdias_wq;
+static DECLARE_COMPLETION(evbuf_accepted);
+static DECLARE_COMPLETION(evbuf_done);
 static DEFINE_MUTEX(sdias_mutex);
 
+/*
+ * Called by SCLP base when read event data has been completed (async mode only)
+ */
+static void sclp_sdias_receiver_fn(struct evbuf_header *evbuf)
+{
+	memcpy(&sdias_evbuf, evbuf,
+	       min_t(unsigned long, sizeof(sdias_evbuf), evbuf->length));
+	complete(&evbuf_done);
+	TRACE("sclp_sdias_receiver_fn done\n");
+}
+
+/*
+ * Called by SCLP base when sdias event has been accepted
+ */
 static void sdias_callback(struct sclp_req *request, void *data)
 {
-	sclp_req_done = 1;
-	wake_up(&sdias_wq); /* Inform caller, that request is complete */
+	complete(&evbuf_accepted);
 	TRACE("callback done\n");
 }
 
@@ -80,7 +95,6 @@
 	int rc;
 
 	for (retries = SDIAS_RETRIES; retries; retries--) {
-		sclp_req_done = 0;
 		TRACE("add request\n");
 		rc = sclp_add_request(req);
 		if (rc) {
@@ -91,16 +105,31 @@
 			continue;
 		}
 		/* initiated, wait for completion of service call */
-		wait_event(sdias_wq, (sclp_req_done == 1));
+		wait_for_completion(&evbuf_accepted);
 		if (req->status == SCLP_REQ_FAILED) {
 			TRACE("sclp request failed\n");
-			rc = -EIO;
 			continue;
 		}
+		/* if not accepted, retry */
+		if (!(sccb.evbuf.hdr.flags & 0x80)) {
+			TRACE("sclp request failed: flags=%x\n",
+			      sccb.evbuf.hdr.flags);
+			continue;
+		}
+		/*
+		 * for the sync interface the response is in the initial sccb
+		 */
+		if (!sclp_sdias_register.receiver_fn) {
+			memcpy(&sdias_evbuf, &sccb.evbuf, sizeof(sdias_evbuf));
+			TRACE("sync request done\n");
+			return 0;
+		}
+		/* otherwise we wait for completion */
+		wait_for_completion(&evbuf_done);
 		TRACE("request done\n");
-		break;
+		return 0;
 	}
-	return rc;
+	return -EIO;
 }
 
 /*
@@ -140,13 +169,12 @@
 		goto out;
 	}
 
-	switch (sccb.evbuf.event_status) {
+	switch (sdias_evbuf.event_status) {
 		case 0:
-			rc = sccb.evbuf.blk_cnt;
+			rc = sdias_evbuf.blk_cnt;
 			break;
 		default:
-			pr_err("SCLP error: %x\n",
-			       sccb.evbuf.event_status);
+			pr_err("SCLP error: %x\n", sdias_evbuf.event_status);
 			rc = -EIO;
 			goto out;
 	}
@@ -211,18 +239,18 @@
 		goto out;
 	}
 
-	switch (sccb.evbuf.event_status) {
+	switch (sdias_evbuf.event_status) {
 		case EVSTATE_ALL_STORED:
 			TRACE("all stored\n");
 		case EVSTATE_PART_STORED:
-			TRACE("part stored: %i\n", sccb.evbuf.blk_cnt);
+			TRACE("part stored: %i\n", sdias_evbuf.blk_cnt);
 			break;
 		case EVSTATE_NO_DATA:
 			TRACE("no data\n");
 		default:
 			pr_err("Error from SCLP while copying hsa. "
 			       "Event status = %x\n",
-			       sccb.evbuf.event_status);
+			       sdias_evbuf.event_status);
 			rc = -EIO;
 	}
 out:
@@ -230,19 +258,50 @@
 	return rc;
 }
 
-int __init sclp_sdias_init(void)
+static int __init sclp_sdias_register_check(void)
 {
 	int rc;
 
+	rc = sclp_register(&sclp_sdias_register);
+	if (rc)
+		return rc;
+	if (sclp_sdias_blk_count() == 0) {
+		sclp_unregister(&sclp_sdias_register);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int __init sclp_sdias_init_sync(void)
+{
+	TRACE("Try synchronous mode\n");
+	sclp_sdias_register.receive_mask = 0;
+	sclp_sdias_register.receiver_fn = NULL;
+	return sclp_sdias_register_check();
+}
+
+static int __init sclp_sdias_init_async(void)
+{
+	TRACE("Try asynchronous mode\n");
+	sclp_sdias_register.receive_mask = EVTYP_SDIAS_MASK;
+	sclp_sdias_register.receiver_fn = sclp_sdias_receiver_fn;
+	return sclp_sdias_register_check();
+}
+
+int __init sclp_sdias_init(void)
+{
 	if (ipl_info.type != IPL_TYPE_FCP_DUMP)
 		return 0;
 	sdias_dbf = debug_register("dump_sdias", 4, 1, 4 * sizeof(long));
 	debug_register_view(sdias_dbf, &debug_sprintf_view);
 	debug_set_level(sdias_dbf, 6);
-	rc = sclp_register(&sclp_sdias_register);
-	if (rc)
-		return rc;
-	init_waitqueue_head(&sdias_wq);
+	if (sclp_sdias_init_sync() == 0)
+		goto out;
+	if (sclp_sdias_init_async() == 0)
+		goto out;
+	TRACE("init failed\n");
+	return -ENODEV;
+out:
 	TRACE("init done\n");
 	return 0;
 }