dcssblk.c 24.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6
/*
 * dcssblk.c -- the S/390 block driver for dcss memory
 *
 * Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer
 */

7 8 9
#define KMSG_COMPONENT "dcssblk"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

L
Linus Torvalds 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/blkdev.h>
#include <asm/extmem.h>
#include <asm/io.h>
#include <linux/completion.h>
#include <linux/interrupt.h>

#define DCSSBLK_NAME "dcssblk"
#define DCSSBLK_MINORS_PER_DISK 1
#define DCSSBLK_PARM_LEN 400
25
#define DCSS_BUS_ID_SIZE 20
L
Linus Torvalds 已提交
26

A
Al Viro 已提交
27 28
static int dcssblk_open(struct block_device *bdev, fmode_t mode);
static int dcssblk_release(struct gendisk *disk, fmode_t mode);
L
Linus Torvalds 已提交
29
static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
30
static int dcssblk_direct_access(struct block_device *bdev, sector_t secnum,
31
				 void **kaddr, unsigned long *pfn);
L
Linus Torvalds 已提交
32 33 34 35 36

static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";

static int dcssblk_major;
static struct block_device_operations dcssblk_devops = {
37
	.owner   	= THIS_MODULE,
A
Al Viro 已提交
38 39
	.open    	= dcssblk_open,
	.release 	= dcssblk_release,
40
	.direct_access 	= dcssblk_direct_access,
L
Linus Torvalds 已提交
41 42
};

43 44 45
struct dcssblk_dev_info {
	struct list_head lh;
	struct device dev;
46
	char segment_name[DCSS_BUS_ID_SIZE];
47 48 49 50 51 52 53 54 55 56 57 58 59 60
	atomic_t use_count;
	struct gendisk *gd;
	unsigned long start;
	unsigned long end;
	int segment_type;
	unsigned char save_pending;
	unsigned char is_shared;
	struct request_queue *dcssblk_queue;
	int num_of_segments;
	struct list_head seg_list;
};

struct segment_info {
	struct list_head lh;
61
	char segment_name[DCSS_BUS_ID_SIZE];
62 63 64 65 66
	unsigned long start;
	unsigned long end;
	int segment_type;
};

67
static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
L
Linus Torvalds 已提交
68
				  size_t count);
69
static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
L
Linus Torvalds 已提交
70
				  size_t count);
71
static ssize_t dcssblk_save_store(struct device * dev, struct device_attribute *attr, const char * buf,
L
Linus Torvalds 已提交
72
				  size_t count);
73 74
static ssize_t dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t dcssblk_shared_store(struct device * dev, struct device_attribute *attr, const char * buf,
L
Linus Torvalds 已提交
75
				  size_t count);
76
static ssize_t dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf);
77 78 79
static ssize_t dcssblk_seglist_show(struct device *dev,
				struct device_attribute *attr,
				char *buf);
L
Linus Torvalds 已提交
80 81 82

static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
83
static DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show,
L
Linus Torvalds 已提交
84
		   dcssblk_save_store);
85
static DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show,
L
Linus Torvalds 已提交
86
		   dcssblk_shared_store);
87
static DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL);
L
Linus Torvalds 已提交
88 89 90

static struct device *dcssblk_root_dev;

91
static LIST_HEAD(dcssblk_devices);
L
Linus Torvalds 已提交
92 93 94 95 96 97 98 99
static struct rw_semaphore dcssblk_devices_sem;

/*
 * release function for segment device.
 */
static void
dcssblk_release_segment(struct device *dev)
{
100 101 102 103 104 105 106 107 108
	struct dcssblk_dev_info *dev_info;
	struct segment_info *entry, *temp;

	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
	list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) {
		list_del(&entry->lh);
		kfree(entry);
	}
	kfree(dev_info);
L
Linus Torvalds 已提交
109 110 111 112 113 114 115 116 117
	module_put(THIS_MODULE);
}

/*
 * get a minor number. needs to be called with
 * down_write(&dcssblk_devices_sem) and the
 * device needs to be enqueued before the semaphore is
 * freed.
 */
118
static int
L
Linus Torvalds 已提交
119 120 121 122 123 124 125 126 127 128 129
dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
{
	int minor, found;
	struct dcssblk_dev_info *entry;

	if (dev_info == NULL)
		return -EINVAL;
	for (minor = 0; minor < (1<<MINORBITS); minor++) {
		found = 0;
		// test if minor available
		list_for_each_entry(entry, &dcssblk_devices, lh)
130
			if (minor == entry->gd->first_minor)
L
Linus Torvalds 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
				found++;
		if (!found) break; // got unused minor
	}
	if (found)
		return -EBUSY;
	dev_info->gd->first_minor = minor;
	return 0;
}

