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