[S390] pm: power management support for SCLP drivers.

The SCLP base driver defines a new notifier call back for all upper level SCLP
drivers, like the SCLP console, etc. This guarantees that in suspend first the
upper level drivers are suspended and afterwards the SCLP base driver. For
resume it is the other way round. The SCLP base driver itself registers a
new platform device at the platform bus and gets PM notifications via
the dev_pm_ops.

In suspend, the SCLP base driver switches off the receiver and sender mask
This is done in sclp_deactivate(). After suspend all new requests will be
rejected with -EIO and no more interrupts will be received, because the masks
are switched off. For resume the sender and receiver masks are reset in
the sclp_reactivate() function.

When the SCLP console is suspended, all new messages are cached in the
sclp console buffers. In resume, all the cached messages are written to the
console. In addition to that we have an early resume function that removes
the cached messages from the suspend image.

Signed-off-by: Michael Holzheu <holzheu@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c
index 4377e93..a983f50 100644
--- a/drivers/s390/char/sclp.c
+++ b/drivers/s390/char/sclp.c
@@ -1,11 +1,10 @@
 /*
- *  drivers/s390/char/sclp.c
- *     core function to access sclp interface
+ * core function to access sclp interface
  *
- *  S390 version
- *    Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
- *    Author(s): Martin Peschke <mpeschke@de.ibm.com>
- *		 Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Copyright IBM Corp. 1999, 2009
+ *
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ *	      Martin Schwidefsky <schwidefsky@de.ibm.com>
  */
 
 #include <linux/module.h>
@@ -16,6 +15,9 @@
 #include <linux/reboot.h>
 #include <linux/jiffies.h>
 #include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/completion.h>
+#include <linux/platform_device.h>
 #include <asm/types.h>
 #include <asm/s390_ext.h>
 
@@ -47,6 +49,16 @@
 static char sclp_read_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
 static char sclp_init_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
 
+/* Suspend request */
+static DECLARE_COMPLETION(sclp_request_queue_flushed);
+
+static void sclp_suspend_req_cb(struct sclp_req *req, void *data)
+{
+	complete(&sclp_request_queue_flushed);
+}
+
+static struct sclp_req sclp_suspend_req;
+
 /* Timer for request retries. */
 static struct timer_list sclp_request_timer;
 
@@ -84,6 +96,12 @@
 	sclp_mask_state_initializing
 } sclp_mask_state = sclp_mask_state_idle;
 
+/* Internal state: is the driver suspended? */
+static enum sclp_suspend_state_t {
+	sclp_suspend_state_running,
+	sclp_suspend_state_suspended,
+} sclp_suspend_state = sclp_suspend_state_running;
+
 /* Maximum retry counts */
 #define SCLP_INIT_RETRY		3
 #define SCLP_MASK_RETRY		3
@@ -211,6 +229,8 @@
 	del_timer(&sclp_request_timer);
 	while (!list_empty(&sclp_req_queue)) {
 		req = list_entry(sclp_req_queue.next, struct sclp_req, list);
+		if (!req->sccb)
+			goto do_post;
 		rc = __sclp_start_request(req);
 		if (rc == 0)
 			break;
@@ -222,6 +242,7 @@
 						 sclp_request_timeout, 0);
 			break;
 		}
+do_post:
 		/* Post-processing for aborted request */
 		list_del(&req->list);
 		if (req->callback) {
@@ -233,6 +254,19 @@
 	spin_unlock_irqrestore(&sclp_lock, flags);
 }
 
+static int __sclp_can_add_request(struct sclp_req *req)
+{
+	if (req == &sclp_suspend_req || req == &sclp_init_req)
+		return 1;
+	if (sclp_suspend_state != sclp_suspend_state_running)
+		return 0;
+	if (sclp_init_state != sclp_init_state_initialized)
+		return 0;
+	if (sclp_activation_state != sclp_activation_state_active)
+		return 0;
+	return 1;
+}
+
 /* Queue a new request. Return zero on success, non-zero otherwise. */
 int
 sclp_add_request(struct sclp_req *req)
@@ -241,9 +275,7 @@
 	int rc;
 
 	spin_lock_irqsave(&sclp_lock, flags);