/*
 * get the struct dcssblk_dev_info from dcssblk_devices
 * for the given name.
 * down_read(&dcssblk_devices_sem) must be held.
 */
static struct dcssblk_dev_info *
dcssblk_get_device_by_name(char *name)
{
	struct dcssblk_dev_info *entry;

	list_for_each_entry(entry, &dcssblk_devices, lh) {
		if (!strcmp(name, entry->segment_name)) {
			return entry;
		}
	}
	return NULL;
}

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
/*
 * get the struct segment_info from seg_list
 * for the given name.
 * down_read(&dcssblk_devices_sem) must be held.
 */
static struct segment_info *
dcssblk_get_segment_by_name(char *name)
{
	struct dcssblk_dev_info *dev_info;
	struct segment_info *entry;

	list_for_each_entry(dev_info, &dcssblk_devices, lh) {
		list_for_each_entry(entry, &dev_info->seg_list, lh) {
			if (!strcmp(name, entry->segment_name))
				return entry;
		}
	}
	return NULL;
}

/*
 * get the highest address of the multi-segment block.
 */
static unsigned long
dcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info)
{
	unsigned long highest_addr;
	struct segment_info *entry;

	highest_addr = 0;
	list_for_each_entry(entry, &dev_info->seg_list, lh) {
		if (highest_addr < entry->end)
			highest_addr = entry->end;
	}
	return highest_addr;
}

/*
 * get the lowest address of the multi-segment block.
 */
static unsigned long
dcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info)
{
	int set_first;
	unsigned long lowest_addr;
	struct segment_info *entry;

	set_first = 0;
	lowest_addr = 0;
	list_for_each_entry(entry, &dev_info->seg_list, lh) {
		if (set_first == 0) {
			lowest_addr = entry->start;
			set_first = 1;
		} else {
			if (lowest_addr > entry->start)
				lowest_addr = entry->start;
		}
	}
	return lowest_addr;
}

/*
 * Check continuity of segments.
 */
static int
dcssblk_is_continuous(struct dcssblk_dev_info *dev_info)
{
	int i, j, rc;
	struct segment_info *sort_list, *entry, temp;

	if (dev_info->num_of_segments <= 1)
		return 0;

	sort_list = kzalloc(
			sizeof(struct segment_info) * dev_info->num_of_segments,
			GFP_KERNEL);
	if (sort_list == NULL)
		return -ENOMEM;
	i = 0;
	list_for_each_entry(entry, &dev_info->seg_list, lh) {
		memcpy(&sort_list[i], entry, sizeof(struct segment_info));
		i++;
	}

	/* sort segments */
	for (i = 0; i < dev_info->num_of_segments; i++)
		for (j = 0; j < dev_info->num_of_segments; j++)
			if (sort_list[j].start > sort_list[i].start) {
				memcpy(&temp, &sort_list[i],
					sizeof(struct segment_info));
				memcpy(&sort_list[i], &sort_list[j],
					sizeof(struct segment_info));
				memcpy(&sort_list[j], &temp,
					sizeof(struct segment_info));
			}

	/* check continuity */
	for (i = 0; i < dev_info->num_of_segments - 1; i++) {
		if ((sort_list[i].end + 1) != sort_list[i+1].start) {
257 258 259
			pr_err("Adjacent DCSSs %s and %s are not "
			       "contiguous\n", sort_list[i].segment_name,
			       sort_list[i+1].segment_name);
260 261 262 263 264 265 266 267 268 269
			rc = -EINVAL;
			goto out;
		}
		/* EN and EW are allowed in a block device */
		if (sort_list[i].segment_type != sort_list[i+1].segment_type) {
			if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) ||
				(sort_list[i].segment_type == SEG_TYPE_ER) ||
				!(sort_list[i+1].segment_type &
				SEGMENT_EXCLUSIVE) ||
				(sort_list[i+1].segment_type == SEG_TYPE_ER)) {
270 271 272 273
				pr_err("DCSS %s and DCSS %s have "
				       "incompatible types\n",
				       sort_list[i].segment_name,
				       sort_list[i+1].segment_name);
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
				rc = -EINVAL;
				goto out;
			}
		}
	}
	rc = 0;
out:
	kfree(sort_list);
	return rc;
}

