[S390] dasd: automatic recognition of read-only devices
Stefan Weinhuber [Mon, 8 Mar 2010 11:26:24 +0000 (12:26 +0100)]
In z/VM it is possible to attach a device as read-only. To prevent
unintentional write requests and subsequent I/O errors, we can detect
this configuration using the z/VM DIAG 210 interface and set the
respective linux block device to read-only as well.

Signed-off-by: Stefan Weinhuber <wein@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_devmap.c
drivers/s390/block/dasd_diag.c
drivers/s390/block/dasd_eckd.c
drivers/s390/block/dasd_fba.c
drivers/s390/block/dasd_genhd.c
drivers/s390/block/dasd_int.h
drivers/s390/block/dasd_ioctl.c

index 4951aa8..bbea90b 100644 (file)
@@ -26,6 +26,7 @@
 #include <asm/ebcdic.h>
 #include <asm/idals.h>
 #include <asm/itcw.h>
+#include <asm/diag.h>
 
 /* This is ugly... */
 #define PRINTK_HEADER "dasd:"
@@ -2212,6 +2213,13 @@ static int dasd_open(struct block_device *bdev, fmode_t mode)
                goto out;
        }
 
+       if ((mode & FMODE_WRITE) &&
+           (test_bit(DASD_FLAG_DEVICE_RO, &base->flags) ||
+            (base->features & DASD_FEATURE_READONLY))) {
+               rc = -EROFS;
+               goto out;
+       }
+
        return 0;
 
 out:
@@ -2289,6 +2297,34 @@ dasd_exit(void)
  * SECTION: common functions for ccw_driver use
  */
 
