Linux-libre 5.4.49-gnu
[librecmc/linux-libre.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 <asm/diag.h>
24 #include <asm/page.h>
25 #include <asm/pgtable.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  */
293 static int
294 __segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
295 {
296         unsigned long start_addr, end_addr, dummy;
297         struct dcss_segment *seg;
298         int rc, diag_cc;
299
300         start_addr = end_addr = 0;
301         seg = kmalloc(sizeof(*seg), GFP_KERNEL | GFP_DMA);
302         if (seg == NULL) {
303                 rc = -ENOMEM;
304                 goto out;
305         }
306         dcss_mkname (name, seg->dcss_name);
307         rc = query_segment_type (seg);
308         if (rc < 0)
309                 goto out_free;
310
311         if (segment_overlaps_others(seg)) {
312                 rc = -EBUSY;
313                 goto out_free;
314         }
315
316         rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
317
318         if (rc)
319                 goto out_free;
320
321         seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL);
322         if (seg->res == NULL) {
323                 rc = -ENOMEM;
324                 goto out_shared;
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         rc = seg->vm_segtype;
335         if (rc == SEG_TYPE_SC ||
336             ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared))
337                 seg->res->flags |= IORESOURCE_READONLY;
338         if (request_resource(&iomem_resource, seg->res)) {
339                 rc = -EBUSY;
340                 kfree(seg->res);
341                 goto out_shared;
342         }
343
344         if (do_nonshared)
345                 diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
346                                 &start_addr, &end_addr);
347         else
348                 diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
349                                 &start_addr, &end_addr);
350         if (diag_cc < 0) {
351                 dcss_diag(&purgeseg_scode, seg->dcss_name,
352                                 &dummy, &dummy);
353                 rc = diag_cc;
354                 goto out_resource;
355         }
356         if (diag_cc > 1) {
357                 pr_warn("Loading DCSS %s failed with rc=%ld\n", name, end_addr);
358                 rc = dcss_diag_translate_rc(end_addr);
359                 dcss_diag(&purgeseg_scode, seg->dcss_name,
360                                 &dummy, &dummy);
361                 goto out_resource;
362         }
363         seg->start_addr = start_addr;
364         seg->end = end_addr;
365         seg->do_nonshared = do_nonshared;
366         refcount_set(&seg->ref_count, 1);
367         list_add(&seg->list, &dcss_list);
368         *addr = seg->start_addr;
369         *end  = seg->end;
370         if (do_nonshared)
371                 pr_info("DCSS %s of range %px to %px and type %s loaded as "
372                         "exclusive-writable\n", name, (void*) seg->start_addr,
373                         (void*) seg->end, segtype_string[seg->vm_segtype]);
374         else {
375                 pr_info("DCSS %s of range %px to %px and type %s loaded in "
376                         "shared access mode\n", name, (void*) seg->start_addr,
377                         (void*) seg->end, segtype_string[seg->vm_segtype]);
378         }
379         goto out;
380  out_resource:
381         release_resource(seg->res);
382         kfree(seg->res);
383  out_shared:
384         vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
385  out_free:
386         kfree(seg);
387  out:
388         return rc;
389 }
390
391 /*
392  * this function loads a DCSS segment
393  * name         : name of the DCSS
394  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
395  *                1 indicates that the dcss should be exclusive for this linux image
396  * addr         : will be filled with start address of the segment
397  * end          : will be filled with end address of the segment
398  * return values:
399  * -ENOSYS  : we are not running on VM
400  * -EIO     : could not perform query or load diagnose
401  * -ENOENT  : no such segment
402  * -EOPNOTSUPP: multi-part segment cannot be used with linux
403  * -ENOSPC  : segment cannot be used (overlaps with storage)
404  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
405  * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
406  * -EPERM   : segment is currently loaded with incompatible permissions
407  * -ENOMEM  : out of memory
408  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
409  */
410 int
411 segment_load (char *name, int do_nonshared, unsigned long *addr,
412                 unsigned long *end)
413 {
414         struct dcss_segment *seg;
415         int rc;
416
417         if (!MACHINE_IS_VM)
418                 return -ENOSYS;
419
420         mutex_lock(&dcss_lock);
421         seg = segment_by_name (name);
422         if (seg == NULL)
423                 rc = __segment_load (name, do_nonshared, addr, end);
424         else {
425                 if (do_nonshared == seg->do_nonshared) {
426                         refcount_inc(&seg->ref_count);
427                         *addr = seg->start_addr;
428                         *end  = seg->end;
429                         rc    = seg->vm_segtype;
430                 } else {
431                         *addr = *end = 0;
432                         rc    = -EPERM;
433                 }
434         }
435         mutex_unlock(&dcss_lock);
436         return rc;
437 }
438
439 /*
440  * this function modifies the shared state of a DCSS segment. note that
441  * name         : name of the DCSS
442  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
443  *                1 indicates that the dcss should be exclusive for this linux image
444  * return values:
445  * -EIO     : could not perform load diagnose (segment gone!)
446  * -ENOENT  : no such segment (segment gone!)
447  * -EAGAIN  : segment is in use by other exploiters, try later
448  * -EINVAL  : no segment with the given name is currently loaded - name invalid
449  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
450  * 0        : operation succeeded
451  */
452 int
453 segment_modify_shared (char *name, int do_nonshared)
454 {
455         struct dcss_segment *seg;
456         unsigned long start_addr, end_addr, dummy;
457         int rc, diag_cc;
458
459         start_addr = end_addr = 0;
460         mutex_lock(&dcss_lock);
461         seg = segment_by_name (name);
462         if (seg == NULL) {
463                 rc = -EINVAL;
464                 goto out_unlock;
465         }
466         if (do_nonshared == seg->do_nonshared) {
467                 pr_info("DCSS %s is already in the requested access "
468                         "mode\n", name);
469                 rc = 0;
470                 goto out_unlock;
471         }
472         if (refcount_read(&seg->ref_count) != 1) {
473                 pr_warn("DCSS %s is in use and cannot be reloaded\n", name);
474                 rc = -EAGAIN;
475                 goto out_unlock;
476         }
477         release_resource(seg->res);
478         if (do_nonshared)
479                 seg->res->flags &= ~IORESOURCE_READONLY;
480         else
481                 if (seg->vm_segtype == SEG_TYPE_SR ||
482                     seg->vm_segtype == SEG_TYPE_ER)
483                         seg->res->flags |= IORESOURCE_READONLY;
484
485         if (request_resource(&iomem_resource, seg->res)) {
486                 pr_warn("DCSS %s overlaps with used memory resources and cannot be reloaded\n",
487                         name);
488                 rc = -EBUSY;
489                 kfree(seg->res);
490                 goto out_del_mem;
491         }
492
493         dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
494         if (do_nonshared)
495                 diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
496                                 &start_addr, &end_addr);
497         else
498                 diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
499                                 &start_addr, &end_addr);
500         if (diag_cc < 0) {
501                 rc = diag_cc;
502                 goto out_del_res;
503         }
504         if (diag_cc > 1) {
505                 pr_warn("Reloading DCSS %s failed with rc=%ld\n",
506                         name, end_addr);
507                 rc = dcss_diag_translate_rc(end_addr);
508                 goto out_del_res;
509         }
510         seg->start_addr = start_addr;
511         seg->end = end_addr;
512         seg->do_nonshared = do_nonshared;
513         rc = 0;
514         goto out_unlock;
515  out_del_res:
516         release_resource(seg->res);
517         kfree(seg->res);
518  out_del_mem:
519         vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
520         list_del(&seg->list);
521         dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
522         kfree(seg);
523  out_unlock:
524         mutex_unlock(&dcss_lock);
525         return rc;
526 }
527
528 /*
529  * Decrease the use count of a DCSS segment and remove
530  * it from the address space if nobody is using it
531  * any longer.
532  */
533 void
534 segment_unload(char *name)
535 {
536         unsigned long dummy;
537         struct dcss_segment *seg;
538
539         if (!MACHINE_IS_VM)
540                 return;
541
542         mutex_lock(&dcss_lock);
543         seg = segment_by_name (name);
544         if (seg == NULL) {
545                 pr_err("Unloading unknown DCSS %s failed\n", name);
546                 goto out_unlock;
547         }
548         if (!refcount_dec_and_test(&seg->ref_count))
549                 goto out_unlock;
550         release_resource(seg->res);
551         kfree(seg->res);
552         vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
553         list_del(&seg->list);
554         dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
555         kfree(seg);
556 out_unlock:
557         mutex_unlock(&dcss_lock);
558 }
559
560 /*
561  * save segment content permanently
562  */
563 void
564 segment_save(char *name)
565 {
566         struct dcss_segment *seg;
567         char cmd1[160];
568         char cmd2[80];
569         int i, response;
570
571         if (!MACHINE_IS_VM)
572                 return;
573
574         mutex_lock(&dcss_lock);
575         seg = segment_by_name (name);
576
577         if (seg == NULL) {
578                 pr_err("Saving unknown DCSS %s failed\n", name);
579                 goto out;
580         }
581
582         sprintf(cmd1, "DEFSEG %s", name);
583         for (i=0; i<seg->segcnt; i++) {
584                 sprintf(cmd1+strlen(cmd1), " %lX-%lX %s",
585                         seg->range[i].start >> PAGE_SHIFT,
586                         seg->range[i].end >> PAGE_SHIFT,
587                         segtype_string[seg->range[i].start & 0xff]);
588         }
589         sprintf(cmd2, "SAVESEG %s", name);
590         response = 0;
591         cpcmd(cmd1, NULL, 0, &response);
592         if (response) {
593                 pr_err("Saving a DCSS failed with DEFSEG response code "
594                        "%i\n", response);
595                 goto out;
596         }
597         cpcmd(cmd2, NULL, 0, &response);
598         if (response) {
599                 pr_err("Saving a DCSS failed with SAVESEG response code "
600                        "%i\n", response);
601                 goto out;
602         }
603 out:
604         mutex_unlock(&dcss_lock);
605 }
606
607 /*
608  * print appropriate error message for segment_load()/segment_type()
609  * return code
610  */
611 void segment_warning(int rc, char *seg_name)
612 {
613         switch (rc) {
614         case -ENOENT:
615                 pr_err("DCSS %s cannot be loaded or queried\n", seg_name);
616                 break;
617         case -ENOSYS:
618                 pr_err("DCSS %s cannot be loaded or queried without "
619                        "z/VM\n", seg_name);
620                 break;
621         case -EIO:
622                 pr_err("Loading or querying DCSS %s resulted in a "
623                        "hardware error\n", seg_name);
624                 break;
625         case -EOPNOTSUPP:
626                 pr_err("DCSS %s has multiple page ranges and cannot be "
627                        "loaded or queried\n", seg_name);
628                 break;
629         case -ENOSPC:
630                 pr_err("DCSS %s overlaps with used storage and cannot "
631                        "be loaded\n", seg_name);
632                 break;
633         case -EBUSY:
634                 pr_err("%s needs used memory resources and cannot be "
635                        "loaded or queried\n", seg_name);
636                 break;
637         case -EPERM:
638                 pr_err("DCSS %s is already loaded in a different access "
639                        "mode\n", seg_name);
640                 break;
641         case -ENOMEM:
642                 pr_err("There is not enough memory to load or query "
643                        "DCSS %s\n", seg_name);
644                 break;
645         case -ERANGE:
646                 pr_err("DCSS %s exceeds the kernel mapping range (%lu) "
647                        "and cannot be loaded\n", seg_name, VMEM_MAX_PHYS);
648                 break;
649         default:
650                 break;
651         }
652 }
653
654 EXPORT_SYMBOL(segment_load);
655 EXPORT_SYMBOL(segment_unload);
656 EXPORT_SYMBOL(segment_save);
657 EXPORT_SYMBOL(segment_type);
658 EXPORT_SYMBOL(segment_modify_shared);
659 EXPORT_SYMBOL(segment_warning);