/*
 * Load a segment
 */
static int
dcssblk_load_segment(char *name, struct segment_info **seg_info)
{
	int rc;

	/* already loaded? */
	down_read(&dcssblk_devices_sem);
	*seg_info = dcssblk_get_segment_by_name(name);
	up_read(&dcssblk_devices_sem);
	if (*seg_info != NULL)
		return -EEXIST;

	/* get a struct segment_info */
	*seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL);
	if (*seg_info == NULL)
		return -ENOMEM;

	strcpy((*seg_info)->segment_name, name);

	/* load the segment */
	rc = segment_load(name, SEGMENT_SHARED,
			&(*seg_info)->start, &(*seg_info)->end);
	if (rc < 0) {
		segment_warning(rc, (*seg_info)->segment_name);
		kfree(*seg_info);
	} else {
		INIT_LIST_HEAD(&(*seg_info)->lh);
		(*seg_info)->segment_type = rc;
	}
	return rc;
}

320 321 322 323 324 325
static void dcssblk_unregister_callback(struct device *dev)
{
	device_unregister(dev);
	put_device(dev);
}

L
Linus Torvalds 已提交
326 327 328 329 330
/*
 * device attribute for switching shared/nonshared (exclusive)
 * operation (show + store)
 */
static ssize_t
331
dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
L
Linus Torvalds 已提交
332 333 334 335 336 337 338 339
{
	struct dcssblk_dev_info *dev_info;

	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
	return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
}

static ssize_t
340
dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
L
Linus Torvalds 已提交
341 342
{
	struct dcssblk_dev_info *dev_info;
343
	struct segment_info *entry, *temp;
L
Linus Torvalds 已提交
344 345
	int rc;

346
	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
L
Linus Torvalds 已提交
347 348 349 350 351 352 353 354
		return -EINVAL;
	down_write(&dcssblk_devices_sem);
	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
	if (atomic_read(&dev_info->use_count)) {
		rc = -EBUSY;
		goto out;
	}
	if (inbuf[0] == '1') {
355 356 357 358 359 360 361 362
		/* reload segments in shared mode */
		list_for_each_entry(entry, &dev_info->seg_list, lh) {
			rc = segment_modify_shared(entry->segment_name,
						SEGMENT_SHARED);
			if (rc < 0) {
				BUG_ON(rc == -EINVAL);
				if (rc != -EAGAIN)
					goto removeseg;
L
Linus Torvalds 已提交
363 364
			}
		}
365 366 367 368 369 370 371
		dev_info->is_shared = 1;
		switch (dev_info->segment_type) {
		case SEG_TYPE_SR:
		case SEG_TYPE_ER:
		case SEG_TYPE_SC:
			set_disk_ro(dev_info->gd, 1);
		}
L
Linus Torvalds 已提交
372
	} else if (inbuf[0] == '0') {
373
		/* reload segments in exclusive mode */
L
Linus Torvalds 已提交
374
		if (dev_info->segment_type == SEG_TYPE_SC) {
375 376 377
			pr_err("DCSS %s is of type SC and cannot be "
			       "loaded as exclusive-writable\n",
			       dev_info->segment_name);
L
Linus Torvalds 已提交
378 379 380
			rc = -EINVAL;
			goto out;
		}
381 382 383 384 385 386 387 388
		list_for_each_entry(entry, &dev_info->seg_list, lh) {
			rc = segment_modify_shared(entry->segment_name,
						   SEGMENT_EXCLUSIVE);
			if (rc < 0) {
				BUG_ON(rc == -EINVAL);
				if (rc != -EAGAIN)
					goto removeseg;
			}
L
Linus Torvalds 已提交
389
		}
390 391
		dev_info->is_shared = 0;
		set_disk_ro(dev_info->gd, 0);
L
Linus Torvalds 已提交
392 393 394 395 396 397 398 399
	} else {
		rc = -EINVAL;
		goto out;
	}
	rc = count;
	goto out;

removeseg:
400 401
	pr_err("DCSS device %s is removed after a failed access mode "
	       "change\n", dev_info->segment_name);
402 403 404 405 406
	temp = entry;
	list_for_each_entry(entry, &dev_info->seg_list, lh) {
		if (entry != temp)
			segment_unload(entry->segment_name);
	}
L
Linus Torvalds 已提交
407 408 409
	list_del(&dev_info->lh);

	del_gendisk(dev_info->gd);
410
	blk_cleanup_queue(dev_info->dcssblk_queue);
L
Linus Torvalds 已提交
411 412
	dev_info->gd->queue = NULL;
	put_disk(dev_info->gd);
413
	rc = device_schedule_callback(dev, dcssblk_unregister_callback);
L
Linus Torvalds 已提交
414 415 416 417 418 419 420 421 422 423 424 425 426
out:
	up_write(&dcssblk_devices_sem);
	return rc;
}

