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