extmem.c 14.4 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * File...........: arch/s390/mm/extmem.c
 * Author(s)......: Carsten Otte <cotte@de.ibm.com>
 * 		    Rob M van der Heij <rvdheij@nl.ibm.com>
 * 		    Steven Shultz <shultzss@us.ibm.com>
 * Bugreports.to..: <Linux390@de.ibm.com>
 * (C) IBM Corporation 2002-2004
 */

#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/bootmem.h>
17
#include <linux/ctype.h>
18
#include <linux/ioport.h>
L
Linus Torvalds 已提交
19
#include <asm/page.h>
H
Heiko Carstens 已提交
20
#include <asm/pgtable.h>
L
Linus Torvalds 已提交
21 22 23 24
#include <asm/ebcdic.h>
#include <asm/errno.h>
#include <asm/extmem.h>
#include <asm/cpcmd.h>
25
#include <asm/setup.h>
L
Linus Torvalds 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

#define DCSS_DEBUG	/* Debug messages on/off */

#define DCSS_NAME "extmem"
#ifdef DCSS_DEBUG
#define PRINT_DEBUG(x...)	printk(KERN_DEBUG DCSS_NAME " debug:" x)
#else
#define PRINT_DEBUG(x...)   do {} while (0)
#endif
#define PRINT_INFO(x...)	printk(KERN_INFO DCSS_NAME " info:" x)
#define PRINT_WARN(x...)	printk(KERN_WARNING DCSS_NAME " warning:" x)
#define PRINT_ERR(x...)		printk(KERN_ERR DCSS_NAME " error:" x)


#define DCSS_LOADSHR    0x00
#define DCSS_LOADNSR    0x04
#define DCSS_PURGESEG   0x08
#define DCSS_FINDSEG    0x0c
#define DCSS_LOADNOLY   0x10
#define DCSS_SEGEXT     0x18
#define DCSS_FINDSEGA   0x0c

struct qrange {
	unsigned int  start; // 3byte start address, 1 byte type
	unsigned int  end;   // 3byte end address, 1 byte reserved
};

struct qout64 {
	int segstart;
	int segend;
	int segcnt;
	int segrcnt;
	struct qrange range[6];
};

struct qin64 {
	char qopcode;
	char rsrv1[3];
	char qrcode;
	char rsrv2[3];
	char qname[8];
	unsigned int qoutptr;
	short int qoutlen;
};

struct dcss_segment {
	struct list_head list;
	char dcss_name[8];
74
	char res_name[15];
L
Linus Torvalds 已提交
75 76 77 78 79 80 81
	unsigned long start_addr;
	unsigned long end;
	atomic_t ref_count;
	int do_nonshared;
	unsigned int vm_segtype;
	struct qrange range[6];
	int segcnt;
82
	struct resource *res;
L
Linus Torvalds 已提交
83 84
};

85
static DEFINE_MUTEX(dcss_lock);
L
Linus Torvalds 已提交
86 87 88 89 90 91 92 93
static struct list_head dcss_list = LIST_HEAD_INIT(dcss_list);
static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
					"EW/EN-MIXED" };

/*
 * Create the 8 bytes, ebcdic VM segment name from
 * an ascii name.
 */
94
static void
L
Linus Torvalds 已提交
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
dcss_mkname(char *name, char *dcss_name)
{
	int i;

	for (i = 0; i < 8; i++) {
		if (name[i] == '\0')
			break;
		dcss_name[i] = toupper(name[i]);
	};
	for (; i < 8; i++)
		dcss_name[i] = ' ';
	ASCEBC(dcss_name, 8);
}


/*
 * search all segments in dcss_list, and return the one
 * namend *name. If not found, return NULL.
 */
static struct dcss_segment *
segment_by_name (char *name)
{
	char dcss_name[9];
	struct list_head *l;
	struct dcss_segment *tmp, *retval = NULL;

121
	BUG_ON(!mutex_is_locked(&dcss_lock));
L
Linus Torvalds 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
	dcss_mkname (name, dcss_name);
	list_for_each (l, &dcss_list) {
		tmp = list_entry (l, struct dcss_segment, list);
		if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) {
			retval = tmp;
			break;
		}
	}
	return retval;
}