/*
 * device attribute for save operation on current copy
 * of the segment. If the segment is busy, saving will
 * become pending until it gets released, which can be
 * undone by storing a non-true value to this entry.
 * (show + store)
 */
static ssize_t
427
dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
L
Linus Torvalds 已提交
428 429 430 431 432 433 434 435
{
	struct dcssblk_dev_info *dev_info;

	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
	return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
}

static ssize_t
436
dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
L
Linus Torvalds 已提交
437 438
{
	struct dcssblk_dev_info *dev_info;
439
	struct segment_info *entry;
L
Linus Torvalds 已提交
440

441
	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
L
Linus Torvalds 已提交
442 443 444 445 446 447 448
		return -EINVAL;
	dev_info = container_of(dev, struct dcssblk_dev_info, dev);

	down_write(&dcssblk_devices_sem);
	if (inbuf[0] == '1') {
		if (atomic_read(&dev_info->use_count) == 0) {
			// device is idle => we save immediately
449 450
			pr_info("All DCSSs that map to device %s are "
				"saved\n", dev_info->segment_name);
451 452 453
			list_for_each_entry(entry, &dev_info->seg_list, lh) {
				segment_save(entry->segment_name);
			}
L
Linus Torvalds 已提交
454 455 456
		}  else {
			// device is busy => we save it when it becomes
			// idle in dcssblk_release
457 458 459
			pr_info("Device %s is in use, its DCSSs will be "
				"saved when it becomes idle\n",
				dev_info->segment_name);
L
Linus Torvalds 已提交
460 461 462 463 464 465 466
			dev_info->save_pending = 1;
		}
	} else if (inbuf[0] == '0') {
		if (dev_info->save_pending) {
			// device is busy & the user wants to undo his save
			// request
			dev_info->save_pending = 0;
467 468 469
			pr_info("A pending save request for device %s "
				"has been canceled\n",
				dev_info->segment_name);
L
Linus Torvalds 已提交
470 471 472 473 474 475 476 477 478
		}
	} else {
		up_write(&dcssblk_devices_sem);
		return -EINVAL;
	}
	up_write(&dcssblk_devices_sem);
	return count;
}

479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
/*
 * device attribute for showing all segments in a device
 */
static ssize_t
dcssblk_seglist_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	int i;

	struct dcssblk_dev_info *dev_info;
	struct segment_info *entry;

	down_read(&dcssblk_devices_sem);
	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
	i = 0;
	buf[0] = '\0';
	list_for_each_entry(entry, &dev_info->seg_list, lh) {
		strcpy(&buf[i], entry->segment_name);
		i += strlen(entry->segment_name);
		buf[i] = '\n';
		i++;
	}
	up_read(&dcssblk_devices_sem);
	return i;
}

L
Linus Torvalds 已提交
505 506 507 508
/*
 * device attribute for adding devices
 */
static ssize_t
509
dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
L
Linus Torvalds 已提交
510
{
511
	int rc, i, j, num_of_segments;
L
Linus Torvalds 已提交
512
	struct dcssblk_dev_info *dev_info;
513
	struct segment_info *seg_info, *temp;
L
Linus Torvalds 已提交
514 515 516 517
	char *local_buf;
	unsigned long seg_byte_size;

	dev_info = NULL;
518
	seg_info = NULL;
L
Linus Torvalds 已提交
519 520 521 522
	if (dev != dcssblk_root_dev) {
		rc = -EINVAL;
		goto out_nobuf;
	}
523 524 525 526 527
	if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) {
		rc = -ENAMETOOLONG;
		goto out_nobuf;
	}

L
Linus Torvalds 已提交
528 529 530 531 532
	local_buf = kmalloc(count + 1, GFP_KERNEL);
	if (local_buf == NULL) {
		rc = -ENOMEM;
		goto out_nobuf;
	}
533

L
Linus Torvalds 已提交
534 535 536
	/*
	 * parse input
	 */
537
	num_of_segments = 0;
