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