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