L
Linus Torvalds 已提交
538
	for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
		for (j = i; (buf[j] != ':') &&
			(buf[j] != '\0') &&
			(buf[j] != '\n') &&
			j < count; j++) {
			local_buf[j-i] = toupper(buf[j]);
		}
		local_buf[j-i] = '\0';
		if (((j - i) == 0) || ((j - i) > 8)) {
			rc = -ENAMETOOLONG;
			goto seg_list_del;
		}

		rc = dcssblk_load_segment(local_buf, &seg_info);
		if (rc < 0)
			goto seg_list_del;
		/*
		 * get a struct dcssblk_dev_info
		 */
		if (num_of_segments == 0) {
			dev_info = kzalloc(sizeof(struct dcssblk_dev_info),
					GFP_KERNEL);
			if (dev_info == NULL) {
				rc = -ENOMEM;
				goto out;
			}
			strcpy(dev_info->segment_name, local_buf);
			dev_info->segment_type = seg_info->segment_type;
			INIT_LIST_HEAD(&dev_info->seg_list);
		}
		list_add_tail(&seg_info->lh, &dev_info->seg_list);
		num_of_segments++;
		i = j;

		if ((buf[j] == '\0') || (buf[j] == '\n'))
			break;
L
Linus Torvalds 已提交
574
	}
575 576 577

	/* no trailing colon at the end of the input */
	if ((i > 0) && (buf[i-1] == ':')) {
L
Linus Torvalds 已提交
578
		rc = -ENAMETOOLONG;
579
		goto seg_list_del;
L
Linus Torvalds 已提交
580
	}
581 582 583 584 585 586 587 588
	strlcpy(local_buf, buf, i + 1);
	dev_info->num_of_segments = num_of_segments;
	rc = dcssblk_is_continuous(dev_info);
	if (rc < 0)
		goto seg_list_del;

	dev_info->start = dcssblk_find_lowest_addr(dev_info);
	dev_info->end = dcssblk_find_highest_addr(dev_info);
L
Linus Torvalds 已提交
589

590
	dev_set_name(&dev_info->dev, dev_info->segment_name);
L
Linus Torvalds 已提交
591 592 593 594 595
	dev_info->dev.release = dcssblk_release_segment;
	INIT_LIST_HEAD(&dev_info->lh);
	dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
	if (dev_info->gd == NULL) {
		rc = -ENOMEM;
596
		goto seg_list_del;
L
Linus Torvalds 已提交
597 598 599 600 601 602 603
	}
	dev_info->gd->major = dcssblk_major;
	dev_info->gd->fops = &dcssblk_devops;
	dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL);
	dev_info->gd->queue = dev_info->dcssblk_queue;
	dev_info->gd->private_data = dev_info;
	dev_info->gd->driverfs_dev = &dev_info->dev;
604
	blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
605
	blk_queue_logical_block_size(dev_info->dcssblk_queue, 4096);
606

L
Linus Torvalds 已提交
607 608
	seg_byte_size = (dev_info->end - dev_info->start + 1);
	set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
609 610
	pr_info("Loaded %s with total size %lu bytes and capacity %lu "
		"sectors\n", local_buf, seg_byte_size, seg_byte_size >> 9);
L
Linus Torvalds 已提交
611 612 613 614 615 616

	dev_info->save_pending = 0;
	dev_info->is_shared = 1;
	dev_info->dev.parent = dcssblk_root_dev;

	/*
617
	 *get minor, add to list
L
Linus Torvalds 已提交
618 619
	 */
	down_write(&dcssblk_devices_sem);
620
	if (dcssblk_get_segment_by_name(local_buf)) {
621
		rc = -EEXIST;
622
		goto release_gd;
623
	}
L
Linus Torvalds 已提交
624
	rc = dcssblk_assign_free_minor(dev_info);
625 626
	if (rc)
		goto release_gd;
L
Linus Torvalds 已提交
627
	sprintf(dev_info->gd->disk_name, "dcssblk%d",
628
		dev_info->gd->first_minor);
L
Linus Torvalds 已提交
629 630 631 632
	list_add_tail(&dev_info->lh, &dcssblk_devices);

	if (!try_module_get(THIS_MODULE)) {
		rc = -ENODEV;
633
		goto dev_list_del;
L
Linus Torvalds 已提交
634 635 636 637 638 639 640
	}
	/*
	 * register the device
	 */
	rc = device_register(&dev_info->dev);
	if (rc) {
		module_put(THIS_MODULE);
641
		goto dev_list_del;
L
Linus Torvalds 已提交
642 643 644 645 646 647
	}
	get_device(&dev_info->dev);
	rc = device_create_file(&dev_info->dev, &dev_attr_shared);
	if (rc)
		goto unregister_dev;
	rc = device_create_file(&dev_info->dev, &dev_attr_save);
