fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / s390 / block / dcssblk.c
index b7a764c..be9b053 100644 (file)
@@ -15,7 +15,7 @@
 #include <asm/io.h>
 #include <linux/completion.h>
 #include <linux/interrupt.h>
-#include <asm/ccwdev.h>        // for s390_root_dev_(un)register()
+#include <asm/s390_rdev.h>
 
 //#define DCSSBLK_DEBUG                /* Debug messages on/off */
 #define DCSSBLK_NAME "dcssblk"
 static int dcssblk_open(struct inode *inode, struct file *filp);
 static int dcssblk_release(struct inode *inode, struct file *filp);
 static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
+static int dcssblk_direct_access(struct block_device *bdev, sector_t secnum,
+                                unsigned long *data);
 
 static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
 
 static int dcssblk_major;
 static struct block_device_operations dcssblk_devops = {
-       .owner   = THIS_MODULE,
-       .open    = dcssblk_open,
-       .release = dcssblk_release,
+       .owner          = THIS_MODULE,
+       .open           = dcssblk_open,
+       .release        = dcssblk_release,
+       .direct_access  = dcssblk_direct_access,
 };
 
-static ssize_t dcssblk_add_store(struct device * dev, const char * buf,
+static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
                                  size_t count);
-static ssize_t dcssblk_remove_store(struct device * dev, const char * buf,
+static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
                                  size_t count);
-static ssize_t dcssblk_save_store(struct device * dev, const char * buf,
+static ssize_t dcssblk_save_store(struct device * dev, struct device_attribute *attr, const char * buf,
                                  size_t count);
-static ssize_t dcssblk_save_show(struct device *dev, char *buf);
-static ssize_t dcssblk_shared_store(struct device * dev, const char * buf,
+static ssize_t dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t dcssblk_shared_store(struct device * dev, struct device_attribute *attr, const char * buf,
                                  size_t count);
-static ssize_t dcssblk_shared_show(struct device *dev, char *buf);
+static ssize_t dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf);
 
 static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
 static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
@@ -139,12 +142,63 @@ dcssblk_get_device_by_name(char *name)
        return NULL;
 }
 
+/*
+ * print appropriate error message for segment_load()/segment_type()
+ * return code
+ */
+static void
+dcssblk_segment_warn(int rc, char* seg_name)
+{
+       switch (rc) {
+       case -ENOENT:
+               PRINT_WARN("cannot load/query segment %s, does not exist\n",
+                          seg_name);
+               break;
+       case -ENOSYS:
+               PRINT_WARN("cannot load/query segment %s, not running on VM\n",
+                          seg_name);
+               break;
+       case -EIO:
+               PRINT_WARN("cannot load/query segment %s, hardware error\n",
+                          seg_name);
+               break;
+       case -ENOTSUPP:
+               PRINT_WARN("cannot load/query segment %s, is a multi-part "
+                          "segment\n", seg_name);
+               break;
+       case -ENOSPC:
+               PRINT_WARN("cannot load/query segment %s, overlaps with "
+                          "storage\n", seg_name);
+               break;
+       case -EBUSY:
+               PRINT_WARN("cannot load/query segment %s, overlaps with "
+                          "already loaded dcss\n", seg_name);
+               break;
+       case -EPERM:
+               PRINT_WARN("cannot load/query segment %s, already loaded in "
+                          "incompatible mode\n", seg_name);
+               break;
+       case -ENOMEM:
+               PRINT_WARN("cannot load/query segment %s, out of memory\n",
+                          seg_name);
+               break;
+       case -ERANGE:
+               PRINT_WARN("cannot load/query segment %s, exceeds kernel "
+                          "mapping range\n", seg_name);
+               break;
+       default:
+               PRINT_WARN("cannot load/query segment %s, return value %i\n",
+                          seg_name, rc);
+               break;
+       }
+}
+
 /*
  * device attribute for switching shared/nonshared (exclusive)
  * operation (show + store)
  */
 static ssize_t
-dcssblk_shared_show(struct device *dev, char *buf)
+dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct dcssblk_dev_info *dev_info;
 
@@ -153,7 +207,7 @@ dcssblk_shared_show(struct device *dev, char *buf)
 }
 
 static ssize_t
