[PATCH] s390: fix cmm kernel thread handling
[sfrench/cifs-2.6.git] / arch / s390 / mm / cmm.c
index 51596f429235ba1252008fd9bd0f270305d2a225..607f50ead1fd4d0c7077487bf39ca3392121effd 100644 (file)
@@ -8,7 +8,6 @@
  *  Collaborative memory management interface.
  */
 
-#include <linux/config.h>
 #include <linux/errno.h>
 #include <linux/fs.h>
 #include <linux/init.h>
@@ -16,6 +15,8 @@
 #include <linux/sched.h>
 #include <linux/sysctl.h>
 #include <linux/ctype.h>
+#include <linux/swap.h>
+#include <linux/kthread.h>
 
 #include <asm/pgalloc.h>
 #include <asm/uaccess.h>
@@ -35,18 +36,18 @@ struct cmm_page_array {
        unsigned long pages[CMM_NR_PAGES];
 };
 
-static long cmm_pages = 0;
-static long cmm_timed_pages = 0;
-static volatile long cmm_pages_target = 0;
-static volatile long cmm_timed_pages_target = 0;
-static long cmm_timeout_pages = 0;
-static long cmm_timeout_seconds = 0;
+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;
 
-static struct cmm_page_array *cmm_page_list = NULL;
-static struct cmm_page_array *cmm_timed_page_list = NULL;
+static struct cmm_page_array *cmm_page_list;
+static struct cmm_page_array *cmm_timed_page_list;
+static DEFINE_SPINLOCK(cmm_lock);
 
-static unsigned long cmm_thread_active = 0;
-static struct work_struct cmm_thread_starter;
+static struct task_struct *cmm_thread_ptr;
 static wait_queue_head_t cmm_thread_wait;
 static struct timer_list cmm_timer;
 
@@ -54,87 +55,100 @@ static void cmm_timer_fn(unsigned long);
 static void cmm_set_timer(void);
 
 static long
