[S390] pm: dasd power management callbacks.
authorStefan Haberland <stefan.haberland@de.ibm.com>
Tue, 16 Jun 2009 08:30:25 +0000 (10:30 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Tue, 16 Jun 2009 08:31:10 +0000 (10:31 +0200)
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>
drivers/s390/block/dasd.c
drivers/s390/block/dasd_devmap.c
drivers/s390/block/dasd_eckd.c
drivers/s390/block/dasd_fba.c
drivers/s390/block/dasd_int.h

index 442bb98a2821c5a34519b08e452bfb9ffa1bf629..e5b84db0aa037d97bd6df4fb0ee234b3a49ce5ab 100644 (file)
@@ -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;
 
@@ -511,6 +512,25 @@ void dasd_kick_device(struct dasd_device *device)
        schedule_work(&device->kick_work);
 }
 
+/*
+ * 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.
  */
@@ -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,
index e77666c8e6c01af8df79963215c686112b8a2a42..4cac5b54f26a6fff837c17983d4fe79a05811af5 100644 (file)
@@ -1098,6 +1098,7 @@ dasd_get_uid(struct ccw_device *cdev, struct dasd_uid *uid)
        spin_unlock(&dasd_devmap_lock);
        return 0;
 }
+EXPORT_SYMBOL_GPL(dasd_get_uid);
 
 /*
  * Register the given device unique identifier into devmap struct.
index cf0cfdba1244bb95891955820f6e873b24bdb6c7..1c28ec3e4ccb63bb554563c292173d19faa2efa6 100644 (file)
@@ -5,10 +5,9 @@
  *                 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,2000
+ * Copyright IBM Corp. 1999, 2009
  * EMC Symmetrix ioctl Copyright EMC Corporation, 2008
  * Author.........: Nigel Hislop <hislop_nigel@emc.com>
- *
  */
 
 #define KMSG_COMPONENT "dasd"
@@ -104,17 +103,6 @@ dasd_eckd_set_online(struct ccw_device *cdev)
        return dasd_generic_set_online(cdev, &dasd_eckd_discipline);
 }
 
-static struct ccw_driver dasd_eckd_driver = {
-       .name        = "dasd-eckd",
-       .owner       = THIS_MODULE,
-       .ids         = dasd_eckd_ids,
-       .probe       = dasd_eckd_probe,
-       .remove      = dasd_generic_remove,
-       .set_offline = dasd_generic_set_offline,
-       .set_online  = dasd_eckd_set_online,
-       .notify      = dasd_generic_notify,
-};
-
 static const int sizes_trk0[] = { 28, 148, 84 };
 #define LABEL_SIZE 140
 
@@ -3236,6 +3224,98 @@ static void dasd_eckd_dump_sense(struct dasd_device *device,
                dasd_eckd_dump_sense_ccw(device, req, irb);
 }
 
+int dasd_eckd_pm_freeze(struct dasd_device *device)
+{
+       /*
+        * the device should be disconnected from our LCU structure
+        * on restore we will reconnect it and reread LCU specific
+        * information like PAV support that might have changed
+        */
+       dasd_alias_remove_device(device);
+       dasd_alias_disconnect_device_from_lcu(device);
+
+       return 0;
+}
+
+int dasd_eckd_restore_device(struct dasd_device *device)
+{
+       struct dasd_eckd_private *private;
+       int is_known, rc;
+       struct dasd_uid temp_uid;
+
+       /* allow new IO again */
+       device->stopped &= ~DASD_STOPPED_PM;
+
+       private = (struct dasd_eckd_private *) device->private;
+
+       /* Read Configuration Data */
+       rc = dasd_eckd_read_conf(device);
+       if (rc)
+               goto out_err;
+
+       /* Generate device unique id and register in devmap */
+       rc = dasd_eckd_generate_uid(device, &private->uid);
+       dasd_get_uid(device->cdev, &temp_uid);
+       if (memcmp(&private->uid, &temp_uid, sizeof(struct dasd_uid)) != 0)
+               dev_err(&device->cdev->dev, "The UID of the DASD has changed\n");
+       if (rc)
+               goto out_err;
+       dasd_set_uid(device->cdev, &private->uid);
+
+       /* register lcu with alias handling, enable PAV if this is a new lcu */
+       is_known = dasd_alias_make_device_known_to_lcu(device);
+       if (is_known < 0)
+               return is_known;
+       if (!is_known) {
+               /* new lcu found */
+               rc = dasd_eckd_validate_server(device); /* will switch pav on */
+               if (rc)
+                       goto out_err;
+       }
+
+       /* Read Feature Codes */
+       rc = dasd_eckd_read_features(device);
+       if (rc)
+               goto out_err;
+
+       /* Read Device Characteristics */
+       memset(&private->rdc_data, 0, sizeof(private->rdc_data));
+       rc = dasd_generic_read_dev_chars(device, "ECKD",
+                                        &private->rdc_data, 64);
+       if (rc) {
+               DBF_EVENT(DBF_WARNING,
+                         "Read device characteristics failed, rc=%d for "
+                         "device: %s", rc, dev_name(&device->cdev->dev));
+               goto out_err;
+       }
+
+       /* add device to alias management */
+       dasd_alias_add_device(device);
+
+       return 0;
+
+out_err:
+       /*
+        * if the resume failed for the DASD we put it in
+        * an UNRESUMED stop state
+        */
+       device->stopped |= DASD_UNRESUMED_PM;
+       return 0;
+}
+
+static struct ccw_driver dasd_eckd_driver = {
+       .name        = "dasd-eckd",
+       .owner       = THIS_MODULE,
+       .ids         = dasd_eckd_ids,
+       .probe       = dasd_eckd_probe,
+       .remove      = dasd_generic_remove,
+       .set_offline = dasd_generic_set_offline,
+       .set_online  = dasd_eckd_set_online,
+       .notify      = dasd_generic_notify,
+       .freeze      = dasd_generic_pm_freeze,
+       .thaw        = dasd_generic_restore_device,
+       .restore     = dasd_generic_restore_device,
+};
 
 /*
  * max_blocks is dependent on the amount of storage that is available
@@ -3274,6 +3354,8 @@ static struct dasd_discipline dasd_eckd_discipline = {
        .dump_sense_dbf = dasd_eckd_dump_sense_dbf,
        .fill_info = dasd_eckd_fill_info,
        .ioctl = dasd_eckd_ioctl,
+       .freeze = dasd_eckd_pm_freeze,
+       .restore = dasd_eckd_restore_device,
 };
 
 static int __init
index 597c6ffdb9f2308258a292a0b584f440f9c0ec6a..e21ee735f92673c4df91fb91a4c788f0b1d97b46 100644 (file)
@@ -2,8 +2,7 @@
  * File...........: linux/drivers/s390/block/dasd_fba.c
  * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
  * Bugreports.to..: <Linux390@de.ibm.com>
- * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
- *
+ * Copyright IBM Corp. 1999, 2009
  */
 
 #define KMSG_COMPONENT "dasd"
