[S390] dasd: fix fixpoint divide exception in define_extent
[pandora-kernel.git] / drivers / s390 / block / dasd_eckd.c
index 6ab2968..bbcd5e9 100644 (file)
@@ -752,24 +752,13 @@ dasd_eckd_cdl_reclen(int recid)
                return sizes_trk0[recid];
        return LABEL_SIZE;
 }
-
-/*
- * Generate device unique id that specifies the physical device.
- */
-static int dasd_eckd_generate_uid(struct dasd_device *device)
+/* create unique id from private structure. */
+static void create_uid(struct dasd_eckd_private *private)
 {
-       struct dasd_eckd_private *private;
-       struct dasd_uid *uid;
        int count;
-       unsigned long flags;
+       struct dasd_uid *uid;
 
-       private = (struct dasd_eckd_private *) device->private;
-       if (!private)
-               return -ENODEV;
-       if (!private->ned || !private->gneq)
-               return -ENODEV;
        uid = &private->uid;
-       spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
        memset(uid, 0, sizeof(struct dasd_uid));
        memcpy(uid->vendor, private->ned->HDA_manufacturer,
               sizeof(uid->vendor) - 1);
@@ -792,6 +781,23 @@ static int dasd_eckd_generate_uid(struct dasd_device *device)
                                private->vdsneq->uit[count]);
                }
        }
+}
+
+/*
+ * Generate device unique id that specifies the physical device.
+ */
+static int dasd_eckd_generate_uid(struct dasd_device *device)
+{
+       struct dasd_eckd_private *private;
+       unsigned long flags;
+
+       private = (struct dasd_eckd_private *) device->private;
+       if (!private)
+               return -ENODEV;
+       if (!private->ned || !private->gneq)
+               return -ENODEV;
+       spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+       create_uid(private);
        spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
        return 0;
 }
@@ -811,6 +817,21 @@ static int dasd_eckd_get_uid(struct dasd_device *device, struct dasd_uid *uid)
        return -EINVAL;
 }
 
