From: Martin Schwidefsky <schwidefsky@de.ibm.com>

Add collaborative memory management interface.


---

 25-akpm/arch/s390/Kconfig             |   26 +
 25-akpm/arch/s390/defconfig           |    3 
 25-akpm/arch/s390/kernel/s390_ksyms.c |    1 
 25-akpm/arch/s390/mm/Makefile         |    2 
 25-akpm/arch/s390/mm/cmm.c            |  445 ++++++++++++++++++++++++++++++++++
 25-akpm/arch/s390/mm/init.c           |   13 
 25-akpm/drivers/s390/net/Kconfig      |   28 +-
 25-akpm/drivers/s390/net/Makefile     |    5 
 25-akpm/drivers/s390/net/smsgiucv.c   |  180 +++++++++++++
 25-akpm/drivers/s390/net/smsgiucv.h   |   10 
 25-akpm/include/asm-s390/pgalloc.h    |    2 
 11 files changed, 709 insertions(+), 6 deletions(-)

diff -puN arch/s390/defconfig~s390-11-collaborative-memory-management arch/s390/defconfig
--- 25/arch/s390/defconfig~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/arch/s390/defconfig	Fri Feb 20 16:00:53 2004
@@ -76,6 +76,7 @@ CONFIG_BINFMT_MISC=m
 # CONFIG_PROCESS_DEBUG is not set
 CONFIG_PFAULT=y
 # CONFIG_SHARED_KERNEL is not set
+# CONFIG_CMM is not set
 # CONFIG_VIRT_TIMER is not set
 # CONFIG_PCMCIA is not set
 
@@ -312,6 +313,8 @@ CONFIG_NET_ETHERNET=y
 CONFIG_LCS=m
 CONFIG_CTC=m
 CONFIG_IUCV=m
+# CONFIG_NETIUCV is not set
+# CONFIG_SMSGIUCV is not set
 CONFIG_QETH=y
 
 #
diff -puN arch/s390/Kconfig~s390-11-collaborative-memory-management arch/s390/Kconfig
--- 25/arch/s390/Kconfig~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/arch/s390/Kconfig	Fri Feb 20 16:00:53 2004
@@ -255,6 +255,32 @@ config SHARED_KERNEL
 	  You should only select this option if you know what you are
 	  doing and want to exploit this feature.
 
+config CMM
+	tristate "Cooperative memory management"
+	help
+	  Select this option, if you want to enable the kernel interface
+	  to reduce the memory size of the system. This is accomplished
+	  by allocating pages of memory and put them "on hold". This only
+	  makes sense for a system running under VM where the unused pages
+	  will be reused by VM for other guest systems. The interface
+	  allows an external monitor to balance memory of many systems.
+	  Everybody who wants to run Linux under VM should select this
+	  option.
+
+config CMM_PROC
+	bool "/proc interface to cooperative memory management"
+	depends on CMM
+	help
+	  Select this option to enable the /proc interface to the
+	  cooperative memory management.
+
+config CMM_IUCV
+	bool "IUCV special message interface to cooperative memory management"
+	depends on CMM && (SMSGIUCV=y || CMM=SMSGIUCV)
+	help
+	  Select this option to enable the special message interface to
+	  the cooperative memory management.
+
 config VIRT_TIMER
 	bool "Virtual CPU timer support"
 	help
diff -puN arch/s390/kernel/s390_ksyms.c~s390-11-collaborative-memory-management arch/s390/kernel/s390_ksyms.c
--- 25/arch/s390/kernel/s390_ksyms.c~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/arch/s390/kernel/s390_ksyms.c	Fri Feb 20 16:00:53 2004
@@ -32,6 +32,7 @@ EXPORT_SYMBOL_NOVERS(_zb_findmap);
 EXPORT_SYMBOL_NOVERS(__copy_from_user_asm);
 EXPORT_SYMBOL_NOVERS(__copy_to_user_asm);
 EXPORT_SYMBOL_NOVERS(__clear_user_asm);
