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