ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / arch / s390 / mm / cmm.c
1 /*
2  *  arch/s390/mm/cmm.c
3  *
4  *  S390 version
5  *    Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
6  *    Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
7  *
8  *  Collaborative memory management interface.
9  */
10
11 #include <linux/config.h>
12 #include <linux/errno.h>
13 #include <linux/fs.h>
14 #include <linux/init.h>
15 #include <linux/module.h>
16 #include <linux/sched.h>
17 #include <linux/sysctl.h>
18 #include <linux/ctype.h>
19
20 #include <asm/pgalloc.h>
21 #include <asm/uaccess.h>
22
23 #include "../../../drivers/s390/net/smsgiucv.h"
24
25 #define CMM_NR_PAGES ((PAGE_SIZE / sizeof(unsigned long)) - 2)
26
27 struct cmm_page_array {
28         struct cmm_page_array *next;
29         unsigned long index;
30         unsigned long pages[CMM_NR_PAGES];
31 };
32
33 static long cmm_pages = 0;
34 static long cmm_timed_pages = 0;
35 static volatile long cmm_pages_target = 0;
36 static volatile long cmm_timed_pages_target = 0;
37 static long cmm_timeout_pages = 0;
38 static long cmm_timeout_seconds = 0;
39
40 static struct cmm_page_array *cmm_page_list = 0;
41 static struct cmm_page_array *cmm_timed_page_list = 0;
42
43 static unsigned long cmm_thread_active = 0;
44 static struct work_struct cmm_thread_starter;
45 static wait_queue_head_t cmm_thread_wait;
46 static struct timer_list cmm_timer;
47
48 static void cmm_timer_fn(unsigned long);
49 static void cmm_set_timer(void);
50
51 static long
52 cmm_strtoul(const char *cp, char **endp)
53 {
54         unsigned int base = 10;
55
56         if (*cp == '0') {
57                 base = 8;
58                 cp++;
59                 if ((*cp == 'x' || *cp == 'X') && isxdigit(cp[1])) {
60                         base = 16;
61                         cp++;
62                 }
63         }
64         return simple_strtoul(cp, endp, base);
65 }
66
67 static long
68 cmm_alloc_pages(long pages, long *counter, struct cmm_page_array **list)
69 {
70         struct cmm_page_array *pa;
71         unsigned long page;
72
73         pa = *list;
74         while (pages) {
75                 page = __get_free_page(GFP_NOIO);
76                 if (!page)
77                         break;
78                 if (!pa || pa->index >= CMM_NR_PAGES) {
79                         /* Need a new page for the page list. */
80                         pa = (struct cmm_page_array *)
81                                 __get_free_page(GFP_NOIO);
82                         if (!pa) {
83                                 free_page(page);
84                                 break;
85                         }
86                         pa->next = *list;
87                         pa->index = 0;
88                         *list = pa;
89                 }
90                 diag10(page);
91                 pa->pages[pa->index++] = page;
92                 (*counter)++;
93                 pages--;
94         }
95         return pages;
96 }
97
98 static void
99 cmm_free_pages(long pages, long *counter, struct cmm_page_array **list)
100 {
101         struct cmm_page_array *pa;
102         unsigned long page;
103
104         pa = *list;
105         while (pages) {
106                 if (!pa || pa->index <= 0)
107                         break;
108                 page = pa->pages[--pa->index];
109                 if (pa->index == 0) {
110                         pa = pa->next;
111                         free_page((unsigned long) *list);
112                         *list = pa;
113                 }
114                 free_page(page);
115                 (*counter)--;
116                 pages--;
117         }
118 }
119
120 static int
121 cmm_thread(void *dummy)
122 {
123         int rc;
124
125         daemonize("cmmthread");
126         set_cpus_allowed(current, cpumask_of_cpu(0));
127         while (1) {
128                 rc = wait_event_interruptible(cmm_thread_wait,
129                         (cmm_pages != cmm_pages_target ||
130                          cmm_timed_pages != cmm_timed_pages_target));
131                 if (rc == -ERESTARTSYS) {
132                         /* Got kill signal. End thread. */
133                         clear_bit(0, &cmm_thread_active);
134                         cmm_pages_target = cmm_pages;
135                         cmm_timed_pages_target = cmm_timed_pages;
136                         break;
137                 }
138                 if (cmm_pages_target > cmm_pages) {
139                         if (cmm_alloc_pages(1, &cmm_pages, &cmm_page_list))
140                                 cmm_pages_target = cmm_pages;
141                 } else if (cmm_pages_target < cmm_pages) {
142                         cmm_free_pages(1, &cmm_pages, &cmm_page_list);
143                 }
144                 if (cmm_timed_pages_target > cmm_timed_pages) {
145                         if (cmm_alloc_pages(1, &cmm_timed_pages,
146                                            &cmm_timed_page_list))
147                                 cmm_timed_pages_target = cmm_timed_pages;
148                 } else if (cmm_timed_pages_target < cmm_timed_pages) {
149                         cmm_free_pages(1, &cmm_timed_pages,
150                                        &cmm_timed_page_list);
151                 }
152                 if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
153                         cmm_set_timer();
154         }
155         return 0;
156 }
157
158 static void
159 cmm_start_thread(void)
160 {
161         kernel_thread(cmm_thread, 0, 0);
162 }
163
164 static void
165 cmm_kick_thread(void)
166 {
167         if (!test_and_set_bit(0, &cmm_thread_active))
168                 schedule_work(&cmm_thread_starter);
169         wake_up(&cmm_thread_wait);
170 }
171
172 static void
173 cmm_set_timer(void)
174 {
175         if (cmm_timed_pages_target <= 0 || cmm_timeout_seconds <= 0) {
176                 if (timer_pending(&cmm_timer))
177                         del_timer(&cmm_timer);
178                 return;
179         }
180         if (timer_pending(&cmm_timer)) {
181                 if (mod_timer(&cmm_timer, jiffies + cmm_timeout_seconds*HZ))
182                         return;
183         }
184         cmm_timer.function = cmm_timer_fn;
185         cmm_timer.data = 0;
186         cmm_timer.expires = jiffies + cmm_timeout_seconds*HZ;
187         add_timer(&cmm_timer);
188 }
189
190 static void
191 cmm_timer_fn(unsigned long ignored)
192 {
193         long pages;
194
195         pages = cmm_timed_pages_target - cmm_timeout_pages;
196         if (pages < 0)
197                 cmm_timed_pages_target = 0;
198         else
199                 cmm_timed_pages_target = pages;
200         cmm_kick_thread();
201         cmm_set_timer();
202 }
203
204 void
205 cmm_set_pages(long pages)
206 {
207         cmm_pages_target = pages;
208         cmm_kick_thread();
209 }
210
211 long
212 cmm_get_pages(void)
213 {
214         return cmm_pages;
215 }
216
217 void
218 cmm_add_timed_pages(long pages)
219 {
220         cmm_timed_pages_target += pages;
221         cmm_kick_thread();
222 }
223
224 long
225 cmm_get_timed_pages(void)
226 {
227         return cmm_timed_pages;
228 }
229
230 void
231 cmm_set_timeout(long pages, long seconds)
232 {
233         cmm_timeout_pages = pages;
234         cmm_timeout_seconds = seconds;
235         cmm_set_timer();
236 }
237
238 static inline int
239 cmm_skip_blanks(char *cp, char **endp)
240 {
241         char *str;
242
243         for (str = cp; *str == ' ' || *str == '\t'; str++);
244         *endp = str;
245         return str != cp;
246 }
247
248 #ifdef CONFIG_CMM_PROC
249 /* These will someday get removed. */
250 #define VM_CMM_PAGES            1111
251 #define VM_CMM_TIMED_PAGES      1112
252 #define VM_CMM_TIMEOUT          1113
253
254 static struct ctl_table cmm_table[];
255
256 static int
257 cmm_pages_handler(ctl_table *ctl, int write, struct file *filp,
258                   void *buffer, size_t *lenp)
259 {
260         char buf[16], *p;
261         long pages;
262         int len;
263
264         if (!*lenp || (filp->f_pos && !write)) {
265                 *lenp = 0;
266                 return 0;
267         }
268
269         if (write) {
270                 len = *lenp;
271                 if (copy_from_user(buf, buffer,
272                                    len > sizeof(buf) ? sizeof(buf) : len))
273                         return -EFAULT;
274                 buf[sizeof(buf) - 1] = '\0';
275                 cmm_skip_blanks(buf, &p);
276                 pages = cmm_strtoul(p, &p);
277                 if (ctl == &cmm_table[0])
278                         cmm_set_pages(pages);
279                 else
280                         cmm_add_timed_pages(pages);
281         } else {
282                 if (ctl == &cmm_table[0])
283                         pages = cmm_get_pages();
284                 else
285                         pages = cmm_get_timed_pages();
286                 len = sprintf(buf, "%ld\n", pages);
287                 if (len > *lenp)
288                         len = *lenp;
289                 if (copy_to_user(buffer, buf, len))
290                         return -EFAULT;
291         }
292         *lenp = len;
293         filp->f_pos += len;
294         return 0;
295 }
296
297 static int
298 cmm_timeout_handler(ctl_table *ctl, int write, struct file *filp,
299                     void *buffer, size_t *lenp)
300 {
301         char buf[64], *p;
302         long pages, seconds;
303         int len;
304
305         if (!*lenp || (filp->f_pos && !write)) {
306                 *lenp = 0;
307                 return 0;
308         }
309
310         if (write) {
311                 len = *lenp;
312                 if (copy_from_user(buf, buffer,
313                                    len > sizeof(buf) ? sizeof(buf) : len))
314                         return -EFAULT;
315                 buf[sizeof(buf) - 1] = '\0';
316                 cmm_skip_blanks(buf, &p);
317                 pages = cmm_strtoul(p, &p);
318                 cmm_skip_blanks(p, &p);
319                 seconds = cmm_strtoul(p, &p);
320                 cmm_set_timeout(pages, seconds);
321         } else {
322                 len = sprintf(buf, "%ld %ld\n",
323                               cmm_timeout_pages, cmm_timeout_seconds);
324                 if (len > *lenp)
325                         len = *lenp;
326                 if (copy_to_user(buffer, buf, len))
327                         return -EFAULT;
328         }
329         *lenp = len;
330         filp->f_pos += len;
331         return 0;
332 }
333
334 static struct ctl_table cmm_table[] = {
335         {
336                 .ctl_name       = VM_CMM_PAGES,
337                 .procname       = "cmm_pages",
338                 .mode           = 0600,
339                 .proc_handler   = &cmm_pages_handler,
340         },
341         {
342                 .ctl_name       = VM_CMM_TIMED_PAGES,
343                 .procname       = "cmm_timed_pages",
344                 .mode           = 0600,
345                 .proc_handler   = &cmm_pages_handler,
346         },
347         {
348                 .ctl_name       = VM_CMM_TIMEOUT,
349                 .procname       = "cmm_timeout",
350                 .mode           = 0600,
351                 .proc_handler   = &cmm_timeout_handler,
352         },
353         { .ctl_name = 0 }
354 };
355
356 static struct ctl_table cmm_dir_table[] = {
357         {
358                 .ctl_name       = CTL_VM,
359                 .procname       = "vm",
360                 .maxlen         = 0,
361                 .mode           = 0555,
362                 .child          = cmm_table,
363         },
364         { .ctl_name = 0 }
365 };
366 #endif
367
368 #ifdef CONFIG_CMM_IUCV
369 #define SMSG_PREFIX "CMM"
370 static void
371 cmm_smsg_target(char *msg)
372 {
373         long pages, seconds;
374
375         if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
376                 return;
377         if (strncmp(msg, "SHRINK", 6) == 0) {
378                 if (!cmm_skip_blanks(msg + 6, &msg))
379                         return;
380                 pages = cmm_strtoul(msg, &msg);
381                 cmm_skip_blanks(msg, &msg);
382                 if (*msg == '\0')
383                         cmm_set_pages(pages);
384         } else if (strncmp(msg, "RELEASE", 7) == 0) {
385                 if (!cmm_skip_blanks(msg + 7, &msg))
386                         return;
387                 pages = cmm_strtoul(msg, &msg);
388                 cmm_skip_blanks(msg, &msg);
389                 if (*msg == '\0')
390                         cmm_add_timed_pages(pages);
391         } else if (strncmp(msg, "REUSE", 5) == 0) {
392                 if (!cmm_skip_blanks(msg + 5, &msg))
393                         return;
394                 pages = cmm_strtoul(msg, &msg);
395                 if (!cmm_skip_blanks(msg, &msg))
396                         return;
397                 seconds = cmm_strtoul(msg, &msg);
398                 cmm_skip_blanks(msg, &msg);
399                 if (*msg == '\0')
400                         cmm_set_timeout(pages, seconds);
401         }
402 }
403 #endif
404
405 struct ctl_table_header *cmm_sysctl_header;
406
407 static int
408 cmm_init (void)
409 {
410 #ifdef CONFIG_CMM_PROC
411         cmm_sysctl_header = register_sysctl_table(cmm_dir_table, 1);
412 #endif
413 #ifdef CONFIG_CMM_IUCV
414         smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
415 #endif
416         INIT_WORK(&cmm_thread_starter, (void *) cmm_start_thread, 0);
417         init_waitqueue_head(&cmm_thread_wait);
418         init_timer(&cmm_timer);
419         return 0;
420 }
421
422 static void
423 cmm_exit(void)
424 {
425         cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
426         cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
427 #ifdef CONFIG_CMM_PROC
428         unregister_sysctl_table(cmm_sysctl_header);
429 #endif
430 #ifdef CONFIG_CMM_IUCV
431         smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
432 #endif
433 }
434
435 module_init(cmm_init);
436 module_exit(cmm_exit);
437
438 EXPORT_SYMBOL(cmm_set_pages);
439 EXPORT_SYMBOL(cmm_get_pages);
440 EXPORT_SYMBOL(cmm_add_timed_pages);
441 EXPORT_SYMBOL(cmm_get_timed_pages);
442 EXPORT_SYMBOL(cmm_set_timeout);
443
444 MODULE_LICENSE("GPL");