cmm.c 10.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 *  arch/s390/mm/cmm.c
 *
 *  S390 version
 *    Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
 *
 *  Collaborative memory management interface.
 */

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#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>
L
Linus Torvalds 已提交
22 23 24

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

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

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

42 43 44 45 46 47
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;
48
static int cmm_suspended;
L
Linus Torvalds 已提交
49

50 51 52
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 已提交
53

54
static struct task_struct *cmm_thread_ptr;
L
Linus Torvalds 已提交
55 56 57 58 59 60 61
static wait_queue_head_t cmm_thread_wait;
static struct timer_list cmm_timer;

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

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

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

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

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

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

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

	while (1) {
		rc = wait_event_interruptible(cmm_thread_wait,
152 153 154
			(!cmm_suspended && (cmm_pages != cmm_pages_target ||
			 cmm_timed_pages != cmm_timed_pages_target)) ||
			 kthread_should_stop());
155
		if (kthread_should_stop() || rc == -ERESTARTSYS) {
L
Linus Torvalds 已提交
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
			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,
			       	       &cmm_timed_page_list);
		}
		if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
			cmm_set_timer();
	}
	return 0;
}

static void
cmm_kick_thread(void)
{
	wake_up(&cmm_thread_wait);
}

static void
cmm_set_timer(void)
{
	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);
}

static void
cmm_timer_fn(unsigned long ignored)
{
207
	long nr;
L
Linus Torvalds 已提交
208

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

void
219
cmm_set_pages(long nr)
L
Linus Torvalds 已提交
220
{
221
	cmm_pages_target = nr;
L
Linus Torvalds 已提交
222 223 224 225 226 227 228 229 230 231
	cmm_kick_thread();
}

long
cmm_get_pages(void)
{
	return cmm_pages;
}

void
232
cmm_add_timed_pages(long nr)
L
Linus Torvalds 已提交
233
{
234
	cmm_timed_pages_target += nr;
L
Linus Torvalds 已提交
235 236 237 238 239 240 241 242 243 244
	cmm_kick_thread();
}

long
cmm_get_timed_pages(void)
{
	return cmm_timed_pages;
}

void
245
cmm_set_timeout(long nr, long seconds)
L
Linus Torvalds 已提交
246
{
247
	cmm_timeout_pages = nr;
L
Linus Torvalds 已提交
248 249 250 251
	cmm_timeout_seconds = seconds;
	cmm_set_timer();
}

252
static int
L
Linus Torvalds 已提交
253 254 255 256 257 258 259 260 261 262 263 264 265 266
cmm_skip_blanks(char *cp, char **endp)
{
	char *str;

	for (str = cp; *str == ' ' || *str == '\t'; str++);
	*endp = str;
	return str != cp;
}

#ifdef CONFIG_CMM_PROC

static struct ctl_table cmm_table[];

static int
267
cmm_pages_handler(ctl_table *ctl, int write,
268
		  void __user *buffer, size_t *lenp, loff_t *ppos)
L
Linus Torvalds 已提交
269 270
{
	char buf[16], *p;
271
	long nr;
L
Linus Torvalds 已提交
272 273 274 275 276 277 278 279 280 281 282 283 284 285
	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);
286
		nr = simple_strtoul(p, &p, 0);
L
Linus Torvalds 已提交
287
		if (ctl == &cmm_table[0])
288
			cmm_set_pages(nr);
L
Linus Torvalds 已提交
289
		else
290
			cmm_add_timed_pages(nr);
L
Linus Torvalds 已提交
291 292
	} else {
		if (ctl == &cmm_table[0])
293
			nr = cmm_get_pages();
L
Linus Torvalds 已提交
294
		else
295 296
			nr = cmm_get_timed_pages();
		len = sprintf(buf, "%ld\n", nr);
L
Linus Torvalds 已提交
297 298 299 300 301 302 303 304 305 306 307
		if (len > *lenp)
			len = *lenp;
		if (copy_to_user(buffer, buf, len))
			return -EFAULT;
	}
	*lenp = len;
	*ppos += len;
	return 0;
}

static int
308
cmm_timeout_handler(ctl_table *ctl, int write,
309
		    void __user *buffer, size_t *lenp, loff_t *ppos)
L
Linus Torvalds 已提交
310 311
{
	char buf[64], *p;
312
	long nr, seconds;
L
Linus Torvalds 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325 326
	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);
327
		nr = simple_strtoul(p, &p, 0);
L
Linus Torvalds 已提交
328
		cmm_skip_blanks(p, &p);
329
		seconds = simple_strtoul(p, &p, 0);
330
		cmm_set_timeout(nr, seconds);
L
Linus Torvalds 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
	} 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",
347
		.mode		= 0644,
348
		.proc_handler	= cmm_pages_handler,
L
Linus Torvalds 已提交
349 350 351
	},
	{
		.procname	= "cmm_timed_pages",
352
		.mode		= 0644,
353
		.proc_handler	= cmm_pages_handler,
L
Linus Torvalds 已提交
354 355 356
	},
	{
		.procname	= "cmm_timeout",
357
		.mode		= 0644,
358
		.proc_handler	= cmm_timeout_handler,
L
Linus Torvalds 已提交
359
	},