-cmm_strtoul(const char *cp, char **endp)
+cmm_alloc_pages(long nr, long *counter, struct cmm_page_array **list)
 {
-       unsigned int base = 10;
-
-       if (*cp == '0') {
-               base = 8;
-               cp++;
-               if ((*cp == 'x' || *cp == 'X') && isxdigit(cp[1])) {
-                       base = 16;
-                       cp++;
-               }
-       }
-       return simple_strtoul(cp, endp, base);
-}
-
-static long
-cmm_alloc_pages(long pages, long *counter, struct cmm_page_array **list)
-{
-       struct cmm_page_array *pa;
-       unsigned long page;
+       struct cmm_page_array *pa, *npa;
+       unsigned long addr;
 
-       pa = *list;
-       while (pages) {
-               page = __get_free_page(GFP_NOIO);
-               if (!page)
+       while (nr) {
+               addr = __get_free_page(GFP_NOIO);
+               if (!addr)
                        break;
+               spin_lock(&cmm_lock);
+               pa = *list;
                if (!pa || pa->index >= CMM_NR_PAGES) {
                        /* Need a new page for the page list. */
-                       pa = (struct cmm_page_array *)
+                       spin_unlock(&cmm_lock);
+                       npa = (struct cmm_page_array *)
                                __get_free_page(GFP_NOIO);
-                       if (!pa) {
-                               free_page(page);
+                       if (!npa) {
+                               free_page(addr);
                                break;
                        }
-                       pa->next = *list;
-                       pa->index = 0;
-                       *list = pa;
+                       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);
                }
-               diag10(page);
-               pa->pages[pa->index++] = page;
+               diag10(addr);
+               pa->pages[pa->index++] = addr;
                (*counter)++;
-               pages--;
+               spin_unlock(&cmm_lock);
+               nr--;
        }
-       return pages;
+       return nr;
 }
 
-static void
-cmm_free_pages(long pages, long *counter, struct cmm_page_array **list)
+static long
+cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
 {
        struct cmm_page_array *pa;
-       unsigned long page;
+       unsigned long addr;
 
+       spin_lock(&cmm_lock);
        pa = *list;
-       while (pages) {
+       while (nr) {
                if (!pa || pa->index <= 0)
                        break;
-               page = pa->pages[--pa->index];
+               addr = pa->pages[--pa->index];
                if (pa->index == 0) {
                        pa = pa->next;
                        free_page((unsigned long) *list);
                        *list = pa;
                }
-               free_page(page);
+               free_page(addr);
                (*counter)--;
-               pages--;
+               nr--;
        }
+       spin_unlock(&cmm_lock);
+       return nr;
+}
+
+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
+};
+
 static int
 cmm_thread(void *dummy)
 {
        int rc;
 
-       daemonize("cmmthread");
        while (1) {
                rc = wait_event_interruptible(cmm_thread_wait,
                        (cmm_pages != cmm_pages_target ||
-                        cmm_timed_pages != cmm_timed_pages_target));
-               if (rc == -ERESTARTSYS) {
-                       /* Got kill signal. End thread. */
-                       clear_bit(0, &cmm_thread_active);
+                        cmm_timed_pages != cmm_timed_pages_target ||
+                        kthread_should_stop()));
+               if (kthread_should_stop() || rc == -ERESTARTSYS) {
                        cmm_pages_target = cmm_pages;
                        cmm_timed_pages_target = cmm_timed_pages;
                        break;
@@ -159,17 +173,9 @@ cmm_thread(void *dummy)
        return 0;
 }
 
-static void
-cmm_start_thread(void)
-{
-       kernel_thread(cmm_thread, 0, 0);
-}
-
 static void
 cmm_kick_thread(void)
 {
-       if (!test_and_set_bit(0, &cmm_thread_active))
-               schedule_work(&cmm_thread_starter);
        wake_up(&cmm_thread_wait);
 }
 
@@ -194,21 +200,21 @@ cmm_set_timer(void)
 static void
 cmm_timer_fn(unsigned long ignored)
 {
-       long pages;
+       long nr;
 
-       pages = cmm_timed_pages_target - cmm_timeout_pages;
-       if (pages < 0)
+       nr = cmm_timed_pages_target - cmm_timeout_pages;
+       if (nr < 0)
                cmm_timed_pages_target = 0;
        else
-               cmm_timed_pages_target = pages;
+               cmm_timed_pages_target = nr;
        cmm_kick_thread();
        cmm_set_timer();
 }
 
 void
-cmm_set_pages(long pages)
+cmm_set_pages(long nr)
 {
-       cmm_pages_target = pages;
+       cmm_pages_target = nr;
        cmm_kick_thread();
 }
 
@@ -219,9 +225,9 @@ cmm_get_pages(void)
 }
 
 void
-cmm_add_timed_pages(long pages)
+cmm_add_timed_pages(long nr)
 {
-       cmm_timed_pages_target += pages;
+       cmm_timed_pages_target += nr;
        cmm_kick_thread();
 }
 
@@ -232,9 +238,9 @@ cmm_get_timed_pages(void)
 }
 
 void
-cmm_set_timeout(long pages, long seconds)
+cmm_set_timeout(long nr, long seconds)
 {
-       cmm_timeout_pages = pages;
+       cmm_timeout_pages = nr;
        cmm_timeout_seconds = seconds;
        cmm_set_timer();
 }
