Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-2.6
[sfrench/cifs-2.6.git] / arch / s390 / mm / extmem.c
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 = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
291
292         if (rc)
293                 goto out_free;
294
295         seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL);
296         if (seg->res == NULL) {
297                 rc = -ENOMEM;
298                 goto out_shared;
299         }
300         seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM;
301         seg->res->start = seg->start_addr;
302         seg->res->end = seg->end;
303         memcpy(&seg->res_name, seg->dcss_name, 8);
304         EBCASC(seg->res_name, 8);
305         seg->res_name[8] = '\0';
306         strncat(seg->res_name, " (DCSS)", 7);
307         seg->res->name = seg->res_name;
308         rc = seg->vm_segtype;
309         if (rc == SEG_TYPE_SC ||
310             ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared))
311                 seg->res->flags |= IORESOURCE_READONLY;
312         if (request_resource(&iomem_resource, seg->res)) {
313                 rc = -EBUSY;
314                 kfree(seg->res);
315                 goto out_shared;
316         }
317
318         if (do_nonshared)
319                 dcss_command = DCSS_LOADNSR;
320         else
321                 dcss_command = DCSS_LOADNOLY;
322
323         diag_cc = dcss_diag(dcss_command, seg->dcss_name,
324                         &seg->start_addr, &seg->end);
325         if (diag_cc > 1) {
326                 PRINT_WARN ("segment_load: could not load segment %s - "
327                                 "diag returned error (%ld)\n",name,seg->end);
328                 rc = dcss_diag_translate_rc (seg->end);
329                 dcss_diag(DCSS_PURGESEG, seg->dcss_name,
330                                 &seg->start_addr, &seg->end);
331                 goto out_resource;
332         }
333         seg->do_nonshared = do_nonshared;
334         atomic_set(&seg->ref_count, 1);
335         list_add(&seg->list, &dcss_list);
336         *addr = seg->start_addr;
337         *end  = seg->end;
338         if (do_nonshared)
339                 PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
340                                 "type %s in non-shared mode\n", name,
341                                 (void*)seg->start_addr, (void*)seg->end,
342                                 segtype_string[seg->vm_segtype]);
343         else {
344                 PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
345                                 "type %s in shared mode\n", name,
346                                 (void*)seg->start_addr, (void*)seg->end,
347                                 segtype_string[seg->vm_segtype]);
348         }
349         goto out;
350  out_resource:
351         release_resource(seg->res);
352         kfree(seg->res);
353  out_shared:
354         vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
355  out_free:
356         kfree(seg);
357  out:
358         return rc;
359 }
360
361 /*
362  * this function loads a DCSS segment
363  * name         : name of the DCSS
364  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
365  *                1 indicates that the dcss should be exclusive for this linux image
366  * addr         : will be filled with start address of the segment
367  * end          : will be filled with end address of the segment
368  * return values:
369  * -ENOSYS  : we are not running on VM
370  * -EIO     : could not perform query or load diagnose
371  * -ENOENT  : no such segment
372  * -ENOTSUPP: multi-part segment cannot be used with linux
373  * -ENOSPC  : segment cannot be used (overlaps with storage)
374  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
375  * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
376  * -EPERM   : segment is currently loaded with incompatible permissions
377  * -ENOMEM  : out of memory
378  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
379  */
380 int
381 segment_load (char *name, int do_nonshared, unsigned long *addr,
382                 unsigned long *end)
383 {
384         struct dcss_segment *seg;
385         int rc;
386
387         if (!MACHINE_IS_VM)
388                 return -ENOSYS;
389
390         mutex_lock(&dcss_lock);
391         seg = segment_by_name (name);
392         if (seg == NULL)
393                 rc = __segment_load (name, do_nonshared, addr, end);
394         else {
395                 if (do_nonshared == seg->do_nonshared) {
396                         atomic_inc(&seg->ref_count);
397                         *addr = seg->start_addr;
398                         *end  = seg->end;
399                         rc    = seg->vm_segtype;
400                 } else {
401                         *addr = *end = 0;
402                         rc    = -EPERM;
403                 }
404         }
405         mutex_unlock(&dcss_lock);
406         return rc;
407 }
408
409 /*
410  * this function modifies the shared state of a DCSS segment. note that
411  * name         : name of the DCSS
412  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
413  *                1 indicates that the dcss should be exclusive for this linux image
414  * return values:
415  * -EIO     : could not perform load diagnose (segment gone!)
416  * -ENOENT  : no such segment (segment gone!)
417  * -EAGAIN  : segment is in use by other exploiters, try later
418  * -EINVAL  : no segment with the given name is currently loaded - name invalid
419  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
420  * 0        : operation succeeded
421  */
422 int
423 segment_modify_shared (char *name, int do_nonshared)
424 {
425         struct dcss_segment *seg;
426         unsigned long dummy;
427         int dcss_command, rc, diag_cc;
428
429         mutex_lock(&dcss_lock);
430         seg = segment_by_name (name);
431         if (seg == NULL) {
432                 rc = -EINVAL;
433                 goto out_unlock;
434         }
435         if (do_nonshared == seg->do_nonshared) {
436                 PRINT_INFO ("segment_modify_shared: not reloading segment %s"
437                                 " - already in requested mode\n",name);
438                 rc = 0;
439                 goto out_unlock;
440         }
441         if (atomic_read (&seg->ref_count) != 1) {
442                 PRINT_WARN ("segment_modify_shared: not reloading segment %s - "
443                                 "segment is in use by other driver(s)\n",name);
444                 rc = -EAGAIN;
445                 goto out_unlock;
446         }
447         release_resource(seg->res);
448         if (do_nonshared) {
449                 dcss_command = DCSS_LOADNSR;
450                 seg->res->flags &= ~IORESOURCE_READONLY;
451         } else {
452                 dcss_command = DCSS_LOADNOLY;
453                 if (seg->vm_segtype == SEG_TYPE_SR ||
454                     seg->vm_segtype == SEG_TYPE_ER)
455                         seg->res->flags |= IORESOURCE_READONLY;
456         }
457         if (request_resource(&iomem_resource, seg->res)) {
458                 PRINT_WARN("segment_modify_shared: could not reload segment %s"
459                            " - overlapping resources\n", name);
460                 rc = -EBUSY;
461                 kfree(seg->res);
462                 goto out_del;
463         }
464         dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
465         diag_cc = dcss_diag(dcss_command, seg->dcss_name,
466                         &seg->start_addr, &seg->end);
467         if (diag_cc > 1) {
468                 PRINT_WARN ("segment_modify_shared: could not reload segment %s"
469                                 " - diag returned error (%ld)\n",name,seg->end);
470                 rc = dcss_diag_translate_rc (seg->end);
471                 goto out_del;
472         }
473         seg->do_nonshared = do_nonshared;
474         rc = 0;
475         goto out_unlock;
476  out_del:
477         vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
478         list_del(&seg->list);
479         dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
480         kfree(seg);
481  out_unlock:
482         mutex_unlock(&dcss_lock);
483         return rc;
484 }
485
486 /*
487  * Decrease the use count of a DCSS segment and remove
488  * it from the address space if nobody is using it
489  * any longer.
490  */
491 void
492 segment_unload(char *name)
493 {
494         unsigned long dummy;
495         struct dcss_segment *seg;
496
497         if (!MACHINE_IS_VM)
498                 return;
499
500         mutex_lock(&dcss_lock);
501         seg = segment_by_name (name);
502         if (seg == NULL) {
503                 PRINT_ERR ("could not find segment %s in segment_unload, "
504                                 "please report to linux390@de.ibm.com\n",name);
505                 goto out_unlock;
506         }
507         if (atomic_dec_return(&seg->ref_count) != 0)
508                 goto out_unlock;
509         release_resource(seg->res);
510         kfree(seg->res);
511         vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
512         list_del(&seg->list);
513         dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
514         kfree(seg);
515 out_unlock:
516         mutex_unlock(&dcss_lock);
517 }
518
519 /*
520  * save segment content permanently
521  */
522 void
523 segment_save(char *name)
524 {
525         struct dcss_segment *seg;
526         int startpfn = 0;
527         int endpfn = 0;
528         char cmd1[160];
529         char cmd2[80];
530         int i, response;
531
532         if (!MACHINE_IS_VM)
533                 return;
534
535         mutex_lock(&dcss_lock);
536         seg = segment_by_name (name);
537
538         if (seg == NULL) {
539                 PRINT_ERR("could not find segment %s in segment_save, please "
540                           "report to linux390@de.ibm.com\n", name);
541                 goto out;
542         }
543
544         startpfn = seg->start_addr >> PAGE_SHIFT;
545         endpfn = (seg->end) >> PAGE_SHIFT;
546         sprintf(cmd1, "DEFSEG %s", name);
547         for (i=0; i<seg->segcnt; i++) {
548                 sprintf(cmd1+strlen(cmd1), " %X-%X %s",
549                         seg->range[i].start >> PAGE_SHIFT,
550                         seg->range[i].end >> PAGE_SHIFT,
551                         segtype_string[seg->range[i].start & 0xff]);
552         }
553         sprintf(cmd2, "SAVESEG %s", name);
554         response = 0;
555         cpcmd(cmd1, NULL, 0, &response);
556         if (response) {
557                 PRINT_ERR("segment_save: DEFSEG failed with response code %i\n",
558                           response);
559                 goto out;
560         }
561         cpcmd(cmd2, NULL, 0, &response);
562         if (response) {
563                 PRINT_ERR("segment_save: SAVESEG failed with response code %i\n",
564                           response);
565                 goto out;
566         }
567 out:
568         mutex_unlock(&dcss_lock);
569 }
570
571 /*
572  * print appropriate error message for segment_load()/segment_type()
573  * return code
574  */
575 void segment_warning(int rc, char *seg_name)
576 {
577         switch (rc) {
578         case -ENOENT:
579                 PRINT_WARN("cannot load/query segment %s, "
580                            "does not exist\n", seg_name);
581                 break;
582         case -ENOSYS:
583                 PRINT_WARN("cannot load/query segment %s, "
584                            "not running on VM\n", seg_name);
585                 break;
586         case -EIO:
587                 PRINT_WARN("cannot load/query segment %s, "
588                            "hardware error\n", seg_name);
589                 break;
590         case -ENOTSUPP:
591                 PRINT_WARN("cannot load/query segment %s, "
592                            "is a multi-part segment\n", seg_name);
593                 break;
594         case -ENOSPC:
595                 PRINT_WARN("cannot load/query segment %s, "
596                            "overlaps with storage\n", seg_name);
597                 break;
598         case -EBUSY:
599                 PRINT_WARN("cannot load/query segment %s, "
600                            "overlaps with already loaded dcss\n", seg_name);
601                 break;
602         case -EPERM:
603                 PRINT_WARN("cannot load/query segment %s, "
604                            "already loaded in incompatible mode\n", seg_name);
605                 break;
606         case -ENOMEM:
607                 PRINT_WARN("cannot load/query segment %s, "
608                            "out of memory\n", seg_name);
609                 break;
610         case -ERANGE:
611                 PRINT_WARN("cannot load/query segment %s, "
612                            "exceeds kernel mapping range\n", seg_name);
613                 break;
614         default:
615                 PRINT_WARN("cannot load/query segment %s, "
616                            "return value %i\n", seg_name, rc);
617                 break;
618         }
619 }
620
621 EXPORT_SYMBOL(segment_load);
622 EXPORT_SYMBOL(segment_unload);
623 EXPORT_SYMBOL(segment_save);
624 EXPORT_SYMBOL(segment_type);
625 EXPORT_SYMBOL(segment_modify_shared);
626 EXPORT_SYMBOL(segment_warning);