648 649 650
	if (rc)
		goto unregister_dev;
	rc = device_create_file(&dev_info->dev, &dev_attr_seglist);
L
Linus Torvalds 已提交
651 652 653
	if (rc)
		goto unregister_dev;

654 655
	add_disk(dev_info->gd);

L
Linus Torvalds 已提交
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
	switch (dev_info->segment_type) {
		case SEG_TYPE_SR:
		case SEG_TYPE_ER:
		case SEG_TYPE_SC:
			set_disk_ro(dev_info->gd,1);
			break;
		default:
			set_disk_ro(dev_info->gd,0);
			break;
	}
	up_write(&dcssblk_devices_sem);
	rc = count;
	goto out;

unregister_dev:
	list_del(&dev_info->lh);
672
	blk_cleanup_queue(dev_info->dcssblk_queue);
L
Linus Torvalds 已提交
673 674 675
	dev_info->gd->queue = NULL;
	put_disk(dev_info->gd);
	device_unregister(&dev_info->dev);
676 677 678
	list_for_each_entry(seg_info, &dev_info->seg_list, lh) {
		segment_unload(seg_info->segment_name);
	}
L
Linus Torvalds 已提交
679 680 681
	put_device(&dev_info->dev);
	up_write(&dcssblk_devices_sem);
	goto out;
682
dev_list_del:
L
Linus Torvalds 已提交
683
	list_del(&dev_info->lh);
684
release_gd:
685
	blk_cleanup_queue(dev_info->dcssblk_queue);
L
Linus Torvalds 已提交
686 687
	dev_info->gd->queue = NULL;
	put_disk(dev_info->gd);
688 689 690 691 692 693 694 695 696
	up_write(&dcssblk_devices_sem);
seg_list_del:
	if (dev_info == NULL)
		goto out;
	list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) {
		list_del(&seg_info->lh);
		segment_unload(seg_info->segment_name);
		kfree(seg_info);
	}
L
Linus Torvalds 已提交
697 698 699 700 701 702 703 704 705 706 707
	kfree(dev_info);
out:
	kfree(local_buf);
out_nobuf:
	return rc;
}

/*
 * device attribute for removing devices
 */
static ssize_t
708
dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
L
Linus Torvalds 已提交
709 710
{
	struct dcssblk_dev_info *dev_info;
711
	struct segment_info *entry;
L
Linus Torvalds 已提交
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
	int rc, i;
	char *local_buf;

	if (dev != dcssblk_root_dev) {
		return -EINVAL;
	}
	local_buf = kmalloc(count + 1, GFP_KERNEL);
	if (local_buf == NULL) {
		return -ENOMEM;
	}
	/*
	 * parse input
	 */
	for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) {
		local_buf[i] = toupper(buf[i]);
	}
	local_buf[i] = '\0';
	if ((i == 0) || (i > 8)) {
		rc = -ENAMETOOLONG;
		goto out_buf;
	}

	down_write(&dcssblk_devices_sem);
	dev_info = dcssblk_get_device_by_name(local_buf);
	if (dev_info == NULL) {
		up_write(&dcssblk_devices_sem);
738 739
		pr_warning("Device %s cannot be removed because it is not a "
			   "known device\n", local_buf);
L
Linus Torvalds 已提交
740 741 742 743 744
		rc = -ENODEV;
		goto out_buf;
	}
	if (atomic_read(&dev_info->use_count) != 0) {
		up_write(&dcssblk_devices_sem);
745 746
		pr_warning("Device %s cannot be removed while it is in "
			   "use\n", local_buf);
L
Linus Torvalds 已提交
747 748 749 750
		rc = -EBUSY;
		goto out_buf;
	}

751
	list_del(&dev_info->lh);
L
Linus Torvalds 已提交
752
	del_gendisk(dev_info->gd);
753
	blk_cleanup_queue(dev_info->dcssblk_queue);
L
Linus Torvalds 已提交
754 755 756
	dev_info->gd->queue = NULL;
	put_disk(dev_info->gd);
	device_unregister(&dev_info->dev);
757 758 759 760 761

	/* unload all related segments */
	list_for_each_entry(entry, &dev_info->seg_list, lh)
		segment_unload(entry->segment_name);

