cmm.c 10.6 KB
Newer Older
L
Linus Torvalds 已提交
1
/*
2
 *  Collaborative memory management interface.
L
Linus Torvalds 已提交
3
 *
4 5
 *    Copyright IBM Corp 2003,2010
 *    Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>,
L
Linus Torvalds 已提交
6 7 8 9 10 11 12
 *
 */

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
13
#include <linux/gfp.h>
L
Linus Torvalds 已提交
14 15 16
#include <linux/sched.h>
#include <linux/sysctl.h>
#include <linux/ctype.h>
17
#include <linux/swap.h>
18
#include <linux/kthread.h>
19
#include <linux/oom.h>
20
#include <linux/suspend.h>
21
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
22 23

#include <asm/pgalloc.h>
24
#include <asm/diag.h>
L
Linus Torvalds 已提交
25

26
static char *sender = "VMRMSVM";
27
module_param(sender, charp, 0400);
28 29 30
MODULE_PARM_DESC(sender,
		 "Guest name that may send SMSG messages (default VMRMSVM)");

L
Linus Torvalds 已提交
31 32 33 34 35 36 37 38 39 40
#include "../../../drivers/s390/net/smsgiucv.h"

#define CMM_NR_PAGES ((PAGE_SIZE / sizeof(unsigned long)) - 2)

struct cmm_page_array {
	struct cmm_page_array *next;
	unsigned long index;
	unsigned long pages[CMM_NR_PAGES];
};

41 42 43 44 45 46
static long cmm_pages;
static long cmm_timed_pages;
static volatile long cmm_pages_target;
static volatile long cmm_timed_pages_target;
static long cmm_timeout_pages;
static long cmm_timeout_seconds;
47
static int cmm_suspended;
L
Linus Torvalds 已提交
48

49 50 51
static struct cmm_page_array *cmm_page_list;
static struct cmm_page_array *cmm_timed_page_list;
static DEFINE_SPINLOCK(cmm_lock);
L
Linus Torvalds 已提交
52

53
static struct task_struct *cmm_thread_ptr;
54 55
static DECLARE_WAIT_QUEUE_HEAD(cmm_thread_wait);
static DEFINE_TIMER(cmm_timer, NULL, 0, 0);
L
Linus Torvalds 已提交
56 57 58 59

static void cmm_timer_fn(unsigned long);
static void cmm_set_timer(void);

60 61
static long cmm_alloc_pages(long nr, long *counter,
			    struct cmm_page_array **list)
L
Linus Torvalds 已提交
62
{
63 64
	struct cmm_page_array *pa, *npa;
	unsigned long addr;
L
Linus Torvalds 已提交
65

66 67 68
	while (nr) {
		addr = __get_free_page(GFP_NOIO);
		if (!addr)
L
Linus Torvalds 已提交
69
			break;
70 71
		spin_lock(&cmm_lock);
		pa = *list;
L
Linus Torvalds 已提交
72 73
		if (!pa || pa->index >= CMM_NR_PAGES) {
			/* Need a new page for the page list. */
74 75
			spin_unlock(&cmm_lock);
			npa = (struct cmm_page_array *)
L
Linus Torvalds 已提交
76
				__get_free_page(GFP_NOIO);
77 78
			if (!npa) {
				free_page(addr);
L
Linus Torvalds 已提交
79 80
				break;
			}
81 82 83 84 85 86 87 88 89
			spin_lock(&cmm_lock);
			pa = *list;
			if (!pa || pa->index >= CMM_NR_PAGES) {
				npa->next = pa;
				npa->index = 0;
				pa = npa;
				*list = pa;
			} else
				free_page((unsigned long) npa);
L
Linus Torvalds 已提交
90
		}
91 92
		diag10(addr);
		pa->pages[pa->index++] = addr;
L
Linus Torvalds 已提交
93
		(*counter)++;
94 95
		spin_unlock(&cmm_lock);
		nr--;
L
Linus Torvalds 已提交
96
	}
97
	return nr;
L
Linus Torvalds 已提交
98 99
}

