--- /dev/null
+/*
+ * 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/config.h>
+#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>
+
+#include <asm/pgalloc.h>
+#include <asm/uaccess.h>
+
+#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];
+};
+
+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 struct cmm_page_array *cmm_page_list = 0;
+static struct cmm_page_array *cmm_timed_page_list = 0;
+
+static unsigned long cmm_thread_active = 0;
+static struct work_struct cmm_thread_starter;
+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
+cmm_strtoul(const char *cp, char **endp)
+{
+ 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;
+
+ pa = *list;
+ while (pages) {
+ page = __get_free_page(GFP_NOIO);
+ if (!page)
+ break;
+ if (!pa || pa->index >= CMM_NR_PAGES) {
+ /* Need a new page for the page list. */
+ pa = (struct cmm_page_array *)
+ __get_free_page(GFP_NOIO);
+ if (!pa) {
+ free_page(page);
+ break;
+ }
+ pa->next = *list;
+ pa->index = 0;
+ *list = pa;
+ }
+ if (page < 0x80000000UL)
+ diag10(page);
+ pa->pages[pa->index++] = page;
+ (*counter)++;
+ pages--;
+ }
+ return pages;
+}
+
+static void
+cmm_free_pages(long pages, long *counter, struct cmm_page_array **list)
+{
+ struct cmm_page_array *pa;
+ unsigned long page;
+
+ pa = *list;
+ while (pages) {
+ if (!pa || pa->index <= 0)
+ break;
+ page = pa->pages[--pa->index];
+ if (pa->index == 0) {
+ pa = pa->next;
+ free_page((unsigned long) *list);
+ *list = pa;
+ }
+ free_page(page);
+ (*counter)--;
+ pages--;
+ }
+}
+
+static int
+cmm_thread(void *dummy)
+{
+ int rc;
+
+ daemonize("cmmthread");
+ set_cpus_allowed(current, cpumask_of_cpu(0));
+ 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_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_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);
+}
+
+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)
+{
+ long pages;
+
+ pages = cmm_timed_pages_target - cmm_timeout_pages;
+ if (pages < 0)
+ cmm_timed_pages_target = 0;
+ else
+ cmm_timed_pages_target = pages;
+ cmm_kick_thread();
+ cmm_set_timer();
+}
+
+void
+cmm_set_pages(long pages)
+{
+ cmm_pages_target = pages;
+ cmm_kick_thread();
+}
+
+long
+cmm_get_pages(void)
+{
+ return cmm_pages;
+}
+
+void
+cmm_add_timed_pages(long pages)
+{
+ cmm_timed_pages_target += pages;
+ cmm_kick_thread();
+}
+
+long
+cmm_get_timed_pages(void)
+{
+ return cmm_timed_pages;
+}
+
+void
+cmm_set_timeout(long pages, long seconds)
+{
+ cmm_timeout_pages = pages;
+ cmm_timeout_seconds = seconds;
+ cmm_set_timer();
+}
+
+static inline int
+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
+/* These will someday get removed. */
+#define VM_CMM_PAGES 1111
+#define VM_CMM_TIMED_PAGES 1112
+#define VM_CMM_TIMEOUT 1113
+
+static struct ctl_table cmm_table[];
+
+static int
+cmm_pages_handler(ctl_table *ctl, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ char buf[16], *p;
+ long pages;
+ int len;
+
+ if (!*lenp || (filp->f_pos && !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);
+ pages = cmm_strtoul(p, &p);
+ if (ctl == &cmm_table[0])
+ cmm_set_pages(pages);
+ else
+ cmm_add_timed_pages(pages);
+ } else {
+ if (ctl == &cmm_table[0])
+ pages = cmm_get_pages();
+ else
+ pages = cmm_get_timed_pages();
+ len = sprintf(buf, "%ld\n", pages);
+ if (len > *lenp)
+ len = *lenp;
+ if (copy_to_user(buffer, buf, len))
+ return -EFAULT;
+ }
+ *lenp = len;
+ filp->f_pos += len;
+ return 0;
+}
+
+static int
+cmm_timeout_handler(ctl_table *ctl, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ char buf[64], *p;
+ long pages, seconds;
+ int len;
+
+ if (!*lenp || (filp->f_pos && !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);
+ pages = cmm_strtoul(p, &p);
+ cmm_skip_blanks(p, &p);
+ seconds = cmm_strtoul(p, &p);
+ cmm_set_timeout(pages, seconds);
+ } 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;
+ filp->f_pos += len;
+ return 0;
+}
+
+static struct ctl_table cmm_table[] = {
+ {
+ .ctl_name = VM_CMM_PAGES,
+ .procname = "cmm_pages",
+ .mode = 0600,
+ .proc_handler = &cmm_pages_handler,
+ },
+ {
+ .ctl_name = VM_CMM_TIMED_PAGES,
+ .procname = "cmm_timed_pages",
+ .mode = 0600,
+ .proc_handler = &cmm_pages_handler,
+ },
+ {
+ .ctl_name = VM_CMM_TIMEOUT,
+ .procname = "cmm_timeout",
+ .mode = 0600,
+ .proc_handler = &cmm_timeout_handler,
+ },
+ { .ctl_name = 0 }
+};
+
+static struct ctl_table cmm_dir_table[] = {
+ {
+ .ctl_name = CTL_VM,
+ .procname = "vm",
+ .maxlen = 0,
+ .mode = 0555,
+ .child = cmm_table,
+ },
+ { .ctl_name = 0 }
+};
+#endif
+
+#ifdef CONFIG_CMM_IUCV
+#define SMSG_PREFIX "CMM"
+static void
+cmm_smsg_target(char *msg)
+{
+ long pages, seconds;
+
+ if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
+ return;
+ if (strncmp(msg, "SHRINK", 6) == 0) {
+ if (!cmm_skip_blanks(msg + 6, &msg))
+ return;
+ pages = cmm_strtoul(msg, &msg);
+ cmm_skip_blanks(msg, &msg);
+ if (*msg == '\0')
+ cmm_set_pages(pages);
+ } else if (strncmp(msg, "RELEASE", 7) == 0) {
+ if (!cmm_skip_blanks(msg + 7, &msg))
+ return;
+ pages = cmm_strtoul(msg, &msg);
+ cmm_skip_blanks(msg, &msg);
+ if (*msg == '\0')
+ cmm_add_timed_pages(pages);
+ } else if (strncmp(msg, "REUSE", 5) == 0) {
+ if (!cmm_skip_blanks(msg + 5, &msg))
+ return;
+ pages = cmm_strtoul(msg, &msg);
+ if (!cmm_skip_blanks(msg, &msg))
+ return;
+ seconds = cmm_strtoul(msg, &msg);
+ cmm_skip_blanks(msg, &msg);
+ if (*msg == '\0')
+ cmm_set_timeout(pages, seconds);
+ }
+}
+#endif
+
+struct ctl_table_header *cmm_sysctl_header;
+
+static int
+cmm_init (void)
+{
+#ifdef CONFIG_CMM_PROC
+ cmm_sysctl_header = register_sysctl_table(cmm_dir_table, 1);
+#endif
+#ifdef CONFIG_CMM_IUCV
+ smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
+#endif
+ INIT_WORK(&cmm_thread_starter, (void *) cmm_start_thread, 0);
+ init_waitqueue_head(&cmm_thread_wait);
+ init_timer(&cmm_timer);
+ return 0;
+}
+
+static void
+cmm_exit(void)
+{
+ 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");
--- /dev/null
+/*
+ * IUCV special message driver
+ *
+ * Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <asm/cpcmd.h>
+#include <asm/ebcdic.h>
+
+#include "iucv.h"
+
+struct smsg_callback {
+ struct list_head list;
+ char *prefix;
+ int len;
+ void (*callback)(char *str);
+};
+
+MODULE_AUTHOR
+ ("(C) 2003 IBM Corporation by Martin Schwidefsky (schwidefsky@de.ibm.com)");
+MODULE_DESCRIPTION ("Linux for S/390 IUCV special message driver");
+
+static iucv_handle_t smsg_handle;
+static unsigned short smsg_pathid;
+static spinlock_t smsg_list_lock = SPIN_LOCK_UNLOCKED;
+static struct list_head smsg_list = LIST_HEAD_INIT(smsg_list);
+
+static void
+smsg_connection_complete(iucv_ConnectionComplete *eib, void *pgm_data)
+{
+}
+
+
+static void
+smsg_message_pending(iucv_MessagePending *eib, void *pgm_data)
+{
+ struct smsg_callback *cb;
+ unsigned char *msg;
+ unsigned short len;
+ int rc;
+
+ len = eib->ln1msg2.ipbfln1f;
+ msg = kmalloc(len + 1, GFP_ATOMIC|GFP_DMA);
+ if (!msg) {
+ iucv_reject(eib->ippathid, eib->ipmsgid, eib->iptrgcls);
+ return;
+ }
+ rc = iucv_receive(eib->ippathid, eib->ipmsgid, eib->iptrgcls,
+ msg, len, 0, 0, 0);
+ if (rc == 0) {
+ msg[len] = 0;
+ EBCASC(msg, len);
+ spin_lock(&smsg_list_lock);
+ list_for_each_entry(cb, &smsg_list, list)
+ if (strncmp(msg + 8, cb->prefix, cb->len) == 0) {
+ cb->callback(msg + 8);
+ break;
+ }
+ spin_unlock(&smsg_list_lock);
+ }
+ kfree(msg);
+}
+
+static iucv_interrupt_ops_t smsg_ops = {
+ .ConnectionComplete = smsg_connection_complete,
+ .MessagePending = smsg_message_pending,
+};
+
+static struct device_driver smsg_driver = {
+ .name = "SMSGIUCV",
+ .bus = &iucv_bus,
+};
+
+int
+smsg_register_callback(char *prefix, void (*callback)(char *str))
+{
+ struct smsg_callback *cb;
+
+ cb = kmalloc(sizeof(struct smsg_callback), GFP_KERNEL);
+ if (!cb)
+ return -ENOMEM;
+ cb->prefix = prefix;
+ cb->len = strlen(prefix);
+ cb->callback = callback;
+ spin_lock(&smsg_list_lock);
+ list_add_tail(&cb->list, &smsg_list);
+ spin_unlock(&smsg_list_lock);
+ return 0;
+}
+
+void
+smsg_unregister_callback(char *prefix, void (*callback)(char *str))
+{
+ struct smsg_callback *cb, *tmp;
+
+ spin_lock(&smsg_list_lock);
+ cb = 0;
+ list_for_each_entry(tmp, &smsg_list, list)
+ if (tmp->callback == callback &&
+ strcmp(tmp->prefix, prefix) == 0) {
+ cb = tmp;
+ list_del(&cb->list);
+ break;
+ }
+ spin_unlock(&smsg_list_lock);
+ kfree(cb);
+}
+
+static void __exit
+smsg_exit(void)
+{
+ if (smsg_handle > 0) {
+ cpcmd("SET SMSG OFF", 0, 0);
+ iucv_sever(smsg_pathid, 0);
+ iucv_unregister_program(smsg_handle);
+ driver_unregister(&smsg_driver);
+ }
+ return;
+}
+
+static int __init
+smsg_init(void)
+{
+ static unsigned char pgmmask[24] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+ int rc;
+
+ rc = driver_register(&smsg_driver);
+ if (rc != 0) {
+ printk(KERN_ERR "SMSGIUCV: failed to register driver.\n");
+ return rc;
+ }
+ smsg_handle = iucv_register_program("SMSGIUCV ", "*MSG ",
+ pgmmask, &smsg_ops, 0);
+ if (!smsg_handle) {
+ printk(KERN_ERR "SMSGIUCV: failed to register to iucv");
+ driver_unregister(&smsg_driver);
+ return -EIO; /* better errno ? */
+ }
+ rc = iucv_connect (&smsg_pathid, 1, 0, "*MSG ", 0, 0, 0, 0,
+ smsg_handle, 0);
+ if (rc) {
+ printk(KERN_ERR "SMSGIUCV: failed to connect to *MSG");
+ iucv_unregister_program(smsg_handle);
+ driver_unregister(&smsg_driver);
+ smsg_handle = 0;
+ return -EIO;
+ }
+ cpcmd("SET SMSG IUCV", 0, 0);
+ return 0;
+}
+
+module_init(smsg_init);
+module_exit(smsg_exit);
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(smsg_register_callback);
+EXPORT_SYMBOL(smsg_unregister_callback);