Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / s390 / block / dasd_eckd.c
index df89023..7d5a6ce 100644 (file)
@@ -7,7 +7,6 @@
  * Bugreports.to..: <Linux390@de.ibm.com>
  * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
  *
- * $Revision: 1.68 $
  */
 
 #include <linux/config.h>
@@ -56,6 +55,7 @@ static struct dasd_discipline dasd_eckd_discipline;
 struct dasd_eckd_private {
        struct dasd_eckd_characteristics rdc_data;
        struct dasd_eckd_confdata conf_data;
+       struct dasd_eckd_path path_data;
        struct eckd_count count_area[5];
        int init_cqr_status;
        int uses_cdl;
@@ -446,13 +446,110 @@ dasd_eckd_cdl_reclen(int recid)
        return LABEL_SIZE;
 }
 
+/*
+ * Generate device unique id that specifies the physical device.
+ */
+static int
+dasd_eckd_generate_uid(struct dasd_device *device, struct dasd_uid *uid)
+{
+       struct dasd_eckd_private *private;
+       struct dasd_eckd_confdata *confdata;
+
+       private = (struct dasd_eckd_private *) device->private;
+       if (!private)
+               return -ENODEV;
+       confdata = &private->conf_data;
+       if (!confdata)
+               return -ENODEV;
+
+       memset(uid, 0, sizeof(struct dasd_uid));
+       strncpy(uid->vendor, confdata->ned1.HDA_manufacturer,
+               sizeof(uid->vendor) - 1);
+       EBCASC(uid->vendor, sizeof(uid->vendor) - 1);
+       strncpy(uid->serial, confdata->ned1.HDA_location,
+               sizeof(uid->serial) - 1);
+       EBCASC(uid->serial, sizeof(uid->serial) - 1);
+       uid->ssid = confdata->neq.subsystemID;
+       if (confdata->ned2.sneq.flags == 0x40) {
+               uid->alias = 1;
+               uid->unit_addr = confdata->ned2.sneq.base_unit_addr;
+       } else
+               uid->unit_addr = confdata->ned1.unit_addr;
+
+       return 0;
+}
+
+static int
+dasd_eckd_read_conf(struct dasd_device *device)
+{
+       void *conf_data;
+       int conf_len, conf_data_saved;
+       int rc;
+       __u8 lpm;
+       struct dasd_eckd_private *private;
+       struct dasd_eckd_path *path_data;
+
+       private = (struct dasd_eckd_private *) device->private;
+       path_data = (struct dasd_eckd_path *) &private->path_data;
+       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 & path_data->opm){
+                       rc = read_conf_data_lpm(device->cdev, &conf_data,
+                                               &conf_len, lpm);
+                       if (rc && rc != -EOPNOTSUPP) {  /* -EOPNOTSUPP is ok */
+                               MESSAGE(KERN_WARNING,
+                                       "Read configuration data returned "
+                                       "error %d", rc);
+                               return rc;
+                       }
+                       if (conf_data == NULL) {
+                               MESSAGE(KERN_WARNING, "%s", "No configuration "
+                                       "data retrieved");
+                               continue;       /* no errror */
+                       }
+                       if (conf_len != sizeof (struct dasd_eckd_confdata)) {
+                               MESSAGE(KERN_WARNING,
+                                       "sizes of configuration data mismatch"
+                                       "%d (read) vs %ld (expected)",
+                                       conf_len,
+                                       sizeof (struct dasd_eckd_confdata));
+                               kfree(conf_data);
+                               continue;       /* no errror */
+                       }
+                       /* save first valid configuration data */
+                       if (!conf_data_saved){
+                               memcpy(&private->conf_data, conf_data,
+                                      sizeof (struct dasd_eckd_confdata));
+                               conf_data_saved++;
+                       }
+                       switch (((char *)conf_data)[242] & 0x07){
+                       case 0x02:
+                               path_data->npm |= lpm;
+                               break;
+                       case 0x03:
+                               path_data->ppm |= lpm;
+                               break;
+                       }
+                       kfree(conf_data);
+               }
+       }
+       return 0;
+}
+
+/*
+ * Check device characteristics.
+ * If the device is accessible using ECKD discipline, the device is enabled.
+ */
 static int
 dasd_eckd_check_characteristics(struct dasd_device *device)
 {
        struct dasd_eckd_private *private;
+       struct dasd_uid uid;
        void *rdc_data;
-       void *conf_data;
-       int conf_len;
        int rc;
 
        private = (struct dasd_eckd_private *) device->private;
@@ -465,6 +562,7 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
                                    "data");
                        return -ENOMEM;
                }
+               memset(private, 0, sizeof(struct dasd_eckd_private));
                device->private = (void *) private;
        }
        /* Invalidate status of initial analysis. */
