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