+/*
+ * compare device UID with data of a given dasd_eckd_private structure
+ * return 0 for match
+ */
+static int dasd_eckd_compare_path_uid(struct dasd_device *device,
+                                     struct dasd_eckd_private *private)
+{
+       struct dasd_uid device_uid;
+
+       create_uid(private);
+       dasd_eckd_get_uid(device, &device_uid);
+
+       return memcmp(&device_uid, &private->uid, sizeof(struct dasd_uid));
+}
+
 static void dasd_eckd_fill_rcd_cqr(struct dasd_device *device,
                                   struct dasd_ccw_req *cqr,
                                   __u8 *rcd_buffer,
@@ -1005,59 +1026,120 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
        int conf_len, conf_data_saved;
        int rc;
        __u8 lpm, opm;
-       struct dasd_eckd_private *private;
+       struct dasd_eckd_private *private, path_private;
        struct dasd_path *path_data;
+       struct dasd_uid *uid;
+       char print_path_uid[60], print_device_uid[60];
 
        private = (struct dasd_eckd_private *) device->private;
        path_data = &device->path_data;
        opm = ccw_device_get_path_mask(device->cdev);
-       lpm = 0x80;
        conf_data_saved = 0;
        /* get configuration data per operational path */
        for (lpm = 0x80; lpm; lpm>>= 1) {
-               if (lpm & opm) {
-                       rc = dasd_eckd_read_conf_lpm(device, &conf_data,
-                                                    &conf_len, lpm);
-                       if (rc && rc != -EOPNOTSUPP) {  /* -EOPNOTSUPP is ok */
-                               DBF_EVENT_DEVID(DBF_WARNING, device->cdev,
-                                         "Read configuration data returned "
-                                         "error %d", rc);
-                               return rc;
-                       }
-                       if (conf_data == NULL) {
-                               DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
-                                               "No configuration data "
-                                               "retrieved");
-                               /* no further analysis possible */
-                               path_data->opm |= lpm;
-                               continue;       /* no error */
+               if (!(lpm & opm))
+                       continue;
+               rc = dasd_eckd_read_conf_lpm(device, &conf_data,
+                                            &conf_len, lpm);
+               if (rc && rc != -EOPNOTSUPP) {  /* -EOPNOTSUPP is ok */
+                       DBF_EVENT_DEVID(DBF_WARNING, device->cdev,
+                                       "Read configuration data returned "
+                                       "error %d", rc);
+                       return rc;
+               }
+               if (conf_data == NULL) {
+                       DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
+                                       "No configuration data "
+                                       "retrieved");
+                       /* no further analysis possible */
+                       path_data->opm |= lpm;
+                       continue;       /* no error */
+               }
+               /* save first valid configuration data */
+               if (!conf_data_saved) {
+                       kfree(private->conf_data);
+                       private->conf_data = conf_data;
+                       private->conf_len = conf_len;
+                       if (dasd_eckd_identify_conf_parts(private)) {
+                               private->conf_data = NULL;
+                               private->conf_len = 0;
+                               kfree(conf_data);
+                               continue;
                        }
-                       /* save first valid configuration data */
-                       if (!conf_data_saved) {
-                               kfree(private->conf_data);
-                               private->conf_data = conf_data;
-                               private->conf_len = conf_len;
-                               if (dasd_eckd_identify_conf_parts(private)) {
-                                       private->conf_data = NULL;
-                                       private->conf_len = 0;
-                                       kfree(conf_data);
-                                       continue;
-                               }
-                               conf_data_saved++;
+                       /*
+                        * build device UID that other path data
+                        * can be compared to it
+                        */
+                       dasd_eckd_generate_uid(device);
+                       conf_data_saved++;
+               } else {
+                       path_private.conf_data = conf_data;
+                       path_private.conf_len = DASD_ECKD_RCD_DATA_SIZE;
+                       if (dasd_eckd_identify_conf_parts(
+                                   &path_private)) {
+                               path_private.conf_data = NULL;
+                               path_private.conf_len = 0;
+                               kfree(conf_data);
+                               continue;
                        }
-                       switch (dasd_eckd_path_access(conf_data, conf_len)) {
-                       case 0x02:
-                               path_data->npm |= lpm;
-                               break;
-                       case 0x03:
-                               path_data->ppm |= lpm;
-                               break;
+
+                       if (dasd_eckd_compare_path_uid(
+                                   device, &path_private)) {
+                               uid = &path_private.uid;
+                               if (strlen(uid->vduit) > 0)
+                                       snprintf(print_path_uid,
+                                                sizeof(print_path_uid),
+                                                "%s.%s.%04x.%02x.%s",
+                                                uid->vendor, uid->serial,
+                                                uid->ssid, uid->real_unit_addr,
+                                                uid->vduit);
+                               else
+                                       snprintf(print_path_uid,
+                                                sizeof(print_path_uid),
+                                                "%s.%s.%04x.%02x",
+                                                uid->vendor, uid->serial,
+                                                uid->ssid,
+                                                uid->real_unit_addr);
+                               uid = &private->uid;
+                               if (strlen(uid->vduit) > 0)
+                                       snprintf(print_device_uid,
+                                                sizeof(print_device_uid),
+                                                "%s.%s.%04x.%02x.%s",
+                                                uid->vendor, uid->serial,
+                                                uid->ssid, uid->real_unit_addr,
+                                                uid->vduit);
+                               else
+                                       snprintf(print_device_uid,
+                                                sizeof(print_device_uid),
+                                                "%s.%s.%04x.%02x",
+                                                uid->vendor, uid->serial,
+                                                uid->ssid,
+                                                uid->real_unit_addr);
+                               dev_err(&device->cdev->dev,
+                                       "Not all channel paths lead to "
+                                       "the same device, path %02X leads to "
+                                       "device %s instead of %s\n", lpm,
+                                       print_path_uid, print_device_uid);
+                               return -EINVAL;
                        }
-                       path_data->opm |= lpm;
-                       if (conf_data != private->conf_data)
-                               kfree(conf_data);
+
+                       path_private.conf_data = NULL;
+                       path_private.conf_len = 0;
                }
+               switch (dasd_eckd_path_access(conf_data, conf_len)) {
+               case 0x02:
+                       path_data->npm |= lpm;
+                       break;
+               case 0x03:
+                       path_data->ppm |= lpm;
+                       break;
+               }
+               path_data->opm |= lpm;
+
+               if (conf_data != private->conf_data)
+                       kfree(conf_data);
        }
+
        return 0;
 }
 
@@ -1090,12 +1172,61 @@ static int verify_fcx_max_data(struct dasd_device *device, __u8 lpm)
        return 0;
 }
 
+static int rebuild_device_uid(struct dasd_device *device,
+                             struct path_verification_work_data *data)
+{
+       struct dasd_eckd_private *private;
+       struct dasd_path *path_data;
+       __u8 lpm, opm;
+       int rc;
+
+       rc = -ENODEV;
+       private = (struct dasd_eckd_private *) device->private;
+       path_data = &device->path_data;
+       opm = device->path_data.opm;
+
+       for (lpm = 0x80; lpm; lpm >>= 1) {
+               if (!(lpm & opm))
+                       continue;
+               memset(&data->rcd_buffer, 0, sizeof(data->rcd_buffer));
+               memset(&data->cqr, 0, sizeof(data->cqr));
+               data->cqr.cpaddr = &data->ccw;
+               rc = dasd_eckd_read_conf_immediately(device, &data->cqr,
+                                                    data->rcd_buffer,
+                                                    lpm);
+
+               if (rc) {
+                       if (rc == -EOPNOTSUPP) /* -EOPNOTSUPP is ok */
+                               continue;
+                       DBF_EVENT_DEVID(DBF_WARNING, device->cdev,
+                                       "Read configuration data "
+                                       "returned error %d", rc);
+                       break;
+               }
+               memcpy(private->conf_data, data->rcd_buffer,
+                      DASD_ECKD_RCD_DATA_SIZE);
+               if (dasd_eckd_identify_conf_parts(private)) {
+                       rc = -ENODEV;
+               } else /* first valid path is enough */
+                       break;
+       }
+
+       if (!rc)
+               rc = dasd_eckd_generate_uid(device);
+
+       return rc;
+}
+
 static void do_path_verification_work(struct work_struct *work)
 {
        struct path_verification_work_data *data;
        struct dasd_device *device;
+       struct dasd_eckd_private path_private;
+       struct dasd_uid *uid;
+       __u8 path_rcd_buf[DASD_ECKD_RCD_DATA_SIZE];
        __u8 lpm, opm, npm, ppm, epm;
        unsigned long flags;
+       char print_uid[60];
        int rc;
 
        data = container_of(work, struct path_verification_work_data, worker);
@@ -1112,64 +1243,129 @@ static void do_path_verification_work(struct work_struct *work)
        ppm = 0;
        epm = 0;
        for (lpm = 0x80; lpm; lpm >>= 1) {
-               if (lpm & data->tbvpm) {
-                       memset(data->rcd_buffer, 0, sizeof(data->rcd_buffer));
-                       memset(&data->cqr, 0, sizeof(data->cqr));
-                       data->cqr.cpaddr = &data->ccw;
-                       rc = dasd_eckd_read_conf_immediately(device, &data->cqr,
-                                                            data->rcd_buffer,
-                                                            lpm);
-                       if (!rc) {
-                               switch (dasd_eckd_path_access(data->rcd_buffer,
-                                                    DASD_ECKD_RCD_DATA_SIZE)) {
-                               case 0x02:
-                                       npm |= lpm;
-                                       break;
-                               case 0x03:
-                                       ppm |= lpm;
-                                       break;
-                               }
-                               opm |= lpm;
-                       } else if (rc == -EOPNOTSUPP) {
-                               DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
-                                      "path verification: No configuration "
-                                      "data retrieved");
-                               opm |= lpm;
-                       } else if (rc == -EAGAIN) {
-                               DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
+               if (!(lpm & data->tbvpm))
+                       continue;
+               memset(&data->rcd_buffer, 0, sizeof(data->rcd_buffer));
+               memset(&data->cqr, 0, sizeof(data->cqr));
+               data->cqr.cpaddr = &data->ccw;
+               rc = dasd_eckd_read_conf_immediately(device, &data->cqr,
+                                                    data->rcd_buffer,
+                                                    lpm);
+               if (!rc) {
+                       switch (dasd_eckd_path_access(data->rcd_buffer,
+                                                     DASD_ECKD_RCD_DATA_SIZE)
+                               ) {
+                       case 0x02:
+                               npm |= lpm;
+                               break;
+                       case 0x03:
+                               ppm |= lpm;
+                               break;
+                       }
+                       opm |= lpm;
+               } else if (rc == -EOPNOTSUPP) {
+                       DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
+                                       "path verification: No configuration "
+                                       "data retrieved");
+                       opm |= lpm;
+               } else if (rc == -EAGAIN) {
+                       DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
                                        "path verification: device is stopped,"
                                        " try again later");
-                               epm |= lpm;
-                       } else {
-                               dev_warn(&device->cdev->dev,
-                                        "Reading device feature codes failed "
-                                        "(rc=%d) for new path %x\n", rc, lpm);
-                               continue;
-                       }
-                       if (verify_fcx_max_data(device, lpm)) {
+                       epm |= lpm;
+               } else {
+                       dev_warn(&device->cdev->dev,
+                                "Reading device feature codes failed "
+                                "(rc=%d) for new path %x\n", rc, lpm);
+                       continue;
+               }
+               if (verify_fcx_max_data(device, lpm)) {
+                       opm &= ~lpm;
+                       npm &= ~lpm;
+                       ppm &= ~lpm;
+                       continue;
+               }
+
+               /*
+                * save conf_data for comparison after
+                * rebuild_device_uid may have changed
+                * the original data
+                */
+               memcpy(&path_rcd_buf, data->rcd_buffer,
+                      DASD_ECKD_RCD_DATA_SIZE);
+               path_private.conf_data = (void *) &path_rcd_buf;
+               path_private.conf_len = DASD_ECKD_RCD_DATA_SIZE;
+               if (dasd_eckd_identify_conf_parts(&path_private)) {
+                       path_private.conf_data = NULL;
+                       path_private.conf_len = 0;
+                       continue;
+               }
+
+               /*
+                * compare path UID with device UID only if at least
+                * one valid path is left
+                * in other case the device UID may have changed and
+                * the first working path UID will be used as device UID
+                */
+               if (device->path_data.opm &&
+                   dasd_eckd_compare_path_uid(device, &path_private)) {
+                       /*
+                        * the comparison was not successful
+                        * rebuild the device UID with at least one
+                        * known path in case a z/VM hyperswap command
+                        * has changed the device
+                        *
+                        * after this compare again
+                        *
+                        * if either the rebuild or the recompare fails
+                        * the path can not be used
+                        */
+                       if (rebuild_device_uid(device, data) ||
+                           dasd_eckd_compare_path_uid(
+                                   device, &path_private)) {
+                               uid = &path_private.uid;
+                               if (strlen(uid->vduit) > 0)
+                                       snprintf(print_uid, sizeof(print_uid),
+                                                "%s.%s.%04x.%02x.%s",
+                                                uid->vendor, uid->serial,
+                                                uid->ssid, uid->real_unit_addr,
+                                                uid->vduit);
+                               else
+                                       snprintf(print_uid, sizeof(print_uid),
+                                                "%s.%s.%04x.%02x",
+                                                uid->vendor, uid->serial,
+                                                uid->ssid,
+                                                uid->real_unit_addr);
+                               dev_err(&device->cdev->dev,
+                                       "The newly added channel path %02X "
+                                       "will not be used because it leads "
+                                       "to a different device %s\n",
+                                       lpm, print_uid);
                                opm &= ~lpm;
                                npm &= ~lpm;
                                ppm &= ~lpm;
+                               continue;
                        }
                }
+
+               /*
+                * There is a small chance that a path is lost again between
+                * above path verification and the following modification of
+                * the device opm mask. We could avoid that race here by using
+                * yet another path mask, but we rather deal with this unlikely
+                * situation in dasd_start_IO.
+                */
+               spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+               if (!device->path_data.opm && opm) {
+                       device->path_data.opm = opm;
+                       dasd_generic_path_operational(device);
+               } else
+                       device->path_data.opm |= opm;
+               device->path_data.npm |= npm;
+               device->path_data.ppm |= ppm;
+               device->path_data.tbvpm |= epm;
+               spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
        }
-       /*
-        * There is a small chance that a path is lost again between
-        * above path verification and the following modification of
-        * the device opm mask. We could avoid that race here by using
-        * yet another path mask, but we rather deal with this unlikely
-        * situation in dasd_start_IO.
-        */
-       spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
-       if (!device->path_data.opm && opm) {
-               device->path_data.opm = opm;
-               dasd_generic_path_operational(device);
-       } else
-               device->path_data.opm |= opm;
-       device->path_data.npm |= npm;
-       device->path_data.ppm |= ppm;
-       device->path_data.tbvpm |= epm;
-       spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
 
        dasd_put_device(device);
        if (data->isglobal)
@@ -1441,11 +1637,6 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
                        device->default_expires = value;
        }
 
-       /* Generate device unique id */
-       rc = dasd_eckd_generate_uid(device);
-       if (rc)
-               goto out_err1;
-
        dasd_eckd_get_uid(device, &temp_uid);
        if (temp_uid.type == UA_BASE_DEVICE) {
                block = dasd_alloc_block();
@@ -2206,7 +2397,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single(
                                           sizeof(struct PFX_eckd_data));
        } else {
                if (define_extent(ccw++, cqr->data, first_trk,
-                                 last_trk, cmd, startdev) == -EAGAIN) {
+                                 last_trk, cmd, basedev) == -EAGAIN) {
                        /* Clock not in sync and XRC is enabled.
                         * Try again later.
                         */