-dcssblk_shared_store(struct device *dev, const char *inbuf, size_t count)
+dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
 {
        struct dcssblk_dev_info *dev_info;
        int rc;
@@ -167,80 +221,50 @@ dcssblk_shared_store(struct device *dev, const char *inbuf, size_t count)
        if (atomic_read(&dev_info->use_count)) {
                PRINT_ERR("share: segment %s is busy!\n",
                          dev_info->segment_name);
-               up_write(&dcssblk_devices_sem);
-               return -EBUSY;
-       }
-       if ((inbuf[0] == '1') && (dev_info->is_shared == 1)) {
-               PRINT_WARN("Segment %s already loaded in shared mode!\n",
-                          dev_info->segment_name);
-               up_write(&dcssblk_devices_sem);
-               return count;
-       }
-       if ((inbuf[0] == '0') && (dev_info->is_shared == 0)) {
-               PRINT_WARN("Segment %s already loaded in exclusive mode!\n",
-                          dev_info->segment_name);
-               up_write(&dcssblk_devices_sem);
-               return count;
+               rc = -EBUSY;
+               goto out;
        }
        if (inbuf[0] == '1') {
                // reload segment in shared mode
-               segment_unload(dev_info->segment_name);
-               rc = segment_load(dev_info->segment_name, SEGMENT_SHARED_RO,
-                                       &dev_info->start, &dev_info->end);
+               rc = segment_modify_shared(dev_info->segment_name,
+                                          SEGMENT_SHARED);
                if (rc < 0) {
-                       PRINT_ERR("Segment %s not reloaded, rc=%d\n",
-                                       dev_info->segment_name, rc);
-                       goto removeseg;
+                       BUG_ON(rc == -EINVAL);
+                       if (rc == -EIO || rc == -ENOENT)
+                               goto removeseg;
+               } else {
+                       dev_info->is_shared = 1;
+                       switch (dev_info->segment_type) {
+                               case SEG_TYPE_SR:
+                               case SEG_TYPE_ER:
+                               case SEG_TYPE_SC:
+                                       set_disk_ro(dev_info->gd,1);
+                       }
                }
-               dev_info->is_shared = 1;
-               PRINT_INFO("Segment %s reloaded, shared mode.\n",
-                          dev_info->segment_name);
        } else if (inbuf[0] == '0') {
                // reload segment in exclusive mode
-               segment_unload(dev_info->segment_name);
-               rc = segment_load(dev_info->segment_name, SEGMENT_EXCLUSIVE_RW,
-                                       &dev_info->start, &dev_info->end);
+               if (dev_info->segment_type == SEG_TYPE_SC) {
+                       PRINT_ERR("Segment type SC (%s) cannot be loaded in "
+                                 "non-shared mode\n", dev_info->segment_name);
+                       rc = -EINVAL;
+                       goto out;
+               }
+               rc = segment_modify_shared(dev_info->segment_name,
+                                          SEGMENT_EXCLUSIVE);
                if (rc < 0) {
-                       PRINT_ERR("Segment %s not reloaded, rc=%d\n",
-                                       dev_info->segment_name, rc);
-                       goto removeseg;
+                       BUG_ON(rc == -EINVAL);
+                       if (rc == -EIO || rc == -ENOENT)
+                               goto removeseg;
+               } else {
+                       dev_info->is_shared = 0;
+                       set_disk_ro(dev_info->gd, 0);
                }
-               dev_info->is_shared = 0;
-               PRINT_INFO("Segment %s reloaded, exclusive (read-write) mode.\n",
-                          dev_info->segment_name);
        } else {
-               up_write(&dcssblk_devices_sem);
                PRINT_WARN("Invalid value, must be 0 or 1\n");
-               return -EINVAL;
+               rc = -EINVAL;
+               goto out;
        }
-       dev_info->segment_type = rc;
        rc = count;
-
-       switch (dev_info->segment_type) {
-               case SEGMENT_SHARED_RO:
-               case SEGMENT_EXCLUSIVE_RO:
-                       set_disk_ro(dev_info->gd, 1);
-                       break;
-               case SEGMENT_SHARED_RW:
-               case SEGMENT_EXCLUSIVE_RW:
-                       set_disk_ro(dev_info->gd, 0);
-                       break;
-       }
-       if ((inbuf[0] == '1') &&
-          ((dev_info->segment_type == SEGMENT_EXCLUSIVE_RO) ||
-           (dev_info->segment_type == SEGMENT_EXCLUSIVE_RW))) {
-               PRINT_WARN("Could not get shared copy of segment %s\n",
-                               dev_info->segment_name);
-               rc = -EPERM;
-       }
-       if ((inbuf[0] == '0') &&
-          ((dev_info->segment_type == SEGMENT_SHARED_RO) ||
-           (dev_info->segment_type == SEGMENT_SHARED_RW))) {
-               PRINT_WARN("Could not get exclusive copy of segment %s\n",
-                               dev_info->segment_name);
-               rc = -EPERM;
-       }
-       up_write(&dcssblk_devices_sem);
        goto out;
 
 removeseg:
@@ -249,13 +273,13 @@ removeseg:
        list_del(&dev_info->lh);
 
        del_gendisk(dev_info->gd);
-       blk_put_queue(dev_info->dcssblk_queue);
+       blk_cleanup_queue(dev_info->dcssblk_queue);
        dev_info->gd->queue = NULL;
        put_disk(dev_info->gd);
        device_unregister(dev);
        put_device(dev);
-       up_write(&dcssblk_devices_sem);
 out:
+       up_write(&dcssblk_devices_sem);
        return rc;
 }
 