-	if ((sclp_init_state != sclp_init_state_initialized ||
-	     sclp_activation_state != sclp_activation_state_active) &&
-	    req != &sclp_init_req) {
+	if (!__sclp_can_add_request(req)) {
 		spin_unlock_irqrestore(&sclp_lock, flags);
 		return -EIO;
 	}
@@ -254,10 +286,16 @@
 	/* Start if request is first in list */
 	if (sclp_running_state == sclp_running_state_idle &&
 	    req->list.prev == &sclp_req_queue) {
+		if (!req->sccb) {
+			list_del(&req->list);
+			rc = -ENODATA;
+			goto out;
+		}
 		rc = __sclp_start_request(req);
 		if (rc)
 			list_del(&req->list);
 	}
+out:
 	spin_unlock_irqrestore(&sclp_lock, flags);
 	return rc;
 }
@@ -560,6 +598,7 @@
 	/* Trigger initial state change callback */
 	reg->sclp_receive_mask = 0;
 	reg->sclp_send_mask = 0;
+	reg->pm_event_posted = 0;
 	list_add(&reg->list, &sclp_reg_list);
 	spin_unlock_irqrestore(&sclp_lock, flags);
 	rc = sclp_init_mask(1);
@@ -880,20 +919,134 @@
 	.notifier_call = sclp_reboot_event
 };
 