@@ -475,6 +573,7 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
 
        /* Read Device Characteristics */
        rdc_data = (void *) &(private->rdc_data);
+       memset(rdc_data, 0, sizeof(rdc_data));
        rc = read_dev_chars(device->cdev, &rdc_data, 64);
        if (rc) {
                DEV_MESSAGE(KERN_WARNING, device,
@@ -494,30 +593,18 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
                    private->rdc_data.sec_per_trk);
 
        /* Read Configuration Data */
-       rc = read_conf_data(device->cdev, &conf_data, &conf_len);
-       if (rc && rc != -EOPNOTSUPP) {  /* -EOPNOTSUPP is ok */
-               DEV_MESSAGE(KERN_WARNING, device,
-                           "Read configuration data returned error %d", rc);
+       rc = dasd_eckd_read_conf (device);
+       if (rc)
                return rc;
-       }
-       if (conf_data == NULL) {
-               DEV_MESSAGE(KERN_WARNING, device, "%s",
-                           "No configuration data retrieved");
-               return 0;       /* no errror */
-       }
-       if (conf_len != sizeof (struct dasd_eckd_confdata)) {
-               DEV_MESSAGE(KERN_WARNING, device,
-                           "sizes of configuration data mismatch"
-                           "%d (read) vs %ld (expected)",
-                           conf_len, sizeof (struct dasd_eckd_confdata));
 
-               kfree(conf_data); /* allocated by read_conf_data() */
-               return 0;       /* no errror */
-       }
-       memcpy(&private->conf_data, conf_data,
-              sizeof (struct dasd_eckd_confdata));
-       kfree(conf_data); /* allocated by read_conf_data() */
-       return 0;
+       /* Generate device unique id and register in devmap */
+       rc = dasd_eckd_generate_uid(device, &uid);
+       if (rc)
+               return rc;
+
+       rc = dasd_set_uid(device->cdev, &uid);
+
+       return rc;
 }
 
 static struct dasd_ccw_req *
@@ -1000,7 +1087,7 @@ dasd_eckd_build_cp(struct dasd_device * device, struct request *req)
                                /* Eckd can only do full blocks. */
                                return ERR_PTR(-EINVAL);
                        count += bv->bv_len >> (device->s2b_shift + 9);
-#if defined(CONFIG_ARCH_S390X)
+#if defined(CONFIG_64BIT)
                        if (idal_is_needed (page_address(bv->bv_page),
                                            bv->bv_len))
                                cidaw += bv->bv_len >> (device->s2b_shift + 9);
@@ -1060,7 +1147,8 @@ dasd_eckd_build_cp(struct dasd_device * device, struct request *req)
                                if (dasd_eckd_cdl_special(blk_per_trk, recid)){
                                        rcmd |= 0x8;
                                        count = dasd_eckd_cdl_reclen(recid);
-                                       if (count < blksize)
+                                       if (count < blksize &&
+                                           rq_data_dir(req) == READ)
                                                memset(dst + count, 0xe5,
                                                       blksize - count);
                                }
@@ -1094,9 +1182,11 @@ dasd_eckd_build_cp(struct dasd_device * device, struct request *req)
                        recid++;
                }
        }
+       if (req->flags & REQ_FAILFAST)
+               set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
        cqr->device = device;
        cqr->expires = 5 * 60 * HZ;     /* 5 minutes */
-       cqr->lpm = LPM_ANYPATH;
+       cqr->lpm = private->path_data.ppm;
        cqr->retries = 256;
        cqr->buildclk = get_clock();
        cqr->status = DASD_CQR_FILLED;