@@ -262,7 +268,7 @@ cmm_pages_handler(ctl_table *ctl, int write, struct file *filp,
                  void __user *buffer, size_t *lenp, loff_t *ppos)
 {
        char buf[16], *p;
-       long pages;
+       long nr;
        int len;
 
        if (!*lenp || (*ppos && !write)) {
@@ -277,17 +283,17 @@ cmm_pages_handler(ctl_table *ctl, int write, struct file *filp,
                        return -EFAULT;
                buf[sizeof(buf) - 1] = '\0';
                cmm_skip_blanks(buf, &p);
-               pages = cmm_strtoul(p, &p);
+               nr = simple_strtoul(p, &p, 0);
                if (ctl == &cmm_table[0])
-                       cmm_set_pages(pages);
+                       cmm_set_pages(nr);
                else
-                       cmm_add_timed_pages(pages);
+                       cmm_add_timed_pages(nr);
        } else {
                if (ctl == &cmm_table[0])
-                       pages = cmm_get_pages();
+                       nr = cmm_get_pages();
                else
-                       pages = cmm_get_timed_pages();
-               len = sprintf(buf, "%ld\n", pages);
+                       nr = cmm_get_timed_pages();
+               len = sprintf(buf, "%ld\n", nr);
                if (len > *lenp)
                        len = *lenp;
                if (copy_to_user(buffer, buf, len))
@@ -303,7 +309,7 @@ cmm_timeout_handler(ctl_table *ctl, int write, struct file *filp,
                    void __user *buffer, size_t *lenp, loff_t *ppos)
 {
        char buf[64], *p;
-       long pages, seconds;
+       long nr, seconds;
        int len;
 
        if (!*lenp || (*ppos && !write)) {
@@ -318,10 +324,10 @@ cmm_timeout_handler(ctl_table *ctl, int write, struct file *filp,
                        return -EFAULT;
                buf[sizeof(buf) - 1] = '\0';
                cmm_skip_blanks(buf, &p);
-               pages = cmm_strtoul(p, &p);
+               nr = simple_strtoul(p, &p, 0);
                cmm_skip_blanks(p, &p);
-               seconds = cmm_strtoul(p, &p);
-               cmm_set_timeout(pages, seconds);
+               seconds = simple_strtoul(p, &p, 0);
+               cmm_set_timeout(nr, seconds);
        } else {
                len = sprintf(buf, "%ld %ld\n",
                              cmm_timeout_pages, cmm_timeout_seconds);
@@ -374,7 +380,7 @@ static struct ctl_table cmm_dir_table[] = {
 static void
 cmm_smsg_target(char *from, char *msg)
 {
-       long pages, seconds;
+       long nr, seconds;
 
        if (strlen(sender) > 0 && strcmp(from, sender) != 0)
                return;
@@ -383,27 +389,27 @@ cmm_smsg_target(char *from, char *msg)
        if (strncmp(msg, "SHRINK", 6) == 0) {
                if (!cmm_skip_blanks(msg + 6, &msg))
                        return;
-               pages = cmm_strtoul(msg, &msg);
+               nr = simple_strtoul(msg, &msg, 0);
                cmm_skip_blanks(msg, &msg);
                if (*msg == '\0')
-                       cmm_set_pages(pages);
+                       cmm_set_pages(nr);
        } else if (strncmp(msg, "RELEASE", 7) == 0) {
                if (!cmm_skip_blanks(msg + 7, &msg))
                        return;
-               pages = cmm_strtoul(msg, &msg);
+               nr = simple_strtoul(msg, &msg, 0);
                cmm_skip_blanks(msg, &msg);
                if (*msg == '\0')
-                       cmm_add_timed_pages(pages);
+                       cmm_add_timed_pages(nr);
        } else if (strncmp(msg, "REUSE", 5) == 0) {
                if (!cmm_skip_blanks(msg + 5, &msg))
                        return;
-               pages = cmm_strtoul(msg, &msg);
+               nr = simple_strtoul(msg, &msg, 0);
                if (!cmm_skip_blanks(msg, &msg))
                        return;
-               seconds = cmm_strtoul(msg, &msg);
+               seconds = simple_strtoul(msg, &msg, 0);
                cmm_skip_blanks(msg, &msg);
                if (*msg == '\0')
-                       cmm_set_timeout(pages, seconds);
+                       cmm_set_timeout(nr, seconds);
        }
 }
 #endif
@@ -413,21 +419,49 @@ struct ctl_table_header *cmm_sysctl_header;
 static int
 cmm_init (void)
 {
+       int rc = -ENOMEM;
+
 #ifdef CONFIG_CMM_PROC
        cmm_sysctl_header = register_sysctl_table(cmm_dir_table, 1);
+       if (!cmm_sysctl_header)
+               goto out;
 #endif
 #ifdef CONFIG_CMM_IUCV
-       smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
+       rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
+       if (rc < 0)
+               goto out_smsg;
 #endif
-       INIT_WORK(&cmm_thread_starter, (void *) cmm_start_thread, NULL);
+       rc = register_oom_notifier(&cmm_oom_nb);
+       if (rc < 0)
+               goto out_oom_notify;
        init_waitqueue_head(&cmm_thread_wait);
        init_timer(&cmm_timer);
-       return 0;
+       cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
+       rc = IS_ERR(cmm_thread_ptr) ? PTR_ERR(cmm_thread_ptr) : 0;
+       if (!rc)
+               goto out;
+       /*
+        * kthread_create failed. undo all the stuff from above again.
+        */
+       unregister_oom_notifier(&cmm_oom_nb);
+
+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);
+#endif
+out:
+       return rc;
 }
 
 static void
 cmm_exit(void)
 {
+       kthread_stop(cmm_thread_ptr);
+       unregister_oom_notifier(&cmm_oom_nb);
        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