blob: 775bf19e742be2051a74b74fe0c3b06367d8769b [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * File...........: arch/s390/mm/extmem.c
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 * (C) IBM Corporation 2002-2004
8 */
9
10#include <linux/kernel.h>
11#include <linux/string.h>
12#include <linux/spinlock.h>
13#include <linux/list.h>
14#include <linux/slab.h>
15#include <linux/module.h>
16#include <linux/bootmem.h>
Heiko Carstens36a2bd42006-12-04 15:40:38 +010017#include <linux/ctype.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070018#include <asm/page.h>
Heiko Carstensf4eb07c2006-12-08 15:56:07 +010019#include <asm/pgtable.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070020#include <asm/ebcdic.h>
21#include <asm/errno.h>
22#include <asm/extmem.h>
23#include <asm/cpcmd.h>
Heiko Carstens36a2bd42006-12-04 15:40:38 +010024#include <asm/setup.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070025
26#define DCSS_DEBUG /* Debug messages on/off */
27
28#define DCSS_NAME "extmem"
29#ifdef DCSS_DEBUG
30#define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSS_NAME " debug:" x)
31#else
32#define PRINT_DEBUG(x...) do {} while (0)
33#endif
34#define PRINT_INFO(x...) printk(KERN_INFO DCSS_NAME " info:" x)
35#define PRINT_WARN(x...) printk(KERN_WARNING DCSS_NAME " warning:" x)
36#define PRINT_ERR(x...) printk(KERN_ERR DCSS_NAME " error:" x)
37
38
39#define DCSS_LOADSHR 0x00
40#define DCSS_LOADNSR 0x04
41#define DCSS_PURGESEG 0x08
42#define DCSS_FINDSEG 0x0c
43#define DCSS_LOADNOLY 0x10
44#define DCSS_SEGEXT 0x18
45#define DCSS_FINDSEGA 0x0c
46
47struct qrange {
48 unsigned int start; // 3byte start address, 1 byte type
49 unsigned int end; // 3byte end address, 1 byte reserved
50};
51
52struct qout64 {
53 int segstart;
54 int segend;
55 int segcnt;
56 int segrcnt;
57 struct qrange range[6];
58};
59
60struct qin64 {
61 char qopcode;
62 char rsrv1[3];
63 char qrcode;
64 char rsrv2[3];
65 char qname[8];
66 unsigned int qoutptr;
67 short int qoutlen;
68};
69
70struct dcss_segment {
71 struct list_head list;
72 char dcss_name[8];
73 unsigned long start_addr;
74 unsigned long end;
75 atomic_t ref_count;
76 int do_nonshared;
77 unsigned int vm_segtype;
78 struct qrange range[6];
79 int segcnt;
80};
81
Heiko Carstens09252e72006-12-04 15:40:51 +010082static DEFINE_MUTEX(dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070083static struct list_head dcss_list = LIST_HEAD_INIT(dcss_list);
84static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
85 "EW/EN-MIXED" };
86
Linus Torvalds1da177e2005-04-16 15:20:36 -070087/*
88 * Create the 8 bytes, ebcdic VM segment name from
89 * an ascii name.
90 */
91static void inline
92dcss_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 */
111static struct dcss_segment *
112segment_by_name (char *name)
113{
114 char dcss_name[9];
115 struct list_head *l;
116 struct dcss_segment *tmp, *retval = NULL;
117
Heiko Carstens09252e72006-12-04 15:40:51 +0100118 BUG_ON(!mutex_is_locked(&dcss_lock));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700119 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 */
134static inline int
135dcss_diag (__u8 func, void *parameter,
136 unsigned long *ret1, unsigned long *ret2)
137{
138 unsigned long rx, ry;
139 int rc;
140
141 rx = (unsigned long) parameter;
142 ry = (unsigned long) func;
Martin Schwidefsky94c12cc2006-09-28 16:56:43 +0200143 asm volatile(
Martin Schwidefsky347a8dc2006-01-06 00:19:28 -0800144#ifdef CONFIG_64BIT
Martin Schwidefsky94c12cc2006-09-28 16:56:43 +0200145 " sam31\n"
146 " diag %0,%1,0x64\n"
147 " sam64\n"
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148#else
Martin Schwidefsky94c12cc2006-09-28 16:56:43 +0200149 " diag %0,%1,0x64\n"
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150#endif
Martin Schwidefsky94c12cc2006-09-28 16:56:43 +0200151 " ipm %2\n"
152 " srl %2,28\n"
153 : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700154 *ret1 = rx;
155 *ret2 = ry;
156 return rc;
157}
158
159static inline int
160dcss_diag_translate_rc (int vm_rc) {
161 if (vm_rc == 44)
162 return -ENOENT;
163 return -EIO;
164}
165
166
167/* do a diag to get info about a segment.
168 * fills start_address, end and vm_segtype fields
169 */
170static int
171query_segment_type (struct dcss_segment *seg)
172{
173 struct qin64 *qin = kmalloc (sizeof(struct qin64), GFP_DMA);
174 struct qout64 *qout = kmalloc (sizeof(struct qout64), GFP_DMA);
175
176 int diag_cc, rc, i;
177 unsigned long dummy, vmrc;
178
179 if ((qin == NULL) || (qout == NULL)) {
180 rc = -ENOMEM;
181 goto out_free;
182 }
183
184 /* initialize diag input parameters */
185 qin->qopcode = DCSS_FINDSEGA;
186 qin->qoutptr = (unsigned long) qout;
187 qin->qoutlen = sizeof(struct qout64);
188 memcpy (qin->qname, seg->dcss_name, 8);
189
190 diag_cc = dcss_diag (DCSS_SEGEXT, qin, &dummy, &vmrc);
191
192 if (diag_cc > 1) {
Gerald Schaefer9b5dec12006-04-27 18:40:22 -0700193 PRINT_WARN ("segment_type: diag returned error %ld\n", vmrc);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700194 rc = dcss_diag_translate_rc (vmrc);
195 goto out_free;
196 }
197
198 if (qout->segcnt > 6) {
199 rc = -ENOTSUPP;
200 goto out_free;
201 }
202
203 if (qout->segcnt == 1) {
204 seg->vm_segtype = qout->range[0].start & 0xff;
205 } else {
206 /* multi-part segment. only one type supported here:
207 - all parts are contiguous
208 - all parts are either EW or EN type
209 - maximum 6 parts allowed */
210 unsigned long start = qout->segstart >> PAGE_SHIFT;
211 for (i=0; i<qout->segcnt; i++) {
212 if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) &&
213 ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) {
214 rc = -ENOTSUPP;
215 goto out_free;
216 }
217 if (start != qout->range[i].start >> PAGE_SHIFT) {
218 rc = -ENOTSUPP;
219 goto out_free;
220 }
221 start = (qout->range[i].end >> PAGE_SHIFT) + 1;
222 }
223 seg->vm_segtype = SEG_TYPE_EWEN;
224 }
225
226 /* analyze diag output and update seg */
227 seg->start_addr = qout->segstart;
228 seg->end = qout->segend;
229
230 memcpy (seg->range, qout->range, 6*sizeof(struct qrange));
231 seg->segcnt = qout->segcnt;
232
233 rc = 0;
234
235 out_free:
Jesper Juhlb2325fe2005-11-07 01:01:35 -0800236 kfree(qin);
237 kfree(qout);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700238 return rc;
239}
240
241/*
Linus Torvalds1da177e2005-04-16 15:20:36 -0700242 * get info about a segment
243 * possible return values:
244 * -ENOSYS : we are not running on VM
245 * -EIO : could not perform query diagnose
246 * -ENOENT : no such segment
247 * -ENOTSUPP: multi-part segment cannot be used with linux
248 * -ENOSPC : segment cannot be used (overlaps with storage)
249 * -ENOMEM : out of memory
250 * 0 .. 6 : type of segment as defined in include/asm-s390/extmem.h
251 */
252int
253segment_type (char* name)
254{
255 int rc;
256 struct dcss_segment seg;
257
258 if (!MACHINE_IS_VM)
259 return -ENOSYS;
260
261 dcss_mkname(name, seg.dcss_name);
262 rc = query_segment_type (&seg);
263 if (rc < 0)
264 return rc;
265 return seg.vm_segtype;
266}
267
268/*
269 * real segment loading function, called from segment_load
270 */
271static int
272__segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
273{
274 struct dcss_segment *seg = kmalloc(sizeof(struct dcss_segment),
275 GFP_DMA);
276 int dcss_command, rc, diag_cc;
277
278 if (seg == NULL) {
279 rc = -ENOMEM;
280 goto out;
281 }
282 dcss_mkname (name, seg->dcss_name);
283 rc = query_segment_type (seg);
284 if (rc < 0)
285 goto out_free;
Heiko Carstensf4eb07c2006-12-08 15:56:07 +0100286
287 rc = add_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
288
289 switch (rc) {
290 case 0:
291 break;
292 case -ENOSPC:
293 PRINT_WARN("segment_load: not loading segment %s - overlaps "
294 "storage/segment\n", name);
295 goto out_free;
296 case -ERANGE:
297 PRINT_WARN("segment_load: not loading segment %s - exceeds "
298 "kernel mapping range\n", name);
299 goto out_free;
300 default:
301 PRINT_WARN("segment_load: not loading segment %s (rc: %d)\n",
302 name, rc);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700303 goto out_free;
304 }
Heiko Carstensf4eb07c2006-12-08 15:56:07 +0100305
Linus Torvalds1da177e2005-04-16 15:20:36 -0700306 if (do_nonshared)
307 dcss_command = DCSS_LOADNSR;
308 else
309 dcss_command = DCSS_LOADNOLY;
310
311 diag_cc = dcss_diag(dcss_command, seg->dcss_name,
312 &seg->start_addr, &seg->end);
313 if (diag_cc > 1) {
314 PRINT_WARN ("segment_load: could not load segment %s - "
315 "diag returned error (%ld)\n",name,seg->end);
316 rc = dcss_diag_translate_rc (seg->end);
317 dcss_diag(DCSS_PURGESEG, seg->dcss_name,
318 &seg->start_addr, &seg->end);
Heiko Carstensf4eb07c2006-12-08 15:56:07 +0100319 goto out_shared;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700320 }
321 seg->do_nonshared = do_nonshared;
322 atomic_set(&seg->ref_count, 1);
323 list_add(&seg->list, &dcss_list);
324 rc = seg->vm_segtype;
325 *addr = seg->start_addr;
326 *end = seg->end;
327 if (do_nonshared)
328 PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
329 "type %s in non-shared mode\n", name,
330 (void*)seg->start_addr, (void*)seg->end,
331 segtype_string[seg->vm_segtype]);
332 else
333 PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
334 "type %s in shared mode\n", name,
335 (void*)seg->start_addr, (void*)seg->end,
336 segtype_string[seg->vm_segtype]);
337 goto out;
Heiko Carstensf4eb07c2006-12-08 15:56:07 +0100338 out_shared:
339 remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340 out_free:
Jesper Juhlb2325fe2005-11-07 01:01:35 -0800341 kfree(seg);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700342 out:
343 return rc;
344}
345
346/*
347 * this function loads a DCSS segment
348 * name : name of the DCSS
349 * do_nonshared : 0 indicates that the dcss should be shared with other linux images
350 * 1 indicates that the dcss should be exclusive for this linux image
351 * addr : will be filled with start address of the segment
352 * end : will be filled with end address of the segment
353 * return values:
354 * -ENOSYS : we are not running on VM
355 * -EIO : could not perform query or load diagnose
356 * -ENOENT : no such segment
357 * -ENOTSUPP: multi-part segment cannot be used with linux
358 * -ENOSPC : segment cannot be used (overlaps with storage)
359 * -EBUSY : segment can temporarily not be used (overlaps with dcss)
360 * -ERANGE : segment cannot be used (exceeds kernel mapping range)
361 * -EPERM : segment is currently loaded with incompatible permissions
362 * -ENOMEM : out of memory
363 * 0 .. 6 : type of segment as defined in include/asm-s390/extmem.h
364 */
365int
366segment_load (char *name, int do_nonshared, unsigned long *addr,
367 unsigned long *end)
368{
369 struct dcss_segment *seg;
370 int rc;
371
372 if (!MACHINE_IS_VM)
373 return -ENOSYS;
374
Heiko Carstens09252e72006-12-04 15:40:51 +0100375 mutex_lock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700376 seg = segment_by_name (name);
377 if (seg == NULL)
378 rc = __segment_load (name, do_nonshared, addr, end);
379 else {
380 if (do_nonshared == seg->do_nonshared) {
381 atomic_inc(&seg->ref_count);
382 *addr = seg->start_addr;
383 *end = seg->end;
384 rc = seg->vm_segtype;
385 } else {
386 *addr = *end = 0;
387 rc = -EPERM;
388 }
389 }
Heiko Carstens09252e72006-12-04 15:40:51 +0100390 mutex_unlock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700391 return rc;
392}
393
394/*
395 * this function modifies the shared state of a DCSS segment. note that
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 * return values:
400 * -EIO : could not perform load diagnose (segment gone!)
401 * -ENOENT : no such segment (segment gone!)
402 * -EAGAIN : segment is in use by other exploiters, try later
403 * -EINVAL : no segment with the given name is currently loaded - name invalid
404 * 0 : operation succeeded
405 */
406int
407segment_modify_shared (char *name, int do_nonshared)
408{
409 struct dcss_segment *seg;
410 unsigned long dummy;
411 int dcss_command, rc, diag_cc;
412
Heiko Carstens09252e72006-12-04 15:40:51 +0100413 mutex_lock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700414 seg = segment_by_name (name);
415 if (seg == NULL) {
416 rc = -EINVAL;
417 goto out_unlock;
418 }
419 if (do_nonshared == seg->do_nonshared) {
420 PRINT_INFO ("segment_modify_shared: not reloading segment %s"
421 " - already in requested mode\n",name);
422 rc = 0;
423 goto out_unlock;
424 }
425 if (atomic_read (&seg->ref_count) != 1) {
426 PRINT_WARN ("segment_modify_shared: not reloading segment %s - "
427 "segment is in use by other driver(s)\n",name);
428 rc = -EAGAIN;
429 goto out_unlock;
430 }
431 dcss_diag(DCSS_PURGESEG, seg->dcss_name,
432 &dummy, &dummy);
433 if (do_nonshared)
434 dcss_command = DCSS_LOADNSR;
435 else
436 dcss_command = DCSS_LOADNOLY;
437 diag_cc = dcss_diag(dcss_command, seg->dcss_name,
438 &seg->start_addr, &seg->end);
439 if (diag_cc > 1) {
440 PRINT_WARN ("segment_modify_shared: could not reload segment %s"
441 " - diag returned error (%ld)\n",name,seg->end);
442 rc = dcss_diag_translate_rc (seg->end);
443 goto out_del;
444 }
445 seg->do_nonshared = do_nonshared;
446 rc = 0;
447 goto out_unlock;
448 out_del:
449 list_del(&seg->list);
450 dcss_diag(DCSS_PURGESEG, seg->dcss_name,
451 &dummy, &dummy);
Jesper Juhlb2325fe2005-11-07 01:01:35 -0800452 kfree(seg);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700453 out_unlock:
Heiko Carstens09252e72006-12-04 15:40:51 +0100454 mutex_unlock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700455 return rc;
456}
457
458/*
459 * Decrease the use count of a DCSS segment and remove
460 * it from the address space if nobody is using it
461 * any longer.
462 */
463void
464segment_unload(char *name)
465{
466 unsigned long dummy;
467 struct dcss_segment *seg;
468
469 if (!MACHINE_IS_VM)
470 return;
471
Heiko Carstens09252e72006-12-04 15:40:51 +0100472 mutex_lock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700473 seg = segment_by_name (name);
474 if (seg == NULL) {
475 PRINT_ERR ("could not find segment %s in segment_unload, "
476 "please report to linux390@de.ibm.com\n",name);
477 goto out_unlock;
478 }
Heiko Carstensf4eb07c2006-12-08 15:56:07 +0100479 if (atomic_dec_return(&seg->ref_count) != 0)
480 goto out_unlock;
481 remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
482 list_del(&seg->list);
483 dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
484 kfree(seg);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700485out_unlock:
Heiko Carstens09252e72006-12-04 15:40:51 +0100486 mutex_unlock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700487}
488
489/*
490 * save segment content permanently
491 */
492void
493segment_save(char *name)
494{
495 struct dcss_segment *seg;
496 int startpfn = 0;
497 int endpfn = 0;
498 char cmd1[160];
499 char cmd2[80];
Gerald Schaefer9b5dec12006-04-27 18:40:22 -0700500 int i, response;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700501
502 if (!MACHINE_IS_VM)
503 return;
504
Heiko Carstens09252e72006-12-04 15:40:51 +0100505 mutex_lock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700506 seg = segment_by_name (name);
507
508 if (seg == NULL) {
Heiko Carstens6b4044b2006-12-04 15:40:20 +0100509 PRINT_ERR("could not find segment %s in segment_save, please "
510 "report to linux390@de.ibm.com\n", name);
511 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700512 }
513
514 startpfn = seg->start_addr >> PAGE_SHIFT;
515 endpfn = (seg->end) >> PAGE_SHIFT;
516 sprintf(cmd1, "DEFSEG %s", name);
517 for (i=0; i<seg->segcnt; i++) {
518 sprintf(cmd1+strlen(cmd1), " %X-%X %s",
519 seg->range[i].start >> PAGE_SHIFT,
520 seg->range[i].end >> PAGE_SHIFT,
521 segtype_string[seg->range[i].start & 0xff]);
522 }
523 sprintf(cmd2, "SAVESEG %s", name);
Gerald Schaefer9b5dec12006-04-27 18:40:22 -0700524 response = 0;
525 cpcmd(cmd1, NULL, 0, &response);
526 if (response) {
527 PRINT_ERR("segment_save: DEFSEG failed with response code %i\n",
528 response);
529 goto out;
530 }
531 cpcmd(cmd2, NULL, 0, &response);
532 if (response) {
533 PRINT_ERR("segment_save: SAVESEG failed with response code %i\n",
534 response);
535 goto out;
536 }
537out:
Heiko Carstens09252e72006-12-04 15:40:51 +0100538 mutex_unlock(&dcss_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700539}
540
541EXPORT_SYMBOL(segment_load);
542EXPORT_SYMBOL(segment_unload);
543EXPORT_SYMBOL(segment_save);
544EXPORT_SYMBOL(segment_type);
545EXPORT_SYMBOL(segment_modify_shared);