/*
 * Perform a function on a dcss segment.
 */
static inline int
dcss_diag (__u8 func, void *parameter,
           unsigned long *ret1, unsigned long *ret2)
{
	unsigned long rx, ry;
	int rc;

	rx = (unsigned long) parameter;
	ry = (unsigned long) func;
146
	asm volatile(
147
#ifdef CONFIG_64BIT
148 149 150
		"	sam31\n"
		"	diag	%0,%1,0x64\n"
		"	sam64\n"
L
Linus Torvalds 已提交
151
#else
152
		"	diag	%0,%1,0x64\n"
L
Linus Torvalds 已提交
153
#endif
154 155 156
		"	ipm	%2\n"
		"	srl	%2,28\n"
		: "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
L
Linus Torvalds 已提交
157 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
	*ret1 = rx;
	*ret2 = ry;
	return rc;
}

static inline int
dcss_diag_translate_rc (int vm_rc) {
	if (vm_rc == 44)
		return -ENOENT;
	return -EIO;
}


/* do a diag to get info about a segment.
 * fills start_address, end and vm_segtype fields
 */
static int
query_segment_type (struct dcss_segment *seg)
{
	struct qin64  *qin = kmalloc (sizeof(struct qin64), GFP_DMA);
	struct qout64 *qout = kmalloc (sizeof(struct qout64), GFP_DMA);

	int diag_cc, rc, i;
	unsigned long dummy, vmrc;

	if ((qin == NULL) || (qout == NULL)) {
		rc = -ENOMEM;
		goto out_free;
	}

	/* initialize diag input parameters */
	qin->qopcode = DCSS_FINDSEGA;
	qin->qoutptr = (unsigned long) qout;
	qin->qoutlen = sizeof(struct qout64);
	memcpy (qin->qname, seg->dcss_name, 8);

	diag_cc = dcss_diag (DCSS_SEGEXT, qin, &dummy, &vmrc);

	if (diag_cc > 1) {
196
		PRINT_WARN ("segment_type: diag returned error %ld\n", vmrc);
L
Linus Torvalds 已提交
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
		rc = dcss_diag_translate_rc (vmrc);
		goto out_free;
	}

	if (qout->segcnt > 6) {
		rc = -ENOTSUPP;
		goto out_free;
	}

	if (qout->segcnt == 1) {
		seg->vm_segtype = qout->range[0].start & 0xff;
	} else {
		/* multi-part segment. only one type supported here:
		    - all parts are contiguous
		    - all parts are either EW or EN type
		    - maximum 6 parts allowed */
		unsigned long start = qout->segstart >> PAGE_SHIFT;
		for (i=0; i<qout->segcnt; i++) {
			if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) &&
			    ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) {
				rc = -ENOTSUPP;
				goto out_free;
			}
			if (start != qout->range[i].start >> PAGE_SHIFT) {
				rc = -ENOTSUPP;
				goto out_free;
			}
			start = (qout->range[i].end >> PAGE_SHIFT) + 1;
		}
		seg->vm_segtype = SEG_TYPE_EWEN;
	}

	/* analyze diag output and update seg */
	seg->start_addr = qout->segstart;
	seg->end = qout->segend;

	memcpy (seg->range, qout->range, 6*sizeof(struct qrange));
	seg->segcnt = qout->segcnt;

	rc = 0;

 out_free:
J
Jesper Juhl 已提交
239 240
	kfree(qin);
	kfree(qout);
L
Linus Torvalds 已提交
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
	return rc;
}

/*
 * get info about a segment
 * possible return values:
 * -ENOSYS  : we are not running on VM
 * -EIO     : could not perform query diagnose
 * -ENOENT  : no such segment
 * -ENOTSUPP: multi-part segment cannot be used with linux
 * -ENOSPC  : segment cannot be used (overlaps with storage)
 * -ENOMEM  : out of memory
 * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
 */
int
segment_type (char* name)
{
	int rc;
	struct dcss_segment seg;

	if (!MACHINE_IS_VM)
		return -ENOSYS;

	dcss_mkname(name, seg.dcss_name);
	rc = query_segment_type (&seg);
	if (rc < 0)
		return rc;
	return seg.vm_segtype;
}

