xref: /openbmc/linux/arch/s390/mm/extmem.c (revision 545e4006)
1 /*
2  * File...........: arch/s390/mm/extmem.c
3  * Author(s)......: Carsten Otte <cotte@de.ibm.com>
4  * 		    Rob M van der Heij <rvdheij@nl.ibm.com>
5  * 		    Steven Shultz <shultzss@us.ibm.com>
6  * Bugreports.to..: <Linux390@de.ibm.com>
7  * (C) IBM Corporation 2002-2004
8  */
9 
10 #include <linux/kernel.h>
11 #include <linux/string.h>
12 #include <linux/spinlock.h>
13 #include <linux/list.h>
14 #include <linux/slab.h>
15 #include <linux/module.h>
16 #include <linux/bootmem.h>
17 #include <linux/ctype.h>
18 #include <linux/ioport.h>
19 #include <asm/page.h>
20 #include <asm/pgtable.h>
21 #include <asm/ebcdic.h>
22 #include <asm/errno.h>
23 #include <asm/extmem.h>
24 #include <asm/cpcmd.h>
25 #include <asm/setup.h>
26 
27 #define DCSS_DEBUG	/* Debug messages on/off */
28 
29 #define DCSS_NAME "extmem"
30 #ifdef DCSS_DEBUG
31 #define PRINT_DEBUG(x...)	printk(KERN_DEBUG DCSS_NAME " debug:" x)
32 #else
33 #define PRINT_DEBUG(x...)   do {} while (0)
34 #endif
35 #define PRINT_INFO(x...)	printk(KERN_INFO DCSS_NAME " info:" x)
36 #define PRINT_WARN(x...)	printk(KERN_WARNING DCSS_NAME " warning:" x)
37 #define PRINT_ERR(x...)		printk(KERN_ERR DCSS_NAME " error:" x)
38 
39 
40 #define DCSS_LOADSHR    0x00
41 #define DCSS_LOADNSR    0x04
42 #define DCSS_PURGESEG   0x08
43 #define DCSS_FINDSEG    0x0c
44 #define DCSS_LOADNOLY   0x10
45 #define DCSS_SEGEXT     0x18
46 #define DCSS_FINDSEGA   0x0c
47 
48 struct qrange {
49 	unsigned int  start; // 3byte start address, 1 byte type
50 	unsigned int  end;   // 3byte end address, 1 byte reserved
51 };
52 
53 struct qout64 {
54 	int segstart;
55 	int segend;
56 	int segcnt;
57 	int segrcnt;
58 	struct qrange range[6];
59 };
60 
61 struct qin64 {
62 	char qopcode;
63 	char rsrv1[3];
64 	char qrcode;
65 	char rsrv2[3];
66 	char qname[8];
67 	unsigned int qoutptr;
68 	short int qoutlen;
69 };
70 
71 struct dcss_segment {
72 	struct list_head list;
73 	char dcss_name[8];
74 	char res_name[15];
75 	unsigned long start_addr;
76 	unsigned long end;
77 	atomic_t ref_count;
78 	int do_nonshared;
79 	unsigned int vm_segtype;
80 	struct qrange range[6];
81 	int segcnt;
82 	struct resource *res;
83 };
84 
85 static DEFINE_MUTEX(dcss_lock);
86 static LIST_HEAD(dcss_list);
87 static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
88 					"EW/EN-MIXED" };
89 
90 /*
91  * Create the 8 bytes, ebcdic VM segment name from
92  * an ascii name.
93  */
94 static void
95 dcss_mkname(char *name, char *dcss_name)
96 {
97 	int i;
98 
99 	for (i = 0; i < 8; i++) {
100 		if (name[i] == '\0')
101 			break;
102 		dcss_name[i] = toupper(name[i]);
103 	};
104 	for (; i < 8; i++)
105 		dcss_name[i] = ' ';
106 	ASCEBC(dcss_name, 8);
107 }
108 
109 
110 /*
111  * search all segments in dcss_list, and return the one
112  * namend *name. If not found, return NULL.
113  */
114 static struct dcss_segment *
115 segment_by_name (char *name)
116 {
117 	char dcss_name[9];
118 	struct list_head *l;
119 	struct dcss_segment *tmp, *retval = NULL;
120 
121 	BUG_ON(!mutex_is_locked(&dcss_lock));
122 	dcss_mkname (name, dcss_name);
123 	list_for_each (l, &dcss_list) {
124 		tmp = list_entry (l, struct dcss_segment, list);
125 		if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) {
126 			retval = tmp;
127 			break;
128 		}
129 	}
130 	return retval;
131 }
132 
133 
134 /*
135  * Perform a function on a dcss segment.
136  */
137 static inline int
138 dcss_diag (__u8 func, void *parameter,
139            unsigned long *ret1, unsigned long *ret2)
140 {
141 	unsigned long rx, ry;
142 	int rc;
143 
144 	rx = (unsigned long) parameter;
145 	ry = (unsigned long) func;
146 	asm volatile(
147 #ifdef CONFIG_64BIT
148 		"	sam31\n"
149 		"	diag	%0,%1,0x64\n"
150 		"	sam64\n"
151 #else
152 		"	diag	%0,%1,0x64\n"
153 #endif
154 		"	ipm	%2\n"
155 		"	srl	%2,28\n"
156 		: "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
157 	*ret1 = rx;
158 	*ret2 = ry;
159 	return rc;
160 }
161 
162 static inline int
163 dcss_diag_translate_rc (int vm_rc) {
164 	if (vm_rc == 44)
165 		return -ENOENT;
166 	return -EIO;
167 }
168 
169 
170 /* do a diag to get info about a segment.
171  * fills start_address, end and vm_segtype fields
172  */
173 static int
174 query_segment_type (struct dcss_segment *seg)
175 {
176 	struct qin64  *qin = kmalloc (sizeof(struct qin64), GFP_DMA);
177 	struct qout64 *qout = kmalloc (sizeof(struct qout64), GFP_DMA);
178 
179 	int diag_cc, rc, i;
180 	unsigned long dummy, vmrc;
181 
182 	if ((qin == NULL) || (qout == NULL)) {
183 		rc = -ENOMEM;
184 		goto out_free;
185 	}
186 
187 	/* initialize diag input parameters */
188 	qin->qopcode = DCSS_FINDSEGA;
189 	qin->qoutptr = (unsigned long) qout;
190 	qin->qoutlen = sizeof(struct qout64);
191 	memcpy (qin->qname, seg->dcss_name, 8);
192 
193 	diag_cc = dcss_diag (DCSS_SEGEXT, qin, &dummy, &vmrc);
194 
195 	if (diag_cc > 1) {
196 		PRINT_WARN ("segment_type: diag returned error %ld\n", vmrc);
197 		rc = dcss_diag_translate_rc (vmrc);
198 		goto out_free;
199 	}
200 
201 	if (qout->segcnt > 6) {
202 		rc = -ENOTSUPP;
203 		goto out_free;
204 	}
205 
206 	if (qout->segcnt == 1) {
207 		seg->vm_segtype = qout->range[0].start & 0xff;
208 	} else {
209 		/* multi-part segment. only one type supported here:
210 		    - all parts are contiguous
211 		    - all parts are either EW or EN type
212 		    - maximum 6 parts allowed */
213 		unsigned long start = qout->segstart >> PAGE_SHIFT;
214 		for (i=0; i<qout->segcnt; i++) {
215 			if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) &&
216 			    ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) {
217 				rc = -ENOTSUPP;
218 				goto out_free;
219 			}
220 			if (start != qout->range[i].start >> PAGE_SHIFT) {
221 				rc = -ENOTSUPP;
222 				goto out_free;
223 			}
224 			start = (qout->range[i].end >> PAGE_SHIFT) + 1;
225 		}
226 		seg->vm_segtype = SEG_TYPE_EWEN;
227 	}
228 
229 	/* analyze diag output and update seg */
230 	seg->start_addr = qout->segstart;
231 	seg->end = qout->segend;
232 
233 	memcpy (seg->range, qout->range, 6*sizeof(struct qrange));
234 	seg->segcnt = qout->segcnt;
235 
236 	rc = 0;
237 
238  out_free:
239 	kfree(qin);
240 	kfree(qout);
241 	return rc;
242 }
243 
244 /*
245  * get info about a segment
246  * possible return values:
247  * -ENOSYS  : we are not running on VM
248  * -EIO     : could not perform query diagnose
249  * -ENOENT  : no such segment
250  * -ENOTSUPP: multi-part segment cannot be used with linux
251  * -ENOSPC  : segment cannot be used (overlaps with storage)
252  * -ENOMEM  : out of memory
253  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
254  */
255 int
256 segment_type (char* name)
257 {
258 	int rc;
259 	struct dcss_segment seg;
260 
261 	if (!MACHINE_IS_VM)
262 		return -ENOSYS;
263 
264 	dcss_mkname(name, seg.dcss_name);
265 	rc = query_segment_type (&seg);
266 	if (rc < 0)
267 		return rc;
268 	return seg.vm_segtype;
269 }
270 
271 /*
272  * real segment loading function, called from segment_load
273  */
274 static int
275 __segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
276 {
277 	struct dcss_segment *seg = kmalloc(sizeof(struct dcss_segment),
278 			GFP_DMA);
279 	int dcss_command, rc, diag_cc;
280 
281 	if (seg == NULL) {
282 		rc = -ENOMEM;
283 		goto out;
284 	}
285 	dcss_mkname (name, seg->dcss_name);
286 	rc = query_segment_type (seg);
287 	if (rc < 0)
288 		goto out_free;
289 
290 	rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
291 
292 	if (rc)
293 		goto out_free;
294 
295 	seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL);
296 	if (seg->res == NULL) {
297 		rc = -ENOMEM;
298 		goto out_shared;
299 	}
300 	seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM;
301 	seg->res->start = seg->start_addr;
302 	seg->res->end = seg->end;
303 	memcpy(&seg->res_name, seg->dcss_name, 8);
304 	EBCASC(seg->res_name, 8);
305 	seg->res_name[8] = '\0';
306 	strncat(seg->res_name, " (DCSS)", 7);
307 	seg->res->name = seg->res_name;
308 	rc = seg->vm_segtype;
309 	if (rc == SEG_TYPE_SC ||
310 	    ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared))
311 		seg->res->flags |= IORESOURCE_READONLY;
312 	if (request_resource(&iomem_resource, seg->res)) {
313 		rc = -EBUSY;
314 		kfree(seg->res);
315 		goto out_shared;
316 	}
317 
318 	if (do_nonshared)
319 		dcss_command = DCSS_LOADNSR;
320 	else
321 		dcss_command = DCSS_LOADNOLY;
322 
323 	diag_cc = dcss_diag(dcss_command, seg->dcss_name,
324 			&seg->start_addr, &seg->end);
325 	if (diag_cc > 1) {
326 		PRINT_WARN ("segment_load: could not load segment %s - "
327 				"diag returned error (%ld)\n",name,seg->end);
328 		rc = dcss_diag_translate_rc (seg->end);
329 		dcss_diag(DCSS_PURGESEG, seg->dcss_name,
330 				&seg->start_addr, &seg->end);
331 		goto out_resource;
332 	}
333 	seg->do_nonshared = do_nonshared;
334 	atomic_set(&seg->ref_count, 1);
335 	list_add(&seg->list, &dcss_list);
336 	*addr = seg->start_addr;
337 	*end  = seg->end;
338 	if (do_nonshared)
339 		PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
340 				"type %s in non-shared mode\n", name,
341 				(void*)seg->start_addr, (void*)seg->end,
342 				segtype_string[seg->vm_segtype]);
343 	else {
344 		PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
345 				"type %s in shared mode\n", name,
346 				(void*)seg->start_addr, (void*)seg->end,
347 				segtype_string[seg->vm_segtype]);
348 	}
349 	goto out;
350  out_resource:
351 	release_resource(seg->res);
352 	kfree(seg->res);
353  out_shared:
354 	vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
355  out_free:
356 	kfree(seg);
357  out:
358 	return rc;
359 }
360 
361 /*
362  * this function loads a DCSS segment
363  * name         : name of the DCSS
364  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
365  *                1 indicates that the dcss should be exclusive for this linux image
366  * addr         : will be filled with start address of the segment
367  * end          : will be filled with end address of the segment
368  * return values:
369  * -ENOSYS  : we are not running on VM
370  * -EIO     : could not perform query or load diagnose
371  * -ENOENT  : no such segment
372  * -ENOTSUPP: multi-part segment cannot be used with linux
373  * -ENOSPC  : segment cannot be used (overlaps with storage)
374  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
375  * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
376  * -EPERM   : segment is currently loaded with incompatible permissions
377  * -ENOMEM  : out of memory
378  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
379  */
380 int
381 segment_load (char *name, int do_nonshared, unsigned long *addr,
382 		unsigned long *end)
383 {
384 	struct dcss_segment *seg;
385 	int rc;
386 
387 	if (!MACHINE_IS_VM)
388 		return -ENOSYS;
389 
390 	mutex_lock(&dcss_lock);
391 	seg = segment_by_name (name);
392 	if (seg == NULL)
393 		rc = __segment_load (name, do_nonshared, addr, end);
394 	else {
395 		if (do_nonshared == seg->do_nonshared) {
396 			atomic_inc(&seg->ref_count);
397 			*addr = seg->start_addr;
398 			*end  = seg->end;
399 			rc    = seg->vm_segtype;
400 		} else {
401 			*addr = *end = 0;
402 			rc    = -EPERM;
403 		}
404 	}
405 	mutex_unlock(&dcss_lock);
406 	return rc;
407 }
408 
409 /*
410  * this function modifies the shared state of a DCSS segment. note that
411  * name         : name of the DCSS
412  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
413  *                1 indicates that the dcss should be exclusive for this linux image
414  * return values:
415  * -EIO     : could not perform load diagnose (segment gone!)
416  * -ENOENT  : no such segment (segment gone!)
417  * -EAGAIN  : segment is in use by other exploiters, try later
418  * -EINVAL  : no segment with the given name is currently loaded - name invalid
419  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
420  * 0	    : operation succeeded
421  */
422 int
423 segment_modify_shared (char *name, int do_nonshared)
424 {
425 	struct dcss_segment *seg;
426 	unsigned long dummy;
427 	int dcss_command, rc, diag_cc;
428 
429 	mutex_lock(&dcss_lock);
430 	seg = segment_by_name (name);
431 	if (seg == NULL) {
432 		rc = -EINVAL;
433 		goto out_unlock;
434 	}
435 	if (do_nonshared == seg->do_nonshared) {
436 		PRINT_INFO ("segment_modify_shared: not reloading segment %s"
437 				" - already in requested mode\n",name);
438 		rc = 0;
439 		goto out_unlock;
440 	}
441 	if (atomic_read (&seg->ref_count) != 1) {
442 		PRINT_WARN ("segment_modify_shared: not reloading segment %s - "
443 				"segment is in use by other driver(s)\n",name);
444 		rc = -EAGAIN;
445 		goto out_unlock;
446 	}
447 	release_resource(seg->res);
448 	if (do_nonshared) {
449 		dcss_command = DCSS_LOADNSR;
450 		seg->res->flags &= ~IORESOURCE_READONLY;
451 	} else {
452 		dcss_command = DCSS_LOADNOLY;
453 		if (seg->vm_segtype == SEG_TYPE_SR ||
454 		    seg->vm_segtype == SEG_TYPE_ER)
455 			seg->res->flags |= IORESOURCE_READONLY;
456 	}
457 	if (request_resource(&iomem_resource, seg->res)) {
458 		PRINT_WARN("segment_modify_shared: could not reload segment %s"
459 			   " - overlapping resources\n", name);
460 		rc = -EBUSY;
461 		kfree(seg->res);
462 		goto out_del;
463 	}
464 	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
465 	diag_cc = dcss_diag(dcss_command, seg->dcss_name,
466 			&seg->start_addr, &seg->end);
467 	if (diag_cc > 1) {
468 		PRINT_WARN ("segment_modify_shared: could not reload segment %s"
469 				" - diag returned error (%ld)\n",name,seg->end);
470 		rc = dcss_diag_translate_rc (seg->end);
471 		goto out_del;
472 	}
473 	seg->do_nonshared = do_nonshared;
474 	rc = 0;
475 	goto out_unlock;
476  out_del:
477 	vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
478 	list_del(&seg->list);
479 	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
480 	kfree(seg);
481  out_unlock:
482 	mutex_unlock(&dcss_lock);
483 	return rc;
484 }
485 
486 /*
487  * Decrease the use count of a DCSS segment and remove
488  * it from the address space if nobody is using it
489  * any longer.
490  */
491 void
492 segment_unload(char *name)
493 {
494 	unsigned long dummy;
495 	struct dcss_segment *seg;
496 
497 	if (!MACHINE_IS_VM)
498 		return;
499 
500 	mutex_lock(&dcss_lock);
501 	seg = segment_by_name (name);
502 	if (seg == NULL) {
503 		PRINT_ERR ("could not find segment %s in segment_unload, "
504 				"please report to linux390@de.ibm.com\n",name);
505 		goto out_unlock;
506 	}
507 	if (atomic_dec_return(&seg->ref_count) != 0)
508 		goto out_unlock;
509 	release_resource(seg->res);
510 	kfree(seg->res);
511 	vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
512 	list_del(&seg->list);
513 	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
514 	kfree(seg);
515 out_unlock:
516 	mutex_unlock(&dcss_lock);
517 }
518 
519 /*
520  * save segment content permanently
521  */
522 void
523 segment_save(char *name)
524 {
525 	struct dcss_segment *seg;
526 	int startpfn = 0;
527 	int endpfn = 0;
528 	char cmd1[160];
529 	char cmd2[80];
530 	int i, response;
531 
532 	if (!MACHINE_IS_VM)
533 		return;
534 
535 	mutex_lock(&dcss_lock);
536 	seg = segment_by_name (name);
537 
538 	if (seg == NULL) {
539 		PRINT_ERR("could not find segment %s in segment_save, please "
540 			  "report to linux390@de.ibm.com\n", name);
541 		goto out;
542 	}
543 
544 	startpfn = seg->start_addr >> PAGE_SHIFT;
545 	endpfn = (seg->end) >> PAGE_SHIFT;
546 	sprintf(cmd1, "DEFSEG %s", name);
547 	for (i=0; i<seg->segcnt; i++) {
548 		sprintf(cmd1+strlen(cmd1), " %X-%X %s",
549 			seg->range[i].start >> PAGE_SHIFT,
550 			seg->range[i].end >> PAGE_SHIFT,
551 			segtype_string[seg->range[i].start & 0xff]);
552 	}
553 	sprintf(cmd2, "SAVESEG %s", name);
554 	response = 0;
555 	cpcmd(cmd1, NULL, 0, &response);
556 	if (response) {
557 		PRINT_ERR("segment_save: DEFSEG failed with response code %i\n",
558 			  response);
559 		goto out;
560 	}
561 	cpcmd(cmd2, NULL, 0, &response);
562 	if (response) {
563 		PRINT_ERR("segment_save: SAVESEG failed with response code %i\n",
564 			  response);
565 		goto out;
566 	}
567 out:
568 	mutex_unlock(&dcss_lock);
569 }
570 
571 /*
572  * print appropriate error message for segment_load()/segment_type()
573  * return code
574  */
575 void segment_warning(int rc, char *seg_name)
576 {
577 	switch (rc) {
578 	case -ENOENT:
579 		PRINT_WARN("cannot load/query segment %s, "
580 			   "does not exist\n", seg_name);
581 		break;
582 	case -ENOSYS:
583 		PRINT_WARN("cannot load/query segment %s, "
584 			   "not running on VM\n", seg_name);
585 		break;
586 	case -EIO:
587 		PRINT_WARN("cannot load/query segment %s, "
588 			   "hardware error\n", seg_name);
589 		break;
590 	case -ENOTSUPP:
591 		PRINT_WARN("cannot load/query segment %s, "
592 			   "is a multi-part segment\n", seg_name);
593 		break;
594 	case -ENOSPC:
595 		PRINT_WARN("cannot load/query segment %s, "
596 			   "overlaps with storage\n", seg_name);
597 		break;
598 	case -EBUSY:
599 		PRINT_WARN("cannot load/query segment %s, "
600 			   "overlaps with already loaded dcss\n", seg_name);
601 		break;
602 	case -EPERM:
603 		PRINT_WARN("cannot load/query segment %s, "
604 			   "already loaded in incompatible mode\n", seg_name);
605 		break;
606 	case -ENOMEM:
607 		PRINT_WARN("cannot load/query segment %s, "
608 			   "out of memory\n", seg_name);
609 		break;
610 	case -ERANGE:
611 		PRINT_WARN("cannot load/query segment %s, "
612 			   "exceeds kernel mapping range\n", seg_name);
613 		break;
614 	default:
615 		PRINT_WARN("cannot load/query segment %s, "
616 			   "return value %i\n", seg_name, rc);
617 		break;
618 	}
619 }
620 
621 EXPORT_SYMBOL(segment_load);
622 EXPORT_SYMBOL(segment_unload);
623 EXPORT_SYMBOL(segment_save);
624 EXPORT_SYMBOL(segment_type);
625 EXPORT_SYMBOL(segment_modify_shared);
626 EXPORT_SYMBOL(segment_warning);
627