[S390] dasd: add dynamic pav toleration
authorStefan Haberland <stefan.haberland@de.ibm.com>
Mon, 17 May 2010 08:00:10 +0000 (10:00 +0200)
committerMartin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com>
Mon, 17 May 2010 08:00:16 +0000 (10:00 +0200)
For base Parallel Access Volume (PAV) there is a fixed mapping of
base and alias devices. With dynamic PAV this mapping can be changed
so that an alias device is used with another base device.
This patch enables the DASD device driver to tolerate dynamic PAV
changes.

Signed-off-by: Stefan Haberland <stefan.haberland@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/block/dasd.c
drivers/s390/block/dasd_3990_erp.c
drivers/s390/block/dasd_alias.c
drivers/s390/block/dasd_eckd.c
drivers/s390/block/dasd_eckd.h
drivers/s390/block/dasd_int.h

index fa2339cb1681f43fb19e337ad037f75d908b8d89..0e86247d791e1b68abd8fd95a966c83c7845dea8 100644 (file)
@@ -65,6 +65,7 @@ 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 do_reload_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);
@@ -115,6 +116,7 @@ struct dasd_device *dasd_alloc_device(void)
        device->timer.data = (unsigned long) device;
        INIT_WORK(&device->kick_work, do_kick_device);
        INIT_WORK(&device->restore_device, do_restore_device);
+       INIT_WORK(&device->reload_device, do_reload_device);
        device->state = DASD_STATE_NEW;
        device->target = DASD_STATE_NEW;
        mutex_init(&device->state_mutex);
@@ -520,6 +522,26 @@ void dasd_kick_device(struct dasd_device *device)
        schedule_work(&device->kick_work);
 }
 
