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