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