2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* dcssblk.c -- the S/390 block driver for dcss memory
|
|
|
|
*
|
|
|
|
* Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/ctype.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/blkdev.h>
|
|
|
|
#include <asm/extmem.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <linux/completion.h>
|
|
|
|
#include <linux/interrupt.h>
|
2006-01-06 15:19:14 +07:00
|
|
|
#include <asm/s390_rdev.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
//#define DCSSBLK_DEBUG /* Debug messages on/off */
|
|
|
|
#define DCSSBLK_NAME "dcssblk"
|
|
|
|
#define DCSSBLK_MINORS_PER_DISK 1
|
|
|
|
#define DCSSBLK_PARM_LEN 400
|
|
|
|
|
|
|
|
#ifdef DCSSBLK_DEBUG
|
|
|
|
#define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSSBLK_NAME " debug: " x)
|
|
|
|
#else
|
|
|
|
#define PRINT_DEBUG(x...) do {} while (0)
|
|
|
|
#endif
|
|
|
|
#define PRINT_INFO(x...) printk(KERN_INFO DCSSBLK_NAME " info: " x)
|
|
|
|
#define PRINT_WARN(x...) printk(KERN_WARNING DCSSBLK_NAME " warning: " x)
|
|
|
|
#define PRINT_ERR(x...) printk(KERN_ERR DCSSBLK_NAME " error: " x)
|
|
|
|
|
|
|
|
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);
|
2005-06-24 12:05:23 +07:00
|
|
|
static int dcssblk_direct_access(struct block_device *bdev, sector_t secnum,
|
2008-04-28 16:13:02 +07:00
|
|
|
void **kaddr, unsigned long *pfn);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
|
|
|
|
|
|
|
|
static int dcssblk_major;
|
|
|
|
static struct block_device_operations dcssblk_devops = {
|
2005-06-24 12:05:23 +07:00
|
|
|
.owner = THIS_MODULE,
|
[PATCH] beginning of methods conversion
To keep the size of changesets sane we split the switch by drivers;
to keep the damn thing bisectable we do the following:
1) rename the affected methods, add ones with correct
prototypes, make (few) callers handle both. That's this changeset.
2) for each driver convert to new methods. *ALL* drivers
are converted in this series.
3) kill the old (renamed) methods.
Note that it _is_ a flagday; all in-tree drivers are converted and by the
end of this series no trace of old methods remain. The only reason why
we do that this way is to keep the damn thing bisectable and allow per-driver
debugging if anything goes wrong.
New methods:
open(bdev, mode)
release(disk, mode)
ioctl(bdev, mode, cmd, arg) /* Called without BKL */
compat_ioctl(bdev, mode, cmd, arg)
locked_ioctl(bdev, mode, cmd, arg) /* Called with BKL, legacy */
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2008-03-02 21:09:22 +07:00
|
|
|
.__open = dcssblk_open,
|
|
|
|
.__release = dcssblk_release,
|
2005-06-24 12:05:23 +07:00
|
|
|
.direct_access = dcssblk_direct_access,
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
2008-10-11 02:33:21 +07:00
|
|
|
struct dcssblk_dev_info {
|
|
|
|
struct list_head lh;
|
|
|
|
struct device dev;
|
|
|
|
char segment_name[BUS_ID_SIZE];
|
|
|
|
atomic_t use_count;
|
|
|
|
struct gendisk *gd;
|
|
|
|
unsigned long start;
|
|
|
|
unsigned long end;
|
|
|
|
int segment_type;
|
|
|
|
unsigned char save_pending;
|
|
|
|
unsigned char is_shared;
|
|
|
|
struct request_queue *dcssblk_queue;
|
|
|
|
int num_of_segments;
|
|
|
|
struct list_head seg_list;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct segment_info {
|
|
|
|
struct list_head lh;
|
|
|
|
char segment_name[BUS_ID_SIZE];
|
|
|
|
unsigned long start;
|
|
|
|
unsigned long end;
|
|
|
|
int segment_type;
|
|
|
|
};
|
|
|
|
|
2005-05-17 17:42:58 +07:00
|
|
|
static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
|
2005-04-17 05:20:36 +07:00
|
|
|
size_t count);
|
2005-05-17 17:42:58 +07:00
|
|
|
static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
|
2005-04-17 05:20:36 +07:00
|
|
|
size_t count);
|
2005-05-17 17:42:58 +07:00
|
|
|
static ssize_t dcssblk_save_store(struct device * dev, struct device_attribute *attr, const char * buf,
|
2005-04-17 05:20:36 +07:00
|
|
|
size_t count);
|
2005-05-17 17:42:58 +07:00
|
|
|
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,
|
2005-04-17 05:20:36 +07:00
|
|
|
size_t count);
|
2005-05-17 17:42:58 +07:00
|
|
|
static ssize_t dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf);
|
2008-10-11 02:33:21 +07:00
|
|
|
static ssize_t dcssblk_seglist_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
|
|
|
|
static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
|
2008-10-11 02:33:21 +07:00
|
|
|
static DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show,
|
2005-04-17 05:20:36 +07:00
|
|
|
dcssblk_save_store);
|
2008-10-11 02:33:21 +07:00
|
|
|
static DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show,
|
2005-04-17 05:20:36 +07:00
|
|
|
dcssblk_shared_store);
|
2008-10-11 02:33:21 +07:00
|
|
|
static DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static struct device *dcssblk_root_dev;
|
|
|
|
|
2008-01-26 20:11:13 +07:00
|
|
|
static LIST_HEAD(dcssblk_devices);
|
2005-04-17 05:20:36 +07:00
|
|
|
static struct rw_semaphore dcssblk_devices_sem;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* release function for segment device.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
dcssblk_release_segment(struct device *dev)
|
|
|
|
{
|
2008-10-11 02:33:21 +07:00
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
struct segment_info *entry, *temp;
|
|
|
|
|
|
|
|
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
|
|
|
list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) {
|
|
|
|
list_del(&entry->lh);
|
|
|
|
kfree(entry);
|
|
|
|
}
|
|
|
|
kfree(dev_info);
|
2005-04-17 05:20:36 +07:00
|
|
|
module_put(THIS_MODULE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* get a minor number. needs to be called with
|
|
|
|
* down_write(&dcssblk_devices_sem) and the
|
|
|
|
* device needs to be enqueued before the semaphore is
|
|
|
|
* freed.
|
|
|
|
*/
|
2007-02-06 03:18:53 +07:00
|
|
|
static int
|
2005-04-17 05:20:36 +07:00
|
|
|
dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
|
|
|
|
{
|
|
|
|
int minor, found;
|
|
|
|
struct dcssblk_dev_info *entry;
|
|
|
|
|
|
|
|
if (dev_info == NULL)
|
|
|
|
return -EINVAL;
|
|
|
|
for (minor = 0; minor < (1<<MINORBITS); minor++) {
|
|
|
|
found = 0;
|
|
|
|
// test if minor available
|
|
|
|
list_for_each_entry(entry, &dcssblk_devices, lh)
|
2008-09-03 14:01:48 +07:00
|
|
|
if (minor == MINOR(disk_devt(entry->gd)))
|
2005-04-17 05:20:36 +07:00
|
|
|
found++;
|
|
|
|
if (!found) break; // got unused minor
|
|
|
|
}
|
|
|
|
if (found)
|
|
|
|
return -EBUSY;
|
|
|
|
dev_info->gd->first_minor = minor;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* get the struct dcssblk_dev_info from dcssblk_devices
|
|
|
|
* for the given name.
|
|
|
|
* down_read(&dcssblk_devices_sem) must be held.
|
|
|
|
*/
|
|
|
|
static struct dcssblk_dev_info *
|
|
|
|
dcssblk_get_device_by_name(char *name)
|
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *entry;
|
|
|
|
|
|
|
|
list_for_each_entry(entry, &dcssblk_devices, lh) {
|
|
|
|
if (!strcmp(name, entry->segment_name)) {
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2008-10-11 02:33:21 +07:00
|
|
|
/*
|
|
|
|
* get the struct segment_info from seg_list
|
|
|
|
* for the given name.
|
|
|
|
* down_read(&dcssblk_devices_sem) must be held.
|
|
|
|
*/
|
|
|
|
static struct segment_info *
|
|
|
|
dcssblk_get_segment_by_name(char *name)
|
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
struct segment_info *entry;
|
|
|
|
|
|
|
|
list_for_each_entry(dev_info, &dcssblk_devices, lh) {
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
if (!strcmp(name, entry->segment_name))
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* get the highest address of the multi-segment block.
|
|
|
|
*/
|
|
|
|
static unsigned long
|
|
|
|
dcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info)
|
|
|
|
{
|
|
|
|
unsigned long highest_addr;
|
|
|
|
struct segment_info *entry;
|
|
|
|
|
|
|
|
highest_addr = 0;
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
if (highest_addr < entry->end)
|
|
|
|
highest_addr = entry->end;
|
|
|
|
}
|
|
|
|
return highest_addr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* get the lowest address of the multi-segment block.
|
|
|
|
*/
|
|
|
|
static unsigned long
|
|
|
|
dcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info)
|
|
|
|
{
|
|
|
|
int set_first;
|
|
|
|
unsigned long lowest_addr;
|
|
|
|
struct segment_info *entry;
|
|
|
|
|
|
|
|
set_first = 0;
|
|
|
|
lowest_addr = 0;
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
if (set_first == 0) {
|
|
|
|
lowest_addr = entry->start;
|
|
|
|
set_first = 1;
|
|
|
|
} else {
|
|
|
|
if (lowest_addr > entry->start)
|
|
|
|
lowest_addr = entry->start;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return lowest_addr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check continuity of segments.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
dcssblk_is_continuous(struct dcssblk_dev_info *dev_info)
|
|
|
|
{
|
|
|
|
int i, j, rc;
|
|
|
|
struct segment_info *sort_list, *entry, temp;
|
|
|
|
|
|
|
|
if (dev_info->num_of_segments <= 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
sort_list = kzalloc(
|
|
|
|
sizeof(struct segment_info) * dev_info->num_of_segments,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (sort_list == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
i = 0;
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
memcpy(&sort_list[i], entry, sizeof(struct segment_info));
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sort segments */
|
|
|
|
for (i = 0; i < dev_info->num_of_segments; i++)
|
|
|
|
for (j = 0; j < dev_info->num_of_segments; j++)
|
|
|
|
if (sort_list[j].start > sort_list[i].start) {
|
|
|
|
memcpy(&temp, &sort_list[i],
|
|
|
|
sizeof(struct segment_info));
|
|
|
|
memcpy(&sort_list[i], &sort_list[j],
|
|
|
|
sizeof(struct segment_info));
|
|
|
|
memcpy(&sort_list[j], &temp,
|
|
|
|
sizeof(struct segment_info));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check continuity */
|
|
|
|
for (i = 0; i < dev_info->num_of_segments - 1; i++) {
|
|
|
|
if ((sort_list[i].end + 1) != sort_list[i+1].start) {
|
|
|
|
PRINT_ERR("Segment %s is not contiguous with "
|
|
|
|
"segment %s\n",
|
|
|
|
sort_list[i].segment_name,
|
|
|
|
sort_list[i+1].segment_name);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
/* EN and EW are allowed in a block device */
|
|
|
|
if (sort_list[i].segment_type != sort_list[i+1].segment_type) {
|
|
|
|
if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) ||
|
|
|
|
(sort_list[i].segment_type == SEG_TYPE_ER) ||
|
|
|
|
!(sort_list[i+1].segment_type &
|
|
|
|
SEGMENT_EXCLUSIVE) ||
|
|
|
|
(sort_list[i+1].segment_type == SEG_TYPE_ER)) {
|
|
|
|
PRINT_ERR("Segment %s has different type from "
|
|
|
|
"segment %s\n",
|
|
|
|
sort_list[i].segment_name,
|
|
|
|
sort_list[i+1].segment_name);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
out:
|
|
|
|
kfree(sort_list);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Load a segment
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
dcssblk_load_segment(char *name, struct segment_info **seg_info)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* already loaded? */
|
|
|
|
down_read(&dcssblk_devices_sem);
|
|
|
|
*seg_info = dcssblk_get_segment_by_name(name);
|
|
|
|
up_read(&dcssblk_devices_sem);
|
|
|
|
if (*seg_info != NULL)
|
|
|
|
return -EEXIST;
|
|
|
|
|
|
|
|
/* get a struct segment_info */
|
|
|
|
*seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL);
|
|
|
|
if (*seg_info == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
strcpy((*seg_info)->segment_name, name);
|
|
|
|
|
|
|
|
/* load the segment */
|
|
|
|
rc = segment_load(name, SEGMENT_SHARED,
|
|
|
|
&(*seg_info)->start, &(*seg_info)->end);
|
|
|
|
if (rc < 0) {
|
|
|
|
segment_warning(rc, (*seg_info)->segment_name);
|
|
|
|
kfree(*seg_info);
|
|
|
|
} else {
|
|
|
|
INIT_LIST_HEAD(&(*seg_info)->lh);
|
|
|
|
(*seg_info)->segment_type = rc;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2007-11-05 17:10:09 +07:00
|
|
|
static void dcssblk_unregister_callback(struct device *dev)
|
|
|
|
{
|
|
|
|
device_unregister(dev);
|
|
|
|
put_device(dev);
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* device attribute for switching shared/nonshared (exclusive)
|
|
|
|
* operation (show + store)
|
|
|
|
*/
|
|
|
|
static ssize_t
|
2005-05-17 17:42:58 +07:00
|
|
|
dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
|
|
|
|
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
|
|
|
return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
2005-05-17 17:42:58 +07:00
|
|
|
dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
2008-10-11 02:33:21 +07:00
|
|
|
struct segment_info *entry, *temp;
|
2005-04-17 05:20:36 +07:00
|
|
|
int rc;
|
|
|
|
|
2008-07-14 14:59:39 +07:00
|
|
|
if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EINVAL;
|
|
|
|
down_write(&dcssblk_devices_sem);
|
|
|
|
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
|
|
|
if (atomic_read(&dev_info->use_count)) {
|
|
|
|
rc = -EBUSY;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (inbuf[0] == '1') {
|
2008-10-11 02:33:21 +07:00
|
|
|
/* reload segments in shared mode */
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
rc = segment_modify_shared(entry->segment_name,
|
|
|
|
SEGMENT_SHARED);
|
|
|
|
if (rc < 0) {
|
|
|
|
BUG_ON(rc == -EINVAL);
|
|
|
|
if (rc != -EAGAIN)
|
|
|
|
goto removeseg;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2008-10-11 02:33:21 +07:00
|
|
|
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);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
} else if (inbuf[0] == '0') {
|
2008-10-11 02:33:21 +07:00
|
|
|
/* reload segments in exclusive mode */
|
2005-04-17 05:20:36 +07:00
|
|
|
if (dev_info->segment_type == SEG_TYPE_SC) {
|
|
|
|
PRINT_ERR("Segment type SC (%s) cannot be loaded in "
|
2008-10-11 02:33:21 +07:00
|
|
|
"non-shared mode\n", dev_info->segment_name);
|
2005-04-17 05:20:36 +07:00
|
|
|
rc = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
2008-10-11 02:33:21 +07:00
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
rc = segment_modify_shared(entry->segment_name,
|
|
|
|
SEGMENT_EXCLUSIVE);
|
|
|
|
if (rc < 0) {
|
|
|
|
BUG_ON(rc == -EINVAL);
|
|
|
|
if (rc != -EAGAIN)
|
|
|
|
goto removeseg;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2008-10-11 02:33:21 +07:00
|
|
|
dev_info->is_shared = 0;
|
|
|
|
set_disk_ro(dev_info->gd, 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
rc = count;
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
removeseg:
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_ERR("Could not reload segment(s) of the device %s, removing "
|
|
|
|
"segment(s) now!\n",
|
|
|
|
dev_info->segment_name);
|
|
|
|
temp = entry;
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
if (entry != temp)
|
|
|
|
segment_unload(entry->segment_name);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
list_del(&dev_info->lh);
|
|
|
|
|
|
|
|
del_gendisk(dev_info->gd);
|
2006-03-12 23:02:03 +07:00
|
|
|
blk_cleanup_queue(dev_info->dcssblk_queue);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->gd->queue = NULL;
|
|
|
|
put_disk(dev_info->gd);
|
2007-11-05 17:10:09 +07:00
|
|
|
rc = device_schedule_callback(dev, dcssblk_unregister_callback);
|
2005-04-17 05:20:36 +07:00
|
|
|
out:
|
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* device attribute for save operation on current copy
|
|
|
|
* of the segment. If the segment is busy, saving will
|
|
|
|
* become pending until it gets released, which can be
|
|
|
|
* undone by storing a non-true value to this entry.
|
|
|
|
* (show + store)
|
|
|
|
*/
|
|
|
|
static ssize_t
|
2005-05-17 17:42:58 +07:00
|
|
|
dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
|
|
|
|
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
|
|
|
return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
2005-05-17 17:42:58 +07:00
|
|
|
dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
2008-10-11 02:33:21 +07:00
|
|
|
struct segment_info *entry;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-07-14 14:59:39 +07:00
|
|
|
if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EINVAL;
|
|
|
|
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
|
|
|
|
|
|
|
down_write(&dcssblk_devices_sem);
|
|
|
|
if (inbuf[0] == '1') {
|
|
|
|
if (atomic_read(&dev_info->use_count) == 0) {
|
|
|
|
// device is idle => we save immediately
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_INFO("Saving segment(s) of the device %s\n",
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->segment_name);
|
2008-10-11 02:33:21 +07:00
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
segment_save(entry->segment_name);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
|
|
|
// device is busy => we save it when it becomes
|
|
|
|
// idle in dcssblk_release
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_INFO("Device %s is currently busy, segment(s) "
|
|
|
|
"will be saved when it becomes idle...\n",
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->segment_name);
|
|
|
|
dev_info->save_pending = 1;
|
|
|
|
}
|
|
|
|
} else if (inbuf[0] == '0') {
|
|
|
|
if (dev_info->save_pending) {
|
|
|
|
// device is busy & the user wants to undo his save
|
|
|
|
// request
|
|
|
|
dev_info->save_pending = 0;
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_INFO("Pending save for segment(s) of the device "
|
|
|
|
"%s deactivated\n",
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->segment_name);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2008-10-11 02:33:21 +07:00
|
|
|
/*
|
|
|
|
* device attribute for showing all segments in a device
|
|
|
|
*/
|
|
|
|
static ssize_t
|
|
|
|
dcssblk_seglist_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
struct segment_info *entry;
|
|
|
|
|
|
|
|
down_read(&dcssblk_devices_sem);
|
|
|
|
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
|
|
|
|
i = 0;
|
|
|
|
buf[0] = '\0';
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
strcpy(&buf[i], entry->segment_name);
|
|
|
|
i += strlen(entry->segment_name);
|
|
|
|
buf[i] = '\n';
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
up_read(&dcssblk_devices_sem);
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* device attribute for adding devices
|
|
|
|
*/
|
|
|
|
static ssize_t
|
2005-05-17 17:42:58 +07:00
|
|
|
dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2008-10-11 02:33:21 +07:00
|
|
|
int rc, i, j, num_of_segments;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct dcssblk_dev_info *dev_info;
|
2008-10-11 02:33:21 +07:00
|
|
|
struct segment_info *seg_info, *temp;
|
2005-04-17 05:20:36 +07:00
|
|
|
char *local_buf;
|
|
|
|
unsigned long seg_byte_size;
|
|
|
|
|
|
|
|
dev_info = NULL;
|
2008-10-11 02:33:21 +07:00
|
|
|
seg_info = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (dev != dcssblk_root_dev) {
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto out_nobuf;
|
|
|
|
}
|
2008-10-11 02:33:21 +07:00
|
|
|
if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) {
|
|
|
|
rc = -ENAMETOOLONG;
|
|
|
|
goto out_nobuf;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
local_buf = kmalloc(count + 1, GFP_KERNEL);
|
|
|
|
if (local_buf == NULL) {
|
|
|
|
rc = -ENOMEM;
|
|
|
|
goto out_nobuf;
|
|
|
|
}
|
2008-10-11 02:33:21 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* parse input
|
|
|
|
*/
|
2008-10-11 02:33:21 +07:00
|
|
|
num_of_segments = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
|
2008-10-11 02:33:21 +07:00
|
|
|
for (j = i; (buf[j] != ':') &&
|
|
|
|
(buf[j] != '\0') &&
|
|
|
|
(buf[j] != '\n') &&
|
|
|
|
j < count; j++) {
|
|
|
|
local_buf[j-i] = toupper(buf[j]);
|
|
|
|
}
|
|
|
|
local_buf[j-i] = '\0';
|
|
|
|
if (((j - i) == 0) || ((j - i) > 8)) {
|
|
|
|
rc = -ENAMETOOLONG;
|
|
|
|
goto seg_list_del;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = dcssblk_load_segment(local_buf, &seg_info);
|
|
|
|
if (rc < 0)
|
|
|
|
goto seg_list_del;
|
|
|
|
/*
|
|
|
|
* get a struct dcssblk_dev_info
|
|
|
|
*/
|
|
|
|
if (num_of_segments == 0) {
|
|
|
|
dev_info = kzalloc(sizeof(struct dcssblk_dev_info),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (dev_info == NULL) {
|
|
|
|
rc = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
strcpy(dev_info->segment_name, local_buf);
|
|
|
|
dev_info->segment_type = seg_info->segment_type;
|
|
|
|
INIT_LIST_HEAD(&dev_info->seg_list);
|
|
|
|
}
|
|
|
|
list_add_tail(&seg_info->lh, &dev_info->seg_list);
|
|
|
|
num_of_segments++;
|
|
|
|
i = j;
|
|
|
|
|
|
|
|
if ((buf[j] == '\0') || (buf[j] == '\n'))
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2008-10-11 02:33:21 +07:00
|
|
|
|
|
|
|
/* no trailing colon at the end of the input */
|
|
|
|
if ((i > 0) && (buf[i-1] == ':')) {
|
2005-04-17 05:20:36 +07:00
|
|
|
rc = -ENAMETOOLONG;
|
2008-10-11 02:33:21 +07:00
|
|
|
goto seg_list_del;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2008-10-11 02:33:21 +07:00
|
|
|
strlcpy(local_buf, buf, i + 1);
|
|
|
|
dev_info->num_of_segments = num_of_segments;
|
|
|
|
rc = dcssblk_is_continuous(dev_info);
|
|
|
|
if (rc < 0)
|
|
|
|
goto seg_list_del;
|
|
|
|
|
|
|
|
dev_info->start = dcssblk_find_lowest_addr(dev_info);
|
|
|
|
dev_info->end = dcssblk_find_highest_addr(dev_info);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-10-11 02:33:21 +07:00
|
|
|
dev_set_name(&dev_info->dev, dev_info->segment_name);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->dev.release = dcssblk_release_segment;
|
|
|
|
INIT_LIST_HEAD(&dev_info->lh);
|
|
|
|
dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
|
|
|
|
if (dev_info->gd == NULL) {
|
|
|
|
rc = -ENOMEM;
|
2008-10-11 02:33:21 +07:00
|
|
|
goto seg_list_del;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
dev_info->gd->major = dcssblk_major;
|
|
|
|
dev_info->gd->fops = &dcssblk_devops;
|
|
|
|
dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL);
|
|
|
|
dev_info->gd->queue = dev_info->dcssblk_queue;
|
|
|
|
dev_info->gd->private_data = dev_info;
|
|
|
|
dev_info->gd->driverfs_dev = &dev_info->dev;
|
2008-02-05 22:50:50 +07:00
|
|
|
blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
|
|
|
|
blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
|
2008-10-11 02:33:21 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
seg_byte_size = (dev_info->end - dev_info->start + 1);
|
|
|
|
set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_INFO("Loaded segment(s) %s, size = %lu Byte, "
|
2005-04-17 05:20:36 +07:00
|
|
|
"capacity = %lu (512 Byte) sectors\n", local_buf,
|
|
|
|
seg_byte_size, seg_byte_size >> 9);
|
|
|
|
|
|
|
|
dev_info->save_pending = 0;
|
|
|
|
dev_info->is_shared = 1;
|
|
|
|
dev_info->dev.parent = dcssblk_root_dev;
|
|
|
|
|
|
|
|
/*
|
2008-10-11 02:33:21 +07:00
|
|
|
*get minor, add to list
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
down_write(&dcssblk_devices_sem);
|
2008-10-11 02:33:21 +07:00
|
|
|
if (dcssblk_get_segment_by_name(local_buf)) {
|
2008-08-22 00:46:40 +07:00
|
|
|
rc = -EEXIST;
|
2008-10-11 02:33:21 +07:00
|
|
|
goto release_gd;
|
2008-08-22 00:46:40 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
rc = dcssblk_assign_free_minor(dev_info);
|
2008-10-11 02:33:21 +07:00
|
|
|
if (rc)
|
|
|
|
goto release_gd;
|
2005-04-17 05:20:36 +07:00
|
|
|
sprintf(dev_info->gd->disk_name, "dcssblk%d",
|
2008-09-03 14:01:48 +07:00
|
|
|
MINOR(disk_devt(dev_info->gd)));
|
2005-04-17 05:20:36 +07:00
|
|
|
list_add_tail(&dev_info->lh, &dcssblk_devices);
|
|
|
|
|
|
|
|
if (!try_module_get(THIS_MODULE)) {
|
|
|
|
rc = -ENODEV;
|
2008-10-11 02:33:21 +07:00
|
|
|
goto dev_list_del;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* register the device
|
|
|
|
*/
|
|
|
|
rc = device_register(&dev_info->dev);
|
|
|
|
if (rc) {
|
|
|
|
module_put(THIS_MODULE);
|
2008-10-11 02:33:21 +07:00
|
|
|
goto dev_list_del;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
get_device(&dev_info->dev);
|
|
|
|
rc = device_create_file(&dev_info->dev, &dev_attr_shared);
|
|
|
|
if (rc)
|
|
|
|
goto unregister_dev;
|
|
|
|
rc = device_create_file(&dev_info->dev, &dev_attr_save);
|
2008-10-11 02:33:21 +07:00
|
|
|
if (rc)
|
|
|
|
goto unregister_dev;
|
|
|
|
rc = device_create_file(&dev_info->dev, &dev_attr_seglist);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (rc)
|
|
|
|
goto unregister_dev;
|
|
|
|
|
2007-12-04 22:09:03 +07:00
|
|
|
add_disk(dev_info->gd);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
switch (dev_info->segment_type) {
|
|
|
|
case SEG_TYPE_SR:
|
|
|
|
case SEG_TYPE_ER:
|
|
|
|
case SEG_TYPE_SC:
|
|
|
|
set_disk_ro(dev_info->gd,1);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
set_disk_ro(dev_info->gd,0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
rc = count;
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
unregister_dev:
|
|
|
|
list_del(&dev_info->lh);
|
2006-03-12 23:02:03 +07:00
|
|
|
blk_cleanup_queue(dev_info->dcssblk_queue);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->gd->queue = NULL;
|
|
|
|
put_disk(dev_info->gd);
|
|
|
|
device_unregister(&dev_info->dev);
|
2008-10-11 02:33:21 +07:00
|
|
|
list_for_each_entry(seg_info, &dev_info->seg_list, lh) {
|
|
|
|
segment_unload(seg_info->segment_name);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
put_device(&dev_info->dev);
|
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
goto out;
|
2008-10-11 02:33:21 +07:00
|
|
|
dev_list_del:
|
2005-04-17 05:20:36 +07:00
|
|
|
list_del(&dev_info->lh);
|
2008-10-11 02:33:21 +07:00
|
|
|
release_gd:
|
2006-03-12 23:02:03 +07:00
|
|
|
blk_cleanup_queue(dev_info->dcssblk_queue);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->gd->queue = NULL;
|
|
|
|
put_disk(dev_info->gd);
|
2008-10-11 02:33:21 +07:00
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
seg_list_del:
|
|
|
|
if (dev_info == NULL)
|
|
|
|
goto out;
|
|
|
|
list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) {
|
|
|
|
list_del(&seg_info->lh);
|
|
|
|
segment_unload(seg_info->segment_name);
|
|
|
|
kfree(seg_info);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(dev_info);
|
|
|
|
out:
|
|
|
|
kfree(local_buf);
|
|
|
|
out_nobuf:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* device attribute for removing devices
|
|
|
|
*/
|
|
|
|
static ssize_t
|
2005-05-17 17:42:58 +07:00
|
|
|
dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
2008-10-11 02:33:21 +07:00
|
|
|
struct segment_info *entry;
|
2005-04-17 05:20:36 +07:00
|
|
|
int rc, i;
|
|
|
|
char *local_buf;
|
|
|
|
|
|
|
|
if (dev != dcssblk_root_dev) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
local_buf = kmalloc(count + 1, GFP_KERNEL);
|
|
|
|
if (local_buf == NULL) {
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* parse input
|
|
|
|
*/
|
|
|
|
for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) {
|
|
|
|
local_buf[i] = toupper(buf[i]);
|
|
|
|
}
|
|
|
|
local_buf[i] = '\0';
|
|
|
|
if ((i == 0) || (i > 8)) {
|
|
|
|
rc = -ENAMETOOLONG;
|
|
|
|
goto out_buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
down_write(&dcssblk_devices_sem);
|
|
|
|
dev_info = dcssblk_get_device_by_name(local_buf);
|
|
|
|
if (dev_info == NULL) {
|
|
|
|
up_write(&dcssblk_devices_sem);
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_WARN("Device %s is not loaded!\n", local_buf);
|
2005-04-17 05:20:36 +07:00
|
|
|
rc = -ENODEV;
|
|
|
|
goto out_buf;
|
|
|
|
}
|
|
|
|
if (atomic_read(&dev_info->use_count) != 0) {
|
|
|
|
up_write(&dcssblk_devices_sem);
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_WARN("Device %s is in use!\n", local_buf);
|
2005-04-17 05:20:36 +07:00
|
|
|
rc = -EBUSY;
|
|
|
|
goto out_buf;
|
|
|
|
}
|
|
|
|
|
2008-10-11 02:33:21 +07:00
|
|
|
list_del(&dev_info->lh);
|
2005-04-17 05:20:36 +07:00
|
|
|
del_gendisk(dev_info->gd);
|
2006-03-12 23:02:03 +07:00
|
|
|
blk_cleanup_queue(dev_info->dcssblk_queue);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->gd->queue = NULL;
|
|
|
|
put_disk(dev_info->gd);
|
|
|
|
device_unregister(&dev_info->dev);
|
2008-10-11 02:33:21 +07:00
|
|
|
|
|
|
|
/* unload all related segments */
|
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh)
|
|
|
|
segment_unload(entry->segment_name);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
put_device(&dev_info->dev);
|
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
|
|
|
|
rc = count;
|
|
|
|
out_buf:
|
|
|
|
kfree(local_buf);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
dcssblk_open(struct inode *inode, struct file *filp)
|
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
dev_info = inode->i_bdev->bd_disk->private_data;
|
|
|
|
if (NULL == dev_info) {
|
|
|
|
rc = -ENODEV;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
atomic_inc(&dev_info->use_count);
|
|
|
|
inode->i_bdev->bd_block_size = 4096;
|
|
|
|
rc = 0;
|
|
|
|
out:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
dcssblk_release(struct inode *inode, struct file *filp)
|
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
2008-10-11 02:33:21 +07:00
|
|
|
struct segment_info *entry;
|
2005-04-17 05:20:36 +07:00
|
|
|
int rc;
|
|
|
|
|
|
|
|
dev_info = inode->i_bdev->bd_disk->private_data;
|
|
|
|
if (NULL == dev_info) {
|
|
|
|
rc = -ENODEV;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
down_write(&dcssblk_devices_sem);
|
|
|
|
if (atomic_dec_and_test(&dev_info->use_count)
|
|
|
|
&& (dev_info->save_pending)) {
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_INFO("Device %s became idle and is being saved now\n",
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->segment_name);
|
2008-10-11 02:33:21 +07:00
|
|
|
list_for_each_entry(entry, &dev_info->seg_list, lh) {
|
|
|
|
segment_save(entry->segment_name);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_info->save_pending = 0;
|
|
|
|
}
|
|
|
|
up_write(&dcssblk_devices_sem);
|
|
|
|
rc = 0;
|
|
|
|
out:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2007-07-24 14:28:11 +07:00
|
|
|
dcssblk_make_request(struct request_queue *q, struct bio *bio)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
struct bio_vec *bvec;
|
|
|
|
unsigned long index;
|
|
|
|
unsigned long page_addr;
|
|
|
|
unsigned long source_addr;
|
|
|
|
unsigned long bytes_done;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
bytes_done = 0;
|
|
|
|
dev_info = bio->bi_bdev->bd_disk->private_data;
|
|
|
|
if (dev_info == NULL)
|
|
|
|
goto fail;
|
|
|
|
if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
|
|
|
|
/* Request is not page-aligned. */
|
|
|
|
goto fail;
|
|
|
|
if (((bio->bi_size >> 9) + bio->bi_sector)
|
|
|
|
> get_capacity(bio->bi_bdev->bd_disk)) {
|
|
|
|
/* Request beyond end of DCSS segment. */
|
|
|
|
goto fail;
|
|
|
|
}
|
2005-06-24 12:05:23 +07:00
|
|
|
/* 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) {
|
2008-10-11 02:33:21 +07:00
|
|
|
PRINT_WARN("rejecting write to ro device %s\n",
|
2008-10-11 02:33:09 +07:00
|
|
|
dev_name(&dev_info->dev));
|
2005-06-24 12:05:23 +07:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
index = (bio->bi_sector >> 3);
|
|
|
|
bio_for_each_segment(bvec, bio, i) {
|
|
|
|
page_addr = (unsigned long)
|
|
|
|
page_address(bvec->bv_page) + bvec->bv_offset;
|
|
|
|
source_addr = dev_info->start + (index<<12) + bytes_done;
|
2008-02-19 21:29:33 +07:00
|
|
|
if (unlikely((page_addr & 4095) != 0) || (bvec->bv_len & 4095) != 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
// More paranoia.
|
|
|
|
goto fail;
|
|
|
|
if (bio_data_dir(bio) == READ) {
|
|
|
|
memcpy((void*)page_addr, (void*)source_addr,
|
|
|
|
bvec->bv_len);
|
|
|
|
} else {
|
|
|
|
memcpy((void*)source_addr, (void*)page_addr,
|
|
|
|
bvec->bv_len);
|
|
|
|
}
|
|
|
|
bytes_done += bvec->bv_len;
|
|
|
|
}
|
2007-09-27 17:47:43 +07:00
|
|
|
bio_endio(bio, 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
fail:
|
2007-09-27 17:47:43 +07:00
|
|
|
bio_io_error(bio);
|
2005-06-24 12:05:23 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
dcssblk_direct_access (struct block_device *bdev, sector_t secnum,
|
2008-04-28 16:13:02 +07:00
|
|
|
void **kaddr, unsigned long *pfn)
|
2005-06-24 12:05:23 +07:00
|
|
|
{
|
|
|
|
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;
|
2008-04-28 16:13:02 +07:00
|
|
|
*kaddr = (void *) (dev_info->start+pgoff*PAGE_SIZE);
|
|
|
|
*pfn = virt_to_phys(*kaddr) >> PAGE_SHIFT;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dcssblk_check_params(void)
|
|
|
|
{
|
|
|
|
int rc, i, j, k;
|
2008-10-11 02:33:21 +07:00
|
|
|
char buf[DCSSBLK_PARM_LEN + 1];
|
2005-04-17 05:20:36 +07:00
|
|
|
struct dcssblk_dev_info *dev_info;
|
|
|
|
|
|
|
|
for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
|
|
|
|
i++) {
|
|
|
|
for (j = i; (dcssblk_segments[j] != ',') &&
|
|
|
|
(dcssblk_segments[j] != '\0') &&
|
|
|
|
(dcssblk_segments[j] != '(') &&
|
2008-10-11 02:33:21 +07:00
|
|
|
(j < DCSSBLK_PARM_LEN); j++)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
buf[j-i] = dcssblk_segments[j];
|
|
|
|
}
|
|
|
|
buf[j-i] = '\0';
|
2005-06-26 04:55:29 +07:00
|
|
|
rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);
|
2005-04-17 05:20:36 +07:00
|
|
|
if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
|
2008-10-11 02:33:21 +07:00
|
|
|
for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++)
|
2005-04-17 05:20:36 +07:00
|
|
|
buf[k] = toupper(buf[k]);
|
2008-10-11 02:33:21 +07:00
|
|
|
buf[k] = '\0';
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
|
|
|
|
down_read(&dcssblk_devices_sem);
|
|
|
|
dev_info = dcssblk_get_device_by_name(buf);
|
|
|
|
up_read(&dcssblk_devices_sem);
|
|
|
|
if (dev_info)
|
|
|
|
dcssblk_shared_store(&dev_info->dev,
|
2005-06-26 04:55:29 +07:00
|
|
|
NULL, "0\n", 2);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
while ((dcssblk_segments[j] != ',') &&
|
|
|
|
(dcssblk_segments[j] != '\0'))
|
|
|
|
{
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
if (dcssblk_segments[j] == '\0')
|
|
|
|
break;
|
|
|
|
i = j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The init/exit functions.
|
|
|
|
*/
|
|
|
|
static void __exit
|
|
|
|
dcssblk_exit(void)
|
|
|
|
{
|
|
|
|
s390_root_dev_unregister(dcssblk_root_dev);
|
2007-07-17 18:03:46 +07:00
|
|
|
unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __init
|
|
|
|
dcssblk_init(void)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
dcssblk_root_dev = s390_root_dev_register("dcssblk");
|
2008-07-14 14:59:39 +07:00
|
|
|
if (IS_ERR(dcssblk_root_dev))
|
2005-04-17 05:20:36 +07:00
|
|
|
return PTR_ERR(dcssblk_root_dev);
|
|
|
|
rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
|
|
|
|
if (rc) {
|
|
|
|
s390_root_dev_unregister(dcssblk_root_dev);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);
|
|
|
|
if (rc) {
|
|
|
|
s390_root_dev_unregister(dcssblk_root_dev);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
rc = register_blkdev(0, DCSSBLK_NAME);
|
|
|
|
if (rc < 0) {
|
|
|
|
s390_root_dev_unregister(dcssblk_root_dev);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
dcssblk_major = rc;
|
|
|
|
init_rwsem(&dcssblk_devices_sem);
|
|
|
|
|
|
|
|
dcssblk_check_params();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(dcssblk_init);
|
|
|
|
module_exit(dcssblk_exit);
|
|
|
|
|
|
|
|
module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
|
|
|
|
MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
|
2008-10-11 02:33:21 +07:00
|
|
|
"comma-separated list, names in each set separated "
|
|
|
|
"by commas are separated by colons, each set contains "
|
|
|
|
"names of contiguous segments and each name max. 8 chars.\n"
|
|
|
|
"Adding \"(local)\" to the end of each set equals echoing 0 "
|
|
|
|
"to /sys/devices/dcssblk/<device name>/shared after loading "
|
|
|
|
"the contiguous segments - \n"
|
|
|
|
"e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"");
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|