@@ -75,6 +74,9 @@ static struct ccw_driver dasd_fba_driver = {
        .set_offline = dasd_generic_set_offline,
        .set_online  = dasd_fba_set_online,
        .notify      = dasd_generic_notify,
+       .freeze      = dasd_generic_pm_freeze,
+       .thaw        = dasd_generic_restore_device,
+       .restore     = dasd_generic_restore_device,
 };
 
 static void
index f97ceb795078ee9c1cb0df2bba09c0755575dc9f..fd63b2f2bda9baeaefb4e0edefb2bd2407c4c913 100644 (file)
@@ -4,8 +4,7 @@
  *                 Horst Hummel <Horst.Hummel@de.ibm.com>
  *                 Martin Schwidefsky <schwidefsky@de.ibm.com>
  * Bugreports.to..: <Linux390@de.ibm.com>
- * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
- *
+ * Copyright IBM Corp. 1999, 2009
  */
 
 #ifndef DASD_INT_H
@@ -295,6 +294,10 @@ struct dasd_discipline {
        int (*fill_geometry) (struct dasd_block *, struct hd_geometry *);
        int (*fill_info) (struct dasd_device *, struct dasd_information2_t *);
        int (*ioctl) (struct dasd_block *, unsigned int, void __user *);
+
+       /* suspend/resume functions */
+       int (*freeze) (struct dasd_device *);
+       int (*restore) (struct dasd_device *);
 };
 
 extern struct dasd_discipline *dasd_diag_discipline_pointer;
@@ -367,6 +370,7 @@ struct dasd_device {
        atomic_t tasklet_scheduled;
         struct tasklet_struct tasklet;
        struct work_struct kick_work;
+       struct work_struct restore_device;
        struct timer_list timer;
 
        debug_info_t *debug_area;
@@ -410,6 +414,8 @@ struct dasd_block {
 #define DASD_STOPPED_PENDING 4         /* long busy */
 #define DASD_STOPPED_DC_WAIT 8         /* disconnected, wait */
 #define DASD_STOPPED_SU      16        /* summary unit check handling */
+#define DASD_STOPPED_PM      32        /* pm state transition */
+#define DASD_UNRESUMED_PM    64        /* pm resume failed state */
 
 /* per device flags */
 #define DASD_FLAG_OFFLINE      3       /* device is in offline processing */
@@ -556,6 +562,7 @@ void dasd_free_block(struct dasd_block *);
 void dasd_enable_device(struct dasd_device *);
 void dasd_set_target_state(struct dasd_device *, int);
 void dasd_kick_device(struct dasd_device *);
+void dasd_restore_device(struct dasd_device *);
 
 void dasd_add_request_head(struct dasd_ccw_req *);
 void dasd_add_request_tail(struct dasd_ccw_req *);
@@ -578,6 +585,8 @@ int dasd_generic_set_online(struct ccw_device *, struct dasd_discipline *);
 int dasd_generic_set_offline (struct ccw_device *cdev);
 int dasd_generic_notify(struct ccw_device *, int);
 void dasd_generic_handle_state_change(struct dasd_device *);
+int dasd_generic_pm_freeze(struct ccw_device *);
+int dasd_generic_restore_device(struct ccw_device *);
 
 int dasd_generic_read_dev_chars(struct dasd_device *, char *, void *, int);
 char *dasd_get_sense(struct irb *);