@@ -1184,19 +1274,14 @@ dasd_eckd_fill_info(struct dasd_device * device,
  * (see dasd_eckd_reserve) device.
  */
 static int
-dasd_eckd_release(struct block_device *bdev, int no, long args)
+dasd_eckd_release(struct dasd_device *device)
 {
-       struct dasd_device *device;
        struct dasd_ccw_req *cqr;
        int rc;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EACCES;
 
-       device = bdev->bd_disk->private_data;
-       if (device == NULL)
-               return -ENODEV;
-
        cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
                                   1, 32, device);
        if (IS_ERR(cqr)) {
@@ -1210,6 +1295,7 @@ dasd_eckd_release(struct block_device *bdev, int no, long args)
        cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
        cqr->device = device;
        clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+       set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
        cqr->retries = 0;
        cqr->expires = 2 * HZ;
        cqr->buildclk = get_clock();
@@ -1228,19 +1314,14 @@ dasd_eckd_release(struct block_device *bdev, int no, long args)
  * the interrupt is outstanding for a certain time. 
  */
 static int
-dasd_eckd_reserve(struct block_device *bdev, int no, long args)
+dasd_eckd_reserve(struct dasd_device *device)
 {
-       struct dasd_device *device;
        struct dasd_ccw_req *cqr;
        int rc;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EACCES;
 
-       device = bdev->bd_disk->private_data;
-       if (device == NULL)
-               return -ENODEV;
-
        cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
                                   1, 32, device);
        if (IS_ERR(cqr)) {
@@ -1254,6 +1335,7 @@ dasd_eckd_reserve(struct block_device *bdev, int no, long args)
        cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
        cqr->device = device;
        clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+       set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
        cqr->retries = 0;
        cqr->expires = 2 * HZ;
        cqr->buildclk = get_clock();
@@ -1271,19 +1353,14 @@ dasd_eckd_reserve(struct block_device *bdev, int no, long args)
  * (unconditional reserve)
  */
 static int
-dasd_eckd_steal_lock(struct block_device *bdev, int no, long args)
+dasd_eckd_steal_lock(struct dasd_device *device)
 {
-       struct dasd_device *device;
        struct dasd_ccw_req *cqr;
        int rc;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EACCES;
 
-       device = bdev->bd_disk->private_data;
-       if (device == NULL)
-               return -ENODEV;
-
        cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
                                   1, 32, device);
        if (IS_ERR(cqr)) {
@@ -1297,6 +1374,7 @@ dasd_eckd_steal_lock(struct block_device *bdev, int no, long args)
        cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
        cqr->device = device;
        clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+       set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
        cqr->retries = 0;
        cqr->expires = 2 * HZ;
        cqr->buildclk = get_clock();
@@ -1312,19 +1390,14 @@ dasd_eckd_steal_lock(struct block_device *bdev, int no, long args)
  * Read performance statistics
  */
 static int
-dasd_eckd_performance(struct block_device *bdev, int no, long args)
+dasd_eckd_performance(struct dasd_device *device, void __user *argp)
 {
-       struct dasd_device *device;
        struct dasd_psf_prssd_data *prssdp;
        struct dasd_rssd_perf_stats_t *stats;
        struct dasd_ccw_req *cqr;
        struct ccw1 *ccw;
        int rc;
 
-       device = bdev->bd_disk->private_data;
-       if (device == NULL)
-               return -ENODEV;
-
        cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
                                   1 /* PSF */  + 1 /* RSSD */ ,
                                   (sizeof (struct dasd_psf_prssd_data) +
@@ -1368,8 +1441,9 @@ dasd_eckd_performance(struct block_device *bdev, int no, long args)
                /* Prepare for Read Subsystem Data */
                prssdp = (struct dasd_psf_prssd_data *) cqr->data;
                stats = (struct dasd_rssd_perf_stats_t *) (prssdp + 1);
-               rc = copy_to_user((long __user *) args, (long *) stats,
-                                 sizeof(struct dasd_rssd_perf_stats_t));
+               if (copy_to_user(argp, stats,
+                                sizeof(struct dasd_rssd_perf_stats_t)))
+                       rc = -EFAULT;
        }
        dasd_sfree_request(cqr, cqr->device);
        return rc;
@@ -1380,27 +1454,22 @@ dasd_eckd_performance(struct block_device *bdev, int no, long args)
  * Returnes the cache attributes used in Define Extend (DE).
  */
 static int
-dasd_eckd_get_attrib (struct block_device *bdev, int no, long args)
+dasd_eckd_get_attrib(struct dasd_device *device, void __user *argp)
 {
-       struct dasd_device *device;
-        struct dasd_eckd_private *private;
-        struct attrib_data_t attrib;
+       struct dasd_eckd_private *private =
+               (struct dasd_eckd_private *)device->private;
+       struct attrib_data_t attrib = private->attrib;
        int rc;
 
         if (!capable(CAP_SYS_ADMIN))
                 return -EACCES;
-        if (!args)
+       if (!argp)
                 return -EINVAL;
 
-        device = bdev->bd_disk->private_data;
-        if (device == NULL)
-                return -ENODEV;
-
-        private = (struct dasd_eckd_private *) device->private;
-        attrib = private->attrib;
-
-        rc = copy_to_user((long __user *) args, (long *) &attrib,
-                         sizeof (struct attrib_data_t));
+       rc = 0;
+       if (copy_to_user(argp, (long *) &attrib,
+                        sizeof (struct attrib_data_t)))
+               rc = -EFAULT;
 
        return rc;
 }
@@ -1410,26 +1479,19 @@ dasd_eckd_get_attrib (struct block_device *bdev, int no, long args)
  * Stores the attributes for cache operation to be used in Define Extend (DE).
  */
 static int
-dasd_eckd_set_attrib(struct block_device *bdev, int no, long args)
+dasd_eckd_set_attrib(struct dasd_device *device, void __user *argp)
 {
-       struct dasd_device *device;
-       struct dasd_eckd_private *private;
+       struct dasd_eckd_private *private =
+               (struct dasd_eckd_private *)device->private;
        struct attrib_data_t attrib;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EACCES;
-       if (!args)
+       if (!argp)
                return -EINVAL;
 
-       device = bdev->bd_disk->private_data;
-       if (device == NULL)
-               return -ENODEV;
-
-       if (copy_from_user(&attrib, (void __user *) args,
-                          sizeof (struct attrib_data_t))) {
+       if (copy_from_user(&attrib, argp, sizeof(struct attrib_data_t)))
                return -EFAULT;
-       }
-       private = (struct dasd_eckd_private *) device->private;
        private->attrib = attrib;
 
        DEV_MESSAGE(KERN_INFO, device,
@@ -1438,6 +1500,27 @@ dasd_eckd_set_attrib(struct block_device *bdev, int no, long args)
        return 0;
 }
 
+static int
+dasd_eckd_ioctl(struct dasd_device *device, unsigned int cmd, void __user *argp)
+{
+       switch (cmd) {
+       case BIODASDGATTR:
+               return dasd_eckd_get_attrib(device, argp);
+       case BIODASDSATTR:
+               return dasd_eckd_set_attrib(device, argp);
+       case BIODASDPSRD:
+               return dasd_eckd_performance(device, argp);
+       case BIODASDRLSE:
+               return dasd_eckd_release(device);
+       case BIODASDRSRV:
+               return dasd_eckd_reserve(device);
+       case BIODASDSLCK:
+               return dasd_eckd_steal_lock(device);
+       default:
+               return -ENOIOCTLCMD;
+       }
+}
+
 /*
  * Print sense data and related channel program.
  * Parts are printed because printk buffer is only 1024 bytes.
@@ -1596,6 +1679,7 @@ static struct dasd_discipline dasd_eckd_discipline = {
        .free_cp = dasd_eckd_free_cp,
        .dump_sense = dasd_eckd_dump_sense,
        .fill_info = dasd_eckd_fill_info,
+       .ioctl = dasd_eckd_ioctl,
 };
 
 static int __init
@@ -1603,59 +1687,18 @@ dasd_eckd_init(void)
 {
        int ret;
 
-       dasd_ioctl_no_register(THIS_MODULE, BIODASDGATTR,
-                              dasd_eckd_get_attrib);
-       dasd_ioctl_no_register(THIS_MODULE, BIODASDSATTR,
-                              dasd_eckd_set_attrib);
-       dasd_ioctl_no_register(THIS_MODULE, BIODASDPSRD,
-                              dasd_eckd_performance);
-       dasd_ioctl_no_register(THIS_MODULE, BIODASDRLSE,
-                              dasd_eckd_release);
-       dasd_ioctl_no_register(THIS_MODULE, BIODASDRSRV,
-                              dasd_eckd_reserve);
-       dasd_ioctl_no_register(THIS_MODULE, BIODASDSLCK,
-                              dasd_eckd_steal_lock);
-
        ASCEBC(dasd_eckd_discipline.ebcname, 4);
 
        ret = ccw_driver_register(&dasd_eckd_driver);
-       if (ret) {
-               dasd_ioctl_no_unregister(THIS_MODULE, BIODASDGATTR,
-                                        dasd_eckd_get_attrib);
-               dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSATTR,
-                                        dasd_eckd_set_attrib);
-               dasd_ioctl_no_unregister(THIS_MODULE, BIODASDPSRD,
-                                        dasd_eckd_performance);
-               dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRLSE,
-                                        dasd_eckd_release);
-               dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRSRV,
-                                        dasd_eckd_reserve);
-               dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSLCK,
-                                        dasd_eckd_steal_lock);
-               return ret;
-       }
-
-       dasd_generic_auto_online(&dasd_eckd_driver);
-       return 0;
+       if (!ret)
+               dasd_generic_auto_online(&dasd_eckd_driver);
+       return ret;
 }
 
 static void __exit
 dasd_eckd_cleanup(void)
 {
        ccw_driver_unregister(&dasd_eckd_driver);
-
-       dasd_ioctl_no_unregister(THIS_MODULE, BIODASDGATTR,
-                                dasd_eckd_get_attrib);
-       dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSATTR,
-                                dasd_eckd_set_attrib);
-       dasd_ioctl_no_unregister(THIS_MODULE, BIODASDPSRD,
-                                dasd_eckd_performance);
-       dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRLSE,
-                                dasd_eckd_release);
-       dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRSRV,
-                                dasd_eckd_reserve);
-       dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSLCK,
-                                dasd_eckd_steal_lock);
 }
 
 module_init(dasd_eckd_init);