@@ -267,7 +291,7 @@ out:
  * (show + store)
  */
 static ssize_t
-dcssblk_save_show(struct device *dev, char *buf)
+dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct dcssblk_dev_info *dev_info;
 
@@ -276,7 +300,7 @@ dcssblk_save_show(struct device *dev, char *buf)
 }
 
 static ssize_t
-dcssblk_save_store(struct device *dev, const char *inbuf, size_t count)
+dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
 {
        struct dcssblk_dev_info *dev_info;
 
@@ -292,7 +316,7 @@ dcssblk_save_store(struct device *dev, const char *inbuf, size_t count)
                        // device is idle => we save immediately
                        PRINT_INFO("Saving segment %s\n",
                                   dev_info->segment_name);
-                       segment_replace(dev_info->segment_name);
+                       segment_save(dev_info->segment_name);
                }  else {
                        // device is busy => we save it when it becomes
                        // idle in dcssblk_release
@@ -322,7 +346,7 @@ dcssblk_save_store(struct device *dev, const char *inbuf, size_t count)
  * device attribute for adding devices
  */
 static ssize_t
-dcssblk_add_store(struct device *dev, const char *buf, size_t count)
+dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
 {
        int rc, i;
        struct dcssblk_dev_info *dev_info;
@@ -364,12 +388,11 @@ dcssblk_add_store(struct device *dev, const char *buf, size_t count)
        /*
         * get a struct dcssblk_dev_info
         */
-       dev_info = kmalloc(sizeof(struct dcssblk_dev_info), GFP_KERNEL);
+       dev_info = kzalloc(sizeof(struct dcssblk_dev_info), GFP_KERNEL);
        if (dev_info == NULL) {
                rc = -ENOMEM;
                goto out;
        }
-       memset(dev_info, 0, sizeof(struct dcssblk_dev_info));
 
        strcpy(dev_info->segment_name, local_buf);
        strlcpy(dev_info->dev.bus_id, local_buf, BUS_ID_SIZE);
@@ -390,18 +413,17 @@ dcssblk_add_store(struct device *dev, const char *buf, size_t count)
        /*
         * load the segment
         */
-       rc = segment_load(local_buf, SEGMENT_SHARED_RO,
+       rc = segment_load(local_buf, SEGMENT_SHARED,
                                &dev_info->start, &dev_info->end);
        if (rc < 0) {
-               PRINT_ERR("Segment %s not loaded, rc=%d\n", local_buf, rc);
+               dcssblk_segment_warn(rc, dev_info->segment_name);
                goto dealloc_gendisk;
        }
        seg_byte_size = (dev_info->end - dev_info->start + 1);
        set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
-       PRINT_INFO("Loaded segment %s from %p to %p, size = %lu Byte, "
-                  "capacity = %lu sectors (512 Byte)\n", local_buf,
-                       (void *) dev_info->start, (void *) dev_info->end,
-                       seg_byte_size, seg_byte_size >> 9);
+       PRINT_INFO("Loaded segment %s, size = %lu Byte, "
+                  "capacity = %lu (512 Byte) sectors\n", local_buf,
+                  seg_byte_size, seg_byte_size >> 9);
 
        dev_info->segment_type = rc;
        dev_info->save_pending = 0;
@@ -451,12 +473,12 @@ dcssblk_add_store(struct device *dev, const char *buf, size_t count)
        blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
 
        switch (dev_info->segment_type) {
-               case SEGMENT_SHARED_RO:
-               case SEGMENT_EXCLUSIVE_RO:
+               case SEG_TYPE_SR:
+               case SEG_TYPE_ER:
+               case SEG_TYPE_SC:
                        set_disk_ro(dev_info->gd,1);
                        break;
-               case SEGMENT_SHARED_RW:
-               case SEGMENT_EXCLUSIVE_RW:
+               default:
                        set_disk_ro(dev_info->gd,0);
                        break;
        }
@@ -468,7 +490,7 @@ dcssblk_add_store(struct device *dev, const char *buf, size_t count)
 unregister_dev:
        PRINT_ERR("device_create_file() failed!\n");
        list_del(&dev_info->lh);
-       blk_put_queue(dev_info->dcssblk_queue);
+       blk_cleanup_queue(dev_info->dcssblk_queue);
        dev_info->gd->queue = NULL;
        put_disk(dev_info->gd);
        device_unregister(&dev_info->dev);
@@ -482,7 +504,7 @@ list_del:
 unload_seg:
        segment_unload(local_buf);
 dealloc_gendisk:
-       blk_put_queue(dev_info->dcssblk_queue);
+       blk_cleanup_queue(dev_info->dcssblk_queue);
        dev_info->gd->queue = NULL;
        put_disk(dev_info->gd);
 free_dev_info:
@@ -497,7 +519,7 @@ out_nobuf:
  * device attribute for removing devices
  */
 static ssize_t
-dcssblk_remove_store(struct device *dev, const char *buf, size_t count)
+dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
 {
        struct dcssblk_dev_info *dev_info;
        int rc, i;
@@ -539,7 +561,7 @@ dcssblk_remove_store(struct device *dev, const char *buf, size_t count)
        list_del(&dev_info->lh);
 
        del_gendisk(dev_info->gd);
-       blk_put_queue(dev_info->dcssblk_queue);
+       blk_cleanup_queue(dev_info->dcssblk_queue);
        dev_info->gd->queue = NULL;
        put_disk(dev_info->gd);
        device_unregister(&dev_info->dev);
@@ -589,7 +611,7 @@ dcssblk_release(struct inode *inode, struct file *filp)
            && (dev_info->save_pending)) {
                PRINT_INFO("Segment %s became idle and is being saved now\n",
                            dev_info->segment_name);
-               segment_replace(dev_info->segment_name);
+               segment_save(dev_info->segment_name);
                dev_info->save_pending = 0;
        }
        up_write(&dcssblk_devices_sem);
@@ -621,6 +643,20 @@ dcssblk_make_request(request_queue_t *q, struct bio *bio)
                /* Request beyond end of DCSS segment. */
                goto fail;
        }
+       /* verify data transfer direction */
+       if (dev_info->is_shared) {
+               switch (dev_info->segment_type) {
+               case SEG_TYPE_SR:
+               case SEG_TYPE_ER:
+               case SEG_TYPE_SC:
+                       /* cannot write to these segments */
+                       if (bio_data_dir(bio) == WRITE) {
+                               PRINT_WARN("rejecting write to ro segment %s\n", dev_info->dev.bus_id);
+                               goto fail;
+                       }
+               }
+       }
+
        index = (bio->bi_sector >> 3);
        bio_for_each_segment(bvec, bio, i) {
                page_addr = (unsigned long)
@@ -641,7 +677,26 @@ dcssblk_make_request(request_queue_t *q, struct bio *bio)
        bio_endio(bio, bytes_done, 0);
        return 0;
 fail:
-       bio_io_error(bio, bytes_done);
+       bio_io_error(bio, bio->bi_size);
+       return 0;
+}
+
+static int
+dcssblk_direct_access (struct block_device *bdev, sector_t secnum,
+                       unsigned long *data)
+{
+       struct dcssblk_dev_info *dev_info;
+       unsigned long pgoff;
+
+       dev_info = bdev->bd_disk->private_data;
+       if (!dev_info)
+               return -ENODEV;
+       if (secnum % (PAGE_SIZE/512))
+               return -EINVAL;
+       pgoff = secnum / (PAGE_SIZE / 512);
+       if ((pgoff+1)*PAGE_SIZE-1 > dev_info->end - dev_info->start)
+               return -ERANGE;
+       *data = (unsigned long) (dev_info->start+pgoff*PAGE_SIZE);
        return 0;
 }
 
@@ -662,7 +717,7 @@ dcssblk_check_params(void)
                        buf[j-i] = dcssblk_segments[j];
                }
                buf[j-i] = '\0';
-               rc = dcssblk_add_store(dcssblk_root_dev, buf, j-i);
+               rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);
                if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
                        for (k = 0; buf[k] != '\0'; k++)
                                buf[k] = toupper(buf[k]);
@@ -672,7 +727,7 @@ dcssblk_check_params(void)
                                up_read(&dcssblk_devices_sem);
                                if (dev_info)
                                        dcssblk_shared_store(&dev_info->dev,
-                                                            "0\n", 2);
+                                                            NULL, "0\n", 2);
                        }
                }
                while ((dcssblk_segments[j] != ',') &&