#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);
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;
}
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;
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:
put_disk(dev_info->gd);
device_unregister(dev);
put_device(dev);
- up_write(&dcssblk_devices_sem);
out:
+ up_write(&dcssblk_devices_sem);
return rc;
}
* (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;
}
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;
// 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
* 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;
/*
* 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;
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;
}
* 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;
&& (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);
/* 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)
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;
}
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]);
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] != ',') &&