summaryrefslogtreecommitdiff
path: root/drivers/s390/block/dcssblk.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/block/dcssblk.c')
-rw-r--r--drivers/s390/block/dcssblk.c327
1 files changed, 124 insertions, 203 deletions
diff --git a/drivers/s390/block/dcssblk.c b/drivers/s390/block/dcssblk.c
index 4e8aedd50cb0..38e1df8f8a82 100644
--- a/drivers/s390/block/dcssblk.c
+++ b/drivers/s390/block/dcssblk.c
@@ -5,8 +5,7 @@
* Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer
*/
-#define KMSG_COMPONENT "dcssblk"
-#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+#define pr_fmt(fmt) "dcssblk: " fmt
#include <linux/module.h>
#include <linux/moduleparam.h>
@@ -17,50 +16,52 @@
#include <linux/blkdev.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
-#include <linux/platform_device.h>
-#include <linux/pfn_t.h>
#include <linux/uio.h>
#include <linux/dax.h>
+#include <linux/io.h>
#include <asm/extmem.h>
-#include <asm/io.h>
#define DCSSBLK_NAME "dcssblk"
#define DCSSBLK_MINORS_PER_DISK 1
#define DCSSBLK_PARM_LEN 400
#define DCSS_BUS_ID_SIZE 20
-static int dcssblk_open(struct block_device *bdev, fmode_t mode);
-static void dcssblk_release(struct gendisk *disk, fmode_t mode);
-static blk_qc_t dcssblk_make_request(struct request_queue *q,
- struct bio *bio);
+static int dcssblk_open(struct gendisk *disk, blk_mode_t mode);
+static void dcssblk_release(struct gendisk *disk);
+static void dcssblk_submit_bio(struct bio *bio);
static long dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,
- long nr_pages, void **kaddr, pfn_t *pfn);
+ long nr_pages, enum dax_access_mode mode, void **kaddr,
+ unsigned long *pfn);
static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
static int dcssblk_major;
static const struct block_device_operations dcssblk_devops = {
.owner = THIS_MODULE,
+ .submit_bio = dcssblk_submit_bio,
.open = dcssblk_open,
.release = dcssblk_release,
};
-static size_t dcssblk_dax_copy_from_iter(struct dax_device *dax_dev,
- pgoff_t pgoff, void *addr, size_t bytes, struct iov_iter *i)
+static int dcssblk_dax_zero_page_range(struct dax_device *dax_dev,
+ pgoff_t pgoff, size_t nr_pages)
{
- return copy_from_iter(addr, bytes, i);
-}
+ long rc;
+ void *kaddr;
-static size_t dcssblk_dax_copy_to_iter(struct dax_device *dax_dev,
- pgoff_t pgoff, void *addr, size_t bytes, struct iov_iter *i)
-{
- return copy_to_iter(addr, bytes, i);
+ rc = dax_direct_access(dax_dev, pgoff, nr_pages, DAX_ACCESS,
+ &kaddr, NULL);
+ if (rc < 0)
+ return dax_mem2blk_err(rc);
+
+ memset(kaddr, 0, nr_pages << PAGE_SHIFT);
+ dax_flush(dax_dev, kaddr, nr_pages << PAGE_SHIFT);
+ return 0;
}
static const struct dax_operations dcssblk_dax_ops = {
.direct_access = dcssblk_dax_direct_access,
- .copy_from_iter = dcssblk_dax_copy_from_iter,
- .copy_to_iter = dcssblk_dax_copy_to_iter,
+ .zero_page_range = dcssblk_dax_zero_page_range,
};
struct dcssblk_dev_info {
@@ -74,10 +75,11 @@ struct dcssblk_dev_info {
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 dax_device *dax_dev;
+ struct dev_pagemap pgmap;
+ void *pgmap_addr;
};
struct segment_info {
@@ -312,7 +314,7 @@ dcssblk_load_segment(char *name, struct segment_info **seg_info)
if (*seg_info == NULL)
return -ENOMEM;
- strcpy((*seg_info)->segment_name, name);
+ strscpy((*seg_info)->segment_name, name);
/* load the segment */
rc = segment_load(name, SEGMENT_SHARED,
@@ -337,7 +339,7 @@ dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf
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");
+ return sysfs_emit(buf, dev_info->is_shared ? "1\n" : "0\n");
}
static ssize_t
@@ -409,14 +411,15 @@ removeseg:
segment_unload(entry->segment_name);
}
list_del(&dev_info->lh);
+ up_write(&dcssblk_devices_sem);
+ dax_remove_host(dev_info->gd);
kill_dax(dev_info->dax_dev);
put_dax(dev_info->dax_dev);
+ if (dev_info->pgmap_addr)
+ devm_memunmap_pages(&dev_info->dev, &dev_info->pgmap);
del_gendisk(dev_info->gd);
- blk_cleanup_queue(dev_info->dcssblk_queue);
- dev_info->gd->queue = NULL;
put_disk(dev_info->gd);
- up_write(&dcssblk_devices_sem);
if (device_remove_file_self(dev, attr)) {
device_unregister(dev);
@@ -443,7 +446,7 @@ dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
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");
+ return sysfs_emit(buf, dev_info->save_pending ? "1\n" : "0\n");
}
static ssize_t
@@ -505,21 +508,15 @@ 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;
+ int i;
+ i = 0;
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++;
- }
+ list_for_each_entry(entry, &dev_info->seg_list, lh)
+ i += sysfs_emit_at(buf, i, "%s\n", entry->segment_name);
up_read(&dcssblk_devices_sem);
return i;
}
@@ -539,16 +536,33 @@ static const struct attribute_group *dcssblk_dev_attr_groups[] = {
NULL,
};
+static int dcssblk_setup_dax(struct dcssblk_dev_info *dev_info)
+{
+ struct dax_device *dax_dev;
+
+ dax_dev = alloc_dax(dev_info, &dcssblk_dax_ops);
+ if (IS_ERR(dax_dev))
+ return PTR_ERR(dax_dev);
+ set_dax_synchronous(dax_dev);
+ dev_info->dax_dev = dax_dev;
+ return dax_add_host(dev_info->dax_dev, dev_info->gd);
+}
+
/*
* device attribute for adding devices
*/
static ssize_t
dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
+ struct queue_limits lim = {
+ .logical_block_size = 4096,
+ .features = BLK_FEAT_DAX,
+ };
int rc, i, j, num_of_segments;
struct dcssblk_dev_info *dev_info;
struct segment_info *seg_info, *temp;
char *local_buf;
+ void *addr;
unsigned long seg_byte_size;
dev_info = NULL;
@@ -598,7 +612,7 @@ dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char
rc = -ENOMEM;
goto out;
}
- strcpy(dev_info->segment_name, local_buf);
+ strscpy(dev_info->segment_name, local_buf);
dev_info->segment_type = seg_info->segment_type;
INIT_LIST_HEAD(&dev_info->seg_list);
}
@@ -615,7 +629,7 @@ dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char
rc = -ENAMETOOLONG;
goto seg_list_del;
}
- strlcpy(local_buf, buf, i + 1);
+ strscpy(local_buf, buf, i + 1);
dev_info->num_of_segments = num_of_segments;
rc = dcssblk_is_continuous(dev_info);
if (rc < 0)
@@ -628,19 +642,16 @@ dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char
dev_info->dev.release = dcssblk_release_segment;
dev_info->dev.groups = dcssblk_dev_attr_groups;
INIT_LIST_HEAD(&dev_info->lh);
- dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
- if (dev_info->gd == NULL) {
- rc = -ENOMEM;
+ dev_info->gd = blk_alloc_disk(&lim, NUMA_NO_NODE);
+ if (IS_ERR(dev_info->gd)) {
+ rc = PTR_ERR(dev_info->gd);
goto seg_list_del;
}
dev_info->gd->major = dcssblk_major;
+ dev_info->gd->minors = DCSSBLK_MINORS_PER_DISK;
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;
- blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
- blk_queue_logical_block_size(dev_info->dcssblk_queue, 4096);
- blk_queue_flag_set(QUEUE_FLAG_DAX, dev_info->dcssblk_queue);
+ dev_info->gd->flags |= GENHD_FL_NO_PART;
seg_byte_size = (dev_info->end - dev_info->start + 1);
set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
@@ -662,8 +673,8 @@ dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char
rc = dcssblk_assign_free_minor(dev_info);
if (rc)
goto release_gd;
- sprintf(dev_info->gd->disk_name, "dcssblk%d",
- dev_info->gd->first_minor);
+ scnprintf(dev_info->gd->disk_name, sizeof(dev_info->gd->disk_name),
+ "dcssblk%d", dev_info->gd->first_minor);
list_add_tail(&dev_info->lh, &dcssblk_devices);
if (!try_module_get(THIS_MODULE)) {
@@ -677,15 +688,31 @@ dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char
if (rc)
goto put_dev;
- dev_info->dax_dev = alloc_dax(dev_info, dev_info->gd->disk_name,
- &dcssblk_dax_ops);
- if (!dev_info->dax_dev) {
- rc = -ENOMEM;
- goto put_dev;
+ if (!IS_ALIGNED(dev_info->start, SUBSECTION_SIZE) ||
+ !IS_ALIGNED(dev_info->end + 1, SUBSECTION_SIZE)) {
+ pr_info("DCSS %s is not aligned to %lu bytes, DAX support disabled\n",
+ local_buf, SUBSECTION_SIZE);
+ } else {
+ dev_info->pgmap.type = MEMORY_DEVICE_FS_DAX;
+ dev_info->pgmap.range.start = dev_info->start;
+ dev_info->pgmap.range.end = dev_info->end;
+ dev_info->pgmap.nr_range = 1;
+ addr = devm_memremap_pages(&dev_info->dev, &dev_info->pgmap);
+ if (IS_ERR(addr)) {
+ rc = PTR_ERR(addr);
+ goto put_dev;
+ }
+ dev_info->pgmap_addr = addr;
+ rc = dcssblk_setup_dax(dev_info);
+ if (rc)
+ goto out_dax;
+ pr_info("DAX support enabled for DCSS %s\n", local_buf);
}
get_device(&dev_info->dev);
- device_add_disk(&dev_info->dev, dev_info->gd, NULL);
+ rc = device_add_disk(&dev_info->dev, dev_info->gd, NULL);
+ if (rc)
+ goto out_dax_host;
switch (dev_info->segment_type) {
case SEG_TYPE_SR:
@@ -701,10 +728,16 @@ dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char
rc = count;
goto out;
+out_dax_host:
+ put_device(&dev_info->dev);
+ dax_remove_host(dev_info->gd);
+out_dax:
+ kill_dax(dev_info->dax_dev);
+ put_dax(dev_info->dax_dev);
+ if (dev_info->pgmap_addr)
+ devm_memunmap_pages(&dev_info->dev, &dev_info->pgmap);
put_dev:
list_del(&dev_info->lh);
- blk_cleanup_queue(dev_info->dcssblk_queue);
- dev_info->gd->queue = NULL;
put_disk(dev_info->gd);
list_for_each_entry(seg_info, &dev_info->seg_list, lh) {
segment_unload(seg_info->segment_name);
@@ -715,8 +748,6 @@ put_dev:
dev_list_del:
list_del(&dev_info->lh);
release_gd:
- blk_cleanup_queue(dev_info->dcssblk_queue);
- dev_info->gd->queue = NULL;
put_disk(dev_info->gd);
up_write(&dcssblk_devices_sem);
seg_list_del:
@@ -782,19 +813,19 @@ dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const ch
}
list_del(&dev_info->lh);
- kill_dax(dev_info->dax_dev);
- put_dax(dev_info->dax_dev);
- del_gendisk(dev_info->gd);
- blk_cleanup_queue(dev_info->dcssblk_queue);
- dev_info->gd->queue = NULL;
- put_disk(dev_info->gd);
-
/* unload all related segments */
list_for_each_entry(entry, &dev_info->seg_list, lh)
segment_unload(entry->segment_name);
-
up_write(&dcssblk_devices_sem);
+ dax_remove_host(dev_info->gd);
+ kill_dax(dev_info->dax_dev);
+ put_dax(dev_info->dax_dev);
+ if (dev_info->pgmap_addr)
+ devm_memunmap_pages(&dev_info->dev, &dev_info->pgmap);
+ del_gendisk(dev_info->gd);
+ put_disk(dev_info->gd);
+
device_unregister(&dev_info->dev);
put_device(&dev_info->dev);
@@ -805,25 +836,23 @@ out_buf:
}
static int
-dcssblk_open(struct block_device *bdev, fmode_t mode)
+dcssblk_open(struct gendisk *disk, blk_mode_t mode)
{
- struct dcssblk_dev_info *dev_info;
+ struct dcssblk_dev_info *dev_info = disk->private_data;
int rc;
- dev_info = bdev->bd_disk->private_data;
if (NULL == dev_info) {
rc = -ENODEV;
goto out;
}
atomic_inc(&dev_info->use_count);
- bdev->bd_block_size = 4096;
rc = 0;
out:
return rc;
}
static void
-dcssblk_release(struct gendisk *disk, fmode_t mode)
+dcssblk_release(struct gendisk *disk)
{
struct dcssblk_dev_info *dev_info = disk->private_data;
struct segment_info *entry;
@@ -850,31 +879,25 @@ dcssblk_release(struct gendisk *disk, fmode_t mode)
up_write(&dcssblk_devices_sem);
}
-static blk_qc_t
-dcssblk_make_request(struct request_queue *q, struct bio *bio)
+static void
+dcssblk_submit_bio(struct bio *bio)
{
struct dcssblk_dev_info *dev_info;
struct bio_vec bvec;
struct bvec_iter iter;
unsigned long index;
- unsigned long page_addr;
+ void *page_addr;
unsigned long source_addr;
unsigned long bytes_done;
- blk_queue_split(q, &bio);
-
bytes_done = 0;
- dev_info = bio->bi_disk->private_data;
+ dev_info = bio->bi_bdev->bd_disk->private_data;
if (dev_info == NULL)
goto fail;
- if ((bio->bi_iter.bi_sector & 7) != 0 ||
- (bio->bi_iter.bi_size & 4095) != 0)
+ if (!IS_ALIGNED(bio->bi_iter.bi_sector, 8) ||
+ !IS_ALIGNED(bio->bi_iter.bi_size, PAGE_SIZE))
/* Request is not page-aligned. */
goto fail;
- if (bio_end_sector(bio) > get_capacity(bio->bi_disk)) {
- /* Request beyond end of DCSS segment. */
- goto fail;
- }
/* verify data transfer direction */
if (dev_info->is_shared) {
switch (dev_info->segment_type) {
@@ -892,48 +915,44 @@ dcssblk_make_request(struct request_queue *q, struct bio *bio)
index = (bio->bi_iter.bi_sector >> 3);
bio_for_each_segment(bvec, bio, iter) {
- page_addr = (unsigned long)
- page_address(bvec.bv_page) + bvec.bv_offset;
+ page_addr = bvec_virt(&bvec);
source_addr = dev_info->start + (index<<12) + bytes_done;
- if (unlikely((page_addr & 4095) != 0) || (bvec.bv_len & 4095) != 0)
+ if (unlikely(!IS_ALIGNED((unsigned long)page_addr, PAGE_SIZE) ||
+ !IS_ALIGNED(bvec.bv_len, PAGE_SIZE)))
// 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);
- }
+ if (bio_data_dir(bio) == READ)
+ memcpy(page_addr, __va(source_addr), bvec.bv_len);
+ else
+ memcpy(__va(source_addr), page_addr, bvec.bv_len);
bytes_done += bvec.bv_len;
}
bio_endio(bio);
- return BLK_QC_T_NONE;
+ return;
fail:
bio_io_error(bio);
- return BLK_QC_T_NONE;
}
static long
__dcssblk_direct_access(struct dcssblk_dev_info *dev_info, pgoff_t pgoff,
- long nr_pages, void **kaddr, pfn_t *pfn)
+ long nr_pages, void **kaddr, unsigned long *pfn)
{
resource_size_t offset = pgoff * PAGE_SIZE;
unsigned long dev_sz;
dev_sz = dev_info->end - dev_info->start + 1;
if (kaddr)
- *kaddr = (void *) dev_info->start + offset;
+ *kaddr = __va(dev_info->start + offset);
if (pfn)
- *pfn = __pfn_to_pfn_t(PFN_DOWN(dev_info->start + offset),
- PFN_DEV|PFN_SPECIAL);
+ *pfn = PFN_DOWN(dev_info->start + offset);
return (dev_sz - offset) / PAGE_SIZE;
}
static long
dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,
- long nr_pages, void **kaddr, pfn_t *pfn)
+ long nr_pages, enum dax_access_mode mode, void **kaddr,
+ unsigned long *pfn)
{
struct dcssblk_dev_info *dev_info = dax_get_private(dax_dev);
@@ -983,94 +1002,11 @@ dcssblk_check_params(void)
}
/*
- * Suspend / Resume
- */
-static int dcssblk_freeze(struct device *dev)
-{
- struct dcssblk_dev_info *dev_info;
- int rc = 0;
-
- list_for_each_entry(dev_info, &dcssblk_devices, lh) {
- switch (dev_info->segment_type) {
- case SEG_TYPE_SR:
- case SEG_TYPE_ER:
- case SEG_TYPE_SC:
- if (!dev_info->is_shared)
- rc = -EINVAL;
- break;
- default:
- rc = -EINVAL;
- break;
- }
- if (rc)
- break;
- }
- if (rc)
- pr_err("Suspending the system failed because DCSS device %s "
- "is writable\n",
- dev_info->segment_name);
- return rc;
-}
-
-static int dcssblk_restore(struct device *dev)
-{
- struct dcssblk_dev_info *dev_info;
- struct segment_info *entry;
- unsigned long start, end;
- int rc = 0;
-
- list_for_each_entry(dev_info, &dcssblk_devices, lh) {
- list_for_each_entry(entry, &dev_info->seg_list, lh) {
- segment_unload(entry->segment_name);
- rc = segment_load(entry->segment_name, SEGMENT_SHARED,
- &start, &end);
- if (rc < 0) {
-// TODO in_use check ?
- segment_warning(rc, entry->segment_name);
- goto out_panic;
- }
- if (start != entry->start || end != entry->end) {
- pr_err("The address range of DCSS %s changed "
- "while the system was suspended\n",
- entry->segment_name);
- goto out_panic;
- }
- }
- }
- return 0;
-out_panic:
- panic("fatal dcssblk resume error\n");
-}
-
-static int dcssblk_thaw(struct device *dev)
-{
- return 0;
-}
-
-static const struct dev_pm_ops dcssblk_pm_ops = {
- .freeze = dcssblk_freeze,
- .thaw = dcssblk_thaw,
- .restore = dcssblk_restore,
-};
-
-static struct platform_driver dcssblk_pdrv = {
- .driver = {
- .name = "dcssblk",
- .pm = &dcssblk_pm_ops,
- },
-};
-
-static struct platform_device *dcssblk_pdev;
-
-
-/*
* The init/exit functions.
*/
static void __exit
dcssblk_exit(void)
{
- platform_device_unregister(dcssblk_pdev);
- platform_driver_unregister(&dcssblk_pdrv);
root_device_unregister(dcssblk_root_dev);
unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
}
@@ -1080,22 +1016,9 @@ dcssblk_init(void)
{
int rc;
- rc = platform_driver_register(&dcssblk_pdrv);
- if (rc)
- return rc;
-
- dcssblk_pdev = platform_device_register_simple("dcssblk", -1, NULL,
- 0);
- if (IS_ERR(dcssblk_pdev)) {
- rc = PTR_ERR(dcssblk_pdev);
- goto out_pdrv;
- }
-
dcssblk_root_dev = root_device_register("dcssblk");
- if (IS_ERR(dcssblk_root_dev)) {
- rc = PTR_ERR(dcssblk_root_dev);
- goto out_pdev;
- }
+ if (IS_ERR(dcssblk_root_dev))
+ return PTR_ERR(dcssblk_root_dev);
rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
if (rc)
goto out_root;
@@ -1113,10 +1036,7 @@ dcssblk_init(void)
out_root:
root_device_unregister(dcssblk_root_dev);
-out_pdev:
- platform_device_unregister(dcssblk_pdev);
-out_pdrv:
- platform_driver_unregister(&dcssblk_pdrv);
+
return rc;
}
@@ -1133,4 +1053,5 @@ MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
"the contiguous segments - \n"
"e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"");
+MODULE_DESCRIPTION("S/390 block driver for DCSS memory");
MODULE_LICENSE("GPL");