|
@@ -1,10 +1,10 @@
|
|
|
/*
|
|
|
- * drivers/s390/cio/css.c
|
|
|
- * driver for channel subsystem
|
|
|
+ * driver for channel subsystem
|
|
|
*
|
|
|
- * Copyright IBM Corp. 2002,2008
|
|
|
- * Author(s): Arnd Bergmann (arndb@de.ibm.com)
|
|
|
- * Cornelia Huck (cornelia.huck@de.ibm.com)
|
|
|
+ * Copyright IBM Corp. 2002, 2009
|
|
|
+ *
|
|
|
+ * Author(s): Arnd Bergmann (arndb@de.ibm.com)
|
|
|
+ * Cornelia Huck (cornelia.huck@de.ibm.com)
|
|
|
*/
|
|
|
|
|
|
#define KMSG_COMPONENT "cio"
|
|
@@ -17,6 +17,7 @@
|
|
|
#include <linux/errno.h>
|
|
|
#include <linux/list.h>
|
|
|
#include <linux/reboot.h>
|
|
|
+#include <linux/suspend.h>
|
|
|
#include <asm/isc.h>
|
|
|
#include <asm/crw.h>
|
|
|
|
|
@@ -779,6 +780,79 @@ static struct notifier_block css_reboot_notifier = {
|
|
|
.notifier_call = css_reboot_event,
|
|
|
};
|
|
|
|
|
|
+/*
|
|
|
+ * Since the css devices are neither on a bus nor have a class
|
|
|
+ * nor have a special device type, we cannot stop/restart channel
|
|
|
+ * path measurements via the normal suspend/resume callbacks, but have
|
|
|
+ * to use notifiers.
|
|
|
+ */
|
|
|
+static int css_power_event(struct notifier_block *this, unsigned long event,
|
|
|
+ void *ptr)
|
|
|
+{
|
|
|
+ void *secm_area;
|
|
|
+ int ret, i;
|
|
|
+
|
|
|
+ switch (event) {
|
|
|
+ case PM_HIBERNATION_PREPARE:
|
|
|
+ case PM_SUSPEND_PREPARE:
|
|
|
+ ret = NOTIFY_DONE;
|
|
|
+ for (i = 0; i <= __MAX_CSSID; i++) {
|
|
|
+ struct channel_subsystem *css;
|
|
|
+
|
|
|
+ css = channel_subsystems[i];
|
|
|
+ mutex_lock(&css->mutex);
|
|
|
+ if (!css->cm_enabled) {
|
|
|
+ mutex_unlock(&css->mutex);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ secm_area = (void *)get_zeroed_page(GFP_KERNEL |
|
|
|
+ GFP_DMA);
|
|
|
+ if (secm_area) {
|
|
|
+ if (__chsc_do_secm(css, 0, secm_area))
|
|
|
+ ret = NOTIFY_BAD;
|
|
|
+ free_page((unsigned long)secm_area);
|
|
|
+ } else
|
|
|
+ ret = NOTIFY_BAD;
|
|
|
+
|
|
|
+ mutex_unlock(&css->mutex);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case PM_POST_HIBERNATION:
|
|
|
+ case PM_POST_SUSPEND:
|
|
|
+ ret = NOTIFY_DONE;
|
|
|
+ for (i = 0; i <= __MAX_CSSID; i++) {
|
|
|
+ struct channel_subsystem *css;
|
|
|
+
|
|
|
+ css = channel_subsystems[i];
|
|
|
+ mutex_lock(&css->mutex);
|
|
|
+ if (!css->cm_enabled) {
|
|
|
+ mutex_unlock(&css->mutex);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ secm_area = (void *)get_zeroed_page(GFP_KERNEL |
|
|
|
+ GFP_DMA);
|
|
|
+ if (secm_area) {
|
|
|
+ if (__chsc_do_secm(css, 1, secm_area))
|
|
|
+ ret = NOTIFY_BAD;
|
|
|
+ free_page((unsigned long)secm_area);
|
|
|
+ } else
|
|
|
+ ret = NOTIFY_BAD;
|
|
|
+
|
|
|
+ mutex_unlock(&css->mutex);
|
|
|
+ }
|
|
|
+ /* search for subchannels, which appeared during hibernation */
|
|
|
+ css_schedule_reprobe();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ ret = NOTIFY_DONE;
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+
|
|
|
+}
|
|
|
+static struct notifier_block css_power_notifier = {
|
|
|
+ .notifier_call = css_power_event,
|
|
|
+};
|
|
|
+
|
|
|
/*
|
|
|
* Now that the driver core is running, we can setup our channel subsystem.
|
|
|
* The struct subchannel's are created during probing (except for the
|
|
@@ -852,6 +926,11 @@ init_channel_subsystem (void)
|
|
|
ret = register_reboot_notifier(&css_reboot_notifier);
|
|
|
if (ret)
|
|
|
goto out_unregister;
|
|
|
+ ret = register_pm_notifier(&css_power_notifier);
|
|
|
+ if (ret) {
|
|
|
+ unregister_reboot_notifier(&css_reboot_notifier);
|
|
|
+ goto out_unregister;
|
|
|
+ }
|
|
|
css_init_done = 1;
|
|
|
|
|
|
/* Enable default isc for I/O subchannels. */
|
|
@@ -953,6 +1032,73 @@ static int css_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static int css_pm_prepare(struct device *dev)
|
|
|
+{
|
|
|
+ struct subchannel *sch = to_subchannel(dev);
|
|
|
+ struct css_driver *drv;
|
|
|
+
|
|
|
+ if (mutex_is_locked(&sch->reg_mutex))
|
|
|
+ return -EAGAIN;
|
|
|
+ if (!sch->dev.driver)
|
|
|
+ return 0;
|
|
|
+ drv = to_cssdriver(sch->dev.driver);
|
|
|
+ /* Notify drivers that they may not register children. */
|
|
|
+ return drv->prepare ? drv->prepare(sch) : 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void css_pm_complete(struct device *dev)
|
|
|
+{
|
|
|
+ struct subchannel *sch = to_subchannel(dev);
|
|
|
+ struct css_driver *drv;
|
|
|
+
|
|
|
+ if (!sch->dev.driver)
|
|
|
+ return;
|
|
|
+ drv = to_cssdriver(sch->dev.driver);
|
|
|
+ if (drv->complete)
|
|
|
+ drv->complete(sch);
|
|
|
+}
|
|
|
+
|
|
|
+static int css_pm_freeze(struct device *dev)
|
|
|
+{
|
|
|
+ struct subchannel *sch = to_subchannel(dev);
|
|
|
+ struct css_driver *drv;
|
|
|
+
|
|
|
+ if (!sch->dev.driver)
|
|
|
+ return 0;
|
|
|
+ drv = to_cssdriver(sch->dev.driver);
|
|
|
+ return drv->freeze ? drv->freeze(sch) : 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int css_pm_thaw(struct device *dev)
|
|
|
+{
|
|
|
+ struct subchannel *sch = to_subchannel(dev);
|
|
|
+ struct css_driver *drv;
|
|
|
+
|
|
|
+ if (!sch->dev.driver)
|
|
|
+ return 0;
|
|
|
+ drv = to_cssdriver(sch->dev.driver);
|
|
|
+ return drv->thaw ? drv->thaw(sch) : 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int css_pm_restore(struct device *dev)
|
|
|
+{
|
|
|
+ struct subchannel *sch = to_subchannel(dev);
|
|
|
+ struct css_driver *drv;
|
|
|
+
|
|
|
+ if (!sch->dev.driver)
|
|
|
+ return 0;
|
|
|
+ drv = to_cssdriver(sch->dev.driver);
|
|
|
+ return drv->restore ? drv->restore(sch) : 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct dev_pm_ops css_pm_ops = {
|
|
|
+ .prepare = css_pm_prepare,
|
|
|
+ .complete = css_pm_complete,
|
|
|
+ .freeze = css_pm_freeze,
|
|
|
+ .thaw = css_pm_thaw,
|
|
|
+ .restore = css_pm_restore,
|
|
|
+};
|
|
|
+
|
|
|
struct bus_type css_bus_type = {
|
|
|
.name = "css",
|
|
|
.match = css_bus_match,
|
|
@@ -960,6 +1106,7 @@ struct bus_type css_bus_type = {
|
|
|
.remove = css_remove,
|
|
|
.shutdown = css_shutdown,
|
|
|
.uevent = css_uevent,
|
|
|
+ .pm = &css_pm_ops,
|
|
|
};
|
|
|
|
|
|
/**
|