100
static long cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
L
Linus Torvalds 已提交
101 102
{
	struct cmm_page_array *pa;
103
	unsigned long addr;
L
Linus Torvalds 已提交
104

105
	spin_lock(&cmm_lock);
L
Linus Torvalds 已提交
106
	pa = *list;
107
	while (nr) {
L
Linus Torvalds 已提交
108 109
		if (!pa || pa->index <= 0)
			break;
110
		addr = pa->pages[--pa->index];
L
Linus Torvalds 已提交
111 112 113 114 115
		if (pa->index == 0) {
			pa = pa->next;
			free_page((unsigned long) *list);
			*list = pa;
		}
116
		free_page(addr);
L
Linus Torvalds 已提交
117
		(*counter)--;
118
		nr--;
L
Linus Torvalds 已提交
119
	}
120 121
	spin_unlock(&cmm_lock);
	return nr;
L
Linus Torvalds 已提交
122 123
}

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
static int cmm_oom_notify(struct notifier_block *self,
			  unsigned long dummy, void *parm)
{
	unsigned long *freed = parm;
	long nr = 256;

	nr = cmm_free_pages(nr, &cmm_timed_pages, &cmm_timed_page_list);
	if (nr > 0)
		nr = cmm_free_pages(nr, &cmm_pages, &cmm_page_list);
	cmm_pages_target = cmm_pages;
	cmm_timed_pages_target = cmm_timed_pages;
	*freed += 256 - nr;
	return NOTIFY_OK;
}

static struct notifier_block cmm_oom_nb = {
140
	.notifier_call = cmm_oom_notify,
141 142
};

143
static int cmm_thread(void *dummy)
L
Linus Torvalds 已提交
144 145 146 147 148
{
	int rc;

	while (1) {
		rc = wait_event_interruptible(cmm_thread_wait,
149 150 151
			(!cmm_suspended && (cmm_pages != cmm_pages_target ||
			 cmm_timed_pages != cmm_timed_pages_target)) ||
			 kthread_should_stop());
152
		if (kthread_should_stop() || rc == -ERESTARTSYS) {
L
Linus Torvalds 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
			cmm_pages_target = cmm_pages;
			cmm_timed_pages_target = cmm_timed_pages;
			break;
		}
		if (cmm_pages_target > cmm_pages) {
			if (cmm_alloc_pages(1, &cmm_pages, &cmm_page_list))
				cmm_pages_target = cmm_pages;
		} else if (cmm_pages_target < cmm_pages) {
			cmm_free_pages(1, &cmm_pages, &cmm_page_list);
		}
		if (cmm_timed_pages_target > cmm_timed_pages) {
			if (cmm_alloc_pages(1, &cmm_timed_pages,
					   &cmm_timed_page_list))
				cmm_timed_pages_target = cmm_timed_pages;
		} else if (cmm_timed_pages_target < cmm_timed_pages) {
			cmm_free_pages(1, &cmm_timed_pages,
169
				       &cmm_timed_page_list);
L
Linus Torvalds 已提交
170 171 172 173 174 175 176
		}
		if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
			cmm_set_timer();
	}
	return 0;
}

177
static void cmm_kick_thread(void)
L
Linus Torvalds 已提交
178 179 180 181
{
	wake_up(&cmm_thread_wait);
}

182
static void cmm_set_timer(void)
L
Linus Torvalds 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
{
	if (cmm_timed_pages_target <= 0 || cmm_timeout_seconds <= 0) {
		if (timer_pending(&cmm_timer))
			del_timer(&cmm_timer);
		return;
	}
	if (timer_pending(&cmm_timer)) {
		if (mod_timer(&cmm_timer, jiffies + cmm_timeout_seconds*HZ))
			return;
	}
	cmm_timer.function = cmm_timer_fn;
	cmm_timer.data = 0;
	cmm_timer.expires = jiffies + cmm_timeout_seconds*HZ;
	add_timer(&cmm_timer);
}

199
static void cmm_timer_fn(unsigned long ignored)
L
Linus Torvalds 已提交
200
{
201
	long nr;
L
Linus Torvalds 已提交
202

203 204
	nr = cmm_timed_pages_target - cmm_timeout_pages;
	if (nr < 0)
L
Linus Torvalds 已提交
205 206
		cmm_timed_pages_target = 0;
	else
207
		cmm_timed_pages_target = nr;
L
Linus Torvalds 已提交
208 209 210 211
	cmm_kick_thread();
	cmm_set_timer();
}

212
static void cmm_set_pages(long nr)
L
Linus Torvalds 已提交
213
{
214
	cmm_pages_target = nr;
L
Linus Torvalds 已提交
215 216 217
	cmm_kick_thread();
}

218
static long cmm_get_pages(void)
L
Linus Torvalds 已提交
219 220 221 222
{
	return cmm_pages;
}

223
static void cmm_add_timed_pages(long nr)
L
Linus Torvalds 已提交
224
{
225
	cmm_timed_pages_target += nr;
L
Linus Torvalds 已提交
226 227 228
	cmm_kick_thread();
}

229
static long cmm_get_timed_pages(void)
L
Linus Torvalds 已提交
230 231 232 233
{
	return cmm_timed_pages;
}

