X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fs390%2Fblock%2Fdcssblk.c;h=be9b05347b4f9b839bcc75e8a7b57f98e2ad3113;hb=refs%2Fheads%2Fvserver;hp=b7a764ca36b345b3757be3395e88ecc32a46f968;hpb=c7b5ebbddf7bcd3651947760f423e3783bbe6573;p=linux-2.6.git diff --git a/drivers/s390/block/dcssblk.c b/drivers/s390/block/dcssblk.c index b7a764ca3..be9b05347 100644 --- a/drivers/s390/block/dcssblk.c +++ b/drivers/s390/block/dcssblk.c @@ -15,7 +15,7 @@ #include #include #include -#include // for s390_root_dev_(un)register() +#include //#define DCSSBLK_DEBUG /* Debug messages on/off */ #define DCSSBLK_NAME "dcssblk" @@ -35,26 +35,29 @@ 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] != ',') &&