cmm.c 10.7 KB
Newer Older
L
Linus Torvalds 已提交
1
/*
2
 *  Collaborative memory management interface.
L
Linus Torvalds 已提交
3
 *
4
 *    Copyright IBM Corp 2003, 2010
5
 *    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/moduleparam.h>
14
#include <linux/gfp.h>
L
Linus Torvalds 已提交
15 16 17
#include <linux/sched.h>
#include <linux/sysctl.h>
#include <linux/ctype.h>
18
#include <linux/swap.h>
19
#include <linux/kthread.h>
20
#include <linux/oom.h>
21
#include <linux/suspend.h>
22
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
23 24

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

27 28 29 30
#ifdef CONFIG_CMM_IUCV
static char *cmm_default_sender = "VMRMSVM";
#endif
static char *sender;
31
module_param(sender, charp, 0400);
32 33 34
MODULE_PARM_DESC(sender,
		 "Guest name that may send SMSG messages (default VMRMSVM)");

L
Linus Torvalds 已提交
35 36 37 38 39 40 41 42 43 44
#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];
};

45 46 47 48 49 50
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;
51
static int cmm_suspended;
L
Linus Torvalds 已提交
52

53 54 55
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 已提交
56

57
static struct task_struct *cmm_thread_ptr;
58
static DECLARE_WAIT_QUEUE_HEAD(cmm_thread_wait);
59
static DEFINE_TIMER(cmm_timer, NULL);
L
Linus Torvalds 已提交
60 61 62 63

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

64 65
static long cmm_alloc_pages(long nr, long *counter,
			    struct cmm_page_array **list)
L
Linus Torvalds 已提交
66
{
67 68
	struct cmm_page_array *pa, *npa;
	unsigned long addr;
L
Linus Torvalds 已提交
69

70 71 72
	while (nr) {
		addr = __get_free_page(GFP_NOIO);
		if (!addr)
L
Linus Torvalds 已提交
73
			break;
74 75
		spin_lock(&cmm_lock);
		pa = *list;
L
Linus Torvalds 已提交
76 77
		if (!pa || pa->index >= CMM_NR_PAGES) {
			/* Need a new page for the page list. */
78 79
			spin_unlock(&cmm_lock);
			npa = (struct cmm_page_array *)
L
Linus Torvalds 已提交
80
				__get_free_page(GFP_NOIO);
81 82
			if (!npa) {
				free_page(addr);
L
Linus Torvalds 已提交
83 84
				break;
			}
85 86 87 88 89 90 91 92 93
			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 已提交
94
		}
95
		diag10_range(addr >> PAGE_SHIFT, 1);
96
		pa->pages[pa->index++] = addr;
L
Linus Torvalds 已提交
97
		(*counter)++;
98 99
		spin_unlock(&cmm_lock);
		nr--;
L
Linus Torvalds 已提交
100
	}
101
	return nr;
L
Linus Torvalds 已提交
102 103
}

104
static long cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
L
Linus Torvalds 已提交
105 106
{
	struct cmm_page_array *pa;
107
	unsigned long addr;
L
Linus Torvalds 已提交
108

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

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
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 = {
144
	.notifier_call = cmm_oom_notify,
145 146
};

147
static int cmm_thread(void *dummy)
L
Linus Torvalds 已提交
148 149 150 151 152
{
	int rc;

	while (1) {
		rc = wait_event_interruptible(cmm_thread_wait,
153 154 155
			(!cmm_suspended && (cmm_pages != cmm_pages_target ||
			 cmm_timed_pages != cmm_timed_pages_target)) ||
			 kthread_should_stop());
156
		if (kthread_should_stop() || rc == -ERESTARTSYS) {
L
Linus Torvalds 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
			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,
173
				       &cmm_timed_page_list);
L
Linus Torvalds 已提交
174 175 176 177 178 179 180
		}
		if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
			cmm_set_timer();
	}
	return 0;
}

181
static void cmm_kick_thread(void)
L
Linus Torvalds 已提交
182 183 184 185
{
	wake_up(&cmm_thread_wait);
}

186
static void cmm_set_timer(void)
L
Linus Torvalds 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
{
	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);
}

203
static void cmm_timer_fn(unsigned long ignored)
L
Linus Torvalds 已提交
204
{
205
	long nr;
L
Linus Torvalds 已提交
206

207 208
	nr = cmm_timed_pages_target - cmm_timeout_pages;
	if (nr < 0)
L
Linus Torvalds 已提交
209 210
		cmm_timed_pages_target = 0;
	else
211
		cmm_timed_pages_target = nr;
L
Linus Torvalds 已提交
212 213 214 215
	cmm_kick_thread();
	cmm_set_timer();
}

216
static void cmm_set_pages(long nr)
L
Linus Torvalds 已提交
217
{
218
	cmm_pages_target = nr;
L
Linus Torvalds 已提交
219 220 221
	cmm_kick_thread();
}

222
static long cmm_get_pages(void)
L
Linus Torvalds 已提交
223 224 225 226
{
	return cmm_pages;
}