234
static void cmm_set_timeout(long nr, long seconds)
L
Linus Torvalds 已提交
235
{
236
	cmm_timeout_pages = nr;
L
Linus Torvalds 已提交
237 238 239 240
	cmm_timeout_seconds = seconds;
	cmm_set_timer();
}

241
static int cmm_skip_blanks(char *cp, char **endp)
L
Linus Torvalds 已提交
242 243 244
{
	char *str;

245 246
	for (str = cp; *str == ' ' || *str == '\t'; str++)
		;
L
Linus Torvalds 已提交
247 248 249 250 251 252
	*endp = str;
	return str != cp;
}

static struct ctl_table cmm_table[];

253 254
static int cmm_pages_handler(ctl_table *ctl, int write, void __user *buffer,
			     size_t *lenp, loff_t *ppos)
L
Linus Torvalds 已提交
255 256
{
	char buf[16], *p;
257
	long nr;
L
Linus Torvalds 已提交
258 259 260 261 262 263 264 265 266 267 268 269 270 271
	int len;

	if (!*lenp || (*ppos && !write)) {
		*lenp = 0;
		return 0;
	}

	if (write) {
		len = *lenp;
		if (copy_from_user(buf, buffer,
				   len > sizeof(buf) ? sizeof(buf) : len))
			return -EFAULT;
		buf[sizeof(buf) - 1] = '\0';
		cmm_skip_blanks(buf, &p);
272
		nr = simple_strtoul(p, &p, 0);
L
Linus Torvalds 已提交
273
		if (ctl == &cmm_table[0])
274
			cmm_set_pages(nr);
L
Linus Torvalds 已提交
275
		else
276
			cmm_add_timed_pages(nr);
L
Linus Torvalds 已提交
277 278
	} else {
		if (ctl == &cmm_table[0])
279
			nr = cmm_get_pages();
L
Linus Torvalds 已提交
280
		else
281 282
			nr = cmm_get_timed_pages();
		len = sprintf(buf, "%ld\n", nr);
L
Linus Torvalds 已提交
283 284 285 286 287 288 289 290 291 292
		if (len > *lenp)
			len = *lenp;
		if (copy_to_user(buffer, buf, len))
			return -EFAULT;
	}
	*lenp = len;
	*ppos += len;
	return 0;
}

293 294
static int cmm_timeout_handler(ctl_table *ctl, int write,  void __user *buffer,
			       size_t *lenp, loff_t *ppos)
L
Linus Torvalds 已提交
295 296
{
	char buf[64], *p;
297
	long nr, seconds;
L
Linus Torvalds 已提交
298 299 300 301 302 303 304 305 306 307 308 309 310 311
	int len;

	if (!*lenp || (*ppos && !write)) {
		*lenp = 0;
		return 0;
	}

	if (write) {
		len = *lenp;
		if (copy_from_user(buf, buffer,
				   len > sizeof(buf) ? sizeof(buf) : len))
			return -EFAULT;
		buf[sizeof(buf) - 1] = '\0';
		cmm_skip_blanks(buf, &p);
312
		nr = simple_strtoul(p, &p, 0);
L
Linus Torvalds 已提交
313
		cmm_skip_blanks(p, &p);
314
		seconds = simple_strtoul(p, &p, 0);
315
		cmm_set_timeout(nr, seconds);
L
Linus Torvalds 已提交
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
	} else {
		len = sprintf(buf, "%ld %ld\n",
			      cmm_timeout_pages, cmm_timeout_seconds);
		if (len > *lenp)
			len = *lenp;
		if (copy_to_user(buffer, buf, len))
			return -EFAULT;
	}
	*lenp = len;
	*ppos += len;
	return 0;
}

static struct ctl_table cmm_table[] = {
	{
		.procname	= "cmm_pages",
332
		.mode		= 0644,
333
		.proc_handler	= cmm_pages_handler,
L
Linus Torvalds 已提交
334 335 336
	},
	{
		.procname	= "cmm_timed_pages",
337
		.mode		= 0644,
338
		.proc_handler	= cmm_pages_handler,
L
Linus Torvalds 已提交
339 340 341
	},
	{
		.procname	= "cmm_timeout",
342
		.mode		= 0644,
343
		.proc_handler	= cmm_timeout_handler,
L
Linus Torvalds 已提交
344
	},
345
	{ }
L
Linus Torvalds 已提交
346 347 348 349 350 351 352 353 354
};

static struct ctl_table cmm_dir_table[] = {
	{
		.procname	= "vm",
		.maxlen		= 0,
		.mode		= 0555,
		.child		= cmm_table,
	},
355
	{ }
L
Linus Torvalds 已提交
356 357 358 359
};