L
Linus Torvalds 已提交
762 763 764 765 766 767 768 769 770 771
	put_device(&dev_info->dev);
	up_write(&dcssblk_devices_sem);

	rc = count;
out_buf:
	kfree(local_buf);
	return rc;
}

static int
A
Al Viro 已提交
772
dcssblk_open(struct block_device *bdev, fmode_t mode)
L
Linus Torvalds 已提交
773 774 775 776
{
	struct dcssblk_dev_info *dev_info;
	int rc;

A
Al Viro 已提交
777
	dev_info = bdev->bd_disk->private_data;
L
Linus Torvalds 已提交
778 779 780 781 782
	if (NULL == dev_info) {
		rc = -ENODEV;
		goto out;
	}
	atomic_inc(&dev_info->use_count);
A
Al Viro 已提交
783
	bdev->bd_block_size = 4096;
L
Linus Torvalds 已提交
784 785 786 787 788 789
	rc = 0;
out:
	return rc;
}

static int
A
Al Viro 已提交
790
dcssblk_release(struct gendisk *disk, fmode_t mode)
L
Linus Torvalds 已提交
791
{
A
Al Viro 已提交
792
	struct dcssblk_dev_info *dev_info = disk->private_data;
793
	struct segment_info *entry;
L
Linus Torvalds 已提交
794 795
	int rc;

A
Al Viro 已提交
796
	if (!dev_info) {
L
Linus Torvalds 已提交
797 798 799 800 801 802
		rc = -ENODEV;
		goto out;
	}
	down_write(&dcssblk_devices_sem);
	if (atomic_dec_and_test(&dev_info->use_count)
	    && (dev_info->save_pending)) {
803 804
		pr_info("Device %s has become idle and is being saved "
			"now\n", dev_info->segment_name);
805 806 807
		list_for_each_entry(entry, &dev_info->seg_list, lh) {
			segment_save(entry->segment_name);
		}
L
Linus Torvalds 已提交
808 809 810 811 812 813 814 815 816
		dev_info->save_pending = 0;
	}
	up_write(&dcssblk_devices_sem);
	rc = 0;
out:
	return rc;
}

static int
817
dcssblk_make_request(struct request_queue *q, struct bio *bio)
L
Linus Torvalds 已提交
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
{
	struct dcssblk_dev_info *dev_info;
	struct bio_vec *bvec;
	unsigned long index;
	unsigned long page_addr;
	unsigned long source_addr;
	unsigned long bytes_done;
	int i;

	bytes_done = 0;
	dev_info = bio->bi_bdev->bd_disk->private_data;
	if (dev_info == NULL)
		goto fail;
	if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
		/* Request is not page-aligned. */
		goto fail;
	if (((bio->bi_size >> 9) + bio->bi_sector)
			> get_capacity(bio->bi_bdev->bd_disk)) {
		/* Request beyond end of DCSS segment. */
		goto fail;
	}
839 840 841 842 843 844 845 846
	/* verify data transfer direction */
	if (dev_info->is_shared) {
		switch (dev_info->segment_type) {
		case SEG_TYPE_SR:
		case SEG_TYPE_ER:
		case SEG_TYPE_SC:
			/* cannot write to these segments */
			if (bio_data_dir(bio) == WRITE) {
847 848
				pr_warning("Writing to %s failed because it "
					   "is a read-only device\n",
849
					   dev_name(&dev_info->dev));
850 851 852 853 854
				goto fail;
			}
		}
	}

L
Linus Torvalds 已提交
855 856 857 858 859
	index = (bio->bi_sector >> 3);
	bio_for_each_segment(bvec, bio, i) {
		page_addr = (unsigned long)
			page_address(bvec->bv_page) + bvec->bv_offset;
		source_addr = dev_info->start + (index<<12) + bytes_done;
R
Roel Kluin 已提交
860
		if (unlikely((page_addr & 4095) != 0) || (bvec->bv_len & 4095) != 0)
L
Linus Torvalds 已提交
861 862 863 864 865 866 867 868 869 870 871
			// More paranoia.
			goto fail;
		if (bio_data_dir(bio) == READ) {
			memcpy((void*)page_addr, (void*)source_addr,
				bvec->bv_len);
		} else {
			memcpy((void*)source_addr, (void*)page_addr,
				bvec->bv_len);
		}
		bytes_done += bvec->bv_len;
	}
872
	bio_endio(bio, 0);
L
Linus Torvalds 已提交
873 874
	return 0;
fail:
875
	bio_io_error(bio);
876 877 878 879 880
	return 0;
}

