Commit 7131c7a6 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] s390: collaborative memory management.

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

Add collaborative memory management interface.
parent 457ed08f
......@@ -250,6 +250,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
......
......@@ -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
#
......
......@@ -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
......
......@@ -3,3 +3,5 @@
#
obj-y := init.o fault.o ioremap.o
obj-$(CONFIG_CMM) += cmm.o
/*
* 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");
......@@ -40,6 +40,19 @@ DEFINE_PER_CPU(struct mmu_gather, mmu_gathers);
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;
......
......@@ -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
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. This option is also available
as a module which will be called iucv.ko. If unsure, say "Y".
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
......
......@@ -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
......
/*
* 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);
/*
* 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 *));
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment