xref: /openbmc/linux/arch/s390/mm/extmem.c (revision a1e58bbd)
1 /*
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>
17 #include <linux/ctype.h>
18 #include <linux/ioport.h>
19 #include <asm/page.h>
20 #include <asm/pgtable.h>
21 #include <asm/ebcdic.h>
22 #include <asm/errno.h>
23 #include <asm/extmem.h>
24 #include <asm/cpcmd.h>
25 #include <asm/setup.h>
26 
27 #define DCSS_DEBUG	/* Debug messages on/off */
28 
29 #define DCSS_NAME "extmem"
30 #ifdef DCSS_DEBUG
31 #define PRINT_DEBUG(x...)	printk(KERN_DEBUG DCSS_NAME " debug:" x)
32 #else
33 #define PRINT_DEBUG(x...)   do {} while (0)
34 #endif
35 #define PRINT_INFO(x...)	printk(KERN_INFO DCSS_NAME " info:" x)
36 #define PRINT_WARN(x...)	printk(KERN_WARNING DCSS_NAME " warning:" x)
37 #define PRINT_ERR(x...)		printk(KERN_ERR DCSS_NAME " error:" x)
38 
39 
40 #define DCSS_LOADSHR    0x00
41 #define DCSS_LOADNSR    0x04
42 #define DCSS_PURGESEG   0x08
43 #define DCSS_FINDSEG    0x0c
44 #define DCSS_LOADNOLY   0x10
45 #define DCSS_SEGEXT     0x18
46 #define DCSS_FINDSEGA   0x0c
47 
48 struct qrange {
49 	unsigned int  start; // 3byte start address, 1 byte type
50 	unsigned int  end;   // 3byte end address, 1 byte reserved
51 };
52 
53 struct qout64 {
54 	int segstart;
55 	int segend;
56 	int segcnt;
57 	int segrcnt;
58 	struct qrange range[6];
59 };
60 
61 struct qin64 {
62 	char qopcode;
63 	char rsrv1[3];
64 	char qrcode;
65 	char rsrv2[3];
66 	char qname[8];
67 	unsigned int qoutptr;
68 	short int qoutlen;
69 };
70 
71 struct dcss_segment {
72 	struct list_head list;
73 	char dcss_name[8];
74 	char res_name[15];
75 	unsigned long start_addr;
76 	unsigned long end;
77 	atomic_t ref_count;
78 	int do_nonshared;
79 	unsigned int vm_segtype;
80 	struct qrange range[6];
81 	int segcnt;
82 	struct resource *res;
83 };
84 
85 static DEFINE_MUTEX(dcss_lock);
86 static LIST_HEAD(dcss_list);
87 static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
88 					"EW/EN-MIXED" };
89 
90 /*
91  * Create the 8 bytes, ebcdic VM segment name from
92  * an ascii name.
93  */
94 static void
95 dcss_mkname(char *name, char *dcss_name)
96 {
97 	int i;
98 
99 	for (i = 0; i < 8; i++) {
100 		if (name[i] == '\0')
101 			break;
102 		dcss_name[i] = toupper(name[i]);
103 	};
104 	for (; i < 8; i++)
105 		dcss_name[i] = ' ';
106 	ASCEBC(dcss_name, 8);
107 }
108 
109 
110 /*
111  * search all segments in dcss_list, and return the one
112  * namend *name. If not found, return NULL.
113  */
114 static struct dcss_segment *
115 segment_by_name (char *name)
116 {
117 	char dcss_name[9];
118 	struct list_head *l;
119 	struct dcss_segment *tmp, *retval = NULL;
120 
121 	BUG_ON(!mutex_is_locked(&dcss_lock));
122 	dcss_mkname (name, dcss_name);
123 	list_for_each (l, &dcss_list) {
124 		tmp = list_entry (l, struct dcss_segment, list);
125 		if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) {
126 			retval = tmp;
127 			break;
128 		}
129 	}
130 	return retval;
131 }
132 
133 
134 /*
135  * Perform a function on a dcss segment.
136  */
137 static inline int
138 dcss_diag (__u8 func, void *parameter,
139            unsigned long *ret1, unsigned long *ret2)
140 {
141 	unsigned long rx, ry;
142 	int rc;
143 
144 	rx = (unsigned long) parameter;
145 	ry = (unsigned long) func;
146 	asm volatile(
147 #ifdef CONFIG_64BIT
148 		"	sam31\n"
149 		"	diag	%0,%1,0x64\n"
150 		"	sam64\n"
151 #else
152 		"	diag	%0,%1,0x64\n"
153 #endif
154 		"	ipm	%2\n"
155 		"	srl	%2,28\n"
156 		: "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
157 	*ret1 = rx;
158 	*ret2 = ry;
159 	return rc;
160 }
161 
162 static inline int
163 dcss_diag_translate_rc (int vm_rc) {
164 	if (vm_rc == 44)
165 		return -ENOENT;
166 	return -EIO;
167 }
168 
169 
170 /* do a diag to get info about a segment.
171  * fills start_address, end and vm_segtype fields
172  */
173 static int
174 query_segment_type (struct dcss_segment *seg)
175 {
176 	struct qin64  *qin = kmalloc (sizeof(struct qin64), GFP_DMA);
177 	struct qout64 *qout = kmalloc (sizeof(struct qout64), GFP_DMA);
178 
179 	int diag_cc, rc, i;
180 	unsigned long dummy, vmrc;
181 
182 	if ((qin == NULL) || (qout == NULL)) {
183 		rc = -ENOMEM;
184 		goto out_free;
185 	}
186 
187 	/* initialize diag input parameters */
188 	qin->qopcode = DCSS_FINDSEGA;
189 	qin->qoutptr = (unsigned long) qout;
190 	qin->qoutlen = sizeof(struct qout64);
191 	memcpy (qin->qname, seg->dcss_name, 8);
192 
193 	diag_cc = dcss_diag (DCSS_SEGEXT, qin, &dummy, &vmrc);
194 
195 	if (diag_cc > 1) {
196 		PRINT_WARN ("segment_type: diag returned error %ld\n", vmrc);
197 		rc = dcss_diag_translate_rc (vmrc);
198 		goto out_free;
199 	}
200 
201 	if (qout->segcnt > 6) {
202 		rc = -ENOTSUPP;
203 		goto out_free;
204 	}
205 
206 	if (qout->segcnt == 1) {
207 		seg->vm_segtype = qout->range[0].start & 0xff;
208 	} else {
209 		/* multi-part segment. only one type supported here:
210 		    - all parts are contiguous
211 		    - all parts are either EW or EN type
212 		    - maximum 6 parts allowed */
213 		unsigned long start = qout->segstart >> PAGE_SHIFT;
214 		for (i=0; i<qout->segcnt; i++) {
215 			if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) &&
216 			    ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) {
217 				rc = -ENOTSUPP;
218 				goto out_free;
219 			}
220 			if (start != qout->range[i].start >> PAGE_SHIFT) {
221 				rc = -ENOTSUPP;
222 				goto out_free;
223 			}
224 			start = (qout->range[i].end >> PAGE_SHIFT) + 1;
225 		}
226 		seg->vm_segtype = SEG_TYPE_EWEN;
227 	}
228 
229 	/* analyze diag output and update seg */
230 	seg->start_addr = qout->segstart;
231 	seg->end = qout->segend;
232 
233 	memcpy (seg->range, qout->range, 6*sizeof(struct qrange));
234 	seg->segcnt = qout->segcnt;
235 
236 	rc = 0;
237 
238  out_free:
239 	kfree(qin);
240 	kfree(qout);
241 	return rc;
242 }
243 
244 /*
245  * get info about a segment
246  * possible return values:
247  * -ENOSYS  : we are not running on VM
248  * -EIO     : could not perform query diagnose
249  * -ENOENT  : no such segment
250  * -ENOTSUPP: multi-part segment cannot be used with linux
251  * -ENOSPC  : segment cannot be used (overlaps with storage)
252  * -ENOMEM  : out of memory
253  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
254  */
255 int
256 segment_type (char* name)
257 {
258 	int rc;
259 	struct dcss_segment seg;
260 
261 	if (!MACHINE_IS_VM)
262 		return -ENOSYS;
263 
264 	dcss_mkname(name, seg.dcss_name);
265 	rc = query_segment_type (&seg);
266 	if (rc < 0)
267 		return rc;
268 	return seg.vm_segtype;
269 }
270 
271 /*
272  * real segment loading function, called from segment_load
273  */
274 static int
275 __segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
276 {
277 	struct dcss_segment *seg = kmalloc(sizeof(struct dcss_segment),
278 			GFP_DMA);
279 	int dcss_command, rc, diag_cc;
280 
281 	if (seg == NULL) {
282 		rc = -ENOMEM;
283 		goto out;
284 	}
285 	dcss_mkname (name, seg->dcss_name);
286 	rc = query_segment_type (seg);
287 	if (rc < 0)
288 		goto out_free;
289 
290 	rc = add_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
291 
292 	switch (rc) {
293 	case 0:
294 		break;
295 	case -ENOSPC:
296 		PRINT_WARN("segment_load: not loading segment %s - overlaps "
297 			   "storage/segment\n", name);
298 		goto out_free;
299 	case -ERANGE:
300 		PRINT_WARN("segment_load: not loading segment %s - exceeds "
301 			   "kernel mapping range\n", name);
302 		goto out_free;
303 	default:
304 		PRINT_WARN("segment_load: not loading segment %s (rc: %d)\n",
305 			   name, rc);
306 		goto out_free;
307 	}
308 
309 	seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL);
310 	if (seg->res == NULL) {
311 		rc = -ENOMEM;
312 		goto out_shared;
313 	}
314 	seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM;
315 	seg->res->start = seg->start_addr;
316 	seg->res->end = seg->end;
317 	memcpy(&seg->res_name, seg->dcss_name, 8);
318 	EBCASC(seg->res_name, 8);
319 	seg->res_name[8] = '\0';
320 	strncat(seg->res_name, " (DCSS)", 7);
321 	seg->res->name = seg->res_name;
322 	rc = seg->vm_segtype;
323 	if (rc == SEG_TYPE_SC ||
324 	    ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared))
325 		seg->res->flags |= IORESOURCE_READONLY;
326 	if (request_resource(&iomem_resource, seg->res)) {
327 		rc = -EBUSY;
328 		kfree(seg->res);
329 		goto out_shared;
330 	}
331 
332 	if (do_nonshared)
333 		dcss_command = DCSS_LOADNSR;
334 	else
335 		dcss_command = DCSS_LOADNOLY;
336 
337 	diag_cc = dcss_diag(dcss_command, seg->dcss_name,
338 			&seg->start_addr, &seg->end);
339 	if (diag_cc > 1) {
340 		PRINT_WARN ("segment_load: could not load segment %s - "
341 				"diag returned error (%ld)\n",name,seg->end);
342 		rc = dcss_diag_translate_rc (seg->end);
343 		dcss_diag(DCSS_PURGESEG, seg->dcss_name,
344 				&seg->start_addr, &seg->end);
345 		goto out_resource;
346 	}
347 	seg->do_nonshared = do_nonshared;
348 	atomic_set(&seg->ref_count, 1);
349 	list_add(&seg->list, &dcss_list);
350 	*addr = seg->start_addr;
351 	*end  = seg->end;
352 	if (do_nonshared)
353 		PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
354 				"type %s in non-shared mode\n", name,
355 				(void*)seg->start_addr, (void*)seg->end,
356 				segtype_string[seg->vm_segtype]);
357 	else {
358 		PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
359 				"type %s in shared mode\n", name,
360 				(void*)seg->start_addr, (void*)seg->end,
361 				segtype_string[seg->vm_segtype]);
362 	}
363 	goto out;
364  out_resource:
365 	release_resource(seg->res);
366 	kfree(seg->res);
367  out_shared:
368 	remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
369  out_free:
370 	kfree(seg);
371  out:
372 	return rc;
373 }
374 
375 /*
376  * this function loads a DCSS segment
377  * name         : name of the DCSS
378  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
379  *                1 indicates that the dcss should be exclusive for this linux image
380  * addr         : will be filled with start address of the segment
381  * end          : will be filled with end address of the segment
382  * return values:
383  * -ENOSYS  : we are not running on VM
384  * -EIO     : could not perform query or load diagnose
385  * -ENOENT  : no such segment
386  * -ENOTSUPP: multi-part segment cannot be used with linux
387  * -ENOSPC  : segment cannot be used (overlaps with storage)
388  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
389  * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
390  * -EPERM   : segment is currently loaded with incompatible permissions
391  * -ENOMEM  : out of memory
392  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
393  */
394 int
395 segment_load (char *name, int do_nonshared, unsigned long *addr,
396 		unsigned long *end)
397 {
398 	struct dcss_segment *seg;
399 	int rc;
400 
401 	if (!MACHINE_IS_VM)
402 		return -ENOSYS;
403 
404 	mutex_lock(&dcss_lock);
405 	seg = segment_by_name (name);
406 	if (seg == NULL)
407 		rc = __segment_load (name, do_nonshared, addr, end);
408 	else {
409 		if (do_nonshared == seg->do_nonshared) {
410 			atomic_inc(&seg->ref_count);
411 			*addr = seg->start_addr;
412 			*end  = seg->end;
413 			rc    = seg->vm_segtype;
414 		} else {
415 			*addr = *end = 0;
416 			rc    = -EPERM;
417 		}
418 	}
419 	mutex_unlock(&dcss_lock);
420 	return rc;
421 }
422 
423 /*
424  * this function modifies the shared state of a DCSS segment. note that
425  * name         : name of the DCSS
426  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
427  *                1 indicates that the dcss should be exclusive for this linux image
428  * return values:
429  * -EIO     : could not perform load diagnose (segment gone!)
430  * -ENOENT  : no such segment (segment gone!)
431  * -EAGAIN  : segment is in use by other exploiters, try later
432  * -EINVAL  : no segment with the given name is currently loaded - name invalid
433  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
434  * 0	    : operation succeeded
435  */
436 int
437 segment_modify_shared (char *name, int do_nonshared)
438 {
439 	struct dcss_segment *seg;
440 	unsigned long dummy;
441 	int dcss_command, rc, diag_cc;
442 
443 	mutex_lock(&dcss_lock);
444 	seg = segment_by_name (name);
445 	if (seg == NULL) {
446 		rc = -EINVAL;
447 		goto out_unlock;
448 	}
449 	if (do_nonshared == seg->do_nonshared) {
450 		PRINT_INFO ("segment_modify_shared: not reloading segment %s"
451 				" - already in requested mode\n",name);
452 		rc = 0;
453 		goto out_unlock;
454 	}
455 	if (atomic_read (&seg->ref_count) != 1) {
456 		PRINT_WARN ("segment_modify_shared: not reloading segment %s - "
457 				"segment is in use by other driver(s)\n",name);
458 		rc = -EAGAIN;
459 		goto out_unlock;
460 	}
461 	release_resource(seg->res);
462 	if (do_nonshared) {
463 		dcss_command = DCSS_LOADNSR;
464 		seg->res->flags &= ~IORESOURCE_READONLY;
465 	} else {
466 		dcss_command = DCSS_LOADNOLY;
467 		if (seg->vm_segtype == SEG_TYPE_SR ||
468 		    seg->vm_segtype == SEG_TYPE_ER)
469 			seg->res->flags |= IORESOURCE_READONLY;
470 	}
471 	if (request_resource(&iomem_resource, seg->res)) {
472 		PRINT_WARN("segment_modify_shared: could not reload segment %s"
473 			   " - overlapping resources\n", name);
474 		rc = -EBUSY;
475 		kfree(seg->res);
476 		goto out_del;
477 	}
478 	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
479 	diag_cc = dcss_diag(dcss_command, seg->dcss_name,
480 			&seg->start_addr, &seg->end);
481 	if (diag_cc > 1) {
482 		PRINT_WARN ("segment_modify_shared: could not reload segment %s"
483 				" - diag returned error (%ld)\n",name,seg->end);
484 		rc = dcss_diag_translate_rc (seg->end);
485 		goto out_del;
486 	}
487 	seg->do_nonshared = do_nonshared;
488 	rc = 0;
489 	goto out_unlock;
490  out_del:
491 	remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
492 	list_del(&seg->list);
493 	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
494 	kfree(seg);
495  out_unlock:
496 	mutex_unlock(&dcss_lock);
497 	return rc;
498 }
499 
500 /*
501  * Decrease the use count of a DCSS segment and remove
502  * it from the address space if nobody is using it
503  * any longer.
504  */
505 void
506 segment_unload(char *name)
507 {
508 	unsigned long dummy;
509 	struct dcss_segment *seg;
510 
511 	if (!MACHINE_IS_VM)
512 		return;
513 
514 	mutex_lock(&dcss_lock);
515 	seg = segment_by_name (name);
516 	if (seg == NULL) {
517 		PRINT_ERR ("could not find segment %s in segment_unload, "
518 				"please report to linux390@de.ibm.com\n",name);
519 		goto out_unlock;
520 	}
521 	if (atomic_dec_return(&seg->ref_count) != 0)
522 		goto out_unlock;
523 	release_resource(seg->res);
524 	kfree(seg->res);
525 	remove_shared_memory(seg->start_addr, seg->end - seg->start_addr + 1);
526 	list_del(&seg->list);
527 	dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
528 	kfree(seg);
529 out_unlock:
530 	mutex_unlock(&dcss_lock);
531 }
532 
533 /*
534  * save segment content permanently
535  */
536 void
537 segment_save(char *name)
538 {
539 	struct dcss_segment *seg;
540 	int startpfn = 0;
541 	int endpfn = 0;
542 	char cmd1[160];
543 	char cmd2[80];
544 	int i, response;
545 
546 	if (!MACHINE_IS_VM)
547 		return;
548 
549 	mutex_lock(&dcss_lock);
550 	seg = segment_by_name (name);
551 
552 	if (seg == NULL) {
553 		PRINT_ERR("could not find segment %s in segment_save, please "
554 			  "report to linux390@de.ibm.com\n", name);
555 		goto out;
556 	}
557 
558 	startpfn = seg->start_addr >> PAGE_SHIFT;
559 	endpfn = (seg->end) >> PAGE_SHIFT;
560 	sprintf(cmd1, "DEFSEG %s", name);
561 	for (i=0; i<seg->segcnt; i++) {
562 		sprintf(cmd1+strlen(cmd1), " %X-%X %s",
563 			seg->range[i].start >> PAGE_SHIFT,
564 			seg->range[i].end >> PAGE_SHIFT,
565 			segtype_string[seg->range[i].start & 0xff]);
566 	}
567 	sprintf(cmd2, "SAVESEG %s", name);
568 	response = 0;
569 	cpcmd(cmd1, NULL, 0, &response);
570 	if (response) {
571 		PRINT_ERR("segment_save: DEFSEG failed with response code %i\n",
572 			  response);
573 		goto out;
574 	}
575 	cpcmd(cmd2, NULL, 0, &response);
576 	if (response) {
577 		PRINT_ERR("segment_save: SAVESEG failed with response code %i\n",
578 			  response);
579 		goto out;
580 	}
581 out:
582 	mutex_unlock(&dcss_lock);
583 }
584 
585 EXPORT_SYMBOL(segment_load);
586 EXPORT_SYMBOL(segment_unload);
587 EXPORT_SYMBOL(segment_save);
588 EXPORT_SYMBOL(segment_type);
589 EXPORT_SYMBOL(segment_modify_shared);
590