brintos

brintos / linux-shallow public Read only

0
0
Text · 25.3 KiB · 02a4a51 Raw
1037 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * dcssblk.c -- the S/390 block driver for dcss memory4 *5 * Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer6 */7 8#define KMSG_COMPONENT "dcssblk"9#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt10 11#include <linux/module.h>12#include <linux/moduleparam.h>13#include <linux/ctype.h>14#include <linux/errno.h>15#include <linux/init.h>16#include <linux/slab.h>17#include <linux/blkdev.h>18#include <linux/completion.h>19#include <linux/interrupt.h>20#include <linux/pfn_t.h>21#include <linux/uio.h>22#include <linux/dax.h>23#include <linux/io.h>24#include <asm/extmem.h>25 26#define DCSSBLK_NAME "dcssblk"27#define DCSSBLK_MINORS_PER_DISK 128#define DCSSBLK_PARM_LEN 40029#define DCSS_BUS_ID_SIZE 2030 31static int dcssblk_open(struct gendisk *disk, blk_mode_t mode);32static void dcssblk_release(struct gendisk *disk);33static void dcssblk_submit_bio(struct bio *bio);34static long dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,35		long nr_pages, enum dax_access_mode mode, void **kaddr,36		pfn_t *pfn);37 38static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";39 40static int dcssblk_major;41static const struct block_device_operations dcssblk_devops = {42	.owner   	= THIS_MODULE,43	.submit_bio	= dcssblk_submit_bio,44	.open    	= dcssblk_open,45	.release 	= dcssblk_release,46};47 48static int dcssblk_dax_zero_page_range(struct dax_device *dax_dev,49				       pgoff_t pgoff, size_t nr_pages)50{51	long rc;52	void *kaddr;53 54	rc = dax_direct_access(dax_dev, pgoff, nr_pages, DAX_ACCESS,55			&kaddr, NULL);56	if (rc < 0)57		return dax_mem2blk_err(rc);58 59	memset(kaddr, 0, nr_pages << PAGE_SHIFT);60	dax_flush(dax_dev, kaddr, nr_pages << PAGE_SHIFT);61	return 0;62}63 64static const struct dax_operations dcssblk_dax_ops = {65	.direct_access = dcssblk_dax_direct_access,66	.zero_page_range = dcssblk_dax_zero_page_range,67};68 69struct dcssblk_dev_info {70	struct list_head lh;71	struct device dev;72	char segment_name[DCSS_BUS_ID_SIZE];73	atomic_t use_count;74	struct gendisk *gd;75	unsigned long start;76	unsigned long end;77	int segment_type;78	unsigned char save_pending;79	unsigned char is_shared;80	int num_of_segments;81	struct list_head seg_list;82	struct dax_device *dax_dev;83};84 85struct segment_info {86	struct list_head lh;87	char segment_name[DCSS_BUS_ID_SIZE];88	unsigned long start;89	unsigned long end;90	int segment_type;91};92 93static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,94				  size_t count);95static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,96				  size_t count);97 98static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);99static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);100 101static struct device *dcssblk_root_dev;102 103static LIST_HEAD(dcssblk_devices);104static struct rw_semaphore dcssblk_devices_sem;105 106/*107 * release function for segment device.108 */109static void110dcssblk_release_segment(struct device *dev)111{112	struct dcssblk_dev_info *dev_info;113	struct segment_info *entry, *temp;114 115	dev_info = container_of(dev, struct dcssblk_dev_info, dev);116	list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) {117		list_del(&entry->lh);118		kfree(entry);119	}120	kfree(dev_info);121	module_put(THIS_MODULE);122}123 124/*125 * get a minor number. needs to be called with126 * down_write(&dcssblk_devices_sem) and the127 * device needs to be enqueued before the semaphore is128 * freed.129 */130static int131dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)132{133	int minor, found;134	struct dcssblk_dev_info *entry;135 136	if (dev_info == NULL)137		return -EINVAL;138	for (minor = 0; minor < (1<<MINORBITS); minor++) {139		found = 0;140		// test if minor available141		list_for_each_entry(entry, &dcssblk_devices, lh)142			if (minor == entry->gd->first_minor)143				found++;144		if (!found) break; // got unused minor145	}146	if (found)147		return -EBUSY;148	dev_info->gd->first_minor = minor;149	return 0;150}151 152/*153 * get the struct dcssblk_dev_info from dcssblk_devices154 * for the given name.155 * down_read(&dcssblk_devices_sem) must be held.156 */157static struct dcssblk_dev_info *158dcssblk_get_device_by_name(char *name)159{160	struct dcssblk_dev_info *entry;161 162	list_for_each_entry(entry, &dcssblk_devices, lh) {163		if (!strcmp(name, entry->segment_name)) {164			return entry;165		}166	}167	return NULL;168}169 170/*171 * get the struct segment_info from seg_list172 * for the given name.173 * down_read(&dcssblk_devices_sem) must be held.174 */175static struct segment_info *176dcssblk_get_segment_by_name(char *name)177{178	struct dcssblk_dev_info *dev_info;179	struct segment_info *entry;180 181	list_for_each_entry(dev_info, &dcssblk_devices, lh) {182		list_for_each_entry(entry, &dev_info->seg_list, lh) {183			if (!strcmp(name, entry->segment_name))184				return entry;185		}186	}187	return NULL;188}189 190/*191 * get the highest address of the multi-segment block.192 */193static unsigned long194dcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info)195{196	unsigned long highest_addr;197	struct segment_info *entry;198 199	highest_addr = 0;200	list_for_each_entry(entry, &dev_info->seg_list, lh) {201		if (highest_addr < entry->end)202			highest_addr = entry->end;203	}204	return highest_addr;205}206 207/*208 * get the lowest address of the multi-segment block.209 */210static unsigned long211dcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info)212{213	int set_first;214	unsigned long lowest_addr;215	struct segment_info *entry;216 217	set_first = 0;218	lowest_addr = 0;219	list_for_each_entry(entry, &dev_info->seg_list, lh) {220		if (set_first == 0) {221			lowest_addr = entry->start;222			set_first = 1;223		} else {224			if (lowest_addr > entry->start)225				lowest_addr = entry->start;226		}227	}228	return lowest_addr;229}230 231/*232 * Check continuity of segments.233 */234static int235dcssblk_is_continuous(struct dcssblk_dev_info *dev_info)236{237	int i, j, rc;238	struct segment_info *sort_list, *entry, temp;239 240	if (dev_info->num_of_segments <= 1)241		return 0;242 243	sort_list = kcalloc(dev_info->num_of_segments,244			    sizeof(struct segment_info),245			    GFP_KERNEL);246	if (sort_list == NULL)247		return -ENOMEM;248	i = 0;249	list_for_each_entry(entry, &dev_info->seg_list, lh) {250		memcpy(&sort_list[i], entry, sizeof(struct segment_info));251		i++;252	}253 254	/* sort segments */255	for (i = 0; i < dev_info->num_of_segments; i++)256		for (j = 0; j < dev_info->num_of_segments; j++)257			if (sort_list[j].start > sort_list[i].start) {258				memcpy(&temp, &sort_list[i],259					sizeof(struct segment_info));260				memcpy(&sort_list[i], &sort_list[j],261					sizeof(struct segment_info));262				memcpy(&sort_list[j], &temp,263					sizeof(struct segment_info));264			}265 266	/* check continuity */267	for (i = 0; i < dev_info->num_of_segments - 1; i++) {268		if ((sort_list[i].end + 1) != sort_list[i+1].start) {269			pr_err("Adjacent DCSSs %s and %s are not "270			       "contiguous\n", sort_list[i].segment_name,271			       sort_list[i+1].segment_name);272			rc = -EINVAL;273			goto out;274		}275		/* EN and EW are allowed in a block device */276		if (sort_list[i].segment_type != sort_list[i+1].segment_type) {277			if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) ||278				(sort_list[i].segment_type == SEG_TYPE_ER) ||279				!(sort_list[i+1].segment_type &280				SEGMENT_EXCLUSIVE) ||281				(sort_list[i+1].segment_type == SEG_TYPE_ER)) {282				pr_err("DCSS %s and DCSS %s have "283				       "incompatible types\n",284				       sort_list[i].segment_name,285				       sort_list[i+1].segment_name);286				rc = -EINVAL;287				goto out;288			}289		}290	}291	rc = 0;292out:293	kfree(sort_list);294	return rc;295}296 297/*298 * Load a segment299 */300static int301dcssblk_load_segment(char *name, struct segment_info **seg_info)302{303	int rc;304 305	/* already loaded? */306	down_read(&dcssblk_devices_sem);307	*seg_info = dcssblk_get_segment_by_name(name);308	up_read(&dcssblk_devices_sem);309	if (*seg_info != NULL)310		return -EEXIST;311 312	/* get a struct segment_info */313	*seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL);314	if (*seg_info == NULL)315		return -ENOMEM;316 317	strcpy((*seg_info)->segment_name, name);318 319	/* load the segment */320	rc = segment_load(name, SEGMENT_SHARED,321			&(*seg_info)->start, &(*seg_info)->end);322	if (rc < 0) {323		segment_warning(rc, (*seg_info)->segment_name);324		kfree(*seg_info);325	} else {326		INIT_LIST_HEAD(&(*seg_info)->lh);327		(*seg_info)->segment_type = rc;328	}329	return rc;330}331 332/*333 * device attribute for switching shared/nonshared (exclusive)334 * operation (show + store)335 */336static ssize_t337dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)338{339	struct dcssblk_dev_info *dev_info;340 341	dev_info = container_of(dev, struct dcssblk_dev_info, dev);342	return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");343}344 345static ssize_t346dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)347{348	struct dcssblk_dev_info *dev_info;349	struct segment_info *entry, *temp;350	int rc;351 352	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))353		return -EINVAL;354	down_write(&dcssblk_devices_sem);355	dev_info = container_of(dev, struct dcssblk_dev_info, dev);356	if (atomic_read(&dev_info->use_count)) {357		rc = -EBUSY;358		goto out;359	}360	if (inbuf[0] == '1') {361		/* reload segments in shared mode */362		list_for_each_entry(entry, &dev_info->seg_list, lh) {363			rc = segment_modify_shared(entry->segment_name,364						SEGMENT_SHARED);365			if (rc < 0) {366				BUG_ON(rc == -EINVAL);367				if (rc != -EAGAIN)368					goto removeseg;369			}370		}371		dev_info->is_shared = 1;372		switch (dev_info->segment_type) {373		case SEG_TYPE_SR:374		case SEG_TYPE_ER:375		case SEG_TYPE_SC:376			set_disk_ro(dev_info->gd, 1);377		}378	} else if (inbuf[0] == '0') {379		/* reload segments in exclusive mode */380		if (dev_info->segment_type == SEG_TYPE_SC) {381			pr_err("DCSS %s is of type SC and cannot be "382			       "loaded as exclusive-writable\n",383			       dev_info->segment_name);384			rc = -EINVAL;385			goto out;386		}387		list_for_each_entry(entry, &dev_info->seg_list, lh) {388			rc = segment_modify_shared(entry->segment_name,389						   SEGMENT_EXCLUSIVE);390			if (rc < 0) {391				BUG_ON(rc == -EINVAL);392				if (rc != -EAGAIN)393					goto removeseg;394			}395		}396		dev_info->is_shared = 0;397		set_disk_ro(dev_info->gd, 0);398	} else {399		rc = -EINVAL;400		goto out;401	}402	rc = count;403	goto out;404 405removeseg:406	pr_err("DCSS device %s is removed after a failed access mode "407	       "change\n", dev_info->segment_name);408	temp = entry;409	list_for_each_entry(entry, &dev_info->seg_list, lh) {410		if (entry != temp)411			segment_unload(entry->segment_name);412	}413	list_del(&dev_info->lh);414	up_write(&dcssblk_devices_sem);415 416	dax_remove_host(dev_info->gd);417	kill_dax(dev_info->dax_dev);418	put_dax(dev_info->dax_dev);419	del_gendisk(dev_info->gd);420	put_disk(dev_info->gd);421 422	if (device_remove_file_self(dev, attr)) {423		device_unregister(dev);424		put_device(dev);425	}426	return rc;427out:428	up_write(&dcssblk_devices_sem);429	return rc;430}431static DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show,432		   dcssblk_shared_store);433 434/*435 * device attribute for save operation on current copy436 * of the segment. If the segment is busy, saving will437 * become pending until it gets released, which can be438 * undone by storing a non-true value to this entry.439 * (show + store)440 */441static ssize_t442dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)443{444	struct dcssblk_dev_info *dev_info;445 446	dev_info = container_of(dev, struct dcssblk_dev_info, dev);447	return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");448}449 450static ssize_t451dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)452{453	struct dcssblk_dev_info *dev_info;454	struct segment_info *entry;455 456	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))457		return -EINVAL;458	dev_info = container_of(dev, struct dcssblk_dev_info, dev);459 460	down_write(&dcssblk_devices_sem);461	if (inbuf[0] == '1') {462		if (atomic_read(&dev_info->use_count) == 0) {463			// device is idle => we save immediately464			pr_info("All DCSSs that map to device %s are "465				"saved\n", dev_info->segment_name);466			list_for_each_entry(entry, &dev_info->seg_list, lh) {467				if (entry->segment_type == SEG_TYPE_EN ||468				    entry->segment_type == SEG_TYPE_SN)469					pr_warn("DCSS %s is of type SN or EN"470						" and cannot be saved\n",471						entry->segment_name);472				else473					segment_save(entry->segment_name);474			}475		}  else {476			// device is busy => we save it when it becomes477			// idle in dcssblk_release478			pr_info("Device %s is in use, its DCSSs will be "479				"saved when it becomes idle\n",480				dev_info->segment_name);481			dev_info->save_pending = 1;482		}483	} else if (inbuf[0] == '0') {484		if (dev_info->save_pending) {485			// device is busy & the user wants to undo his save486			// request487			dev_info->save_pending = 0;488			pr_info("A pending save request for device %s "489				"has been canceled\n",490				dev_info->segment_name);491		}492	} else {493		up_write(&dcssblk_devices_sem);494		return -EINVAL;495	}496	up_write(&dcssblk_devices_sem);497	return count;498}499static DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show,500		   dcssblk_save_store);501 502/*503 * device attribute for showing all segments in a device504 */505static ssize_t506dcssblk_seglist_show(struct device *dev, struct device_attribute *attr,507		char *buf)508{509	int i;510 511	struct dcssblk_dev_info *dev_info;512	struct segment_info *entry;513 514	down_read(&dcssblk_devices_sem);515	dev_info = container_of(dev, struct dcssblk_dev_info, dev);516	i = 0;517	buf[0] = '\0';518	list_for_each_entry(entry, &dev_info->seg_list, lh) {519		strcpy(&buf[i], entry->segment_name);520		i += strlen(entry->segment_name);521		buf[i] = '\n';522		i++;523	}524	up_read(&dcssblk_devices_sem);525	return i;526}527static DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL);528 529static struct attribute *dcssblk_dev_attrs[] = {530	&dev_attr_shared.attr,531	&dev_attr_save.attr,532	&dev_attr_seglist.attr,533	NULL,534};535static struct attribute_group dcssblk_dev_attr_group = {536	.attrs = dcssblk_dev_attrs,537};538static const struct attribute_group *dcssblk_dev_attr_groups[] = {539	&dcssblk_dev_attr_group,540	NULL,541};542 543/*544 * device attribute for adding devices545 */546static ssize_t547dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)548{549	struct queue_limits lim = {550		.logical_block_size	= 4096,551		.features		= BLK_FEAT_DAX,552	};553	int rc, i, j, num_of_segments;554	struct dcssblk_dev_info *dev_info;555	struct segment_info *seg_info, *temp;556	struct dax_device *dax_dev;557	char *local_buf;558	unsigned long seg_byte_size;559 560	dev_info = NULL;561	seg_info = NULL;562	if (dev != dcssblk_root_dev) {563		rc = -EINVAL;564		goto out_nobuf;565	}566	if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) {567		rc = -ENAMETOOLONG;568		goto out_nobuf;569	}570 571	local_buf = kmalloc(count + 1, GFP_KERNEL);572	if (local_buf == NULL) {573		rc = -ENOMEM;574		goto out_nobuf;575	}576 577	/*578	 * parse input579	 */580	num_of_segments = 0;581	for (i = 0; (i < count && (buf[i] != '\0') && (buf[i] != '\n')); i++) {582		for (j = i; j < count &&583			(buf[j] != ':') &&584			(buf[j] != '\0') &&585			(buf[j] != '\n'); j++) {586			local_buf[j-i] = toupper(buf[j]);587		}588		local_buf[j-i] = '\0';589		if (((j - i) == 0) || ((j - i) > 8)) {590			rc = -ENAMETOOLONG;591			goto seg_list_del;592		}593 594		rc = dcssblk_load_segment(local_buf, &seg_info);595		if (rc < 0)596			goto seg_list_del;597		/*598		 * get a struct dcssblk_dev_info599		 */600		if (num_of_segments == 0) {601			dev_info = kzalloc(sizeof(struct dcssblk_dev_info),602					GFP_KERNEL);603			if (dev_info == NULL) {604				rc = -ENOMEM;605				goto out;606			}607			strcpy(dev_info->segment_name, local_buf);608			dev_info->segment_type = seg_info->segment_type;609			INIT_LIST_HEAD(&dev_info->seg_list);610		}611		list_add_tail(&seg_info->lh, &dev_info->seg_list);612		num_of_segments++;613		i = j;614 615		if ((buf[j] == '\0') || (buf[j] == '\n'))616			break;617	}618 619	/* no trailing colon at the end of the input */620	if ((i > 0) && (buf[i-1] == ':')) {621		rc = -ENAMETOOLONG;622		goto seg_list_del;623	}624	strscpy(local_buf, buf, i + 1);625	dev_info->num_of_segments = num_of_segments;626	rc = dcssblk_is_continuous(dev_info);627	if (rc < 0)628		goto seg_list_del;629 630	dev_info->start = dcssblk_find_lowest_addr(dev_info);631	dev_info->end = dcssblk_find_highest_addr(dev_info);632 633	dev_set_name(&dev_info->dev, "%s", dev_info->segment_name);634	dev_info->dev.release = dcssblk_release_segment;635	dev_info->dev.groups = dcssblk_dev_attr_groups;636	INIT_LIST_HEAD(&dev_info->lh);637	dev_info->gd = blk_alloc_disk(&lim, NUMA_NO_NODE);638	if (IS_ERR(dev_info->gd)) {639		rc = PTR_ERR(dev_info->gd);640		goto seg_list_del;641	}642	dev_info->gd->major = dcssblk_major;643	dev_info->gd->minors = DCSSBLK_MINORS_PER_DISK;644	dev_info->gd->fops = &dcssblk_devops;645	dev_info->gd->private_data = dev_info;646	dev_info->gd->flags |= GENHD_FL_NO_PART;647 648	seg_byte_size = (dev_info->end - dev_info->start + 1);649	set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors650	pr_info("Loaded %s with total size %lu bytes and capacity %lu "651		"sectors\n", local_buf, seg_byte_size, seg_byte_size >> 9);652 653	dev_info->save_pending = 0;654	dev_info->is_shared = 1;655	dev_info->dev.parent = dcssblk_root_dev;656 657	/*658	 *get minor, add to list659	 */660	down_write(&dcssblk_devices_sem);661	if (dcssblk_get_segment_by_name(local_buf)) {662		rc = -EEXIST;663		goto release_gd;664	}665	rc = dcssblk_assign_free_minor(dev_info);666	if (rc)667		goto release_gd;668	sprintf(dev_info->gd->disk_name, "dcssblk%d",669		dev_info->gd->first_minor);670	list_add_tail(&dev_info->lh, &dcssblk_devices);671 672	if (!try_module_get(THIS_MODULE)) {673		rc = -ENODEV;674		goto dev_list_del;675	}676	/*677	 * register the device678	 */679	rc = device_register(&dev_info->dev);680	if (rc)681		goto put_dev;682 683	dax_dev = alloc_dax(dev_info, &dcssblk_dax_ops);684	if (IS_ERR(dax_dev)) {685		rc = PTR_ERR(dax_dev);686		goto put_dev;687	}688	set_dax_synchronous(dax_dev);689	dev_info->dax_dev = dax_dev;690	rc = dax_add_host(dev_info->dax_dev, dev_info->gd);691	if (rc)692		goto out_dax;693 694	get_device(&dev_info->dev);695	rc = device_add_disk(&dev_info->dev, dev_info->gd, NULL);696	if (rc)697		goto out_dax_host;698 699	switch (dev_info->segment_type) {700		case SEG_TYPE_SR:701		case SEG_TYPE_ER:702		case SEG_TYPE_SC:703			set_disk_ro(dev_info->gd,1);704			break;705		default:706			set_disk_ro(dev_info->gd,0);707			break;708	}709	up_write(&dcssblk_devices_sem);710	rc = count;711	goto out;712 713out_dax_host:714	put_device(&dev_info->dev);715	dax_remove_host(dev_info->gd);716out_dax:717	kill_dax(dev_info->dax_dev);718	put_dax(dev_info->dax_dev);719put_dev:720	list_del(&dev_info->lh);721	put_disk(dev_info->gd);722	list_for_each_entry(seg_info, &dev_info->seg_list, lh) {723		segment_unload(seg_info->segment_name);724	}725	put_device(&dev_info->dev);726	up_write(&dcssblk_devices_sem);727	goto out;728dev_list_del:729	list_del(&dev_info->lh);730release_gd:731	put_disk(dev_info->gd);732	up_write(&dcssblk_devices_sem);733seg_list_del:734	if (dev_info == NULL)735		goto out;736	list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) {737		list_del(&seg_info->lh);738		segment_unload(seg_info->segment_name);739		kfree(seg_info);740	}741	kfree(dev_info);742out:743	kfree(local_buf);744out_nobuf:745	return rc;746}747 748/*749 * device attribute for removing devices750 */751static ssize_t752dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)753{754	struct dcssblk_dev_info *dev_info;755	struct segment_info *entry;756	int rc, i;757	char *local_buf;758 759	if (dev != dcssblk_root_dev) {760		return -EINVAL;761	}762	local_buf = kmalloc(count + 1, GFP_KERNEL);763	if (local_buf == NULL) {764		return -ENOMEM;765	}766	/*767	 * parse input768	 */769	for (i = 0; (i < count && (*(buf+i)!='\0') && (*(buf+i)!='\n')); i++) {770		local_buf[i] = toupper(buf[i]);771	}772	local_buf[i] = '\0';773	if ((i == 0) || (i > 8)) {774		rc = -ENAMETOOLONG;775		goto out_buf;776	}777 778	down_write(&dcssblk_devices_sem);779	dev_info = dcssblk_get_device_by_name(local_buf);780	if (dev_info == NULL) {781		up_write(&dcssblk_devices_sem);782		pr_warn("Device %s cannot be removed because it is not a known device\n",783			local_buf);784		rc = -ENODEV;785		goto out_buf;786	}787	if (atomic_read(&dev_info->use_count) != 0) {788		up_write(&dcssblk_devices_sem);789		pr_warn("Device %s cannot be removed while it is in use\n",790			local_buf);791		rc = -EBUSY;792		goto out_buf;793	}794 795	list_del(&dev_info->lh);796	/* unload all related segments */797	list_for_each_entry(entry, &dev_info->seg_list, lh)798		segment_unload(entry->segment_name);799	up_write(&dcssblk_devices_sem);800 801	dax_remove_host(dev_info->gd);802	kill_dax(dev_info->dax_dev);803	put_dax(dev_info->dax_dev);804	del_gendisk(dev_info->gd);805	put_disk(dev_info->gd);806 807	device_unregister(&dev_info->dev);808	put_device(&dev_info->dev);809 810	rc = count;811out_buf:812	kfree(local_buf);813	return rc;814}815 816static int817dcssblk_open(struct gendisk *disk, blk_mode_t mode)818{819	struct dcssblk_dev_info *dev_info = disk->private_data;820	int rc;821 822	if (NULL == dev_info) {823		rc = -ENODEV;824		goto out;825	}826	atomic_inc(&dev_info->use_count);827	rc = 0;828out:829	return rc;830}831 832static void833dcssblk_release(struct gendisk *disk)834{835	struct dcssblk_dev_info *dev_info = disk->private_data;836	struct segment_info *entry;837 838	if (!dev_info) {839		WARN_ON(1);840		return;841	}842	down_write(&dcssblk_devices_sem);843	if (atomic_dec_and_test(&dev_info->use_count)844	    && (dev_info->save_pending)) {845		pr_info("Device %s has become idle and is being saved "846			"now\n", dev_info->segment_name);847		list_for_each_entry(entry, &dev_info->seg_list, lh) {848			if (entry->segment_type == SEG_TYPE_EN ||849			    entry->segment_type == SEG_TYPE_SN)850				pr_warn("DCSS %s is of type SN or EN and cannot"851					" be saved\n", entry->segment_name);852			else853				segment_save(entry->segment_name);854		}855		dev_info->save_pending = 0;856	}857	up_write(&dcssblk_devices_sem);858}859 860static void861dcssblk_submit_bio(struct bio *bio)862{863	struct dcssblk_dev_info *dev_info;864	struct bio_vec bvec;865	struct bvec_iter iter;866	unsigned long index;867	void *page_addr;868	unsigned long source_addr;869	unsigned long bytes_done;870 871	bytes_done = 0;872	dev_info = bio->bi_bdev->bd_disk->private_data;873	if (dev_info == NULL)874		goto fail;875	if (!IS_ALIGNED(bio->bi_iter.bi_sector, 8) ||876	    !IS_ALIGNED(bio->bi_iter.bi_size, PAGE_SIZE))877		/* Request is not page-aligned. */878		goto fail;879	/* verify data transfer direction */880	if (dev_info->is_shared) {881		switch (dev_info->segment_type) {882		case SEG_TYPE_SR:883		case SEG_TYPE_ER:884		case SEG_TYPE_SC:885			/* cannot write to these segments */886			if (bio_data_dir(bio) == WRITE) {887				pr_warn("Writing to %s failed because it is a read-only device\n",888					dev_name(&dev_info->dev));889				goto fail;890			}891		}892	}893 894	index = (bio->bi_iter.bi_sector >> 3);895	bio_for_each_segment(bvec, bio, iter) {896		page_addr = bvec_virt(&bvec);897		source_addr = dev_info->start + (index<<12) + bytes_done;898		if (unlikely(!IS_ALIGNED((unsigned long)page_addr, PAGE_SIZE) ||899			     !IS_ALIGNED(bvec.bv_len, PAGE_SIZE)))900			// More paranoia.901			goto fail;902		if (bio_data_dir(bio) == READ)903			memcpy(page_addr, __va(source_addr), bvec.bv_len);904		else905			memcpy(__va(source_addr), page_addr, bvec.bv_len);906		bytes_done += bvec.bv_len;907	}908	bio_endio(bio);909	return;910fail:911	bio_io_error(bio);912}913 914static long915__dcssblk_direct_access(struct dcssblk_dev_info *dev_info, pgoff_t pgoff,916		long nr_pages, void **kaddr, pfn_t *pfn)917{918	resource_size_t offset = pgoff * PAGE_SIZE;919	unsigned long dev_sz;920 921	dev_sz = dev_info->end - dev_info->start + 1;922	if (kaddr)923		*kaddr = __va(dev_info->start + offset);924	if (pfn)925		*pfn = __pfn_to_pfn_t(PFN_DOWN(dev_info->start + offset),926				PFN_DEV|PFN_SPECIAL);927 928	return (dev_sz - offset) / PAGE_SIZE;929}930 931static long932dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,933		long nr_pages, enum dax_access_mode mode, void **kaddr,934		pfn_t *pfn)935{936	struct dcssblk_dev_info *dev_info = dax_get_private(dax_dev);937 938	return __dcssblk_direct_access(dev_info, pgoff, nr_pages, kaddr, pfn);939}940 941static void942dcssblk_check_params(void)943{944	int rc, i, j, k;945	char buf[DCSSBLK_PARM_LEN + 1];946	struct dcssblk_dev_info *dev_info;947 948	for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');949	     i++) {950		for (j = i; (j < DCSSBLK_PARM_LEN) &&951			    (dcssblk_segments[j] != ',')  &&952			    (dcssblk_segments[j] != '\0') &&953			    (dcssblk_segments[j] != '('); j++)954		{955			buf[j-i] = dcssblk_segments[j];956		}957		buf[j-i] = '\0';958		rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);959		if ((rc >= 0) && (dcssblk_segments[j] == '(')) {960			for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++)961				buf[k] = toupper(buf[k]);962			buf[k] = '\0';963			if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {964				down_read(&dcssblk_devices_sem);965				dev_info = dcssblk_get_device_by_name(buf);966				up_read(&dcssblk_devices_sem);967				if (dev_info)968					dcssblk_shared_store(&dev_info->dev,969							     NULL, "0\n", 2);970			}971		}972		while ((dcssblk_segments[j] != ',') &&973		       (dcssblk_segments[j] != '\0'))974		{975			j++;976		}977		if (dcssblk_segments[j] == '\0')978			break;979		i = j;980	}981}982 983/*984 * The init/exit functions.985 */986static void __exit987dcssblk_exit(void)988{989	root_device_unregister(dcssblk_root_dev);990	unregister_blkdev(dcssblk_major, DCSSBLK_NAME);991}992 993static int __init994dcssblk_init(void)995{996	int rc;997 998	dcssblk_root_dev = root_device_register("dcssblk");999	if (IS_ERR(dcssblk_root_dev))1000		return PTR_ERR(dcssblk_root_dev);1001	rc = device_create_file(dcssblk_root_dev, &dev_attr_add);1002	if (rc)1003		goto out_root;1004	rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);1005	if (rc)1006		goto out_root;1007	rc = register_blkdev(0, DCSSBLK_NAME);1008	if (rc < 0)1009		goto out_root;1010	dcssblk_major = rc;1011	init_rwsem(&dcssblk_devices_sem);1012 1013	dcssblk_check_params();1014	return 0;1015 1016out_root:1017	root_device_unregister(dcssblk_root_dev);1018 1019	return rc;1020}1021 1022module_init(dcssblk_init);1023module_exit(dcssblk_exit);1024 1025module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);1026MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "1027		 "comma-separated list, names in each set separated "1028		 "by commas are separated by colons, each set contains "1029		 "names of contiguous segments and each name max. 8 chars.\n"1030		 "Adding \"(local)\" to the end of each set equals echoing 0 "1031		 "to /sys/devices/dcssblk/<device name>/shared after loading "1032		 "the contiguous segments - \n"1033		 "e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"");1034 1035MODULE_DESCRIPTION("S/390 block driver for DCSS memory");1036MODULE_LICENSE("GPL");1037