+EXPORT_SYMBOL(diag10);
 
 /*
  * semaphore ops
diff -puN /dev/null arch/s390/mm/cmm.c
--- /dev/null	Thu Apr 11 07:25:15 2002
+++ 25-akpm/arch/s390/mm/cmm.c	Fri Feb 20 16:00:53 2004
@@ -0,0 +1,445 @@
+/*
+ *  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");
diff -puN arch/s390/mm/init.c~s390-11-collaborative-memory-management arch/s390/mm/init.c
--- 25/arch/s390/mm/init.c~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/arch/s390/mm/init.c	Fri Feb 20 16:00:53 2004
@@ -40,6 +40,19 @@ DEFINE_PER_CPU(struct mmu_gather, mmu_ga
 pgd_t swapper_pg_dir[PTRS_PER_PGD] __attribute__((__aligned__(PAGE_SIZE)));
 char  empty_zero_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
 
+void diag10(unsigned long addr)
+{
+#ifdef __s390x__
+        if (addr >= 0x80000000)
+                return;
+        asm volatile ("sam31\n\t"
+                      "diag %0,%0,0x10\n\t"
+                      "sam64" : : "a" (addr) );
+#else
+        asm volatile ("diag %0,%0,0x10" : : "a" (addr) );
+#endif
+}
+
 void show_mem(void)
 {
         int i, total = 0, reserved = 0;
diff -puN arch/s390/mm/Makefile~s390-11-collaborative-memory-management arch/s390/mm/Makefile
--- 25/arch/s390/mm/Makefile~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/arch/s390/mm/Makefile	Fri Feb 20 16:00:53 2004
@@ -3,3 +3,5 @@
 #
 
 obj-y	 := init.o fault.o ioremap.o
+obj-$(CONFIG_CMM) += cmm.o
+
diff -puN drivers/s390/net/Kconfig~s390-11-collaborative-memory-management drivers/s390/net/Kconfig
--- 25/drivers/s390/net/Kconfig~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/drivers/s390/net/Kconfig	Fri Feb 20 16:00:53 2004
@@ -23,12 +23,32 @@ config CTC
 	  called ctc.ko.  If you do not know what it is, it's safe to say "Y".
 
 config IUCV
-	tristate "IUCV device support (VM only)"
-	depends on NETDEVICES
+	tristate "IUCV support (VM only)"
 	help
 	  Select this option if you want to use inter-user communication
-	  vehicle networking under VM or VIF.  This option is also available
-	  as a module which will be called iucv.ko. If unsure, say "Y".
+	  under VM or VIF. If unsure, say "Y" to enable a fast communication
+	  link between VM guests. At boot time the user ID of the guest needs
+	  to be passed to the kernel. Note that both kernels need to be
+	  compiled with this option and both need to be booted with the user ID
+	  of the other VM guest.
+
+config NETIUCV
+	tristate "IUCV network device support (VM only)"
+	depends on IUCV && NETDEVICES
+	help
+	  Select this option if you want to use inter-user communication
+	  vehicle networking under VM or VIF. It enables a fast communication
+	  link between VM guests. Using ifconfig a point-to-point connection
+	  can be established to the Linux for zSeries and S7390 system
+	  running on the other VM guest. This option is also available
+	  as a module which will be called netiucv.ko. If unsure, say "Y".
+
+config SMSGIUCV
+	tristate "IUCV special message support (VM only)"
+	depends on IUCV
+	help
+	  Select this option if you want to be able to receive SMSG messages
+	  from other VM guest systems.
 
 
 config QETH
diff -puN drivers/s390/net/Makefile~s390-11-collaborative-memory-management drivers/s390/net/Makefile
--- 25/drivers/s390/net/Makefile~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/drivers/s390/net/Makefile	Fri Feb 20 16:00:53 2004
@@ -4,9 +4,10 @@
 
 ctc-objs := ctcmain.o ctctty.o
 
-obj-$(CONFIG_IUCV) += iucv.o fsm.o
+obj-$(CONFIG_IUCV) += iucv.o
+obj-$(CONFIG_NETIUCV) += netiucv.o fsm.o
+obj-$(CONFIG_SMSGIUCV) += smsgiucv.o
 obj-$(CONFIG_CTC) += ctc.o fsm.o cu3088.o
-obj-$(CONFIG_IUCV) += netiucv.o
 obj-$(CONFIG_LCS) += lcs.o cu3088.o
 qeth_mod-objs := qeth.o qeth_mpc.o
 obj-$(CONFIG_QETH) += qeth_mod.o
diff -puN /dev/null drivers/s390/net/smsgiucv.c
--- /dev/null	Thu Apr 11 07:25:15 2002
+++ 25-akpm/drivers/s390/net/smsgiucv.c	Fri Feb 20 16:00:53 2004
@@ -0,0 +1,180 @@
+/*
+ * 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);
diff -puN /dev/null drivers/s390/net/smsgiucv.h
--- /dev/null	Thu Apr 11 07:25:15 2002
+++ 25-akpm/drivers/s390/net/smsgiucv.h	Fri Feb 20 16:00:53 2004
@@ -0,0 +1,10 @@
+/*
+ * IUCV special message driver
+ *
+ * Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
+ */
+
+int  smsg_register_callback(char *, void (*)(char *));
+void smsg_unregister_callback(char *, void (*)(char *));
+
diff -puN include/asm-s390/pgalloc.h~s390-11-collaborative-memory-management include/asm-s390/pgalloc.h
--- 25/include/asm-s390/pgalloc.h~s390-11-collaborative-memory-management	Fri Feb 20 16:00:53 2004
+++ 25-akpm/include/asm-s390/pgalloc.h	Fri Feb 20 16:00:53 2004
@@ -21,6 +21,8 @@
 
 #define check_pgt_cache()	do {} while (0)
 
+extern void diag10(unsigned long addr);
+
 /*
  * Allocate and free page tables. The xxx_kernel() versions are
  * used to allocate a kernel page table - this turns on ASN bits

_