+/*
+ * Is the device read-only?
+ * Note that this function does not report the setting of the
+ * readonly device attribute, but how it is configured in z/VM.
+ */
+int dasd_device_is_ro(struct dasd_device *device)
+{
+       struct ccw_dev_id dev_id;
+       struct diag210 diag_data;
+       int rc;
+
+       if (!MACHINE_IS_VM)
+               return 0;
+       ccw_device_get_id(device->cdev, &dev_id);
+       memset(&diag_data, 0, sizeof(diag_data));
+       diag_data.vrdcdvno = dev_id.devno;
+       diag_data.vrdclen = sizeof(diag_data);
+       rc = diag210(&diag_data);
+       if (rc == 0 || rc == 2) {
+               return diag_data.vrdcvfla & 0x80;
+       } else {
+               DBF_EVENT(DBF_WARNING, "diag210 failed for dev=%04x with rc=%d",
+                         dev_id.devno, rc);
+               return 0;
+       }
+}
+EXPORT_SYMBOL_GPL(dasd_device_is_ro);
+
 static void dasd_generic_auto_online(void *data, async_cookie_t cookie)
 {
        struct ccw_device *cdev = data;
index 44796ba..51224f7 100644 (file)
@@ -1045,6 +1045,10 @@ dasd_3990_erp_com_rej(struct dasd_ccw_req * erp, char *sense)
 
                erp->retries = 5;
 
+       } else if (sense[1] & SNS1_WRITE_INHIBITED) {
+               dev_err(&device->cdev->dev, "An I/O request was rejected"
+                       " because writing is inhibited\n");
+               erp = dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
        } else {
                /* fatal error -  set status to FAILED
                   internal error 09 - Command Reject */
index d49766f..8e23919 100644 (file)
@@ -742,6 +742,7 @@ dasd_ro_store(struct device *dev, struct device_attribute *attr,
              const char *buf, size_t count)
 {
        struct dasd_devmap *devmap;
+       struct dasd_device *device;
        int val;
        char *endp;
 
@@ -758,12 +759,14 @@ dasd_ro_store(struct device *dev, struct device_attribute *attr,
                devmap->features |= DASD_FEATURE_READONLY;
        else
                devmap->features &= ~DASD_FEATURE_READONLY;
-       if (devmap->device)
-               devmap->device->features = devmap->features;
-       if (devmap->device && devmap->device->block
-           && devmap->device->block->gdp)
-               set_disk_ro(devmap->device->block->gdp, val);
+       device = devmap->device;
+       if (device) {
+               device->features = devmap->features;
+               val = val || test_bit(DASD_FLAG_DEVICE_RO, &device->flags);
+       }
        spin_unlock(&dasd_devmap_lock);
+       if (device && device->block && device->block->gdp)
+               set_disk_ro(device->block->gdp, val);
        return count;
 }
 
index 6e14863..687f323 100644 (file)
@@ -145,12 +145,10 @@ dasd_diag_erp(struct dasd_device *device)
        mdsk_term_io(device);
        rc = mdsk_init_io(device, device->block->bp_block, 0, NULL);
        if (rc == 4) {
-               if (!(device->features & DASD_FEATURE_READONLY)) {
+               if (!(test_and_set_bit(DASD_FLAG_DEVICE_RO, &device->flags)))
                        pr_warning("%s: The access mode of a DIAG device "
                                   "changed to read-only\n",
                                   dev_name(&device->cdev->dev));
-                       device->features |= DASD_FEATURE_READONLY;
-               }
                rc = 0;
        }
        if (rc)
@@ -449,7 +447,7 @@ dasd_diag_check_device(struct dasd_device *device)
                rc = -EIO;
        } else {
                if (rc == 4)
-                       device->features |= DASD_FEATURE_READONLY;
+                       set_bit(DASD_FLAG_DEVICE_RO, &device->flags);
                pr_info("%s: New DASD with %ld byte/block, total size %ld "
                        "KB%s\n", dev_name(&device->cdev->dev),
                        (unsigned long) block->bp_block,
index d7163f9..01f4e7a 100644 (file)
@@ -1089,6 +1089,7 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
        struct dasd_eckd_private *private;
        struct dasd_block *block;
        int is_known, rc;
+       int readonly;
 
        if (!ccw_device_is_pathgroup(device->cdev)) {
                dev_warn(&device->cdev->dev,
@@ -1182,15 +1183,20 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
        else
                private->real_cyl = private->rdc_data.no_cyl;
 
+       readonly = dasd_device_is_ro(device);
+       if (readonly)
+               set_bit(DASD_FLAG_DEVICE_RO, &device->flags);
+
        dev_info(&device->cdev->dev, "New DASD %04X/%02X (CU %04X/%02X) "
-                "with %d cylinders, %d heads, %d sectors\n",
+                "with %d cylinders, %d heads, %d sectors%s\n",
                 private->rdc_data.dev_type,
                 private->rdc_data.dev_model,
                 private->rdc_data.cu_type,
                 private->rdc_data.cu_model.model,
                 private->real_cyl,
                 private->rdc_data.trk_per_cyl,
-                private->rdc_data.sec_per_trk);
+                private->rdc_data.sec_per_trk,
+                readonly ? ", read-only device" : "");
        return 0;
 
 out_err3:
index 0f15244..37282b9 100644 (file)
@@ -124,6 +124,7 @@ dasd_fba_check_characteristics(struct dasd_device *device)
        struct dasd_fba_private *private;
        struct ccw_device *cdev = device->cdev;
        int rc;
+       int readonly;
 
        private = (struct dasd_fba_private *) device->private;
        if (!private) {
@@ -162,16 +163,21 @@ dasd_fba_check_characteristics(struct dasd_device *device)
                return rc;
        }
 
+       readonly = dasd_device_is_ro(device);
+       if (readonly)
+               set_bit(DASD_FLAG_DEVICE_RO, &device->flags);
+
        dev_info(&device->cdev->dev,
                 "New FBA DASD %04X/%02X (CU %04X/%02X) with %d MB "
-                "and %d B/blk\n",
+                "and %d B/blk%s\n",
                 cdev->id.dev_type,
                 cdev->id.dev_model,
                 cdev->id.cu_type,
                 cdev->id.cu_model,
                 ((private->rdc_data.blk_bdsa *
                   (private->rdc_data.blk_size >> 9)) >> 11),
-                private->rdc_data.blk_size);
+                private->rdc_data.blk_size,
+                readonly ? ", read-only device" : "");
        return 0;
 }
 
index 94f92a1..30a1ca3 100644 (file)
@@ -70,7 +70,8 @@ int dasd_gendisk_alloc(struct dasd_block *block)
        }
        len += sprintf(gdp->disk_name + len, "%c", 'a'+(base->devindex%26));
 
-       if (block->base->features & DASD_FEATURE_READONLY)
+       if (base->features & DASD_FEATURE_READONLY ||
+           test_bit(DASD_FLAG_DEVICE_RO, &base->flags))
                set_disk_ro(gdp, 1);
        gdp->private_data = block;
        gdp->queue = block->request_queue;
index ed73ce5..a91d4a9 100644 (file)
@@ -436,6 +436,10 @@ struct dasd_block {
 #define DASD_FLAG_OFFLINE      3       /* device is in offline processing */
 #define DASD_FLAG_EER_SNSS     4       /* A SNSS is required */
 #define DASD_FLAG_EER_IN_USE   5       /* A SNSS request is running */
+#define DASD_FLAG_DEVICE_RO    6       /* The device itself is read-only. Don't
+                                        * confuse this with the user specified
+                                        * read-only feature.
+                                        */
 
 void dasd_put_device_wake(struct dasd_device *);
 
@@ -609,6 +613,9 @@ char *dasd_get_sense(struct irb *);
 void dasd_device_set_stop_bits(struct dasd_device *, int);
 void dasd_device_remove_stop_bits(struct dasd_device *, int);
 
+int dasd_device_is_ro(struct dasd_device *);
+
+
 /* externals in dasd_devmap.c */
 extern int dasd_max_devindex;
 extern int dasd_probeonly;
index 7039d9c..3479f81 100644 (file)
@@ -199,7 +199,8 @@ dasd_ioctl_format(struct block_device *bdev, void __user *argp)
        if (!argp)
                return -EINVAL;
 
-       if (block->base->features & DASD_FEATURE_READONLY)
+       if (block->base->features & DASD_FEATURE_READONLY ||
+           test_bit(DASD_FLAG_DEVICE_RO, &block->base->flags))
                return -EROFS;
        if (copy_from_user(&fdata, argp, sizeof(struct format_data_t)))
                return -EFAULT;
@@ -349,7 +350,8 @@ dasd_ioctl_set_ro(struct block_device *bdev, void __user *argp)
                return -EINVAL;
        if (get_user(intval, (int __user *)argp))
                return -EFAULT;
-
+       if (!intval && test_bit(DASD_FLAG_DEVICE_RO, &block->base->flags))
+               return -EROFS;
        set_disk_ro(bdev->bd_disk, intval);
        return dasd_set_feature(block->base->cdev, DASD_FEATURE_READONLY, intval);
 }