linux/arch/s390/mm/cmm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 *  Collaborative memory management interface.
   4 *
   5 *    Copyright IBM Corp 2003, 2010
   6 *    Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>,
   7 *
   8 */
   9
  10#include <linux/errno.h>
  11#include <linux/fs.h>
  12#include <linux/init.h>
  13#include <linux/module.h>
  14#include <linux/moduleparam.h>
  15#include <linux/gfp.h>
  16#include <linux/sched.h>
  17#include <linux/sysctl.h>
  18#include <linux/ctype.h>
  19#include <linux/swap.h>
  20#include <linux/kthread.h>
  21#include <linux/oom.h>
  22#include <linux/uaccess.h>
  23
  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;
  50
  51static struct cmm_page_array *cmm_page_list;
  52static struct cmm_page_array *cmm_timed_page_list;
  53static DEFINE_SPINLOCK(cmm_lock);
  54
  55static struct task_struct *cmm_thread_ptr;
  56static DECLARE_WAIT_QUEUE_HEAD(cmm_thread_wait);
  57
  58static void cmm_timer_fn(struct timer_list *);
  59static void cmm_set_timer(void);
  60static DEFINE_TIMER(cmm_timer, cmm_timer_fn);
  61
  62static long cmm_alloc_pages(long nr, long *counter,
  63                            struct cmm_page_array **list)
  64{
  65        struct cmm_page_array *pa, *npa;
  66        unsigned long addr;
  67
  68        while (nr) {
  69                addr = __get_free_page(GFP_NOIO);
  70                if (!addr)
  71                        break;
  72                spin_lock(&cmm_lock);
  73                pa = *list;
  74                if (!pa || pa->index >= CMM_NR_PAGES) {
  75                        /* Need a new page for the page list. */
  76                        spin_unlock(&cmm_lock);
  77                        npa = (struct cmm_page_array *)
  78                                __get_free_page(GFP_NOIO);
  79                        if (!npa) {
  80                                free_page(addr);
  81                                break;
  82                        }
  83                        spin_lock(&cmm_lock);
  84                        pa = *list;
  85                        if (!pa || pa->index >= CMM_NR_PAGES) {
  86                                npa->next = pa;
  87                                npa->index = 0;
  88                                pa = npa;
  89                                *list = pa;
  90                        } else
  91                                free_page((unsigned long) npa);
  92                }
  93                diag10_range(addr >> PAGE_SHIFT, 1);
  94                pa->pages[pa->index++] = addr;
  95                (*counter)++;
  96                spin_unlock(&cmm_lock);
  97                nr--;
  98        }
  99        return nr;
 100}
 101
 102static long cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
 103{
 104        struct cmm_page_array *pa;
 105        unsigned long addr;
 106
 107        spin_lock(&cmm_lock);
 108        pa = *list;
 109        while (nr) {
 110                if (!pa || pa->index <= 0)
 111                        break;
 112                addr = pa->pages[--pa->index];
 113                if (pa->index == 0) {
 114                        pa = pa->next;
 115                        free_page((unsigned long) *list);
 116                        *list = pa;
 117                }
 118                free_page(addr);
 119                (*counter)--;
 120                nr--;
 121        }
 122        spin_unlock(&cmm_lock);
 123        return nr;
 124}
 125
 126static int cmm_oom_notify(struct notifier_block *self,
 127                          unsigned long dummy, void *parm)
 128{
 129        unsigned long *freed = parm;
 130        long nr = 256;
 131
 132        nr = cmm_free_pages(nr, &cmm_timed_pages, &cmm_timed_page_list);
 133        if (nr > 0)
 134                nr = cmm_free_pages(nr, &cmm_pages, &cmm_page_list);
 135        cmm_pages_target = cmm_pages;
 136        cmm_timed_pages_target = cmm_timed_pages;
 137        *freed += 256 - nr;
 138        return NOTIFY_OK;
 139}
 140
 141static struct notifier_block cmm_oom_nb = {
 142        .notifier_call = cmm_oom_notify,
 143};
 144
 145static int cmm_thread(void *dummy)
 146{
 147        int rc;
 148
 149        while (1) {
 150                rc = wait_event_interruptible(cmm_thread_wait,
 151                        cmm_pages != cmm_pages_target ||
 152                        cmm_timed_pages != cmm_timed_pages_target ||
 153                        kthread_should_stop());
 154                if (kthread_should_stop() || rc == -ERESTARTSYS) {
 155                        cmm_pages_target = cmm_pages;
 156                        cmm_timed_pages_target = cmm_timed_pages;
 157                        break;
 158                }
 159                if (cmm_pages_target > cmm_pages) {
 160                        if (cmm_alloc_pages(1, &cmm_pages, &cmm_page_list))
 161                                cmm_pages_target = cmm_pages;
 162                } else if (cmm_pages_target < cmm_pages) {
 163                        cmm_free_pages(1, &cmm_pages, &cmm_page_list);
 164                }
 165                if (cmm_timed_pages_target > cmm_timed_pages) {
 166                        if (cmm_alloc_pages(1, &cmm_timed_pages,
 167                                           &cmm_timed_page_list))
 168                                cmm_timed_pages_target = cmm_timed_pages;
 169                } else if (cmm_timed_pages_target < cmm_timed_pages) {
 170                        cmm_free_pages(1, &cmm_timed_pages,
 171                                       &cmm_timed_page_list);
 172                }
 173                if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
 174                        cmm_set_timer();
 175        }
 176        return 0;
 177}
 178
 179static void cmm_kick_thread(void)
 180{
 181        wake_up(&cmm_thread_wait);
 182}
 183
 184static void cmm_set_timer(void)
 185{
 186        if (cmm_timed_pages_target <= 0 || cmm_timeout_seconds <= 0) {
 187                if (timer_pending(&cmm_timer))
 188                        del_timer(&cmm_timer);
 189                return;
 190        }
 191        mod_timer(&cmm_timer, jiffies + msecs_to_jiffies(cmm_timeout_seconds * MSEC_PER_SEC));
 192}
 193
 194static void cmm_timer_fn(struct timer_list *unused)
 195{
 196        long nr;
 197
 198        nr = cmm_timed_pages_target - cmm_timeout_pages;
 199        if (nr < 0)
 200                cmm_timed_pages_target = 0;
 201        else
 202                cmm_timed_pages_target = nr;
 203        cmm_kick_thread();
 204        cmm_set_timer();
 205}
 206
 207static void cmm_set_pages(long nr)
 208{
 209        cmm_pages_target = nr;
 210        cmm_kick_thread();
 211}
 212
 213static long cmm_get_pages(void)
 214{
 215        return cmm_pages;
 216}
 217
 218static void cmm_add_timed_pages(long nr)
 219{
 220        cmm_timed_pages_target += nr;
 221        cmm_kick_thread();
 222}
 223
 224static long cmm_get_timed_pages(void)
 225{
 226        return cmm_timed_pages;
 227}
 228
 229static void cmm_set_timeout(long nr, long seconds)
 230{
 231        cmm_timeout_pages = nr;
 232        cmm_timeout_seconds = seconds;
 233        cmm_set_timer();
 234}
 235
 236static int cmm_skip_blanks(char *cp, char **endp)
 237{
 238        char *str;
 239
 240        for (str = cp; *str == ' ' || *str == '\t'; str++)
 241                ;
 242        *endp = str;
 243        return str != cp;
 244}
 245
 246static int cmm_pages_handler(struct ctl_table *ctl, int write,
 247                             void *buffer, size_t *lenp, loff_t *ppos)
 248{
 249        long nr = cmm_get_pages();
 250        struct ctl_table ctl_entry = {
 251                .procname       = ctl->procname,
 252                .data           = &nr,
 253                .maxlen         = sizeof(long),
 254        };
 255        int rc;
 256
 257        rc = proc_doulongvec_minmax(&ctl_entry, write, buffer, lenp, ppos);
 258        if (rc < 0 || !write)
 259                return rc;
 260
 261        cmm_set_pages(nr);
 262        return 0;
 263}
 264
 265static int cmm_timed_pages_handler(struct ctl_table *ctl, int write,
 266                                   void *buffer, size_t *lenp,
 267                                   loff_t *ppos)
 268{
 269        long nr = cmm_get_timed_pages();
 270        struct ctl_table ctl_entry = {
 271                .procname       = ctl->procname,
 272                .data           = &nr,
 273                .maxlen         = sizeof(long),
 274        };
 275        int rc;
 276
 277        rc = proc_doulongvec_minmax(&ctl_entry, write, buffer, lenp, ppos);
 278        if (rc < 0 || !write)
 279                return rc;
 280
 281        cmm_add_timed_pages(nr);
 282        return 0;
 283}
 284
 285static int cmm_timeout_handler(struct ctl_table *ctl, int write,
 286                               void *buffer, size_t *lenp, loff_t *ppos)
 287{
 288        char buf[64], *p;
 289        long nr, seconds;
 290        unsigned int len;
 291
 292        if (!*lenp || (*ppos && !write)) {
 293                *lenp = 0;
 294                return 0;
 295        }
 296
 297        if (write) {
 298                len = min(*lenp, sizeof(buf));
 299                memcpy(buf, buffer, len);
 300                buf[len - 1] = '\0';
 301                cmm_skip_blanks(buf, &p);
 302                nr = simple_strtoul(p, &p, 0);
 303                cmm_skip_blanks(p, &p);
 304                seconds = simple_strtoul(p, &p, 0);
 305                cmm_set_timeout(nr, seconds);
 306                *ppos += *lenp;
 307        } else {
 308                len = sprintf(buf, "%ld %ld\n",
 309                              cmm_timeout_pages, cmm_timeout_seconds);
 310                if (len > *lenp)
 311                        len = *lenp;
 312                memcpy(buffer, buf, len);
 313                *lenp = len;
 314                *ppos += len;
 315        }
 316        return 0;
 317}
 318
 319static struct ctl_table cmm_table[] = {
 320        {
 321                .procname       = "cmm_pages",
 322                .mode           = 0644,
 323                .proc_handler   = cmm_pages_handler,
 324        },
 325        {
 326                .procname       = "cmm_timed_pages",
 327                .mode           = 0644,
 328                .proc_handler   = cmm_timed_pages_handler,
 329        },
 330        {
 331                .procname       = "cmm_timeout",
 332                .mode           = 0644,
 333                .proc_handler   = cmm_timeout_handler,
 334        },
 335        { }
 336};
 337
 338static struct ctl_table cmm_dir_table[] = {
 339        {
 340                .procname       = "vm",
 341                .maxlen         = 0,
 342                .mode           = 0555,
 343                .child          = cmm_table,
 344        },
 345        { }
 346};
 347
 348#ifdef CONFIG_CMM_IUCV
 349#define SMSG_PREFIX "CMM"
 350static void cmm_smsg_target(const char *from, char *msg)
 351{
 352        long nr, seconds;
 353
 354        if (strlen(sender) > 0 && strcmp(from, sender) != 0)
 355                return;
 356        if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
 357                return;
 358        if (strncmp(msg, "SHRINK", 6) == 0) {
 359                if (!cmm_skip_blanks(msg + 6, &msg))
 360                        return;
 361                nr = simple_strtoul(msg, &msg, 0);
 362                cmm_skip_blanks(msg, &msg);
 363                if (*msg == '\0')
 364                        cmm_set_pages(nr);
 365        } else if (strncmp(msg, "RELEASE", 7) == 0) {
 366                if (!cmm_skip_blanks(msg + 7, &msg))
 367                        return;
 368                nr = simple_strtoul(msg, &msg, 0);
 369                cmm_skip_blanks(msg, &msg);
 370                if (*msg == '\0')
 371                        cmm_add_timed_pages(nr);
 372        } else if (strncmp(msg, "REUSE", 5) == 0) {
 373                if (!cmm_skip_blanks(msg + 5, &msg))
 374                        return;
 375                nr = simple_strtoul(msg, &msg, 0);
 376                if (!cmm_skip_blanks(msg, &msg))
 377                        return;
 378                seconds = simple_strtoul(msg, &msg, 0);
 379                cmm_skip_blanks(msg, &msg);
 380                if (*msg == '\0')
 381                        cmm_set_timeout(nr, seconds);
 382        }
 383}
 384#endif
 385
 386static struct ctl_table_header *cmm_sysctl_header;
 387
 388static int __init cmm_init(void)
 389{
 390        int rc = -ENOMEM;
 391
 392        cmm_sysctl_header = register_sysctl_table(cmm_dir_table);
 393        if (!cmm_sysctl_header)
 394                goto out_sysctl;
 395#ifdef CONFIG_CMM_IUCV
 396        /* convert sender to uppercase characters */
 397        if (sender) {
 398                int len = strlen(sender);
 399                while (len--)
 400                        sender[len] = toupper(sender[len]);
 401        } else {
 402                sender = cmm_default_sender;
 403        }
 404
 405        rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
 406        if (rc < 0)
 407                goto out_smsg;
 408#endif
 409        rc = register_oom_notifier(&cmm_oom_nb);
 410        if (rc < 0)
 411                goto out_oom_notify;
 412        cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
 413        if (!IS_ERR(cmm_thread_ptr))
 414                return 0;
 415
 416        rc = PTR_ERR(cmm_thread_ptr);
 417        unregister_oom_notifier(&cmm_oom_nb);
 418out_oom_notify:
 419#ifdef CONFIG_CMM_IUCV
 420        smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
 421out_smsg:
 422#endif
 423        unregister_sysctl_table(cmm_sysctl_header);
 424out_sysctl:
 425        del_timer_sync(&cmm_timer);
 426        return rc;
 427}
 428module_init(cmm_init);
 429
 430static void __exit cmm_exit(void)
 431{
 432        unregister_sysctl_table(cmm_sysctl_header);
 433#ifdef CONFIG_CMM_IUCV
 434        smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
 435#endif
 436        unregister_oom_notifier(&cmm_oom_nb);
 437        kthread_stop(cmm_thread_ptr);
 438        del_timer_sync(&cmm_timer);
 439        cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
 440        cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
 441}
 442module_exit(cmm_exit);
 443
 444MODULE_LICENSE("GPL");
 445