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