360
	{ }
L
Linus Torvalds 已提交
361 362 363 364 365 366 367 368 369
};

static struct ctl_table cmm_dir_table[] = {
	{
		.procname	= "vm",
		.maxlen		= 0,
		.mode		= 0555,
		.child		= cmm_table,
	},
370
	{ }
L
Linus Torvalds 已提交
371 372 373 374 375 376
};
#endif

#ifdef CONFIG_CMM_IUCV
#define SMSG_PREFIX "CMM"
static void
377
cmm_smsg_target(char *from, char *msg)
L
Linus Torvalds 已提交
378
{
379
	long nr, seconds;
L
Linus Torvalds 已提交
380

381 382
	if (strlen(sender) > 0 && strcmp(from, sender) != 0)
		return;
L
Linus Torvalds 已提交
383 384 385 386 387
	if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
		return;
	if (strncmp(msg, "SHRINK", 6) == 0) {
		if (!cmm_skip_blanks(msg + 6, &msg))
			return;
388
		nr = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
389 390
		cmm_skip_blanks(msg, &msg);
		if (*msg == '\0')
391
			cmm_set_pages(nr);
L
Linus Torvalds 已提交
392 393 394
	} else if (strncmp(msg, "RELEASE", 7) == 0) {
		if (!cmm_skip_blanks(msg + 7, &msg))
			return;
395
		nr = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
396 397
		cmm_skip_blanks(msg, &msg);
		if (*msg == '\0')
398
			cmm_add_timed_pages(nr);
L
Linus Torvalds 已提交
399 400 401
	} else if (strncmp(msg, "REUSE", 5) == 0) {
		if (!cmm_skip_blanks(msg + 5, &msg))
			return;
402
		nr = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
403 404
		if (!cmm_skip_blanks(msg, &msg))
			return;
405
		seconds = simple_strtoul(msg, &msg, 0);
L
Linus Torvalds 已提交
406 407
		cmm_skip_blanks(msg, &msg);
		if (*msg == '\0')
408
			cmm_set_timeout(nr, seconds);
L
Linus Torvalds 已提交
409 410 411 412
	}
}
#endif

413
static struct ctl_table_header *cmm_sysctl_header;
L
Linus Torvalds 已提交
414

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
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,
};

L
Linus Torvalds 已提交
447 448 449
static int
cmm_init (void)
{
450 451
	int rc = -ENOMEM;

L
Linus Torvalds 已提交
452
#ifdef CONFIG_CMM_PROC
453
	cmm_sysctl_header = register_sysctl_table(cmm_dir_table);
454
	if (!cmm_sysctl_header)
455
		goto out_sysctl;
L
Linus Torvalds 已提交
456 457
#endif
#ifdef CONFIG_CMM_IUCV
458 459 460
	rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
	if (rc < 0)
		goto out_smsg;
L
Linus Torvalds 已提交
461
#endif
462 463 464
	rc = register_oom_notifier(&cmm_oom_nb);
	if (rc < 0)
		goto out_oom_notify;
465 466 467
	rc = register_pm_notifier(&cmm_power_notifier);
	if (rc)
		goto out_pm;
L
Linus Torvalds 已提交
468 469
	init_waitqueue_head(&cmm_thread_wait);
	init_timer(&cmm_timer);
470 471
	cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
	rc = IS_ERR(cmm_thread_ptr) ? PTR_ERR(cmm_thread_ptr) : 0;
472 473 474
	if (rc)
		goto out_kthread;
	return 0;
475

476 477 478 479
out_kthread:
	unregister_pm_notifier(&cmm_power_notifier);
out_pm:
	unregister_oom_notifier(&cmm_oom_nb);
480 481 482 483 484 485 486
out_oom_notify:
#ifdef CONFIG_CMM_IUCV
	smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
out_smsg:
#endif
#ifdef CONFIG_CMM_PROC
	unregister_sysctl_table(cmm_sysctl_header);
487
out_sysctl:
488 489
#endif
	return rc;
L
Linus Torvalds 已提交
490 491 492 493 494
}

static void
cmm_exit(void)
{
495
	kthread_stop(cmm_thread_ptr);
496
	unregister_pm_notifier(&cmm_power_notifier);
497
	unregister_oom_notifier(&cmm_oom_nb);
L
Linus Torvalds 已提交
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
	cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
	cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
#ifdef CONFIG_CMM_PROC
	unregister_sysctl_table(cmm_sysctl_header);
#endif
#ifdef CONFIG_CMM_IUCV
	smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
#endif
}

module_init(cmm_init);
module_exit(cmm_exit);

EXPORT_SYMBOL(cmm_set_pages);
EXPORT_SYMBOL(cmm_get_pages);
EXPORT_SYMBOL(cmm_add_timed_pages);
EXPORT_SYMBOL(cmm_get_timed_pages);
EXPORT_SYMBOL(cmm_set_timeout);

MODULE_LICENSE("GPL");