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(p: tmp->dcss_name, q: dcss_name, size: 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 = virt_to_phys(address: 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(size: sizeof(*qin), GFP_KERNEL | GFP_DMA); |
173 | qout = kmalloc(size: 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 = virt_to_phys(address: qout); |
182 | qin->qoutlen = sizeof(struct qout64); |
183 | memcpy (qin->qname, seg->dcss_name, 8); |
184 | |
185 | diag_cc = dcss_diag(func: &segext_scode, parameter: qin, ret1: &dummy, ret2: &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 (vm_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(objp: qin); |
236 | kfree(objp: 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, dcss_name: seg.dcss_name); |
260 | rc = query_segment_type (seg: &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(size: sizeof(*seg), GFP_KERNEL | GFP_DMA); |
304 | if (seg == NULL) { |
305 | rc = -ENOMEM; |
306 | goto out; |
307 | } |
308 | dcss_mkname (name, dcss_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(size: 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(p: seg->res_name, q: " (DCSS)" , avail: 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(root: &iomem_resource, new: 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(func: &loadnsr_scode, parameter: seg->dcss_name, |
348 | ret1: &start_addr, ret2: &end_addr); |
349 | else |
350 | diag_cc = dcss_diag(func: &loadshr_scode, parameter: seg->dcss_name, |
351 | ret1: &start_addr, ret2: &end_addr); |
352 | if (diag_cc < 0) { |
353 | dcss_diag(func: &purgeseg_scode, parameter: seg->dcss_name, |
354 | ret1: &dummy, ret2: &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(vm_rc: end_addr); |
361 | dcss_diag(func: &purgeseg_scode, parameter: seg->dcss_name, |
362 | ret1: &dummy, ret2: &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(r: &seg->ref_count, n: 1); |
369 | list_add(new: &seg->list, head: &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(new: seg->res); |
386 | out_free_resource: |
387 | kfree(objp: seg->res); |
388 | out_free: |
389 | kfree(objp: 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(r: &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(lock: &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(r: &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(new: 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(root: &iomem_resource, new: seg->res)) { |
488 | pr_warn("DCSS %s overlaps with used memory resources and cannot be reloaded\n" , |
489 | name); |
490 | rc = -EBUSY; |
491 | kfree(objp: seg->res); |
492 | goto out_del_mem; |
493 | } |
494 | |
495 | dcss_diag(func: &purgeseg_scode, parameter: seg->dcss_name, ret1: &dummy, ret2: &dummy); |
496 | if (do_nonshared) |
497 | diag_cc = dcss_diag(func: &loadnsr_scode, parameter: seg->dcss_name, |
498 | ret1: &start_addr, ret2: &end_addr); |
499 | else |
500 | diag_cc = dcss_diag(func: &loadshr_scode, parameter: seg->dcss_name, |
501 | ret1: &start_addr, ret2: &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(vm_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(new: seg->res); |
519 | kfree(objp: seg->res); |
520 | out_del_mem: |
521 | vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1); |
522 | list_del(entry: &seg->list); |
523 | dcss_diag(func: &purgeseg_scode, parameter: seg->dcss_name, ret1: &dummy, ret2: &dummy); |
524 | kfree(objp: seg); |
525 | out_unlock: |
526 | mutex_unlock(lock: &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(r: &seg->ref_count)) |
551 | goto out_unlock; |
552 | release_resource(new: seg->res); |
553 | kfree(objp: seg->res); |
554 | vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1); |
555 | list_del(entry: &seg->list); |
556 | dcss_diag(func: &purgeseg_scode, parameter: seg->dcss_name, ret1: &dummy, ret2: &dummy); |
557 | kfree(objp: seg); |
558 | out_unlock: |
559 | mutex_unlock(lock: &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(buf: cmd1, fmt: "DEFSEG %s" , name); |
585 | for (i=0; i<seg->segcnt; i++) { |
586 | sprintf(buf: cmd1+strlen(cmd1), fmt: " %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(buf: cmd2, fmt: "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(lock: &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); |
661 | |