/*
 * real segment loading function, called from segment_load
 */
static int
__segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
{
	struct dcss_segment *seg = kmalloc(sizeof(struct dcss_segment),
			GFP_DMA);
	int dcss_command, rc, diag_cc;

	if (seg == NULL) {
		rc = -ENOMEM;
		goto out;
	}
	dcss_mkname (name, seg->dcss_name);
	rc = query_segment_type (seg);
	if (rc < 0)
		goto out_free;
H
Heiko Carstens 已提交
289 290 291 292 293 294 295 296 297

	rc = add_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);

	switch (rc) {
	case 0:
		break;
	case -ENOSPC:
		PRINT_WARN("segment_load: not loading segment %s - overlaps "
			   "storage/segment\n", name);
L
Linus Torvalds 已提交
298
		goto out_free;
H
Heiko Carstens 已提交
299 300 301
	case -ERANGE:
		PRINT_WARN("segment_load: not loading segment %s - exceeds "
			   "kernel mapping range\n", name);
L
Linus Torvalds 已提交
302
		goto out_free;
H
Heiko Carstens 已提交
303 304 305
	default:
		PRINT_WARN("segment_load: not loading segment %s (rc: %d)\n",
			   name, rc);
L
Linus Torvalds 已提交
306 307
		goto out_free;
	}
H
Heiko Carstens 已提交
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
	seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL);
	if (seg->res == NULL) {
		rc = -ENOMEM;
		goto out_shared;
	}
	seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM;
	seg->res->start = seg->start_addr;
	seg->res->end = seg->end;
	memcpy(&seg->res_name, seg->dcss_name, 8);
	EBCASC(seg->res_name, 8);
	seg->res_name[8] = '\0';
	strncat(seg->res_name, " (DCSS)", 7);
	seg->res->name = seg->res_name;
	rc = seg->vm_segtype;
	if (rc == SEG_TYPE_SC ||
	    ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared))
		seg->res->flags |= IORESOURCE_READONLY;
	if (request_resource(&iomem_resource, seg->res)) {
		rc = -EBUSY;
		kfree(seg->res);
		goto out_shared;
	}

L
Linus Torvalds 已提交
332 333 334 335 336 337 338 339 340 341 342 343 344
	if (do_nonshared)
		dcss_command = DCSS_LOADNSR;
	else
		dcss_command = DCSS_LOADNOLY;

	diag_cc = dcss_diag(dcss_command, seg->dcss_name,
			&seg->start_addr, &seg->end);
	if (diag_cc > 1) {
		PRINT_WARN ("segment_load: could not load segment %s - "
				"diag returned error (%ld)\n",name,seg->end);
		rc = dcss_diag_translate_rc (seg->end);
		dcss_diag(DCSS_PURGESEG, seg->dcss_name,
				&seg->start_addr, &seg->end);
345
		goto out_resource;
L
Linus Torvalds 已提交
346 347 348 349 350 351 352 353 354 355 356
	}
	seg->do_nonshared = do_nonshared;
	atomic_set(&seg->ref_count, 1);
	list_add(&seg->list, &dcss_list);
	*addr = seg->start_addr;
	*end  = seg->end;
	if (do_nonshared)
		PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
				"type %s in non-shared mode\n", name,
				(void*)seg->start_addr, (void*)seg->end,
				segtype_string[seg->vm_segtype]);
357
	else {
L
Linus Torvalds 已提交
358 359 360 361
		PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
				"type %s in shared mode\n", name,
				(void*)seg->start_addr, (void*)seg->end,
				segtype_string[seg->vm_segtype]);
362
	}
L
Linus Torvalds 已提交
363
	goto out;
364 365 366
 out_resource:
	release_resource(seg->res);
	kfree(seg->res);
H
Heiko Carstens 已提交
367 368
 out_shared:
	remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
L
Linus Torvalds 已提交
369
 out_free:
J
Jesper Juhl 已提交
370
	kfree(seg);
L
Linus Torvalds 已提交
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
 out:
	return rc;
}