+/*
+ * Suspend/resume SCLP notifier implementation
+ */
+
+static void sclp_pm_event(enum sclp_pm_event sclp_pm_event, int rollback)
+{
+	struct sclp_register *reg;
+	unsigned long flags;
+
+	if (!rollback) {
+		spin_lock_irqsave(&sclp_lock, flags);
+		list_for_each_entry(reg, &sclp_reg_list, list)
+			reg->pm_event_posted = 0;
+		spin_unlock_irqrestore(&sclp_lock, flags);
+	}
+	do {
+		spin_lock_irqsave(&sclp_lock, flags);
+		list_for_each_entry(reg, &sclp_reg_list, list) {
+			if (rollback && reg->pm_event_posted)
+				goto found;
+			if (!rollback && !reg->pm_event_posted)
+				goto found;
+		}
+		spin_unlock_irqrestore(&sclp_lock, flags);
+		return;
+found:
+		spin_unlock_irqrestore(&sclp_lock, flags);
+		if (reg->pm_event_fn)
+			reg->pm_event_fn(reg, sclp_pm_event);
+		reg->pm_event_posted = rollback ? 0 : 1;
+	} while (1);
+}
+
+/*
+ * Susend/resume callbacks for platform device
+ */
+
+static int sclp_freeze(struct device *dev)
+{
+	unsigned long flags;
+	int rc;
+
+	sclp_pm_event(SCLP_PM_EVENT_FREEZE, 0);
+
+	spin_lock_irqsave(&sclp_lock, flags);
+	sclp_suspend_state = sclp_suspend_state_suspended;
+	spin_unlock_irqrestore(&sclp_lock, flags);
+
+	/* Init supend data */
+	memset(&sclp_suspend_req, 0, sizeof(sclp_suspend_req));
+	sclp_suspend_req.callback = sclp_suspend_req_cb;
+	sclp_suspend_req.status = SCLP_REQ_FILLED;
+	init_completion(&sclp_request_queue_flushed);
+
+	rc = sclp_add_request(&sclp_suspend_req);
+	if (rc == 0)
+		wait_for_completion(&sclp_request_queue_flushed);
+	else if (rc != -ENODATA)
+		goto fail_thaw;
+
+	rc = sclp_deactivate();
+	if (rc)
+		goto fail_thaw;
+	return 0;
+
+fail_thaw:
+	spin_lock_irqsave(&sclp_lock, flags);
+	sclp_suspend_state = sclp_suspend_state_running;
+	spin_unlock_irqrestore(&sclp_lock, flags);
+	sclp_pm_event(SCLP_PM_EVENT_THAW, 1);
+	return rc;
+}
+
+static int sclp_undo_suspend(enum sclp_pm_event event)
+{
+	unsigned long flags;
+	int rc;
+
+	rc = sclp_reactivate();
+	if (rc)
+		return rc;
+
+	spin_lock_irqsave(&sclp_lock, flags);
+	sclp_suspend_state = sclp_suspend_state_running;
+	spin_unlock_irqrestore(&sclp_lock, flags);
+
+	sclp_pm_event(event, 0);
+	return 0;
+}
+
+static int sclp_thaw(struct device *dev)
+{
+	return sclp_undo_suspend(SCLP_PM_EVENT_THAW);
+}
+
+static int sclp_restore(struct device *dev)
+{
+	return sclp_undo_suspend(SCLP_PM_EVENT_RESTORE);
+}
+
+static struct dev_pm_ops sclp_pm_ops = {
+	.freeze		= sclp_freeze,
+	.thaw		= sclp_thaw,
+	.restore	= sclp_restore,
+};
+
+static struct platform_driver sclp_pdrv = {
+	.driver = {
+		.name	= "sclp",
+		.owner	= THIS_MODULE,
+		.pm	= &sclp_pm_ops,
+	},
+};
+
+static struct platform_device *sclp_pdev;
+
 /* Initialize SCLP driver. Return zero if driver is operational, non-zero
  * otherwise. */
 static int
 sclp_init(void)
 {
 	unsigned long flags;
-	int rc;
+	int rc = 0;
 
 	spin_lock_irqsave(&sclp_lock, flags);
 	/* Check for previous or running initialization */
-	if (sclp_init_state != sclp_init_state_uninitialized) {
-		spin_unlock_irqrestore(&sclp_lock, flags);
-		return 0;
-	}
+	if (sclp_init_state != sclp_init_state_uninitialized)
+		goto fail_unlock;
 	sclp_init_state = sclp_init_state_initializing;
 	/* Set up variables */
 	INIT_LIST_HEAD(&sclp_req_queue);
@@ -904,27 +1057,17 @@
 	spin_unlock_irqrestore(&sclp_lock, flags);
 	rc = sclp_check_interface();
 	spin_lock_irqsave(&sclp_lock, flags);
-	if (rc) {
-		sclp_init_state = sclp_init_state_uninitialized;
-		spin_unlock_irqrestore(&sclp_lock, flags);
-		return rc;
-	}
+	if (rc)
+		goto fail_init_state_uninitialized;
 	/* Register reboot handler */
 	rc = register_reboot_notifier(&sclp_reboot_notifier);
-	if (rc) {
-		sclp_init_state = sclp_init_state_uninitialized;
-		spin_unlock_irqrestore(&sclp_lock, flags);
-		return rc;
-	}
+	if (rc)
+		goto fail_init_state_uninitialized;
 	/* Register interrupt handler */
 	rc = register_early_external_interrupt(0x2401, sclp_interrupt_handler,
 					       &ext_int_info_hwc);
-	if (rc) {
-		unregister_reboot_notifier(&sclp_reboot_notifier);
-		sclp_init_state = sclp_init_state_uninitialized;
-		spin_unlock_irqrestore(&sclp_lock, flags);
-		return rc;
-	}
+	if (rc)
+		goto fail_unregister_reboot_notifier;
 	sclp_init_state = sclp_init_state_initialized;
 	spin_unlock_irqrestore(&sclp_lock, flags);
 	/* Enable service-signal external interruption - needs to happen with
@@ -932,11 +1075,56 @@
 	ctl_set_bit(0, 9);
 	sclp_init_mask(1);
 	return 0;
+
+fail_unregister_reboot_notifier:
+	unregister_reboot_notifier(&sclp_reboot_notifier);
+fail_init_state_uninitialized:
+	sclp_init_state = sclp_init_state_uninitialized;
+fail_unlock:
+	spin_unlock_irqrestore(&sclp_lock, flags);
+	return rc;
 }
 
+/*
+ * SCLP panic notifier: If we are suspended, we thaw SCLP in order to be able
+ * to print the panic message.
+ */
+static int sclp_panic_notify(struct notifier_block *self,
+			     unsigned long event, void *data)
+{
+	if (sclp_suspend_state == sclp_suspend_state_suspended)
+		sclp_undo_suspend(SCLP_PM_EVENT_THAW);
+	return NOTIFY_OK;
+}
+
+static struct notifier_block sclp_on_panic_nb = {
+	.notifier_call = sclp_panic_notify,
+	.priority = SCLP_PANIC_PRIO,
+};
+
 static __init int sclp_initcall(void)
 {
+	int rc;
+
+	rc = platform_driver_register(&sclp_pdrv);
+	if (rc)
+		return rc;
+	sclp_pdev = platform_device_register_simple("sclp", -1, NULL, 0);
+	rc = IS_ERR(sclp_pdev) ? PTR_ERR(sclp_pdev) : 0;
+	if (rc)
+		goto fail_platform_driver_unregister;
+	rc = atomic_notifier_chain_register(&panic_notifier_list,
+					    &sclp_on_panic_nb);
+	if (rc)
+		goto fail_platform_device_unregister;
+
 	return sclp_init();
+
+fail_platform_device_unregister:
+	platform_device_unregister(sclp_pdev);
+fail_platform_driver_unregister:
+	platform_driver_unregister(&sclp_pdrv);
+	return rc;
 }
 
 arch_initcall(sclp_initcall);