static int
dcssblk_direct_access (struct block_device *bdev, sector_t secnum,
881
			void **kaddr, unsigned long *pfn)
882 883 884 885 886 887 888 889 890 891 892 893
{
	struct dcssblk_dev_info *dev_info;
	unsigned long pgoff;

	dev_info = bdev->bd_disk->private_data;
	if (!dev_info)
		return -ENODEV;
	if (secnum % (PAGE_SIZE/512))
		return -EINVAL;
	pgoff = secnum / (PAGE_SIZE / 512);
	if ((pgoff+1)*PAGE_SIZE-1 > dev_info->end - dev_info->start)
		return -ERANGE;
894 895 896
	*kaddr = (void *) (dev_info->start+pgoff*PAGE_SIZE);
	*pfn = virt_to_phys(*kaddr) >> PAGE_SHIFT;

L
Linus Torvalds 已提交
897 898 899 900 901 902 903
	return 0;
}

static void
dcssblk_check_params(void)
{
	int rc, i, j, k;
904
	char buf[DCSSBLK_PARM_LEN + 1];
L
Linus Torvalds 已提交
905 906 907 908 909 910 911
	struct dcssblk_dev_info *dev_info;

	for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
	     i++) {
		for (j = i; (dcssblk_segments[j] != ',')  &&
			    (dcssblk_segments[j] != '\0') &&
			    (dcssblk_segments[j] != '(')  &&
912
			    (j < DCSSBLK_PARM_LEN); j++)
L
Linus Torvalds 已提交
913 914 915 916
		{
			buf[j-i] = dcssblk_segments[j];
		}
		buf[j-i] = '\0';
917
		rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);
L
Linus Torvalds 已提交
918
		if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
919
			for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++)
L
Linus Torvalds 已提交
920
				buf[k] = toupper(buf[k]);
921
			buf[k] = '\0';
L
Linus Torvalds 已提交
922 923 924 925 926 927
			if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
				down_read(&dcssblk_devices_sem);
				dev_info = dcssblk_get_device_by_name(buf);
				up_read(&dcssblk_devices_sem);
				if (dev_info)
					dcssblk_shared_store(&dev_info->dev,
928
							     NULL, "0\n", 2);
L
Linus Torvalds 已提交
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
			}
		}
		while ((dcssblk_segments[j] != ',') &&
		       (dcssblk_segments[j] != '\0'))
		{
			j++;
		}
		if (dcssblk_segments[j] == '\0')
			break;
		i = j;
	}
}

/*
 * The init/exit functions.
 */
static void __exit
dcssblk_exit(void)
{
M
Mark McLoughlin 已提交
948
	root_device_unregister(dcssblk_root_dev);
949
	unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
L
Linus Torvalds 已提交
950 951 952 953 954 955 956
}

static int __init
dcssblk_init(void)
{
	int rc;

M
Mark McLoughlin 已提交
957
	dcssblk_root_dev = root_device_register("dcssblk");
958
	if (IS_ERR(dcssblk_root_dev))
L
Linus Torvalds 已提交
959 960 961
		return PTR_ERR(dcssblk_root_dev);
	rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
	if (rc) {
M
Mark McLoughlin 已提交
962
		root_device_unregister(dcssblk_root_dev);
L
Linus Torvalds 已提交
963 964 965 966
		return rc;
	}
	rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);
	if (rc) {
M
Mark McLoughlin 已提交
967
		root_device_unregister(dcssblk_root_dev);
L
Linus Torvalds 已提交
968 969 970 971
		return rc;
	}
	rc = register_blkdev(0, DCSSBLK_NAME);
	if (rc < 0) {
M
Mark McLoughlin 已提交
972
		root_device_unregister(dcssblk_root_dev);
L
Linus Torvalds 已提交
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987
		return rc;
	}
	dcssblk_major = rc;
	init_rwsem(&dcssblk_devices_sem);

	dcssblk_check_params();

	return 0;
}

module_init(dcssblk_init);
module_exit(dcssblk_exit);

module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
988 989 990 991 992 993 994
		 "comma-separated list, names in each set separated "
		 "by commas are separated by colons, each set contains "
		 "names of contiguous segments and each name max. 8 chars.\n"
		 "Adding \"(local)\" to the end of each set equals echoing 0 "
		 "to /sys/devices/dcssblk/<device name>/shared after loading "
		 "the contiguous segments - \n"
		 "e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"");
L
Linus Torvalds 已提交
995 996

MODULE_LICENSE("GPL");