Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/arch/s390/mm/extmem.c
26424 views
1
// SPDX-License-Identifier: GPL-2.0
2
/*
3
* Author(s)......: Carsten Otte <[email protected]>
4
* Rob M van der Heij <[email protected]>
5
* Steven Shultz <[email protected]>
6
* Bugreports.to..: <[email protected]>
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
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 *
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
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
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
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
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
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
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
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
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
static void __dcss_diag_purge_on_cpu_0(void *data)
534
{
535
struct dcss_segment *seg = (struct dcss_segment *)data;
536
unsigned long dummy;
537
538
dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
539
}
540
541
/*
542
* Decrease the use count of a DCSS segment and remove
543
* it from the address space if nobody is using it
544
* any longer.
545
*/
546
void
547
segment_unload(char *name)
548
{
549
struct dcss_segment *seg;
550
551
if (!machine_is_vm())
552
return;
553
554
mutex_lock(&dcss_lock);
555
seg = segment_by_name (name);
556
if (seg == NULL) {
557
pr_err("Unloading unknown DCSS %s failed\n", name);
558
goto out_unlock;
559
}
560
if (!refcount_dec_and_test(&seg->ref_count))
561
goto out_unlock;
562
release_resource(seg->res);
563
kfree(seg->res);
564
vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
565
list_del(&seg->list);
566
/*
567
* Workaround for z/VM issue, where calling the DCSS unload diag on
568
* a non-IPL CPU would cause bogus sclp maximum memory detection on
569
* next IPL.
570
* IPL CPU 0 cannot be set offline, so the dcss_diag() call can
571
* directly be scheduled to that CPU.
572
*/
573
smp_call_function_single(0, __dcss_diag_purge_on_cpu_0, seg, 1);
574
kfree(seg);
575
out_unlock:
576
mutex_unlock(&dcss_lock);
577
}
578
579
/*
580
* save segment content permanently
581
*/
582
void
583
segment_save(char *name)
584
{
585
struct dcss_segment *seg;
586
char cmd1[160];
587
char cmd2[80];
588
int i, response;
589
590
if (!machine_is_vm())
591
return;
592
593
mutex_lock(&dcss_lock);
594
seg = segment_by_name (name);
595
596
if (seg == NULL) {
597
pr_err("Saving unknown DCSS %s failed\n", name);
598
goto out;
599
}
600
601
sprintf(cmd1, "DEFSEG %s", name);
602
for (i=0; i<seg->segcnt; i++) {
603
sprintf(cmd1+strlen(cmd1), " %lX-%lX %s",
604
seg->range[i].start >> PAGE_SHIFT,
605
seg->range[i].end >> PAGE_SHIFT,
606
segtype_string[seg->range[i].start & 0xff]);
607
}
608
sprintf(cmd2, "SAVESEG %s", name);
609
response = 0;
610
cpcmd(cmd1, NULL, 0, &response);
611
if (response) {
612
pr_err("Saving a DCSS failed with DEFSEG response code "
613
"%i\n", response);
614
goto out;
615
}
616
cpcmd(cmd2, NULL, 0, &response);
617
if (response) {
618
pr_err("Saving a DCSS failed with SAVESEG response code "
619
"%i\n", response);
620
goto out;
621
}
622
out:
623
mutex_unlock(&dcss_lock);
624
}
625
626
/*
627
* print appropriate error message for segment_load()/segment_type()
628
* return code
629
*/
630
void segment_warning(int rc, char *seg_name)
631
{
632
switch (rc) {
633
case -ENOENT:
634
pr_err("DCSS %s cannot be loaded or queried\n", seg_name);
635
break;
636
case -ENOSYS:
637
pr_err("DCSS %s cannot be loaded or queried without "
638
"z/VM\n", seg_name);
639
break;
640
case -EIO:
641
pr_err("Loading or querying DCSS %s resulted in a "
642
"hardware error\n", seg_name);
643
break;
644
case -EOPNOTSUPP:
645
pr_err("DCSS %s has multiple page ranges and cannot be "
646
"loaded or queried\n", seg_name);
647
break;
648
case -EBUSY:
649
pr_err("%s needs used memory resources and cannot be "
650
"loaded or queried\n", seg_name);
651
break;
652
case -EPERM:
653
pr_err("DCSS %s is already loaded in a different access "
654
"mode\n", seg_name);
655
break;
656
case -ENOMEM:
657
pr_err("There is not enough memory to load or query "
658
"DCSS %s\n", seg_name);
659
break;
660
case -ERANGE: {
661
struct range mhp_range = arch_get_mappable_range();
662
663
pr_err("DCSS %s exceeds the kernel mapping range (%llu) "
664
"and cannot be loaded\n", seg_name, mhp_range.end + 1);
665
break;
666
}
667
default:
668
break;
669
}
670
}
671
672
EXPORT_SYMBOL(segment_load);
673
EXPORT_SYMBOL(segment_unload);
674
EXPORT_SYMBOL(segment_save);
675
EXPORT_SYMBOL(segment_type);
676
EXPORT_SYMBOL(segment_modify_shared);
677
EXPORT_SYMBOL(segment_warning);
678
679