/*
 * this function loads a DCSS segment
 * name         : name of the DCSS
 * do_nonshared : 0 indicates that the dcss should be shared with other linux images
 *                1 indicates that the dcss should be exclusive for this linux image
 * addr         : will be filled with start address of the segment
 * end          : will be filled with end address of the segment
 * return values:
 * -ENOSYS  : we are not running on VM
 * -EIO     : could not perform query or load diagnose
 * -ENOENT  : no such segment
 * -ENOTSUPP: multi-part segment cannot be used with linux
 * -ENOSPC  : segment cannot be used (overlaps with storage)
 * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
 * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
 * -EPERM   : segment is currently loaded with incompatible permissions
 * -ENOMEM  : out of memory
 * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
 */
int
segment_load (char *name, int do_nonshared, unsigned long *addr,
		unsigned long *end)
{
	struct dcss_segment *seg;
	int rc;

	if (!MACHINE_IS_VM)
		return -ENOSYS;

404
	mutex_lock(&dcss_lock);
L
Linus Torvalds 已提交
405 406 407 408 409 410 411 412 413 414 415 416 417 418
	seg = segment_by_name (name);
	if (seg == NULL)
		rc = __segment_load (name, do_nonshared, addr, end);
	else {
		if (do_nonshared == seg->do_nonshared) {
			atomic_inc(&seg->ref_count);
			*addr = seg->start_addr;
			*end  = seg->end;
			rc    = seg->vm_segtype;
		} else {
			*addr = *end = 0;
			rc    = -EPERM;
		}
	}
419
	mutex_unlock(&dcss_lock);
L
Linus Torvalds 已提交
420 421 422 423 424 425 426 427 428 429 430 431 432
	return rc;
}

/*
 * this function modifies the shared state of a DCSS segment. note that
 * name         : name of the DCSS
 * do_nonshared : 0 indicates that the dcss should be shared with other linux images
 *                1 indicates that the dcss should be exclusive for this linux image
 * return values:
 * -EIO     : could not perform load diagnose (segment gone!)
 * -ENOENT  : no such segment (segment gone!)
 * -EAGAIN  : segment is in use by other exploiters, try later
 * -EINVAL  : no segment with the given name is currently loaded - name invalid
433
 * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
L
Linus Torvalds 已提交
434 435 436 437 438 439 440 441 442
 * 0	    : operation succeeded
 */
int
segment_modify_shared (char *name, int do_nonshared)
{
	struct dcss_segment *seg;
	unsigned long dummy;
	int dcss_command, rc, diag_cc;

443
	mutex_lock(&dcss_lock);
L
Linus Torvalds 已提交
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
	seg = segment_by_name (name);
	if (seg == NULL) {
		rc = -EINVAL;
		goto out_unlock;
	}
	if (do_nonshared == seg->do_nonshared) {
		PRINT_INFO ("segment_modify_shared: not reloading segment %s"
				" - already in requested mode\n",name);
		rc = 0;
		goto out_unlock;
	}
	if (atomic_read (&seg->ref_count) != 1) {
		PRINT_WARN ("segment_modify_shared: not reloading segment %s - "
				"segment is in use by other driver(s)\n",name);
		rc = -EAGAIN;
		goto out_unlock;
	}
461 462
	release_resource(seg->res);
	if (do_nonshared) {
L
Linus Torvalds 已提交
463
		dcss_command = DCSS_LOADNSR;
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
		seg->res->flags &= ~IORESOURCE_READONLY;
	} else {
		dcss_command = DCSS_LOADNOLY;
		if (seg->vm_segtype == SEG_TYPE_SR ||
		    seg->vm_segtype == SEG_TYPE_ER)
			seg->res->flags |= IORESOURCE_READONLY;
	}
	if (request_resource(&iomem_resource, seg->res)) {
		PRINT_WARN("segment_modify_shared: could not reload segment %s"
			   " - overlapping resources\n", name);
		rc = -EBUSY;
		kfree(seg->res);
		goto out_del;
	}
	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
L
Linus Torvalds 已提交
479 480 481 482 483 484 485 486 487 488 489 490
	diag_cc = dcss_diag(dcss_command, seg->dcss_name,
			&seg->start_addr, &seg->end);
	if (diag_cc > 1) {
		PRINT_WARN ("segment_modify_shared: could not reload segment %s"
				" - diag returned error (%ld)\n",name,seg->end);
		rc = dcss_diag_translate_rc (seg->end);
		goto out_del;
	}
	seg->do_nonshared = do_nonshared;
	rc = 0;
	goto out_unlock;
 out_del:
491
	remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
L
Linus Torvalds 已提交
492
	list_del(&seg->list);
493
	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
J
Jesper Juhl 已提交
494
	kfree(seg);
L
Linus Torvalds 已提交
495
 out_unlock:
496
	mutex_unlock(&dcss_lock);
L
Linus Torvalds 已提交
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
	return rc;
}

