patch-2_6_7-vs1_9_1_12
[linux-2.6.git] / arch / s390 / mm / extmem.c
1 /*
2  * File...........: arch/s390/mm/dcss.c
3  * Author(s)......: Steven Shultz <shultzss@us.ibm.com>
4  *                  Carsten Otte <cotte@de.ibm.com>
5  * Bugreports.to..: <Linux390@de.ibm.com>
6  * thanks to Rob M van der Heij
7  * - he wrote the diag64 function
8  * (C) IBM Corporation 2002
9  */
10
11 #include <linux/kernel.h>
12 #include <linux/string.h>
13 #include <linux/spinlock.h>
14 #include <linux/list.h>
15 #include <linux/slab.h>
16 #include <linux/module.h>
17 #include <linux/bootmem.h>
18 #include <asm/page.h>
19 #include <asm/ebcdic.h>
20 #include <asm/errno.h>
21 #include <asm/extmem.h>
22 #include <asm/cpcmd.h>
23 #include <linux/ctype.h>
24
25 #define DCSS_DEBUG      /* Debug messages on/off */
26
27 #define DCSS_NAME "extmem"
28 #ifdef DCSS_DEBUG
29 #define PRINT_DEBUG(x...)       printk(KERN_DEBUG DCSS_NAME " debug:" x)
30 #else
31 #define PRINT_DEBUG(x...)   do {} while (0)
32 #endif
33 #define PRINT_INFO(x...)        printk(KERN_INFO DCSS_NAME " info:" x)
34 #define PRINT_WARN(x...)        printk(KERN_WARNING DCSS_NAME " warning:" x)
35 #define PRINT_ERR(x...)         printk(KERN_ERR DCSS_NAME " error:" x)
36
37
38 #define DCSS_LOADSHR    0x00
39 #define DCSS_LOADNSR    0x04
40 #define DCSS_PURGESEG   0x08
41 #define DCSS_FINDSEG    0x0c
42 #define DCSS_LOADNOLY   0x10
43 #define DCSS_SEGEXT     0x18
44 #define DCSS_QACTV      0x0c
45
46 struct dcss_segment {
47         struct list_head list;
48         char dcss_name[8];
49         unsigned long start_addr;
50         unsigned long end;
51         atomic_t ref_count;
52         int dcss_attr;
53         int shared_attr;
54 };
55
56 static spinlock_t dcss_lock = SPIN_LOCK_UNLOCKED;
57 static struct list_head dcss_list = LIST_HEAD_INIT(dcss_list);
58 extern struct {
59         unsigned long addr, size, type;
60 } memory_chunk[MEMORY_CHUNKS];
61
62 /*
63  * Create the 8 bytes, ebcdic VM segment name from
64  * an ascii name.
65  */
66 static void inline dcss_mkname(char *name, char *dcss_name)
67 {
68         int i;
69
70         for (i = 0; i <= 8; i++) {
71                 if (name[i] == '\0')
72                         break;
73                 dcss_name[i] = toupper(name[i]);
74         };
75         for (; i <= 8; i++)
76                 dcss_name[i] = ' ';
77         ASCEBC(dcss_name, 8);
78 }
79
80 /*
81  * Perform a function on a dcss segment.
82  */
83 static inline int
84 dcss_diag (__u8 func, void *parameter,
85            unsigned long *ret1, unsigned long *ret2)
86 {
87         unsigned long rx, ry;
88         int rc;
89
90         rx = (unsigned long) parameter;
91         ry = (unsigned long) func;
92         __asm__ __volatile__(
93 #ifdef CONFIG_ARCH_S390X
94                              "   sam31\n" // switch to 31 bit
95                              "   diag    %0,%1,0x64\n"
96                              "   sam64\n" // switch back to 64 bit
97 #else
98                              "   diag    %0,%1,0x64\n"
99 #endif
100                              "   ipm     %2\n"
101                              "   srl     %2,28\n"
102                              : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc" );
103         *ret1 = rx;
104         *ret2 = ry;
105         return rc;
106 }
107
108
109 /* use to issue "extended" dcss query */
110 static inline int
111 dcss_diag_query(char *name, int *rwattr, int *shattr, unsigned long *segstart, unsigned long *segend)
112 {
113         int i,j,rc;
114         unsigned long  rx, ry;
115
116         typedef struct segentry {
117                 char thisseg[8];
118         } segentry;
119
120         struct qout64 {
121                 int segstart;
122                 int segend;
123                 int segcnt;
124                 int segrcnt;
125                 segentry segout[6];
126         };
127
128         struct qin64 {
129                 char qopcode;
130                 char rsrv1[3];
131                 char qrcode;
132                 char rsrv2[3];
133                 char qname[8];
134                 unsigned int qoutptr;
135                 short int qoutlen;
136         };
137
138
139         struct qin64  *qinarea;
140         struct qout64 *qoutarea;
141
142         qinarea = (struct qin64*) get_zeroed_page (GFP_DMA);
143         if (!qinarea) {
144                 rc =-ENOMEM;
145                 goto out;
146         }
147         qoutarea = (struct qout64*) get_zeroed_page (GFP_DMA);
148         if (!qoutarea) {
149                 rc = -ENOMEM;
150                 free_page ((unsigned long) qinarea);
151                 goto out;
152         }
153         memset (qinarea,0,PAGE_SIZE);
154         memset (qoutarea,0,PAGE_SIZE);
155
156         qinarea->qopcode = DCSS_QACTV; /* do a query for active
157                                           segments */
158         qinarea->qoutptr = (unsigned long) qoutarea;
159         qinarea->qoutlen = sizeof(struct qout64);
160
161         /* Move segment name into double word aligned
162            field and pad with blanks to 8 long.
163          */
164
165         for (i = j = 0 ; i < 8; i++) {
166                 qinarea->qname[i] = (name[j] == '\0') ? ' ' : name[j++];
167         }
168
169         /* name already in EBCDIC */
170         /* ASCEBC ((void *)&qinarea.qname, 8); */
171
172         /* set the assembler variables */
173         rx = (unsigned long) qinarea;
174         ry = DCSS_SEGEXT; /* this is extended function */
175
176         /* issue diagnose x'64' */
177         __asm__ __volatile__(
178 #ifdef CONFIG_ARCH_S390X
179                              "   sam31\n" // switch to 31 bit
180                              "   diag    %0,%1,0x64\n"
181                              "   sam64\n" // switch back to 64 bit
182 #else
183                              "   diag    %0,%1,0x64\n"
184 #endif
185                              "   ipm     %2\n"
186                              "   srl     %2,28\n"
187                              : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc" );
188
189         /* parse the query output area */
190         *segstart=qoutarea->segstart;
191         *segend=qoutarea->segend;
192
193         if (rc > 1)
194                 {
195                         *rwattr = 2;
196                         *shattr = 2;
197                         rc = 0;
198                         goto free;
199                 }
200
201         if (qoutarea->segcnt > 6)
202                 {
203                         *rwattr = 3;
204                         *shattr = 3;
205                         rc = 0;
206                         goto free;
207                 }
208
209         *rwattr = 1;
210         *shattr = 1;
211
212         for (i=0; i < qoutarea->segrcnt; i++) {
213                 if (qoutarea->segout[i].thisseg[3] == 2 ||
214                     qoutarea->segout[i].thisseg[3] == 3 ||
215                     qoutarea->segout[i].thisseg[3] == 6 )
216                         *rwattr = 0;
217                 if (qoutarea->segout[i].thisseg[3] == 1 ||
218                     qoutarea->segout[i].thisseg[3] == 3 ||
219                     qoutarea->segout[i].thisseg[3] == 5 )
220                         *shattr = 0;
221         } /* end of for statement */
222         rc = 0;
223  free:
224         free_page ((unsigned long) qoutarea);
225         free_page ((unsigned long) qinarea);
226  out:
227         return rc;
228 }
229
230 /*
231  * Load a DCSS segment via the diag 0x64.
232  */
233 int segment_load(char *name, int segtype, unsigned long *addr,
234                  unsigned long *end)
235 {
236         char dcss_name[8];
237         struct list_head *l;
238         struct dcss_segment *seg, *tmp;
239         unsigned long dummy;
240         unsigned long segstart, segend;
241         int rc = 0,i;
242         int rwattr, shattr;
243
244         if (!MACHINE_IS_VM)
245                 return -ENOSYS;
246         dcss_mkname(name, dcss_name);
247         /* search for the dcss in list of currently loaded segments */
248         spin_lock(&dcss_lock);
249         seg = NULL;
250         list_for_each(l, &dcss_list) {
251                 tmp = list_entry(l, struct dcss_segment, list);
252                 if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) {
253                         seg = tmp;
254                         break;
255                 }
256         }
257
258         if (seg == NULL) {
259                 /* find out the attributes of this
260                    shared segment */
261                 dcss_diag_query(dcss_name, &rwattr, &shattr, &segstart, &segend);
262                 /* does segment collide with main memory? */
263                 for (i=0; i < MEMORY_CHUNKS; i++) {
264                         if (memory_chunk[i].type != 0)
265                                 continue;
266                         if (memory_chunk[i].addr > segend)
267                                 continue;
268                         if (memory_chunk[i].addr + memory_chunk[i].size <= segstart)
269                                 continue;
270                         spin_unlock(&dcss_lock);
271                         return -ENOENT;
272                 }
273                 /* or does it collide with other (loaded) segments? */
274                 list_for_each(l, &dcss_list) {
275                         tmp = list_entry(l, struct dcss_segment, list);
276                         if ((segstart <= tmp->end && segstart >= tmp->start_addr) ||
277                                 (segend <= tmp->end && segend >= tmp->start_addr) ||
278                                 (segstart <= tmp->start_addr && segend >= tmp->end)) {
279                                 PRINT_ERR("Segment Overlap!\n");
280                                 spin_unlock(&dcss_lock);
281                                 return -ENOENT;
282                         }
283                 }
284
285                 /* do case statement on segtype */
286                 /* if asking for shared ro,
287                    shared rw works */
288                 /* if asking for exclusive ro,
289                    exclusive rw works */
290
291                 switch(segtype) {
292                 case SEGMENT_SHARED_RO:
293                         if (shattr > 1 || rwattr > 1) {
294                                 spin_unlock(&dcss_lock);
295                                 return -ENOENT;
296                         } else {
297                                 if (shattr == 0 && rwattr == 0)
298                                         rc = SEGMENT_EXCLUSIVE_RO;
299                                 if (shattr == 0 && rwattr == 1)
300                                         rc = SEGMENT_EXCLUSIVE_RW;
301                                 if (shattr == 1 && rwattr == 0)
302                                         rc = SEGMENT_SHARED_RO;
303                                 if (shattr == 1 && rwattr == 1)
304                                         rc = SEGMENT_SHARED_RW;
305                         }
306                         break;
307                 case SEGMENT_SHARED_RW:
308                         if (shattr > 1 || rwattr != 1) {
309                                 spin_unlock(&dcss_lock);
310                                 return -ENOENT;
311                         } else {
312                                 if (shattr == 0)
313                                         rc = SEGMENT_EXCLUSIVE_RW;
314                                 if (shattr == 1)
315                                         rc = SEGMENT_SHARED_RW;
316                         }
317                         break;
318
319                 case SEGMENT_EXCLUSIVE_RO:
320                         if (shattr > 0 || rwattr > 1) {
321                                 spin_unlock(&dcss_lock);
322                                 return -ENOENT;
323                         } else {
324                                 if (rwattr == 0)
325                                         rc = SEGMENT_EXCLUSIVE_RO;
326                                 if (rwattr == 1)
327                                         rc = SEGMENT_EXCLUSIVE_RW;
328                         }
329                         break;
330
331                 case SEGMENT_EXCLUSIVE_RW:
332 /*                        if (shattr != 0 || rwattr != 1) {
333                                 spin_unlock(&dcss_lock);
334                                 return -ENOENT;
335                         } else {
336 */
337                                 rc = SEGMENT_EXCLUSIVE_RW;
338 //                        }
339                         break;
340
341                 default:
342                         spin_unlock(&dcss_lock);
343                         return -ENOENT;
344                 } /* end switch */
345
346                 seg = kmalloc(sizeof(struct dcss_segment), GFP_DMA);
347                 if (seg != NULL) {
348                         memcpy(seg->dcss_name, dcss_name, 8);
349                         if (rc == SEGMENT_EXCLUSIVE_RW) {
350                                 if (dcss_diag(DCSS_LOADNSR, seg->dcss_name,
351                                                 &seg->start_addr, &seg->end) == 0) {
352                                         if (seg->end < max_low_pfn*PAGE_SIZE ) {
353                                                 atomic_set(&seg->ref_count, 1);
354                                                 list_add(&seg->list, &dcss_list);
355                                                 *addr = seg->start_addr;
356                                                 *end = seg->end;
357                                                 seg->dcss_attr = rc;
358                                                 if (shattr == 1 && rwattr == 1)
359                                                         seg->shared_attr = SEGMENT_SHARED_RW;
360                                                 else if (shattr == 1 && rwattr == 0)
361                                                         seg->shared_attr = SEGMENT_SHARED_RO;
362                                                 else
363                                                         seg->shared_attr = SEGMENT_EXCLUSIVE_RW;
364                                         } else {
365                                                 dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
366                                                 kfree (seg);
367                                                 rc = -ENOENT;
368                                         }
369                                 } else {
370                                         kfree(seg);
371                                         rc = -ENOENT;
372                                 }
373                                 goto out;
374                         }
375                         if (dcss_diag(DCSS_LOADNOLY, seg->dcss_name,
376                                       &seg->start_addr, &seg->end) == 0) {
377                                 if (seg->end < max_low_pfn*PAGE_SIZE ) {
378                                         atomic_set(&seg->ref_count, 1);
379                                         list_add(&seg->list, &dcss_list);
380                                         *addr = seg->start_addr;
381                                         *end = seg->end;
382                                         seg->dcss_attr = rc;
383                                         seg->shared_attr = rc;
384                                 } else {
385                                         dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
386                                         kfree (seg);
387                                         rc = -ENOENT;
388                                 }
389                         } else {
390                                 kfree(seg);
391                                 rc = -ENOENT;
392                         }
393                 } else rc = -ENOMEM;
394         } else {
395                 /* found */
396                 if ((segtype == SEGMENT_EXCLUSIVE_RW) && (seg->dcss_attr != SEGMENT_EXCLUSIVE_RW)) {
397                         PRINT_ERR("Segment already loaded in other mode than EXCLUSIVE_RW!\n");
398                         rc = -EPERM;
399                         goto out;
400                         /* reload segment in exclusive mode */
401 /*                      dcss_diag(DCSS_LOADNSR, seg->dcss_name,
402                                   &seg->start_addr, &seg->end);
403                         seg->dcss_attr = SEGMENT_EXCLUSIVE_RW;*/
404                 }
405                 if ((segtype != SEGMENT_EXCLUSIVE_RW) && (seg->dcss_attr == SEGMENT_EXCLUSIVE_RW)) {
406                         PRINT_ERR("Segment already loaded in EXCLUSIVE_RW mode!\n");
407                         rc = -EPERM;
408                         goto out;
409                 }
410                 atomic_inc(&seg->ref_count);
411                 *addr = seg->start_addr;
412                 *end = seg->end;
413                 rc = seg->dcss_attr;
414         }
415 out:
416         spin_unlock(&dcss_lock);
417         return rc;
418 }
419
420 /*
421  * Decrease the use count of a DCSS segment and remove
422  * it from the address space if nobody is using it
423  * any longer.
424  */
425 void segment_unload(char *name)
426 {
427         char dcss_name[8];
428         unsigned long dummy;
429         struct list_head *l,*l_tmp;
430         struct dcss_segment *seg;
431
432         if (!MACHINE_IS_VM)
433                 return;
434         dcss_mkname(name, dcss_name);
435         spin_lock(&dcss_lock);
436         list_for_each_safe(l, l_tmp, &dcss_list) {
437                 seg = list_entry(l, struct dcss_segment, list);
438                 if (memcmp(seg->dcss_name, dcss_name, 8) == 0) {
439                         if (atomic_dec_return(&seg->ref_count) == 0) {
440                                 /* Last user of the segment is
441                                    gone. */
442                                 list_del(&seg->list);
443                                 dcss_diag(DCSS_PURGESEG, seg->dcss_name,
444                                           &dummy, &dummy);
445                                 kfree(seg);
446                         }
447                         break;
448                 }
449         }
450         spin_unlock(&dcss_lock);
451 }
452
453 /*
454  * Replace an existing DCSS segment, so that machines
455  * that load it anew will see the new version.
456  */
457 void segment_replace(char *name)
458 {
459         char dcss_name[8];
460         struct list_head *l;
461         struct dcss_segment *seg;
462         int mybeg = 0;
463         int myend = 0;
464         char mybuff1[80];
465         char mybuff2[80];
466
467         if (!MACHINE_IS_VM)
468                 return;
469         dcss_mkname(name, dcss_name);
470
471         memset (mybuff1, 0, sizeof(mybuff1));
472         memset (mybuff2, 0, sizeof(mybuff2));
473
474         spin_lock(&dcss_lock);
475         list_for_each(l, &dcss_list) {
476                 seg = list_entry(l, struct dcss_segment, list);
477                 if (memcmp(seg->dcss_name, dcss_name, 8) == 0) {
478                         mybeg = seg->start_addr >> 12;
479                         myend = (seg->end) >> 12;
480                         if (seg->shared_attr == SEGMENT_EXCLUSIVE_RW)
481                                 sprintf(mybuff1, "DEFSEG %s %X-%X EW",
482                                         name, mybeg, myend);
483                         if (seg->shared_attr == SEGMENT_EXCLUSIVE_RO)
484                                 sprintf(mybuff1, "DEFSEG %s %X-%X RO",
485                                         name, mybeg, myend);
486                         if (seg->shared_attr == SEGMENT_SHARED_RW)
487                                 sprintf(mybuff1, "DEFSEG %s %X-%X SW",
488                                         name, mybeg, myend);
489                         if (seg->shared_attr == SEGMENT_SHARED_RO)
490                                 sprintf(mybuff1, "DEFSEG %s %X-%X SR",
491                                         name, mybeg, myend);
492                         spin_unlock(&dcss_lock);
493                         sprintf(mybuff2, "SAVESEG %s", name);
494                         cpcmd(mybuff1, NULL, 80);
495                         cpcmd(mybuff2, NULL, 80);
496                         break;
497                 }
498
499         }
500         if (myend == 0) spin_unlock(&dcss_lock);
501 }
502
503 EXPORT_SYMBOL(segment_load);
504 EXPORT_SYMBOL(segment_unload);
505 EXPORT_SYMBOL(segment_replace);