162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * dcssblk.c -- the S/390 block driver for dcss memory
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define KMSG_COMPONENT "dcssblk"
962306a36Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/moduleparam.h>
1362306a36Sopenharmony_ci#include <linux/ctype.h>
1462306a36Sopenharmony_ci#include <linux/errno.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci#include <linux/blkdev.h>
1862306a36Sopenharmony_ci#include <linux/completion.h>
1962306a36Sopenharmony_ci#include <linux/interrupt.h>
2062306a36Sopenharmony_ci#include <linux/pfn_t.h>
2162306a36Sopenharmony_ci#include <linux/uio.h>
2262306a36Sopenharmony_ci#include <linux/dax.h>
2362306a36Sopenharmony_ci#include <linux/io.h>
2462306a36Sopenharmony_ci#include <asm/extmem.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define DCSSBLK_NAME "dcssblk"
2762306a36Sopenharmony_ci#define DCSSBLK_MINORS_PER_DISK 1
2862306a36Sopenharmony_ci#define DCSSBLK_PARM_LEN 400
2962306a36Sopenharmony_ci#define DCSS_BUS_ID_SIZE 20
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic int dcssblk_open(struct gendisk *disk, blk_mode_t mode);
3262306a36Sopenharmony_cistatic void dcssblk_release(struct gendisk *disk);
3362306a36Sopenharmony_cistatic void dcssblk_submit_bio(struct bio *bio);
3462306a36Sopenharmony_cistatic long dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,
3562306a36Sopenharmony_ci		long nr_pages, enum dax_access_mode mode, void **kaddr,
3662306a36Sopenharmony_ci		pfn_t *pfn);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic int dcssblk_major;
4162306a36Sopenharmony_cistatic const struct block_device_operations dcssblk_devops = {
4262306a36Sopenharmony_ci	.owner   	= THIS_MODULE,
4362306a36Sopenharmony_ci	.submit_bio	= dcssblk_submit_bio,
4462306a36Sopenharmony_ci	.open    	= dcssblk_open,
4562306a36Sopenharmony_ci	.release 	= dcssblk_release,
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic int dcssblk_dax_zero_page_range(struct dax_device *dax_dev,
4962306a36Sopenharmony_ci				       pgoff_t pgoff, size_t nr_pages)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	long rc;
5262306a36Sopenharmony_ci	void *kaddr;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	rc = dax_direct_access(dax_dev, pgoff, nr_pages, DAX_ACCESS,
5562306a36Sopenharmony_ci			&kaddr, NULL);
5662306a36Sopenharmony_ci	if (rc < 0)
5762306a36Sopenharmony_ci		return dax_mem2blk_err(rc);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	memset(kaddr, 0, nr_pages << PAGE_SHIFT);
6062306a36Sopenharmony_ci	dax_flush(dax_dev, kaddr, nr_pages << PAGE_SHIFT);
6162306a36Sopenharmony_ci	return 0;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic const struct dax_operations dcssblk_dax_ops = {
6562306a36Sopenharmony_ci	.direct_access = dcssblk_dax_direct_access,
6662306a36Sopenharmony_ci	.zero_page_range = dcssblk_dax_zero_page_range,
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistruct dcssblk_dev_info {
7062306a36Sopenharmony_ci	struct list_head lh;
7162306a36Sopenharmony_ci	struct device dev;
7262306a36Sopenharmony_ci	char segment_name[DCSS_BUS_ID_SIZE];
7362306a36Sopenharmony_ci	atomic_t use_count;
7462306a36Sopenharmony_ci	struct gendisk *gd;
7562306a36Sopenharmony_ci	unsigned long start;
7662306a36Sopenharmony_ci	unsigned long end;
7762306a36Sopenharmony_ci	int segment_type;
7862306a36Sopenharmony_ci	unsigned char save_pending;
7962306a36Sopenharmony_ci	unsigned char is_shared;
8062306a36Sopenharmony_ci	int num_of_segments;
8162306a36Sopenharmony_ci	struct list_head seg_list;
8262306a36Sopenharmony_ci	struct dax_device *dax_dev;
8362306a36Sopenharmony_ci};
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistruct segment_info {
8662306a36Sopenharmony_ci	struct list_head lh;
8762306a36Sopenharmony_ci	char segment_name[DCSS_BUS_ID_SIZE];
8862306a36Sopenharmony_ci	unsigned long start;
8962306a36Sopenharmony_ci	unsigned long end;
9062306a36Sopenharmony_ci	int segment_type;
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
9462306a36Sopenharmony_ci				  size_t count);
9562306a36Sopenharmony_cistatic ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
9662306a36Sopenharmony_ci				  size_t count);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
9962306a36Sopenharmony_cistatic DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic struct device *dcssblk_root_dev;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic LIST_HEAD(dcssblk_devices);
10462306a36Sopenharmony_cistatic struct rw_semaphore dcssblk_devices_sem;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci/*
10762306a36Sopenharmony_ci * release function for segment device.
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_cistatic void
11062306a36Sopenharmony_cidcssblk_release_segment(struct device *dev)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
11362306a36Sopenharmony_ci	struct segment_info *entry, *temp;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
11662306a36Sopenharmony_ci	list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) {
11762306a36Sopenharmony_ci		list_del(&entry->lh);
11862306a36Sopenharmony_ci		kfree(entry);
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci	kfree(dev_info);
12162306a36Sopenharmony_ci	module_put(THIS_MODULE);
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci/*
12562306a36Sopenharmony_ci * get a minor number. needs to be called with
12662306a36Sopenharmony_ci * down_write(&dcssblk_devices_sem) and the
12762306a36Sopenharmony_ci * device needs to be enqueued before the semaphore is
12862306a36Sopenharmony_ci * freed.
12962306a36Sopenharmony_ci */
13062306a36Sopenharmony_cistatic int
13162306a36Sopenharmony_cidcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	int minor, found;
13462306a36Sopenharmony_ci	struct dcssblk_dev_info *entry;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (dev_info == NULL)
13762306a36Sopenharmony_ci		return -EINVAL;
13862306a36Sopenharmony_ci	for (minor = 0; minor < (1<<MINORBITS); minor++) {
13962306a36Sopenharmony_ci		found = 0;
14062306a36Sopenharmony_ci		// test if minor available
14162306a36Sopenharmony_ci		list_for_each_entry(entry, &dcssblk_devices, lh)
14262306a36Sopenharmony_ci			if (minor == entry->gd->first_minor)
14362306a36Sopenharmony_ci				found++;
14462306a36Sopenharmony_ci		if (!found) break; // got unused minor
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci	if (found)
14762306a36Sopenharmony_ci		return -EBUSY;
14862306a36Sopenharmony_ci	dev_info->gd->first_minor = minor;
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci/*
15362306a36Sopenharmony_ci * get the struct dcssblk_dev_info from dcssblk_devices
15462306a36Sopenharmony_ci * for the given name.
15562306a36Sopenharmony_ci * down_read(&dcssblk_devices_sem) must be held.
15662306a36Sopenharmony_ci */
15762306a36Sopenharmony_cistatic struct dcssblk_dev_info *
15862306a36Sopenharmony_cidcssblk_get_device_by_name(char *name)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct dcssblk_dev_info *entry;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	list_for_each_entry(entry, &dcssblk_devices, lh) {
16362306a36Sopenharmony_ci		if (!strcmp(name, entry->segment_name)) {
16462306a36Sopenharmony_ci			return entry;
16562306a36Sopenharmony_ci		}
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci	return NULL;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci/*
17162306a36Sopenharmony_ci * get the struct segment_info from seg_list
17262306a36Sopenharmony_ci * for the given name.
17362306a36Sopenharmony_ci * down_read(&dcssblk_devices_sem) must be held.
17462306a36Sopenharmony_ci */
17562306a36Sopenharmony_cistatic struct segment_info *
17662306a36Sopenharmony_cidcssblk_get_segment_by_name(char *name)
17762306a36Sopenharmony_ci{
17862306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
17962306a36Sopenharmony_ci	struct segment_info *entry;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	list_for_each_entry(dev_info, &dcssblk_devices, lh) {
18262306a36Sopenharmony_ci		list_for_each_entry(entry, &dev_info->seg_list, lh) {
18362306a36Sopenharmony_ci			if (!strcmp(name, entry->segment_name))
18462306a36Sopenharmony_ci				return entry;
18562306a36Sopenharmony_ci		}
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci	return NULL;
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci/*
19162306a36Sopenharmony_ci * get the highest address of the multi-segment block.
19262306a36Sopenharmony_ci */
19362306a36Sopenharmony_cistatic unsigned long
19462306a36Sopenharmony_cidcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci	unsigned long highest_addr;
19762306a36Sopenharmony_ci	struct segment_info *entry;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	highest_addr = 0;
20062306a36Sopenharmony_ci	list_for_each_entry(entry, &dev_info->seg_list, lh) {
20162306a36Sopenharmony_ci		if (highest_addr < entry->end)
20262306a36Sopenharmony_ci			highest_addr = entry->end;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci	return highest_addr;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci/*
20862306a36Sopenharmony_ci * get the lowest address of the multi-segment block.
20962306a36Sopenharmony_ci */
21062306a36Sopenharmony_cistatic unsigned long
21162306a36Sopenharmony_cidcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	int set_first;
21462306a36Sopenharmony_ci	unsigned long lowest_addr;
21562306a36Sopenharmony_ci	struct segment_info *entry;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	set_first = 0;
21862306a36Sopenharmony_ci	lowest_addr = 0;
21962306a36Sopenharmony_ci	list_for_each_entry(entry, &dev_info->seg_list, lh) {
22062306a36Sopenharmony_ci		if (set_first == 0) {
22162306a36Sopenharmony_ci			lowest_addr = entry->start;
22262306a36Sopenharmony_ci			set_first = 1;
22362306a36Sopenharmony_ci		} else {
22462306a36Sopenharmony_ci			if (lowest_addr > entry->start)
22562306a36Sopenharmony_ci				lowest_addr = entry->start;
22662306a36Sopenharmony_ci		}
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci	return lowest_addr;
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci/*
23262306a36Sopenharmony_ci * Check continuity of segments.
23362306a36Sopenharmony_ci */
23462306a36Sopenharmony_cistatic int
23562306a36Sopenharmony_cidcssblk_is_continuous(struct dcssblk_dev_info *dev_info)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	int i, j, rc;
23862306a36Sopenharmony_ci	struct segment_info *sort_list, *entry, temp;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	if (dev_info->num_of_segments <= 1)
24162306a36Sopenharmony_ci		return 0;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	sort_list = kcalloc(dev_info->num_of_segments,
24462306a36Sopenharmony_ci			    sizeof(struct segment_info),
24562306a36Sopenharmony_ci			    GFP_KERNEL);
24662306a36Sopenharmony_ci	if (sort_list == NULL)
24762306a36Sopenharmony_ci		return -ENOMEM;
24862306a36Sopenharmony_ci	i = 0;
24962306a36Sopenharmony_ci	list_for_each_entry(entry, &dev_info->seg_list, lh) {
25062306a36Sopenharmony_ci		memcpy(&sort_list[i], entry, sizeof(struct segment_info));
25162306a36Sopenharmony_ci		i++;
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/* sort segments */
25562306a36Sopenharmony_ci	for (i = 0; i < dev_info->num_of_segments; i++)
25662306a36Sopenharmony_ci		for (j = 0; j < dev_info->num_of_segments; j++)
25762306a36Sopenharmony_ci			if (sort_list[j].start > sort_list[i].start) {
25862306a36Sopenharmony_ci				memcpy(&temp, &sort_list[i],
25962306a36Sopenharmony_ci					sizeof(struct segment_info));
26062306a36Sopenharmony_ci				memcpy(&sort_list[i], &sort_list[j],
26162306a36Sopenharmony_ci					sizeof(struct segment_info));
26262306a36Sopenharmony_ci				memcpy(&sort_list[j], &temp,
26362306a36Sopenharmony_ci					sizeof(struct segment_info));
26462306a36Sopenharmony_ci			}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/* check continuity */
26762306a36Sopenharmony_ci	for (i = 0; i < dev_info->num_of_segments - 1; i++) {
26862306a36Sopenharmony_ci		if ((sort_list[i].end + 1) != sort_list[i+1].start) {
26962306a36Sopenharmony_ci			pr_err("Adjacent DCSSs %s and %s are not "
27062306a36Sopenharmony_ci			       "contiguous\n", sort_list[i].segment_name,
27162306a36Sopenharmony_ci			       sort_list[i+1].segment_name);
27262306a36Sopenharmony_ci			rc = -EINVAL;
27362306a36Sopenharmony_ci			goto out;
27462306a36Sopenharmony_ci		}
27562306a36Sopenharmony_ci		/* EN and EW are allowed in a block device */
27662306a36Sopenharmony_ci		if (sort_list[i].segment_type != sort_list[i+1].segment_type) {
27762306a36Sopenharmony_ci			if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) ||
27862306a36Sopenharmony_ci				(sort_list[i].segment_type == SEG_TYPE_ER) ||
27962306a36Sopenharmony_ci				!(sort_list[i+1].segment_type &
28062306a36Sopenharmony_ci				SEGMENT_EXCLUSIVE) ||
28162306a36Sopenharmony_ci				(sort_list[i+1].segment_type == SEG_TYPE_ER)) {
28262306a36Sopenharmony_ci				pr_err("DCSS %s and DCSS %s have "
28362306a36Sopenharmony_ci				       "incompatible types\n",
28462306a36Sopenharmony_ci				       sort_list[i].segment_name,
28562306a36Sopenharmony_ci				       sort_list[i+1].segment_name);
28662306a36Sopenharmony_ci				rc = -EINVAL;
28762306a36Sopenharmony_ci				goto out;
28862306a36Sopenharmony_ci			}
28962306a36Sopenharmony_ci		}
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci	rc = 0;
29262306a36Sopenharmony_ciout:
29362306a36Sopenharmony_ci	kfree(sort_list);
29462306a36Sopenharmony_ci	return rc;
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci/*
29862306a36Sopenharmony_ci * Load a segment
29962306a36Sopenharmony_ci */
30062306a36Sopenharmony_cistatic int
30162306a36Sopenharmony_cidcssblk_load_segment(char *name, struct segment_info **seg_info)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	int rc;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	/* already loaded? */
30662306a36Sopenharmony_ci	down_read(&dcssblk_devices_sem);
30762306a36Sopenharmony_ci	*seg_info = dcssblk_get_segment_by_name(name);
30862306a36Sopenharmony_ci	up_read(&dcssblk_devices_sem);
30962306a36Sopenharmony_ci	if (*seg_info != NULL)
31062306a36Sopenharmony_ci		return -EEXIST;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/* get a struct segment_info */
31362306a36Sopenharmony_ci	*seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL);
31462306a36Sopenharmony_ci	if (*seg_info == NULL)
31562306a36Sopenharmony_ci		return -ENOMEM;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	strcpy((*seg_info)->segment_name, name);
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	/* load the segment */
32062306a36Sopenharmony_ci	rc = segment_load(name, SEGMENT_SHARED,
32162306a36Sopenharmony_ci			&(*seg_info)->start, &(*seg_info)->end);
32262306a36Sopenharmony_ci	if (rc < 0) {
32362306a36Sopenharmony_ci		segment_warning(rc, (*seg_info)->segment_name);
32462306a36Sopenharmony_ci		kfree(*seg_info);
32562306a36Sopenharmony_ci	} else {
32662306a36Sopenharmony_ci		INIT_LIST_HEAD(&(*seg_info)->lh);
32762306a36Sopenharmony_ci		(*seg_info)->segment_type = rc;
32862306a36Sopenharmony_ci	}
32962306a36Sopenharmony_ci	return rc;
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci/*
33362306a36Sopenharmony_ci * device attribute for switching shared/nonshared (exclusive)
33462306a36Sopenharmony_ci * operation (show + store)
33562306a36Sopenharmony_ci */
33662306a36Sopenharmony_cistatic ssize_t
33762306a36Sopenharmony_cidcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
33862306a36Sopenharmony_ci{
33962306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
34262306a36Sopenharmony_ci	return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
34362306a36Sopenharmony_ci}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_cistatic ssize_t
34662306a36Sopenharmony_cidcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
34962306a36Sopenharmony_ci	struct segment_info *entry, *temp;
35062306a36Sopenharmony_ci	int rc;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
35362306a36Sopenharmony_ci		return -EINVAL;
35462306a36Sopenharmony_ci	down_write(&dcssblk_devices_sem);
35562306a36Sopenharmony_ci	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
35662306a36Sopenharmony_ci	if (atomic_read(&dev_info->use_count)) {
35762306a36Sopenharmony_ci		rc = -EBUSY;
35862306a36Sopenharmony_ci		goto out;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci	if (inbuf[0] == '1') {
36162306a36Sopenharmony_ci		/* reload segments in shared mode */
36262306a36Sopenharmony_ci		list_for_each_entry(entry, &dev_info->seg_list, lh) {
36362306a36Sopenharmony_ci			rc = segment_modify_shared(entry->segment_name,
36462306a36Sopenharmony_ci						SEGMENT_SHARED);
36562306a36Sopenharmony_ci			if (rc < 0) {
36662306a36Sopenharmony_ci				BUG_ON(rc == -EINVAL);
36762306a36Sopenharmony_ci				if (rc != -EAGAIN)
36862306a36Sopenharmony_ci					goto removeseg;
36962306a36Sopenharmony_ci			}
37062306a36Sopenharmony_ci		}
37162306a36Sopenharmony_ci		dev_info->is_shared = 1;
37262306a36Sopenharmony_ci		switch (dev_info->segment_type) {
37362306a36Sopenharmony_ci		case SEG_TYPE_SR:
37462306a36Sopenharmony_ci		case SEG_TYPE_ER:
37562306a36Sopenharmony_ci		case SEG_TYPE_SC:
37662306a36Sopenharmony_ci			set_disk_ro(dev_info->gd, 1);
37762306a36Sopenharmony_ci		}
37862306a36Sopenharmony_ci	} else if (inbuf[0] == '0') {
37962306a36Sopenharmony_ci		/* reload segments in exclusive mode */
38062306a36Sopenharmony_ci		if (dev_info->segment_type == SEG_TYPE_SC) {
38162306a36Sopenharmony_ci			pr_err("DCSS %s is of type SC and cannot be "
38262306a36Sopenharmony_ci			       "loaded as exclusive-writable\n",
38362306a36Sopenharmony_ci			       dev_info->segment_name);
38462306a36Sopenharmony_ci			rc = -EINVAL;
38562306a36Sopenharmony_ci			goto out;
38662306a36Sopenharmony_ci		}
38762306a36Sopenharmony_ci		list_for_each_entry(entry, &dev_info->seg_list, lh) {
38862306a36Sopenharmony_ci			rc = segment_modify_shared(entry->segment_name,
38962306a36Sopenharmony_ci						   SEGMENT_EXCLUSIVE);
39062306a36Sopenharmony_ci			if (rc < 0) {
39162306a36Sopenharmony_ci				BUG_ON(rc == -EINVAL);
39262306a36Sopenharmony_ci				if (rc != -EAGAIN)
39362306a36Sopenharmony_ci					goto removeseg;
39462306a36Sopenharmony_ci			}
39562306a36Sopenharmony_ci		}
39662306a36Sopenharmony_ci		dev_info->is_shared = 0;
39762306a36Sopenharmony_ci		set_disk_ro(dev_info->gd, 0);
39862306a36Sopenharmony_ci	} else {
39962306a36Sopenharmony_ci		rc = -EINVAL;
40062306a36Sopenharmony_ci		goto out;
40162306a36Sopenharmony_ci	}
40262306a36Sopenharmony_ci	rc = count;
40362306a36Sopenharmony_ci	goto out;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ciremoveseg:
40662306a36Sopenharmony_ci	pr_err("DCSS device %s is removed after a failed access mode "
40762306a36Sopenharmony_ci	       "change\n", dev_info->segment_name);
40862306a36Sopenharmony_ci	temp = entry;
40962306a36Sopenharmony_ci	list_for_each_entry(entry, &dev_info->seg_list, lh) {
41062306a36Sopenharmony_ci		if (entry != temp)
41162306a36Sopenharmony_ci			segment_unload(entry->segment_name);
41262306a36Sopenharmony_ci	}
41362306a36Sopenharmony_ci	list_del(&dev_info->lh);
41462306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	dax_remove_host(dev_info->gd);
41762306a36Sopenharmony_ci	kill_dax(dev_info->dax_dev);
41862306a36Sopenharmony_ci	put_dax(dev_info->dax_dev);
41962306a36Sopenharmony_ci	del_gendisk(dev_info->gd);
42062306a36Sopenharmony_ci	put_disk(dev_info->gd);
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	if (device_remove_file_self(dev, attr)) {
42362306a36Sopenharmony_ci		device_unregister(dev);
42462306a36Sopenharmony_ci		put_device(dev);
42562306a36Sopenharmony_ci	}
42662306a36Sopenharmony_ci	return rc;
42762306a36Sopenharmony_ciout:
42862306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
42962306a36Sopenharmony_ci	return rc;
43062306a36Sopenharmony_ci}
43162306a36Sopenharmony_cistatic DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show,
43262306a36Sopenharmony_ci		   dcssblk_shared_store);
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci/*
43562306a36Sopenharmony_ci * device attribute for save operation on current copy
43662306a36Sopenharmony_ci * of the segment. If the segment is busy, saving will
43762306a36Sopenharmony_ci * become pending until it gets released, which can be
43862306a36Sopenharmony_ci * undone by storing a non-true value to this entry.
43962306a36Sopenharmony_ci * (show + store)
44062306a36Sopenharmony_ci */
44162306a36Sopenharmony_cistatic ssize_t
44262306a36Sopenharmony_cidcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
44362306a36Sopenharmony_ci{
44462306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
44762306a36Sopenharmony_ci	return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
44862306a36Sopenharmony_ci}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_cistatic ssize_t
45162306a36Sopenharmony_cidcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
45462306a36Sopenharmony_ci	struct segment_info *entry;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
45762306a36Sopenharmony_ci		return -EINVAL;
45862306a36Sopenharmony_ci	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	down_write(&dcssblk_devices_sem);
46162306a36Sopenharmony_ci	if (inbuf[0] == '1') {
46262306a36Sopenharmony_ci		if (atomic_read(&dev_info->use_count) == 0) {
46362306a36Sopenharmony_ci			// device is idle => we save immediately
46462306a36Sopenharmony_ci			pr_info("All DCSSs that map to device %s are "
46562306a36Sopenharmony_ci				"saved\n", dev_info->segment_name);
46662306a36Sopenharmony_ci			list_for_each_entry(entry, &dev_info->seg_list, lh) {
46762306a36Sopenharmony_ci				if (entry->segment_type == SEG_TYPE_EN ||
46862306a36Sopenharmony_ci				    entry->segment_type == SEG_TYPE_SN)
46962306a36Sopenharmony_ci					pr_warn("DCSS %s is of type SN or EN"
47062306a36Sopenharmony_ci						" and cannot be saved\n",
47162306a36Sopenharmony_ci						entry->segment_name);
47262306a36Sopenharmony_ci				else
47362306a36Sopenharmony_ci					segment_save(entry->segment_name);
47462306a36Sopenharmony_ci			}
47562306a36Sopenharmony_ci		}  else {
47662306a36Sopenharmony_ci			// device is busy => we save it when it becomes
47762306a36Sopenharmony_ci			// idle in dcssblk_release
47862306a36Sopenharmony_ci			pr_info("Device %s is in use, its DCSSs will be "
47962306a36Sopenharmony_ci				"saved when it becomes idle\n",
48062306a36Sopenharmony_ci				dev_info->segment_name);
48162306a36Sopenharmony_ci			dev_info->save_pending = 1;
48262306a36Sopenharmony_ci		}
48362306a36Sopenharmony_ci	} else if (inbuf[0] == '0') {
48462306a36Sopenharmony_ci		if (dev_info->save_pending) {
48562306a36Sopenharmony_ci			// device is busy & the user wants to undo his save
48662306a36Sopenharmony_ci			// request
48762306a36Sopenharmony_ci			dev_info->save_pending = 0;
48862306a36Sopenharmony_ci			pr_info("A pending save request for device %s "
48962306a36Sopenharmony_ci				"has been canceled\n",
49062306a36Sopenharmony_ci				dev_info->segment_name);
49162306a36Sopenharmony_ci		}
49262306a36Sopenharmony_ci	} else {
49362306a36Sopenharmony_ci		up_write(&dcssblk_devices_sem);
49462306a36Sopenharmony_ci		return -EINVAL;
49562306a36Sopenharmony_ci	}
49662306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
49762306a36Sopenharmony_ci	return count;
49862306a36Sopenharmony_ci}
49962306a36Sopenharmony_cistatic DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show,
50062306a36Sopenharmony_ci		   dcssblk_save_store);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci/*
50362306a36Sopenharmony_ci * device attribute for showing all segments in a device
50462306a36Sopenharmony_ci */
50562306a36Sopenharmony_cistatic ssize_t
50662306a36Sopenharmony_cidcssblk_seglist_show(struct device *dev, struct device_attribute *attr,
50762306a36Sopenharmony_ci		char *buf)
50862306a36Sopenharmony_ci{
50962306a36Sopenharmony_ci	int i;
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
51262306a36Sopenharmony_ci	struct segment_info *entry;
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	down_read(&dcssblk_devices_sem);
51562306a36Sopenharmony_ci	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
51662306a36Sopenharmony_ci	i = 0;
51762306a36Sopenharmony_ci	buf[0] = '\0';
51862306a36Sopenharmony_ci	list_for_each_entry(entry, &dev_info->seg_list, lh) {
51962306a36Sopenharmony_ci		strcpy(&buf[i], entry->segment_name);
52062306a36Sopenharmony_ci		i += strlen(entry->segment_name);
52162306a36Sopenharmony_ci		buf[i] = '\n';
52262306a36Sopenharmony_ci		i++;
52362306a36Sopenharmony_ci	}
52462306a36Sopenharmony_ci	up_read(&dcssblk_devices_sem);
52562306a36Sopenharmony_ci	return i;
52662306a36Sopenharmony_ci}
52762306a36Sopenharmony_cistatic DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_cistatic struct attribute *dcssblk_dev_attrs[] = {
53062306a36Sopenharmony_ci	&dev_attr_shared.attr,
53162306a36Sopenharmony_ci	&dev_attr_save.attr,
53262306a36Sopenharmony_ci	&dev_attr_seglist.attr,
53362306a36Sopenharmony_ci	NULL,
53462306a36Sopenharmony_ci};
53562306a36Sopenharmony_cistatic struct attribute_group dcssblk_dev_attr_group = {
53662306a36Sopenharmony_ci	.attrs = dcssblk_dev_attrs,
53762306a36Sopenharmony_ci};
53862306a36Sopenharmony_cistatic const struct attribute_group *dcssblk_dev_attr_groups[] = {
53962306a36Sopenharmony_ci	&dcssblk_dev_attr_group,
54062306a36Sopenharmony_ci	NULL,
54162306a36Sopenharmony_ci};
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci/*
54462306a36Sopenharmony_ci * device attribute for adding devices
54562306a36Sopenharmony_ci */
54662306a36Sopenharmony_cistatic ssize_t
54762306a36Sopenharmony_cidcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
54862306a36Sopenharmony_ci{
54962306a36Sopenharmony_ci	int rc, i, j, num_of_segments;
55062306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
55162306a36Sopenharmony_ci	struct segment_info *seg_info, *temp;
55262306a36Sopenharmony_ci	char *local_buf;
55362306a36Sopenharmony_ci	unsigned long seg_byte_size;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	dev_info = NULL;
55662306a36Sopenharmony_ci	seg_info = NULL;
55762306a36Sopenharmony_ci	if (dev != dcssblk_root_dev) {
55862306a36Sopenharmony_ci		rc = -EINVAL;
55962306a36Sopenharmony_ci		goto out_nobuf;
56062306a36Sopenharmony_ci	}
56162306a36Sopenharmony_ci	if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) {
56262306a36Sopenharmony_ci		rc = -ENAMETOOLONG;
56362306a36Sopenharmony_ci		goto out_nobuf;
56462306a36Sopenharmony_ci	}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	local_buf = kmalloc(count + 1, GFP_KERNEL);
56762306a36Sopenharmony_ci	if (local_buf == NULL) {
56862306a36Sopenharmony_ci		rc = -ENOMEM;
56962306a36Sopenharmony_ci		goto out_nobuf;
57062306a36Sopenharmony_ci	}
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	/*
57362306a36Sopenharmony_ci	 * parse input
57462306a36Sopenharmony_ci	 */
57562306a36Sopenharmony_ci	num_of_segments = 0;
57662306a36Sopenharmony_ci	for (i = 0; (i < count && (buf[i] != '\0') && (buf[i] != '\n')); i++) {
57762306a36Sopenharmony_ci		for (j = i; j < count &&
57862306a36Sopenharmony_ci			(buf[j] != ':') &&
57962306a36Sopenharmony_ci			(buf[j] != '\0') &&
58062306a36Sopenharmony_ci			(buf[j] != '\n'); j++) {
58162306a36Sopenharmony_ci			local_buf[j-i] = toupper(buf[j]);
58262306a36Sopenharmony_ci		}
58362306a36Sopenharmony_ci		local_buf[j-i] = '\0';
58462306a36Sopenharmony_ci		if (((j - i) == 0) || ((j - i) > 8)) {
58562306a36Sopenharmony_ci			rc = -ENAMETOOLONG;
58662306a36Sopenharmony_ci			goto seg_list_del;
58762306a36Sopenharmony_ci		}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci		rc = dcssblk_load_segment(local_buf, &seg_info);
59062306a36Sopenharmony_ci		if (rc < 0)
59162306a36Sopenharmony_ci			goto seg_list_del;
59262306a36Sopenharmony_ci		/*
59362306a36Sopenharmony_ci		 * get a struct dcssblk_dev_info
59462306a36Sopenharmony_ci		 */
59562306a36Sopenharmony_ci		if (num_of_segments == 0) {
59662306a36Sopenharmony_ci			dev_info = kzalloc(sizeof(struct dcssblk_dev_info),
59762306a36Sopenharmony_ci					GFP_KERNEL);
59862306a36Sopenharmony_ci			if (dev_info == NULL) {
59962306a36Sopenharmony_ci				rc = -ENOMEM;
60062306a36Sopenharmony_ci				goto out;
60162306a36Sopenharmony_ci			}
60262306a36Sopenharmony_ci			strcpy(dev_info->segment_name, local_buf);
60362306a36Sopenharmony_ci			dev_info->segment_type = seg_info->segment_type;
60462306a36Sopenharmony_ci			INIT_LIST_HEAD(&dev_info->seg_list);
60562306a36Sopenharmony_ci		}
60662306a36Sopenharmony_ci		list_add_tail(&seg_info->lh, &dev_info->seg_list);
60762306a36Sopenharmony_ci		num_of_segments++;
60862306a36Sopenharmony_ci		i = j;
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci		if ((buf[j] == '\0') || (buf[j] == '\n'))
61162306a36Sopenharmony_ci			break;
61262306a36Sopenharmony_ci	}
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	/* no trailing colon at the end of the input */
61562306a36Sopenharmony_ci	if ((i > 0) && (buf[i-1] == ':')) {
61662306a36Sopenharmony_ci		rc = -ENAMETOOLONG;
61762306a36Sopenharmony_ci		goto seg_list_del;
61862306a36Sopenharmony_ci	}
61962306a36Sopenharmony_ci	strscpy(local_buf, buf, i + 1);
62062306a36Sopenharmony_ci	dev_info->num_of_segments = num_of_segments;
62162306a36Sopenharmony_ci	rc = dcssblk_is_continuous(dev_info);
62262306a36Sopenharmony_ci	if (rc < 0)
62362306a36Sopenharmony_ci		goto seg_list_del;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	dev_info->start = dcssblk_find_lowest_addr(dev_info);
62662306a36Sopenharmony_ci	dev_info->end = dcssblk_find_highest_addr(dev_info);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	dev_set_name(&dev_info->dev, "%s", dev_info->segment_name);
62962306a36Sopenharmony_ci	dev_info->dev.release = dcssblk_release_segment;
63062306a36Sopenharmony_ci	dev_info->dev.groups = dcssblk_dev_attr_groups;
63162306a36Sopenharmony_ci	INIT_LIST_HEAD(&dev_info->lh);
63262306a36Sopenharmony_ci	dev_info->gd = blk_alloc_disk(NUMA_NO_NODE);
63362306a36Sopenharmony_ci	if (dev_info->gd == NULL) {
63462306a36Sopenharmony_ci		rc = -ENOMEM;
63562306a36Sopenharmony_ci		goto seg_list_del;
63662306a36Sopenharmony_ci	}
63762306a36Sopenharmony_ci	dev_info->gd->major = dcssblk_major;
63862306a36Sopenharmony_ci	dev_info->gd->minors = DCSSBLK_MINORS_PER_DISK;
63962306a36Sopenharmony_ci	dev_info->gd->fops = &dcssblk_devops;
64062306a36Sopenharmony_ci	dev_info->gd->private_data = dev_info;
64162306a36Sopenharmony_ci	dev_info->gd->flags |= GENHD_FL_NO_PART;
64262306a36Sopenharmony_ci	blk_queue_logical_block_size(dev_info->gd->queue, 4096);
64362306a36Sopenharmony_ci	blk_queue_flag_set(QUEUE_FLAG_DAX, dev_info->gd->queue);
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	seg_byte_size = (dev_info->end - dev_info->start + 1);
64662306a36Sopenharmony_ci	set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
64762306a36Sopenharmony_ci	pr_info("Loaded %s with total size %lu bytes and capacity %lu "
64862306a36Sopenharmony_ci		"sectors\n", local_buf, seg_byte_size, seg_byte_size >> 9);
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	dev_info->save_pending = 0;
65162306a36Sopenharmony_ci	dev_info->is_shared = 1;
65262306a36Sopenharmony_ci	dev_info->dev.parent = dcssblk_root_dev;
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	/*
65562306a36Sopenharmony_ci	 *get minor, add to list
65662306a36Sopenharmony_ci	 */
65762306a36Sopenharmony_ci	down_write(&dcssblk_devices_sem);
65862306a36Sopenharmony_ci	if (dcssblk_get_segment_by_name(local_buf)) {
65962306a36Sopenharmony_ci		rc = -EEXIST;
66062306a36Sopenharmony_ci		goto release_gd;
66162306a36Sopenharmony_ci	}
66262306a36Sopenharmony_ci	rc = dcssblk_assign_free_minor(dev_info);
66362306a36Sopenharmony_ci	if (rc)
66462306a36Sopenharmony_ci		goto release_gd;
66562306a36Sopenharmony_ci	sprintf(dev_info->gd->disk_name, "dcssblk%d",
66662306a36Sopenharmony_ci		dev_info->gd->first_minor);
66762306a36Sopenharmony_ci	list_add_tail(&dev_info->lh, &dcssblk_devices);
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	if (!try_module_get(THIS_MODULE)) {
67062306a36Sopenharmony_ci		rc = -ENODEV;
67162306a36Sopenharmony_ci		goto dev_list_del;
67262306a36Sopenharmony_ci	}
67362306a36Sopenharmony_ci	/*
67462306a36Sopenharmony_ci	 * register the device
67562306a36Sopenharmony_ci	 */
67662306a36Sopenharmony_ci	rc = device_register(&dev_info->dev);
67762306a36Sopenharmony_ci	if (rc)
67862306a36Sopenharmony_ci		goto put_dev;
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	dev_info->dax_dev = alloc_dax(dev_info, &dcssblk_dax_ops);
68162306a36Sopenharmony_ci	if (IS_ERR(dev_info->dax_dev)) {
68262306a36Sopenharmony_ci		rc = PTR_ERR(dev_info->dax_dev);
68362306a36Sopenharmony_ci		dev_info->dax_dev = NULL;
68462306a36Sopenharmony_ci		goto put_dev;
68562306a36Sopenharmony_ci	}
68662306a36Sopenharmony_ci	set_dax_synchronous(dev_info->dax_dev);
68762306a36Sopenharmony_ci	rc = dax_add_host(dev_info->dax_dev, dev_info->gd);
68862306a36Sopenharmony_ci	if (rc)
68962306a36Sopenharmony_ci		goto out_dax;
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	get_device(&dev_info->dev);
69262306a36Sopenharmony_ci	rc = device_add_disk(&dev_info->dev, dev_info->gd, NULL);
69362306a36Sopenharmony_ci	if (rc)
69462306a36Sopenharmony_ci		goto out_dax_host;
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci	switch (dev_info->segment_type) {
69762306a36Sopenharmony_ci		case SEG_TYPE_SR:
69862306a36Sopenharmony_ci		case SEG_TYPE_ER:
69962306a36Sopenharmony_ci		case SEG_TYPE_SC:
70062306a36Sopenharmony_ci			set_disk_ro(dev_info->gd,1);
70162306a36Sopenharmony_ci			break;
70262306a36Sopenharmony_ci		default:
70362306a36Sopenharmony_ci			set_disk_ro(dev_info->gd,0);
70462306a36Sopenharmony_ci			break;
70562306a36Sopenharmony_ci	}
70662306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
70762306a36Sopenharmony_ci	rc = count;
70862306a36Sopenharmony_ci	goto out;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ciout_dax_host:
71162306a36Sopenharmony_ci	put_device(&dev_info->dev);
71262306a36Sopenharmony_ci	dax_remove_host(dev_info->gd);
71362306a36Sopenharmony_ciout_dax:
71462306a36Sopenharmony_ci	kill_dax(dev_info->dax_dev);
71562306a36Sopenharmony_ci	put_dax(dev_info->dax_dev);
71662306a36Sopenharmony_ciput_dev:
71762306a36Sopenharmony_ci	list_del(&dev_info->lh);
71862306a36Sopenharmony_ci	put_disk(dev_info->gd);
71962306a36Sopenharmony_ci	list_for_each_entry(seg_info, &dev_info->seg_list, lh) {
72062306a36Sopenharmony_ci		segment_unload(seg_info->segment_name);
72162306a36Sopenharmony_ci	}
72262306a36Sopenharmony_ci	put_device(&dev_info->dev);
72362306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
72462306a36Sopenharmony_ci	goto out;
72562306a36Sopenharmony_cidev_list_del:
72662306a36Sopenharmony_ci	list_del(&dev_info->lh);
72762306a36Sopenharmony_cirelease_gd:
72862306a36Sopenharmony_ci	put_disk(dev_info->gd);
72962306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
73062306a36Sopenharmony_ciseg_list_del:
73162306a36Sopenharmony_ci	if (dev_info == NULL)
73262306a36Sopenharmony_ci		goto out;
73362306a36Sopenharmony_ci	list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) {
73462306a36Sopenharmony_ci		list_del(&seg_info->lh);
73562306a36Sopenharmony_ci		segment_unload(seg_info->segment_name);
73662306a36Sopenharmony_ci		kfree(seg_info);
73762306a36Sopenharmony_ci	}
73862306a36Sopenharmony_ci	kfree(dev_info);
73962306a36Sopenharmony_ciout:
74062306a36Sopenharmony_ci	kfree(local_buf);
74162306a36Sopenharmony_ciout_nobuf:
74262306a36Sopenharmony_ci	return rc;
74362306a36Sopenharmony_ci}
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci/*
74662306a36Sopenharmony_ci * device attribute for removing devices
74762306a36Sopenharmony_ci */
74862306a36Sopenharmony_cistatic ssize_t
74962306a36Sopenharmony_cidcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
75062306a36Sopenharmony_ci{
75162306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
75262306a36Sopenharmony_ci	struct segment_info *entry;
75362306a36Sopenharmony_ci	int rc, i;
75462306a36Sopenharmony_ci	char *local_buf;
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci	if (dev != dcssblk_root_dev) {
75762306a36Sopenharmony_ci		return -EINVAL;
75862306a36Sopenharmony_ci	}
75962306a36Sopenharmony_ci	local_buf = kmalloc(count + 1, GFP_KERNEL);
76062306a36Sopenharmony_ci	if (local_buf == NULL) {
76162306a36Sopenharmony_ci		return -ENOMEM;
76262306a36Sopenharmony_ci	}
76362306a36Sopenharmony_ci	/*
76462306a36Sopenharmony_ci	 * parse input
76562306a36Sopenharmony_ci	 */
76662306a36Sopenharmony_ci	for (i = 0; (i < count && (*(buf+i)!='\0') && (*(buf+i)!='\n')); i++) {
76762306a36Sopenharmony_ci		local_buf[i] = toupper(buf[i]);
76862306a36Sopenharmony_ci	}
76962306a36Sopenharmony_ci	local_buf[i] = '\0';
77062306a36Sopenharmony_ci	if ((i == 0) || (i > 8)) {
77162306a36Sopenharmony_ci		rc = -ENAMETOOLONG;
77262306a36Sopenharmony_ci		goto out_buf;
77362306a36Sopenharmony_ci	}
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ci	down_write(&dcssblk_devices_sem);
77662306a36Sopenharmony_ci	dev_info = dcssblk_get_device_by_name(local_buf);
77762306a36Sopenharmony_ci	if (dev_info == NULL) {
77862306a36Sopenharmony_ci		up_write(&dcssblk_devices_sem);
77962306a36Sopenharmony_ci		pr_warn("Device %s cannot be removed because it is not a known device\n",
78062306a36Sopenharmony_ci			local_buf);
78162306a36Sopenharmony_ci		rc = -ENODEV;
78262306a36Sopenharmony_ci		goto out_buf;
78362306a36Sopenharmony_ci	}
78462306a36Sopenharmony_ci	if (atomic_read(&dev_info->use_count) != 0) {
78562306a36Sopenharmony_ci		up_write(&dcssblk_devices_sem);
78662306a36Sopenharmony_ci		pr_warn("Device %s cannot be removed while it is in use\n",
78762306a36Sopenharmony_ci			local_buf);
78862306a36Sopenharmony_ci		rc = -EBUSY;
78962306a36Sopenharmony_ci		goto out_buf;
79062306a36Sopenharmony_ci	}
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	list_del(&dev_info->lh);
79362306a36Sopenharmony_ci	/* unload all related segments */
79462306a36Sopenharmony_ci	list_for_each_entry(entry, &dev_info->seg_list, lh)
79562306a36Sopenharmony_ci		segment_unload(entry->segment_name);
79662306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	dax_remove_host(dev_info->gd);
79962306a36Sopenharmony_ci	kill_dax(dev_info->dax_dev);
80062306a36Sopenharmony_ci	put_dax(dev_info->dax_dev);
80162306a36Sopenharmony_ci	del_gendisk(dev_info->gd);
80262306a36Sopenharmony_ci	put_disk(dev_info->gd);
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	device_unregister(&dev_info->dev);
80562306a36Sopenharmony_ci	put_device(&dev_info->dev);
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_ci	rc = count;
80862306a36Sopenharmony_ciout_buf:
80962306a36Sopenharmony_ci	kfree(local_buf);
81062306a36Sopenharmony_ci	return rc;
81162306a36Sopenharmony_ci}
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_cistatic int
81462306a36Sopenharmony_cidcssblk_open(struct gendisk *disk, blk_mode_t mode)
81562306a36Sopenharmony_ci{
81662306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info = disk->private_data;
81762306a36Sopenharmony_ci	int rc;
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	if (NULL == dev_info) {
82062306a36Sopenharmony_ci		rc = -ENODEV;
82162306a36Sopenharmony_ci		goto out;
82262306a36Sopenharmony_ci	}
82362306a36Sopenharmony_ci	atomic_inc(&dev_info->use_count);
82462306a36Sopenharmony_ci	rc = 0;
82562306a36Sopenharmony_ciout:
82662306a36Sopenharmony_ci	return rc;
82762306a36Sopenharmony_ci}
82862306a36Sopenharmony_ci
82962306a36Sopenharmony_cistatic void
83062306a36Sopenharmony_cidcssblk_release(struct gendisk *disk)
83162306a36Sopenharmony_ci{
83262306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info = disk->private_data;
83362306a36Sopenharmony_ci	struct segment_info *entry;
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	if (!dev_info) {
83662306a36Sopenharmony_ci		WARN_ON(1);
83762306a36Sopenharmony_ci		return;
83862306a36Sopenharmony_ci	}
83962306a36Sopenharmony_ci	down_write(&dcssblk_devices_sem);
84062306a36Sopenharmony_ci	if (atomic_dec_and_test(&dev_info->use_count)
84162306a36Sopenharmony_ci	    && (dev_info->save_pending)) {
84262306a36Sopenharmony_ci		pr_info("Device %s has become idle and is being saved "
84362306a36Sopenharmony_ci			"now\n", dev_info->segment_name);
84462306a36Sopenharmony_ci		list_for_each_entry(entry, &dev_info->seg_list, lh) {
84562306a36Sopenharmony_ci			if (entry->segment_type == SEG_TYPE_EN ||
84662306a36Sopenharmony_ci			    entry->segment_type == SEG_TYPE_SN)
84762306a36Sopenharmony_ci				pr_warn("DCSS %s is of type SN or EN and cannot"
84862306a36Sopenharmony_ci					" be saved\n", entry->segment_name);
84962306a36Sopenharmony_ci			else
85062306a36Sopenharmony_ci				segment_save(entry->segment_name);
85162306a36Sopenharmony_ci		}
85262306a36Sopenharmony_ci		dev_info->save_pending = 0;
85362306a36Sopenharmony_ci	}
85462306a36Sopenharmony_ci	up_write(&dcssblk_devices_sem);
85562306a36Sopenharmony_ci}
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_cistatic void
85862306a36Sopenharmony_cidcssblk_submit_bio(struct bio *bio)
85962306a36Sopenharmony_ci{
86062306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
86162306a36Sopenharmony_ci	struct bio_vec bvec;
86262306a36Sopenharmony_ci	struct bvec_iter iter;
86362306a36Sopenharmony_ci	unsigned long index;
86462306a36Sopenharmony_ci	void *page_addr;
86562306a36Sopenharmony_ci	unsigned long source_addr;
86662306a36Sopenharmony_ci	unsigned long bytes_done;
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	bytes_done = 0;
86962306a36Sopenharmony_ci	dev_info = bio->bi_bdev->bd_disk->private_data;
87062306a36Sopenharmony_ci	if (dev_info == NULL)
87162306a36Sopenharmony_ci		goto fail;
87262306a36Sopenharmony_ci	if (!IS_ALIGNED(bio->bi_iter.bi_sector, 8) ||
87362306a36Sopenharmony_ci	    !IS_ALIGNED(bio->bi_iter.bi_size, PAGE_SIZE))
87462306a36Sopenharmony_ci		/* Request is not page-aligned. */
87562306a36Sopenharmony_ci		goto fail;
87662306a36Sopenharmony_ci	/* verify data transfer direction */
87762306a36Sopenharmony_ci	if (dev_info->is_shared) {
87862306a36Sopenharmony_ci		switch (dev_info->segment_type) {
87962306a36Sopenharmony_ci		case SEG_TYPE_SR:
88062306a36Sopenharmony_ci		case SEG_TYPE_ER:
88162306a36Sopenharmony_ci		case SEG_TYPE_SC:
88262306a36Sopenharmony_ci			/* cannot write to these segments */
88362306a36Sopenharmony_ci			if (bio_data_dir(bio) == WRITE) {
88462306a36Sopenharmony_ci				pr_warn("Writing to %s failed because it is a read-only device\n",
88562306a36Sopenharmony_ci					dev_name(&dev_info->dev));
88662306a36Sopenharmony_ci				goto fail;
88762306a36Sopenharmony_ci			}
88862306a36Sopenharmony_ci		}
88962306a36Sopenharmony_ci	}
89062306a36Sopenharmony_ci
89162306a36Sopenharmony_ci	index = (bio->bi_iter.bi_sector >> 3);
89262306a36Sopenharmony_ci	bio_for_each_segment(bvec, bio, iter) {
89362306a36Sopenharmony_ci		page_addr = bvec_virt(&bvec);
89462306a36Sopenharmony_ci		source_addr = dev_info->start + (index<<12) + bytes_done;
89562306a36Sopenharmony_ci		if (unlikely(!IS_ALIGNED((unsigned long)page_addr, PAGE_SIZE) ||
89662306a36Sopenharmony_ci			     !IS_ALIGNED(bvec.bv_len, PAGE_SIZE)))
89762306a36Sopenharmony_ci			// More paranoia.
89862306a36Sopenharmony_ci			goto fail;
89962306a36Sopenharmony_ci		if (bio_data_dir(bio) == READ)
90062306a36Sopenharmony_ci			memcpy(page_addr, __va(source_addr), bvec.bv_len);
90162306a36Sopenharmony_ci		else
90262306a36Sopenharmony_ci			memcpy(__va(source_addr), page_addr, bvec.bv_len);
90362306a36Sopenharmony_ci		bytes_done += bvec.bv_len;
90462306a36Sopenharmony_ci	}
90562306a36Sopenharmony_ci	bio_endio(bio);
90662306a36Sopenharmony_ci	return;
90762306a36Sopenharmony_cifail:
90862306a36Sopenharmony_ci	bio_io_error(bio);
90962306a36Sopenharmony_ci}
91062306a36Sopenharmony_ci
91162306a36Sopenharmony_cistatic long
91262306a36Sopenharmony_ci__dcssblk_direct_access(struct dcssblk_dev_info *dev_info, pgoff_t pgoff,
91362306a36Sopenharmony_ci		long nr_pages, void **kaddr, pfn_t *pfn)
91462306a36Sopenharmony_ci{
91562306a36Sopenharmony_ci	resource_size_t offset = pgoff * PAGE_SIZE;
91662306a36Sopenharmony_ci	unsigned long dev_sz;
91762306a36Sopenharmony_ci
91862306a36Sopenharmony_ci	dev_sz = dev_info->end - dev_info->start + 1;
91962306a36Sopenharmony_ci	if (kaddr)
92062306a36Sopenharmony_ci		*kaddr = (void *) dev_info->start + offset;
92162306a36Sopenharmony_ci	if (pfn)
92262306a36Sopenharmony_ci		*pfn = __pfn_to_pfn_t(PFN_DOWN(dev_info->start + offset),
92362306a36Sopenharmony_ci				PFN_DEV|PFN_SPECIAL);
92462306a36Sopenharmony_ci
92562306a36Sopenharmony_ci	return (dev_sz - offset) / PAGE_SIZE;
92662306a36Sopenharmony_ci}
92762306a36Sopenharmony_ci
92862306a36Sopenharmony_cistatic long
92962306a36Sopenharmony_cidcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,
93062306a36Sopenharmony_ci		long nr_pages, enum dax_access_mode mode, void **kaddr,
93162306a36Sopenharmony_ci		pfn_t *pfn)
93262306a36Sopenharmony_ci{
93362306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info = dax_get_private(dax_dev);
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_ci	return __dcssblk_direct_access(dev_info, pgoff, nr_pages, kaddr, pfn);
93662306a36Sopenharmony_ci}
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_cistatic void
93962306a36Sopenharmony_cidcssblk_check_params(void)
94062306a36Sopenharmony_ci{
94162306a36Sopenharmony_ci	int rc, i, j, k;
94262306a36Sopenharmony_ci	char buf[DCSSBLK_PARM_LEN + 1];
94362306a36Sopenharmony_ci	struct dcssblk_dev_info *dev_info;
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
94662306a36Sopenharmony_ci	     i++) {
94762306a36Sopenharmony_ci		for (j = i; (j < DCSSBLK_PARM_LEN) &&
94862306a36Sopenharmony_ci			    (dcssblk_segments[j] != ',')  &&
94962306a36Sopenharmony_ci			    (dcssblk_segments[j] != '\0') &&
95062306a36Sopenharmony_ci			    (dcssblk_segments[j] != '('); j++)
95162306a36Sopenharmony_ci		{
95262306a36Sopenharmony_ci			buf[j-i] = dcssblk_segments[j];
95362306a36Sopenharmony_ci		}
95462306a36Sopenharmony_ci		buf[j-i] = '\0';
95562306a36Sopenharmony_ci		rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);
95662306a36Sopenharmony_ci		if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
95762306a36Sopenharmony_ci			for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++)
95862306a36Sopenharmony_ci				buf[k] = toupper(buf[k]);
95962306a36Sopenharmony_ci			buf[k] = '\0';
96062306a36Sopenharmony_ci			if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
96162306a36Sopenharmony_ci				down_read(&dcssblk_devices_sem);
96262306a36Sopenharmony_ci				dev_info = dcssblk_get_device_by_name(buf);
96362306a36Sopenharmony_ci				up_read(&dcssblk_devices_sem);
96462306a36Sopenharmony_ci				if (dev_info)
96562306a36Sopenharmony_ci					dcssblk_shared_store(&dev_info->dev,
96662306a36Sopenharmony_ci							     NULL, "0\n", 2);
96762306a36Sopenharmony_ci			}
96862306a36Sopenharmony_ci		}
96962306a36Sopenharmony_ci		while ((dcssblk_segments[j] != ',') &&
97062306a36Sopenharmony_ci		       (dcssblk_segments[j] != '\0'))
97162306a36Sopenharmony_ci		{
97262306a36Sopenharmony_ci			j++;
97362306a36Sopenharmony_ci		}
97462306a36Sopenharmony_ci		if (dcssblk_segments[j] == '\0')
97562306a36Sopenharmony_ci			break;
97662306a36Sopenharmony_ci		i = j;
97762306a36Sopenharmony_ci	}
97862306a36Sopenharmony_ci}
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ci/*
98162306a36Sopenharmony_ci * The init/exit functions.
98262306a36Sopenharmony_ci */
98362306a36Sopenharmony_cistatic void __exit
98462306a36Sopenharmony_cidcssblk_exit(void)
98562306a36Sopenharmony_ci{
98662306a36Sopenharmony_ci	root_device_unregister(dcssblk_root_dev);
98762306a36Sopenharmony_ci	unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
98862306a36Sopenharmony_ci}
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_cistatic int __init
99162306a36Sopenharmony_cidcssblk_init(void)
99262306a36Sopenharmony_ci{
99362306a36Sopenharmony_ci	int rc;
99462306a36Sopenharmony_ci
99562306a36Sopenharmony_ci	dcssblk_root_dev = root_device_register("dcssblk");
99662306a36Sopenharmony_ci	if (IS_ERR(dcssblk_root_dev))
99762306a36Sopenharmony_ci		return PTR_ERR(dcssblk_root_dev);
99862306a36Sopenharmony_ci	rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
99962306a36Sopenharmony_ci	if (rc)
100062306a36Sopenharmony_ci		goto out_root;
100162306a36Sopenharmony_ci	rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);
100262306a36Sopenharmony_ci	if (rc)
100362306a36Sopenharmony_ci		goto out_root;
100462306a36Sopenharmony_ci	rc = register_blkdev(0, DCSSBLK_NAME);
100562306a36Sopenharmony_ci	if (rc < 0)
100662306a36Sopenharmony_ci		goto out_root;
100762306a36Sopenharmony_ci	dcssblk_major = rc;
100862306a36Sopenharmony_ci	init_rwsem(&dcssblk_devices_sem);
100962306a36Sopenharmony_ci
101062306a36Sopenharmony_ci	dcssblk_check_params();
101162306a36Sopenharmony_ci	return 0;
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_ciout_root:
101462306a36Sopenharmony_ci	root_device_unregister(dcssblk_root_dev);
101562306a36Sopenharmony_ci
101662306a36Sopenharmony_ci	return rc;
101762306a36Sopenharmony_ci}
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_cimodule_init(dcssblk_init);
102062306a36Sopenharmony_cimodule_exit(dcssblk_exit);
102162306a36Sopenharmony_ci
102262306a36Sopenharmony_cimodule_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
102362306a36Sopenharmony_ciMODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
102462306a36Sopenharmony_ci		 "comma-separated list, names in each set separated "
102562306a36Sopenharmony_ci		 "by commas are separated by colons, each set contains "
102662306a36Sopenharmony_ci		 "names of contiguous segments and each name max. 8 chars.\n"
102762306a36Sopenharmony_ci		 "Adding \"(local)\" to the end of each set equals echoing 0 "
102862306a36Sopenharmony_ci		 "to /sys/devices/dcssblk/<device name>/shared after loading "
102962306a36Sopenharmony_ci		 "the contiguous segments - \n"
103062306a36Sopenharmony_ci		 "e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"");
103162306a36Sopenharmony_ci
103262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1033