/*
 * Decrease the use count of a DCSS segment and remove
 * it from the address space if nobody is using it
 * any longer.
 */
void
segment_unload(char *name)
{
	unsigned long dummy;
	struct dcss_segment *seg;

	if (!MACHINE_IS_VM)
		return;

514
	mutex_lock(&dcss_lock);
L
Linus Torvalds 已提交
515 516 517 518 519 520
	seg = segment_by_name (name);
	if (seg == NULL) {
		PRINT_ERR ("could not find segment %s in segment_unload, "
				"please report to linux390@de.ibm.com\n",name);
		goto out_unlock;
	}
H
Heiko Carstens 已提交
521 522
	if (atomic_dec_return(&seg->ref_count) != 0)
		goto out_unlock;
523 524
	release_resource(seg->res);
	kfree(seg->res);
H
Heiko Carstens 已提交
525 526 527 528
	remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
	list_del(&seg->list);
	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
	kfree(seg);
L
Linus Torvalds 已提交
529
out_unlock:
530
	mutex_unlock(&dcss_lock);
L
Linus Torvalds 已提交
531 532 533 534 535 536 537 538 539 540 541 542 543
}

/*
 * save segment content permanently
 */
void
segment_save(char *name)
{
	struct dcss_segment *seg;
	int startpfn = 0;
	int endpfn = 0;
	char cmd1[160];
	char cmd2[80];
544
	int i, response;
L
Linus Torvalds 已提交
545 546 547 548

	if (!MACHINE_IS_VM)
		return;

549
	mutex_lock(&dcss_lock);
L
Linus Torvalds 已提交
550 551 552
	seg = segment_by_name (name);

	if (seg == NULL) {
553 554 555
		PRINT_ERR("could not find segment %s in segment_save, please "
			  "report to linux390@de.ibm.com\n", name);
		goto out;
L
Linus Torvalds 已提交
556 557 558 559 560 561 562 563 564 565 566 567
	}

	startpfn = seg->start_addr >> PAGE_SHIFT;
	endpfn = (seg->end) >> PAGE_SHIFT;
	sprintf(cmd1, "DEFSEG %s", name);
	for (i=0; i<seg->segcnt; i++) {
		sprintf(cmd1+strlen(cmd1), " %X-%X %s",
			seg->range[i].start >> PAGE_SHIFT,
			seg->range[i].end >> PAGE_SHIFT,
			segtype_string[seg->range[i].start & 0xff]);
	}
	sprintf(cmd2, "SAVESEG %s", name);
568 569 570 571 572 573 574 575 576 577 578 579 580 581
	response = 0;
	cpcmd(cmd1, NULL, 0, &response);
	if (response) {
		PRINT_ERR("segment_save: DEFSEG failed with response code %i\n",
			  response);
		goto out;
	}
	cpcmd(cmd2, NULL, 0, &response);
	if (response) {
		PRINT_ERR("segment_save: SAVESEG failed with response code %i\n",
			  response);
		goto out;
	}
out:
582
	mutex_unlock(&dcss_lock);
L
Linus Torvalds 已提交
583 584 585 586 587 588 589
}

EXPORT_SYMBOL(segment_load);
EXPORT_SYMBOL(segment_unload);
EXPORT_SYMBOL(segment_save);
EXPORT_SYMBOL(segment_type);
EXPORT_SYMBOL(segment_modify_shared);