linux/arch/s390/mm/cmm.c
<<
>>
Prefs
   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/errno.h>
  12#include <linux/fs.h>
  13#include <linux/init.h>
  14#include <linux/module.h>
  15#include <linux/sched.h>
  16#include <linux/sysctl.h>
  17#include <linux/ctype.h>
  18#include <linux/swap.h>
  19#include <linux/kthread.h>
  20#include <linux/oom.h>
  21
  22#include <asm/pgalloc.h>
  23#include <asm/uaccess.h>
  24#include <asm/diag.h>
  25
  26static char *sender = "VMRMSVM";
  27module_param(sender, charp, 0400);
  28MODULE_PARM_DESC(sender,
  29                 "Guest name that may send SMSG messages (default VMRMSVM)");
  30
  31#include "../../../drivers/s390/net/smsgiucv.h"
  32
  33#define CMM_NR_PAGES ((PAGE_SIZE / sizeof(unsigned long)) - 2)
  34
  35struct cmm_page_array {
  36        struct cmm_page_array *next;
  37        unsigned long index;
  38        unsigned long pages[CMM_NR_PAGES];
  39};
  40
  41static long cmm_pages;
  42static long cmm_timed_pages;
  43static volatile long cmm_pages_target;
  44static volatile long cmm_timed_pages_target;
  45static long cmm_timeout_pages;
  46static long cmm_timeout_seconds;
  47
  48static struct cmm_page_array *cmm_page_list;
  49static struct cmm_page_array *cmm_timed_page_list;
  50static DEFINE_SPINLOCK(cmm_lock);
  51
  52static struct task_struct *cmm_thread_ptr;
  53static wait_queue_head_t cmm_thread_wait;
  54static struct timer_list cmm_timer;
  55
  56static void cmm_timer_fn(unsigned long);
  57static void cmm_set_timer(void);
  58
  59static long
  60cmm_alloc_pages(long nr, long *counter, struct cmm_page_array **list)
  61{
  62        struct cmm_page_array *pa, *npa;
  63        unsigned long addr;
  64
  65        while (nr) {
  66                addr = __get_free_page(GFP_NOIO);
  67                if (!addr)
  68                        break;
  69                spin_lock(&cmm_lock);
  70                pa = *list;
  71                if (!pa || pa->index >= CMM_NR_PAGES) {
  72                        /* Need a new page for the page list. */
  73                        spin_unlock(&cmm_lock);
  74                        npa = (struct cmm_page_array *)
  75                                __get_free_page(GFP_NOIO);
  76                        if (!npa) {
  77                                free_page(addr);
  78                                break;
  79                        }
  80                        spin_lock(&cmm_lock);
  81                        pa = *list;
  82                        if (!pa || pa->index >= CMM_NR_PAGES) {
  83                                npa->next = pa;
  84                                npa->index = 0;
  85                                pa = npa;
  86                                *list = pa;
  87                        } else
  88                                free_page((unsigned long) npa);
  89                }
  90                diag10(addr);
  91                pa->pages[pa->index++] = addr;
  92                (*counter)++;
  93                spin_unlock(&cmm_lock);
  94                nr--;
  95        }
  96        return nr;
  97}
  98
  99static long
 100cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
 101{
 102        struct cmm_page_array *pa;
 103        unsigned long addr;
 104
 105        spin_lock(&cmm_lock);
 106        pa = *list;
 107        while (nr) {
 108                if (!pa || pa->index <= 0)
 109                        break;
 110                addr = pa->pages[--pa->index];
 111                if (pa->index == 0) {
 112                        pa = pa->next;
 113                        free_page((unsigned long) *list);
 114                        *list = pa;
 115                }
 116                free_page(addr);
 117                (*counter)--;
 118                nr--;
 119        }
 120        spin_unlock(&cmm_lock);
 121        return nr;
 122}
 123
 124static int cmm_oom_notify(struct notifier_block *self,
 125                          unsigned long dummy, void *parm)
 126{
 127        unsigned long *freed = parm;
 128        long nr = 256;
 129
 130        nr = cmm_free_pages(nr, &cmm_timed_pages, &cmm_timed_page_list);
 131        if (nr > 0)
 132                nr = cmm_free_pages(nr, &cmm_pages, &cmm_page_list);
 133        cmm_pages_target = cmm_pages;
 134        cmm_timed_pages_target = cmm_timed_pages;
 135        *freed += 256 - nr;
 136        return NOTIFY_OK;
 137}
 138
 139static struct notifier_block cmm_oom_nb = {
 140        .notifier_call = cmm_oom_notify
 141};
 142
 143static int
 144cmm_thread(void *dummy)
 145{
 146        int rc;
 147
 148        while (1) {
 149                rc = wait_event_interruptible(cmm_thread_wait,
 150                        (cmm_pages != cmm_pages_target ||
 151                         cmm_timed_pages != cmm_timed_pages_target ||
 152                         kthread_should_stop()));
 153                if (kthread_should_stop() || rc == -ERESTARTSYS) {
 154                        cmm_pages_target = cmm_pages;
 155                        cmm_timed_pages_target = cmm_timed_pages;
 156                        break;
 157                }
 158                if (cmm_pages_target > cmm_pages) {
 159                        if (cmm_alloc_pages(1, &cmm_pages, &cmm_page_list))
 160                                cmm_pages_target = cmm_pages;
 161                } else if (cmm_pages_target < cmm_pages) {
 162                        cmm_free_pages(1, &cmm_pages, &cmm_page_list);
 163                }
 164                if (cmm_timed_pages_target > cmm_timed_pages) {
 165                        if (cmm_alloc_pages(1, &cmm_timed_pages,
 166                                           &cmm_timed_page_list))
 167                                cmm_timed_pages_target = cmm_timed_pages;
 168                } else if (cmm_timed_pages_target < cmm_timed_pages) {
 169                        cmm_free_pages(1, &cmm_timed_pages,
 170                                       &cmm_timed_page_list);
 171                }
 172                if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
 173                        cmm_set_timer();
 174        }
 175        return 0;
 176}
 177
 178static void
 179cmm_kick_thread(void)
 180{
 181        wake_up(&cmm_thread_wait);
 182}
 183
 184static void
 185cmm_set_timer(void)
 186{
 187        if (cmm_timed_pages_target <= 0 || cmm_timeout_seconds <= 0) {
 188                if (timer_pending(&cmm_timer))
 189                        del_timer(&cmm_timer);
 190                return;
 191        }
 192        if (timer_pending(&cmm_timer)) {
 193                if (mod_timer(&cmm_timer, jiffies + cmm_timeout_seconds*HZ))
 194                        return;
 195        }
 196        cmm_timer.function = cmm_timer_fn;
 197        cmm_timer.data = 0;
 198        cmm_timer.expires = jiffies + cmm_timeout_seconds*HZ;
 199        add_timer(&cmm_timer);
 200}
 201
 202static void
 203cmm_timer_fn(unsigned long ignored)
 204{
 205        long nr;
 206
 207        nr = cmm_timed_pages_target - cmm_timeout_pages;
 208        if (nr < 0)
 209                cmm_timed_pages_target = 0;
 210        else
 211                cmm_timed_pages_target = nr;
 212        cmm_kick_thread();
 213        cmm_set_timer();
 214}
 215
 216void
 217cmm_set_pages(long nr)
 218{
 219        cmm_pages_target = nr;
 220        cmm_kick_thread();
 221}
 222
 223long
 224cmm_get_pages(void)
 225{
 226        return cmm_pages;
 227}
 228
 229void
 230cmm_add_timed_pages(long nr)
 231{
 232        cmm_timed_pages_target += nr;
 233        cmm_kick_thread();
 234}
 235
 236long
 237cmm_get_timed_pages(void)
 238{
 239        return cmm_timed_pages;
 240}
 241
 242void
 243cmm_set_timeout(long nr, long seconds)
 244{
 245        cmm_timeout_pages = nr;
 246        cmm_timeout_seconds = seconds;
 247        cmm_set_timer();
 248}
 249
 250static int
 251cmm_skip_blanks(char *cp, char **endp)
 252{
 253        char *str;
 254
 255        for (str = cp; *str == ' ' || *str == '\t'; str++);
 256        *endp = str;
 257        return str != cp;
 258}
 259
 260#ifdef CONFIG_CMM_PROC
 261
 262static struct ctl_table cmm_table[];
 263
 264static int
 265cmm_pages_handler(ctl_table *ctl, int write,
 266                  void __user *buffer, size_t *lenp, loff_t *ppos)
 267{
 268        char buf[16], *p;
 269        long nr;
 270        int len;
 271
 272        if (!*lenp || (*ppos && !write)) {
 273                *lenp = 0;
 274                return 0;
 275        }
 276
 277        if (write) {
 278                len = *lenp;
 279                if (copy_from_user(buf, buffer,
 280                                   len > sizeof(buf) ? sizeof(buf) : len))
 281                        return -EFAULT;
 282                buf[sizeof(buf) - 1] = '\0';
 283                cmm_skip_blanks(buf, &p);
 284                nr = simple_strtoul(p, &p, 0);
 285                if (ctl == &cmm_table[0])
 286                        cmm_set_pages(nr);
 287                else
 288                        cmm_add_timed_pages(nr);
 289        } else {
 290                if (ctl == &cmm_table[0])
 291                        nr = cmm_get_pages();
 292                else
 293                        nr = cmm_get_timed_pages();
 294                len = sprintf(buf, "%ld\n", nr);
 295                if (len > *lenp)
 296                        len = *lenp;
 297                if (copy_to_user(buffer, buf, len))
 298                        return -EFAULT;
 299        }
 300        *lenp = len;
 301        *ppos += len;
 302        return 0;
 303}
 304
 305static int
 306cmm_timeout_handler(ctl_table *ctl, int write,
 307                    void __user *buffer, size_t *lenp, loff_t *ppos)
 308{
 309        char buf[64], *p;
 310        long nr, seconds;
 311        int len;
 312
 313        if (!*lenp || (*ppos && !write)) {
 314                *lenp = 0;
 315                return 0;
 316        }
 317
 318        if (write) {
 319                len = *lenp;
 320                if (copy_from_user(buf, buffer,
 321                                   len > sizeof(buf) ? sizeof(buf) : len))
 322                        return -EFAULT;
 323                buf[sizeof(buf) - 1] = '\0';
 324                cmm_skip_blanks(buf, &p);
 325                nr = simple_strtoul(p, &p, 0);
 326                cmm_skip_blanks(p, &p);
 327                seconds = simple_strtoul(p, &p, 0);
 328                cmm_set_timeout(nr, seconds);
 329        } else {
 330                len = sprintf(buf, "%ld %ld\n",
 331                              cmm_timeout_pages, cmm_timeout_seconds);
 332                if (len > *lenp)
 333                        len = *lenp;
 334                if (copy_to_user(buffer, buf, len))
 335                        return -EFAULT;
 336        }
 337        *lenp = len;
 338        *ppos += len;
 339        return 0;
 340}
 341
 342static struct ctl_table cmm_table[] = {
 343        {
 344                .procname       = "cmm_pages",
 345                .mode           = 0644,
 346                .proc_handler   = &cmm_pages_handler,
 347        },
 348        {
 349                .procname       = "cmm_timed_pages",
 350                .mode           = 0644,
 351                .proc_handler   = &cmm_pages_handler,
 352        },
 353        {
 354                .procname       = "cmm_timeout",
 355                .mode           = 0644,
 356                .proc_handler   = &cmm_timeout_handler,
 357        },
 358        { .ctl_name = 0 }
 359};
 360
 361static struct ctl_table cmm_dir_table[] = {
 362        {
 363                .ctl_name       = CTL_VM,
 364                .procname       = "vm",
 365                .maxlen         = 0,
 366                .mode           = 0555,
 367                .child          = cmm_table,
 368        },
 369        { .ctl_name = 0 }
 370};
 371#endif
 372
 373#ifdef CONFIG_CMM_IUCV
 374#define SMSG_PREFIX "CMM"
 375static void
 376cmm_smsg_target(char *from, char *msg)
 377{
 378        long nr, seconds;
 379
 380        if (strlen(sender) > 0 && strcmp(from, sender) != 0)
 381                return;
 382        if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
 383                return;
 384        if (strncmp(msg, "SHRINK", 6) == 0) {
 385                if (!cmm_skip_blanks(msg + 6, &msg))
 386                        return;
 387                nr = simple_strtoul(msg, &msg, 0);
 388                cmm_skip_blanks(msg, &msg);
 389                if (*msg == '\0')
 390                        cmm_set_pages(nr);
 391        } else if (strncmp(msg, "RELEASE", 7) == 0) {
 392                if (!cmm_skip_blanks(msg + 7, &msg))
 393                        return;
 394                nr = simple_strtoul(msg, &msg, 0);
 395                cmm_skip_blanks(msg, &msg);
 396                if (*msg == '\0')
 397                        cmm_add_timed_pages(nr);
 398        } else if (strncmp(msg, "REUSE", 5) == 0) {
 399                if (!cmm_skip_blanks(msg + 5, &msg))
 400                        return;
 401                nr = simple_strtoul(msg, &msg, 0);
 402                if (!cmm_skip_blanks(msg, &msg))
 403                        return;
 404                seconds = simple_strtoul(msg, &msg, 0);
 405                cmm_skip_blanks(msg, &msg);
 406                if (*msg == '\0')
 407                        cmm_set_timeout(nr, seconds);
 408        }
 409}
 410#endif
 411
 412static struct ctl_table_header *cmm_sysctl_header;
 413
 414static int
 415cmm_init (void)
 416{
 417        int rc = -ENOMEM;
 418
 419#ifdef CONFIG_CMM_PROC
 420        cmm_sysctl_header = register_sysctl_table(cmm_dir_table);
 421        if (!cmm_sysctl_header)
 422                goto out;
 423#endif
 424#ifdef CONFIG_CMM_IUCV
 425        rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
 426        if (rc < 0)
 427                goto out_smsg;
 428#endif
 429        rc = register_oom_notifier(&cmm_oom_nb);
 430        if (rc < 0)
 431                goto out_oom_notify;
 432        init_waitqueue_head(&cmm_thread_wait);
 433        init_timer(&cmm_timer);
 434        cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
 435        rc = IS_ERR(cmm_thread_ptr) ? PTR_ERR(cmm_thread_ptr) : 0;
 436        if (!rc)
 437                goto out;
 438        /*
 439         * kthread_create failed. undo all the stuff from above again.
 440         */
 441        unregister_oom_notifier(&cmm_oom_nb);
 442
 443out_oom_notify:
 444#ifdef CONFIG_CMM_IUCV
 445        smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
 446out_smsg:
 447#endif
 448#ifdef CONFIG_CMM_PROC
 449        unregister_sysctl_table(cmm_sysctl_header);
 450#endif
 451out:
 452        return rc;
 453}
 454
 455static void
 456cmm_exit(void)
 457{
 458        kthread_stop(cmm_thread_ptr);
 459        unregister_oom_notifier(&cmm_oom_nb);
 460        cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
 461        cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
 462#ifdef CONFIG_CMM_PROC
 463        unregister_sysctl_table(cmm_sysctl_header);
 464#endif
 465#ifdef CONFIG_CMM_IUCV
 466        smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
 467#endif
 468}
 469
 470module_init(cmm_init);
 471module_exit(cmm_exit);
 472
 473EXPORT_SYMBOL(cmm_set_pages);
 474EXPORT_SYMBOL(cmm_get_pages);
 475EXPORT_SYMBOL(cmm_add_timed_pages);
 476EXPORT_SYMBOL(cmm_get_timed_pages);
 477EXPORT_SYMBOL(cmm_set_timeout);
 478
 479MODULE_LICENSE("GPL");
 480