227
static void cmm_add_timed_pages(long nr)
L
Linus Torvalds 已提交
228
{
229
	cmm_timed_pages_target += nr;
L
Linus Torvalds 已提交
230 231 232
	cmm_kick_thread();
}

233
static long cmm_get_timed_pages(void)
L
Linus Torvalds 已提交
234 235 236 237
{
	return cmm_timed_pages;
}

238
static void cmm_set_timeout(long nr, long seconds)
L
Linus Torvalds 已提交
239
{
240
	cmm_timeout_pages = nr;
L
Linus Torvalds 已提交
241 242 243 244
	cmm_timeout_seconds = seconds;
	cmm_set_timer();
}

245
static int cmm_skip_blanks(char *cp, char **endp)
L
Linus Torvalds 已提交
246 247 248
{
	char *str;

249 250
	for (str = cp; *str == ' ' || *str == '\t'; str++)
		;
L
Linus Torvalds 已提交
251 252 253 254 255 256
	*endp = str;
	return str != cp;
}

static struct ctl_table cmm_table[];

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

	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);
276
		nr = simple_strtoul(p, &p, 0);
L
Linus Torvalds 已提交
277
		if (ctl == &cmm_table[0])
278
			cmm_set_pages(nr);
L
Linus Torvalds 已提交
279
		else
280
			cmm_add_timed_pages(nr);
L
Linus Torvalds 已提交
281 282
	} else {
		if (ctl == &cmm_table[0])
283
			nr = cmm_get_pages();
L
Linus Torvalds 已提交
284
		else
285 286
			nr = cmm_get_timed_pages();
		len = sprintf(buf, "%ld\n", nr);
L
Linus Torvalds 已提交
287 288 289 290 291 292 293 294 295 296
		if (len > *lenp)
			len = *lenp;
		if (copy_to_user(buffer, buf, len))
			return -EFAULT;
	}
	*lenp = len;
	*ppos += len;
	return 0;
}

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

	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);
316
		nr = simple_strtoul(p, &p, 0);
L
Linus Torvalds 已提交
317
		cmm_skip_blanks(p, &p);
318
		seconds = simple_strtoul(p, &p, 0);
319
		cmm_set_timeout(nr, seconds);
L
Linus Torvalds 已提交
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
	} 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",
336
		.mode		= 0644,
337
		.proc_handler	= cmm_pages_handler,
L
Linus Torvalds 已提交
338 339 340
	},
	{
		.procname	= "cmm_timed_pages",
341
		.mode		= 0644,
342
		.proc_handler	= cmm_pages_handler,
L
Linus Torvalds 已提交
343 344 345
	},
	{
		.procname	= "cmm_timeout",
346
		.mode		= 0644,
347
		.proc_handler	= cmm_timeout_handler,
L
Linus Torvalds 已提交
348
	},
349
	{ }
L
Linus Torvalds 已提交
350 351 352 353 354 355 356 357 358
};

static struct ctl_table cmm_dir_table[] = {
	{
		.procname	= "vm",
		.maxlen		= 0,
		.mode		= 0555,
		.child		= cmm_table,
	},
359
	{ }
L
Linus Torvalds 已提交
360 361 362 363
};

#ifdef CONFIG_CMM_IUCV
#define SMSG_PREFIX "CMM"
364
static void cmm_smsg_target(const char *from, char *msg)
L
Linus Torvalds 已提交
365
{
366
	long nr, seconds;
L
Linus Torvalds 已提交
367

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

400
static struct ctl_table_header *cmm_sysctl_header;
L
Linus Torvalds 已提交
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 430 431 432 433
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,
};

434
static int __init cmm_init(void)
L
Linus Torvalds 已提交
435
{
436 437
	int rc = -ENOMEM;

438
	cmm_sysctl_header = register_sysctl_table(cmm_dir_table);
439
	if (!cmm_sysctl_header)
440
		goto out_sysctl;
L
Linus Torvalds 已提交
441
#ifdef CONFIG_CMM_IUCV
442 443 444 445 446
	/* convert sender to uppercase characters */
	if (sender) {
		int len = strlen(sender);
		while (len--)
			sender[len] = toupper(sender[len]);
447 448
	} else {
		sender = cmm_default_sender;
449 450
	}

451 452 453
	rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
	if (rc < 0)
		goto out_smsg;
L
Linus Torvalds 已提交
454
#endif
455 456 457
	rc = register_oom_notifier(&cmm_oom_nb);
	if (rc < 0)
		goto out_oom_notify;
458 459 460
	rc = register_pm_notifier(&cmm_power_notifier);
	if (rc)
		goto out_pm;
461
	cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
462 463
	if (!IS_ERR(cmm_thread_ptr))
		return 0;
464

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

481
static void __exit cmm_exit(void)
L
Linus Torvalds 已提交
482 483 484 485 486
{
	unregister_sysctl_table(cmm_sysctl_header);
#ifdef CONFIG_CMM_IUCV
	smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
#endif
487 488 489 490 491 492
	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 已提交
493 494 495 496
}
module_exit(cmm_exit);

MODULE_LICENSE("GPL");