+/*
+ * dasd_reload_device will schedule a call do do_reload_device to the kernel
+ * event daemon.
+ */
+static void do_reload_device(struct work_struct *work)
+{
+       struct dasd_device *device = container_of(work, struct dasd_device,
+                                                 reload_device);
+       device->discipline->reload(device);
+       dasd_put_device(device);
+}
+
+void dasd_reload_device(struct dasd_device *device)
+{
+       dasd_get_device(device);
+       /* queue call to dasd_reload_device to the kernel event daemon. */
+       schedule_work(&device->reload_device);
+}
+EXPORT_SYMBOL(dasd_reload_device);
+
 /*
  * dasd_restore_device will schedule a call do do_restore_device to the kernel
  * event daemon.
index 6632649dd6aa4ce1d052cd6dc428ec96be8577f3..85bfd87948561cf17e758c9074bdaef2a33140a8 100644 (file)
@@ -1418,9 +1418,29 @@ static struct dasd_ccw_req *dasd_3990_erp_inspect_alias(
                                                struct dasd_ccw_req *erp)
 {
        struct dasd_ccw_req *cqr = erp->refers;
+       char *sense;
 
        if (cqr->block &&
            (cqr->block->base != cqr->startdev)) {
+
+               sense = dasd_get_sense(&erp->refers->irb);
+               /*
+                * dynamic pav may have changed base alias mapping
+                */
+               if (!test_bit(DASD_FLAG_OFFLINE, &cqr->startdev->flags) && sense
+                   && (sense[0] == 0x10) && (sense[7] == 0x0F)
+                   && (sense[8] == 0x67)) {
+                       /*
+                        * remove device from alias handling to prevent new
+                        * requests from being scheduled on the
+                        * wrong alias device
+                        */
+                       dasd_alias_remove_device(cqr->startdev);
+
+                       /* schedule worker to reload device */
+                       dasd_reload_device(cqr->startdev);
+               }
+
                if (cqr->startdev->features & DASD_FEATURE_ERPLOG) {
                        DBF_DEV_EVENT(DBF_ERR, cqr->startdev,
                                    "ERP on alias device for request %p,"
index 8c4814258e93c9fe58e3711be1051a49b0af0269..a564b994111402b6aab9655e9dfa33581dd8b646 100644 (file)
@@ -642,6 +642,14 @@ int dasd_alias_add_device(struct dasd_device *device)
        return rc;
 }
 
+int dasd_alias_update_add_device(struct dasd_device *device)
+{
+       struct dasd_eckd_private *private;
+       private = (struct dasd_eckd_private *) device->private;
+       private->lcu->flags |= UPDATE_PENDING;
+       return dasd_alias_add_device(device);
+}
+
 int dasd_alias_remove_device(struct dasd_device *device)
 {
        struct dasd_eckd_private *private;
index 0cb2331168551ef4f290adb65683d8ee1902b30a..4305c23c57a47a00283b0866f965e8584d471019 100644 (file)
@@ -1451,6 +1451,7 @@ static int dasd_eckd_ready_to_online(struct dasd_device *device)
 
 static int dasd_eckd_online_to_ready(struct dasd_device *device)
 {
+       cancel_work_sync(&device->reload_device);
        return dasd_alias_remove_device(device);
 };
 
@@ -1709,10 +1710,27 @@ static void dasd_eckd_handle_unsolicited_interrupt(struct dasd_device *device,
 {
        char mask;
        char *sense = NULL;
+       struct dasd_eckd_private *private;
 
+       private = (struct dasd_eckd_private *) device->private;
        /* first of all check for state change pending interrupt */
        mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP;
        if ((scsw_dstat(&irb->scsw) & mask) == mask) {
+               /* for alias only and not in offline processing*/
+               if (!device->block && private->lcu &&
+                   !test_bit(DASD_FLAG_OFFLINE, &device->flags)) {
+                       /*
+                        * the state change could be caused by an alias
+                        * reassignment remove device from alias handling
+                        * to prevent new requests from being scheduled on
+                        * the wrong alias device
+                        */
+                       dasd_alias_remove_device(device);
+
+                       /* schedule worker to reload device */
+                       dasd_reload_device(device);
+               }
+
                dasd_generic_handle_state_change(device);
                return;
        }
@@ -3259,7 +3277,7 @@ 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)
+static int dasd_eckd_pm_freeze(struct dasd_device *device)
 {
        /*
         * the device should be disconnected from our LCU structure
@@ -3272,7 +3290,7 @@ int dasd_eckd_pm_freeze(struct dasd_device *device)
        return 0;
 }
 
-int dasd_eckd_restore_device(struct dasd_device *device)
+static int dasd_eckd_restore_device(struct dasd_device *device)
 {
        struct dasd_eckd_private *private;
        struct dasd_eckd_characteristics temp_rdc_data;
@@ -3336,6 +3354,53 @@ out_err:
        return -1;
 }
 
+static int dasd_eckd_reload_device(struct dasd_device *device)
+{
+       struct dasd_eckd_private *private;
+       int rc, old_base;
+       char uid[60];
+
+       private = (struct dasd_eckd_private *) device->private;
+       old_base = private->uid.base_unit_addr;
+       /* Read Configuration Data */
+       rc = dasd_eckd_read_conf(device);
+       if (rc)
+               goto out_err;
+
+       rc = dasd_eckd_generate_uid(device, &private->uid);
+       if (rc)
+               goto out_err;
+
+       dasd_set_uid(device->cdev, &private->uid);
+
+       /*
+        * update unit address configuration and
+        * add device to alias management
+        */
+       dasd_alias_update_add_device(device);
+
+       if (old_base != private->uid.base_unit_addr) {
+               if (strlen(private->uid.vduit) > 0)
+                       snprintf(uid, 60, "%s.%s.%04x.%02x.%s",
+                                private->uid.vendor, private->uid.serial,
+                                private->uid.ssid, private->uid.base_unit_addr,
+                                private->uid.vduit);
+               else
+                       snprintf(uid, 60, "%s.%s.%04x.%02x",
+                                private->uid.vendor, private->uid.serial,
+                                private->uid.ssid,
+                                private->uid.base_unit_addr);
+
+               dev_info(&device->cdev->dev,
+                        "An Alias device was reassigned to a new base device "
+                        "with UID: %s\n", uid);
+       }
+       return 0;
+
+out_err:
+       return -1;
+}
+
 static struct ccw_driver dasd_eckd_driver = {
        .name        = "dasd-eckd",
        .owner       = THIS_MODULE,
@@ -3389,6 +3454,7 @@ static struct dasd_discipline dasd_eckd_discipline = {
        .ioctl = dasd_eckd_ioctl,
        .freeze = dasd_eckd_pm_freeze,
        .restore = dasd_eckd_restore_device,
+       .reload = dasd_eckd_reload_device,
 };
 
 static int __init
index 864d53c04201bdd023a7e30c0cb0e7f544b16b00..dd6385a5af142ae446d47a59f2c5fdbf89413fbc 100644 (file)
@@ -426,7 +426,6 @@ struct alias_pav_group {
        struct dasd_device *next;
 };
 
-
 struct dasd_eckd_private {
        struct dasd_eckd_characteristics rdc_data;
        u8 *conf_data;
@@ -463,4 +462,5 @@ void dasd_alias_handle_summary_unit_check(struct dasd_device *, struct irb *);
 void dasd_eckd_reset_ccw_to_base_io(struct dasd_ccw_req *);
 void dasd_alias_lcu_setup_complete(struct dasd_device *);
 void dasd_alias_wait_for_lcu_setup(struct dasd_device *);
+int dasd_alias_update_add_device(struct dasd_device *);
 #endif                         /* DASD_ECKD_H */
index a91d4a97d4f246b495f2debb3c2b0c7aed432909..1ae7b121628e0e418b14ce6e7368424cea1e75bd 100644 (file)
@@ -312,6 +312,9 @@ struct dasd_discipline {
        /* suspend/resume functions */
        int (*freeze) (struct dasd_device *);
        int (*restore) (struct dasd_device *);
+
+       /* reload device after state change */
+       int (*reload) (struct dasd_device *);
 };
 
 extern struct dasd_discipline *dasd_diag_discipline_pointer;
@@ -386,6 +389,7 @@ struct dasd_device {
         struct tasklet_struct tasklet;
        struct work_struct kick_work;
        struct work_struct restore_device;
+       struct work_struct reload_device;
        struct timer_list timer;
 
        debug_info_t *debug_area;
@@ -582,6 +586,7 @@ 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_reload_device(struct dasd_device *);
 
 void dasd_add_request_head(struct dasd_ccw_req *);
 void dasd_add_request_tail(struct dasd_ccw_req *);