#ifdef CONFIG_CMM_IUCV
#define SMSG_PREFIX "CMM"
360
static void cmm_smsg_target(const char *from, char *msg)
L
Linus Torvalds 已提交
361
{
362
	long nr, seconds;
L
Linus Torvalds 已提交
363

364 365
	if (strlen(sender) > 0 && strcmp(from, sender) != 0)
		return;
L
Linus Torvalds 已提交
366 367 368 369 370
	if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
		return;
	if (strncmp(msg, "SHRINK", 6) == 0) {
		if (!cmm_skip_blanks(msg + 6, &msg))
			return;
371
		nr = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
372 373
		cmm_skip_blanks(msg, &msg);
		if (*msg == '\0')
374
			cmm_set_pages(nr);
L
Linus Torvalds 已提交
375 376 377
	} else if (strncmp(msg, "RELEASE", 7) == 0) {
		if (!cmm_skip_blanks(msg + 7, &msg))
			return;
378
		nr = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
379 380
		cmm_skip_blanks(msg, &msg);
		if (*msg == '\0')
381
			cmm_add_timed_pages(nr);
L
Linus Torvalds 已提交
382 383 384
	} else if (strncmp(msg, "REUSE", 5) == 0) {
		if (!cmm_skip_blanks(msg + 5, &msg))
			return;
385
		nr = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
386 387
		if (!cmm_skip_blanks(msg, &msg))
			return;
388
		seconds = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
389 390
		cmm_skip_blanks(msg, &msg);
		if (*msg == '\0')
391
			cmm_set_timeout(nr, seconds);
L
Linus Torvalds 已提交
392 393 394 395
	}
}
#endif

396
static struct ctl_table_header *cmm_sysctl_header;
L
Linus Torvalds 已提交
397

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
static int cmm_suspend(void)
{
	cmm_suspended = 1;
	cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
	cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
	return 0;
}

static int cmm_resume(void)
{
	cmm_suspended = 0;
	cmm_kick_thread();
	return 0;
}

static int cmm_power_event(struct notifier_block *this,
			   unsigned long event, void *ptr)
{
	switch (event) {
	case PM_POST_HIBERNATION:
		return cmm_resume();
	case PM_HIBERNATION_PREPARE:
		return cmm_suspend();
	default:
		return NOTIFY_DONE;
	}
}

static struct notifier_block cmm_power_notifier = {
	.notifier_call = cmm_power_event,
};

430
static int __init cmm_init(void)
L
Linus Torvalds 已提交
431
{
432 433
	int rc = -ENOMEM;

434
	cmm_sysctl_header = register_sysctl_table(cmm_dir_table);
435
	if (!cmm_sysctl_header)
436
		goto out_sysctl;
L
Linus Torvalds 已提交
437
#ifdef CONFIG_CMM_IUCV
438 439 440 441 442 443 444
	/* convert sender to uppercase characters */
	if (sender) {
		int len = strlen(sender);
		while (len--)
			sender[len] = toupper(sender[len]);
	}

445 446 447
	rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
	if (rc < 0)
		goto out_smsg;
L
Linus Torvalds 已提交
448
#endif
449 450 451
	rc = register_oom_notifier(&cmm_oom_nb);
	if (rc < 0)
		goto out_oom_notify;
452 453 454
	rc = register_pm_notifier(&cmm_power_notifier);
	if (rc)
		goto out_pm;
455 456
	cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
	rc = IS_ERR(cmm_thread_ptr) ? PTR_ERR(cmm_thread_ptr) : 0;
457 458 459
	if (rc)
		goto out_kthread;
	return 0;
460

461 462 463 464
out_kthread:
	unregister_pm_notifier(&cmm_power_notifier);
out_pm:
	unregister_oom_notifier(&cmm_oom_nb);
465 466 467 468 469 470
out_oom_notify:
#ifdef CONFIG_CMM_IUCV
	smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
out_smsg:
#endif
	unregister_sysctl_table(cmm_sysctl_header);
471
out_sysctl:
472
	del_timer_sync(&cmm_timer);
473
	return rc;
L
Linus Torvalds 已提交
474
}
475
module_init(cmm_init);
L
Linus Torvalds 已提交
476

477
static void __exit cmm_exit(void)
L
Linus Torvalds 已提交
478 479 480 481 482
{
	unregister_sysctl_table(cmm_sysctl_header);
#ifdef CONFIG_CMM_IUCV
	smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
#endif
483 484 485 486 487 488
	unregister_pm_notifier(&cmm_power_notifier);
	unregister_oom_notifier(&cmm_oom_nb);
	kthread_stop(cmm_thread_ptr);
	del_timer_sync(&cmm_timer);
	cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
	cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
L
Linus Torvalds 已提交
489 490 491 492
}
module_exit(cmm_exit);

MODULE_LICENSE("GPL");