Merge git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6
[pandora-kernel.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 = 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);