summaryrefslogtreecommitdiff
path: root/drivers/s390/block/dasd.c
diff options
context:
space:
mode:
authorStefan Haberland <stefan.haberland@de.ibm.com>2009-06-16 12:30:25 +0400
committerMartin Schwidefsky <schwidefsky@de.ibm.com>2009-06-16 12:31:10 +0400
commitd41dd122acf960db78c9ddc87684b43751dd36d9 (patch)
tree6005ea4914d1d2556b182ae86d82185d792b2f30 /drivers/s390/block/dasd.c
parentad285ae9fc6b9c0058f2a558b43fe8817685ebfa (diff)
downloadlinux-d41dd122acf960db78c9ddc87684b43751dd36d9.tar.xz
[S390] pm: dasd power management callbacks.
Introduce the power management callbacks to the dasd driver. On suspend the dasd devices are stopped and removed from the focus of alias management. On resume they are reinitialized by rereading the device characteristics and adding the device to the alias management. In case the device has gone away during suspend it will caught in the suspend state with stopped flag set to UNRESUMED. After it appears again the restore function is called again. Signed-off-by: Stefan Haberland <stefan.haberland@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390/block/dasd.c')
-rw-r--r--drivers/s390/block/dasd.c109
1 files changed, 107 insertions, 2 deletions
diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c
index 442bb98a2821..e5b84db0aa03 100644
--- a/drivers/s390/block/dasd.c
+++ b/drivers/s390/block/dasd.c
@@ -5,8 +5,7 @@
* Carsten Otte <Cotte@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
- * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
- *
+ * Copyright IBM Corp. 1999, 2009
*/
#define KMSG_COMPONENT "dasd"
@@ -61,6 +60,7 @@ static int dasd_flush_block_queue(struct dasd_block *);
static void dasd_device_tasklet(struct dasd_device *);
static void dasd_block_tasklet(struct dasd_block *);
static void do_kick_device(struct work_struct *);
+static void do_restore_device(struct work_struct *);
static void dasd_return_cqr_cb(struct dasd_ccw_req *, void *);
static void dasd_device_timeout(unsigned long);
static void dasd_block_timeout(unsigned long);
@@ -109,6 +109,7 @@ struct dasd_device *dasd_alloc_device(void)
device->timer.function = dasd_device_timeout;
device->timer.data = (unsigned long) device;
INIT_WORK(&device->kick_work, do_kick_device);
+ INIT_WORK(&device->restore_device, do_restore_device);
device->state = DASD_STATE_NEW;
device->target = DASD_STATE_NEW;
@@ -512,6 +513,25 @@ void dasd_kick_device(struct dasd_device *device)
}
/*
+ * dasd_restore_device will schedule a call do do_restore_device to the kernel
+ * event daemon.
+ */
+static void do_restore_device(struct work_struct *work)
+{
+ struct dasd_device *device = container_of(work, struct dasd_device,
+ restore_device);
+ device->cdev->drv->restore(device->cdev);
+ dasd_put_device(device);
+}
+
+void dasd_restore_device(struct dasd_device *device)
+{
+ dasd_get_device(device);
+ /* queue call to dasd_restore_device to the kernel event daemon. */
+ schedule_work(&device->restore_device);
+}
+
+/*
* Set the target state for a device and starts the state change.
*/
void dasd_set_target_state(struct dasd_device *device, int target)
@@ -908,6 +928,12 @@ int dasd_start_IO(struct dasd_ccw_req *cqr)
DBF_DEV_EVENT(DBF_DEBUG, device, "%s",
"start_IO: -EIO device gone, retry");
break;
+ case -EINVAL:
+ /* most likely caused in power management context */
+ DBF_DEV_EVENT(DBF_DEBUG, device, "%s",
+ "start_IO: -EINVAL device currently "
+ "not accessible");
+ break;
default:
/* internal error 11 - unknown rc */
snprintf(errorstring, ERRORLENGTH, "11 %d", rc);
@@ -2400,6 +2426,12 @@ int dasd_generic_notify(struct ccw_device *cdev, int event)
case CIO_OPER:
/* FIXME: add a sanity check. */
device->stopped &= ~DASD_STOPPED_DC_WAIT;
+ if (device->stopped & DASD_UNRESUMED_PM) {
+ device->stopped &= ~DASD_UNRESUMED_PM;
+ dasd_restore_device(device);
+ ret = 1;
+ break;
+ }
dasd_schedule_device_bh(device);
if (device->block)
dasd_schedule_block_bh(device->block);
@@ -2410,6 +2442,79 @@ int dasd_generic_notify(struct ccw_device *cdev, int event)
return ret;
}
+int dasd_generic_pm_freeze(struct ccw_device *cdev)
+{
+ struct dasd_ccw_req *cqr, *n;
+ int rc;
+ struct list_head freeze_queue;
+ struct dasd_device *device = dasd_device_from_cdev(cdev);
+
+ if (IS_ERR(device))
+ return PTR_ERR(device);
+ /* disallow new I/O */
+ device->stopped |= DASD_STOPPED_PM;
+ /* clear active requests */
+ INIT_LIST_HEAD(&freeze_queue);
+ spin_lock_irq(get_ccwdev_lock(cdev));
+ rc = 0;
+ list_for_each_entry_safe(cqr, n, &device->ccw_queue, devlist) {
+ /* Check status and move request to flush_queue */
+ if (cqr->status == DASD_CQR_IN_IO) {
+ rc = device->discipline->term_IO(cqr);
+ if (rc) {
+ /* unable to terminate requeust */
+ dev_err(&device->cdev->dev,
+ "Unable to terminate request %p "
+ "on suspend\n", cqr);
+ spin_unlock_irq(get_ccwdev_lock(cdev));
+ dasd_put_device(device);
+ return rc;
+ }
+ }
+ list_move_tail(&cqr->devlist, &freeze_queue);
+ }
+
+ spin_unlock_irq(get_ccwdev_lock(cdev));
+
+ list_for_each_entry_safe(cqr, n, &freeze_queue, devlist) {
+ wait_event(dasd_flush_wq,
+ (cqr->status != DASD_CQR_CLEAR_PENDING));
+ if (cqr->status == DASD_CQR_CLEARED)
+ cqr->status = DASD_CQR_QUEUED;
+ }
+ /* move freeze_queue to start of the ccw_queue */
+ spin_lock_irq(get_ccwdev_lock(cdev));
+ list_splice_tail(&freeze_queue, &device->ccw_queue);
+ spin_unlock_irq(get_ccwdev_lock(cdev));
+
+ if (device->discipline->freeze)
+ rc = device->discipline->freeze(device);
+
+ dasd_put_device(device);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dasd_generic_pm_freeze);
+
+int dasd_generic_restore_device(struct ccw_device *cdev)
+{
+ struct dasd_device *device = dasd_device_from_cdev(cdev);
+ int rc = 0;
+
+ if (IS_ERR(device))
+ return PTR_ERR(device);
+
+ dasd_schedule_device_bh(device);
+ if (device->block)
+ dasd_schedule_block_bh(device->block);
+
+ if (device->discipline->restore)
+ rc = device->discipline->restore(device);
+
+ dasd_put_device(device);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dasd_generic_restore_device);
+
static struct dasd_ccw_req *dasd_generic_build_rdc(struct dasd_device *device,
void *rdc_buffer,
int rdc_buffer_size,