s390/vmcp: make use of contiguous memory allocator

If memory is fragmented it is unlikely that large order memory
allocations succeed. This has been an issue with the vmcp device
driver since a long time, since it requires large physical contiguous
memory ares for large responses.

To hopefully resolve this issue make use of the contiguous memory
allocator (cma). This patch adds a vmcp specific vmcp cma area with a
default size of 4MB. The size can be changed either via the
VMCP_CMA_SIZE config option at compile time or with the "vmcp_cma"
kernel parameter (e.g. "vmcp_cma=16m").

For any vmcp response buffers larger than 16k memory from the cma area
will be allocated. If such an allocation fails, there is a fallback to
the buddy allocator.

Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
Heiko Carstens 2017-08-07 15:16:15 +02:00 committed by Martin Schwidefsky
parent cd4386a931
commit 3f4298427a
6 changed files with 90 additions and 9 deletions

View File

@ -4375,6 +4375,10 @@
decrease the size and leave more room for directly decrease the size and leave more room for directly
mapped kernel RAM. mapped kernel RAM.
vmcp_cma=nn[MG] [KNL,S390]
Sets the memory size reserved for contiguous memory
allocations for the vmcp device driver.
vmhalt= [KNL,S390] Perform z/VM CP command after system halt. vmhalt= [KNL,S390] Perform z/VM CP command after system halt.
Format: <command> Format: <command>

View File

@ -108,6 +108,12 @@ extern void pfault_fini(void);
#define pfault_fini() do { } while (0) #define pfault_fini() do { } while (0)
#endif /* CONFIG_PFAULT */ #endif /* CONFIG_PFAULT */
#ifdef CONFIG_VMCP
void vmcp_cma_reserve(void);
#else
static inline void vmcp_cma_reserve(void) { }
#endif
void report_user_fault(struct pt_regs *regs, long signr, int is_mm_fault); void report_user_fault(struct pt_regs *regs, long signr, int is_mm_fault);
void cmma_init(void); void cmma_init(void);

View File

@ -925,6 +925,7 @@ void __init setup_arch(char **cmdline_p)
setup_memory_end(); setup_memory_end();
setup_memory(); setup_memory();
dma_contiguous_reserve(memory_end); dma_contiguous_reserve(memory_end);
vmcp_cma_reserve();
check_initrd(); check_initrd();
reserve_crashkernel(); reserve_crashkernel();

View File

@ -169,10 +169,21 @@ config VMCP
def_bool y def_bool y
prompt "Support for the z/VM CP interface" prompt "Support for the z/VM CP interface"
depends on S390 depends on S390
select CMA
help help
Select this option if you want to be able to interact with the control Select this option if you want to be able to interact with the control
program on z/VM program on z/VM
config VMCP_CMA_SIZE
int "Memory in MiB reserved for z/VM CP interface"
default "4"
depends on VMCP
help
Specify the default amount of memory in MiB reserved for the z/VM CP
interface. If needed this memory is used for large contiguous memory
allocations. The default can be changed with the kernel command line
parameter "vmcp_cma".
config MONREADER config MONREADER
def_tristate m def_tristate m
prompt "API for reading z/VM monitor service records" prompt "API for reading z/VM monitor service records"

View File

@ -17,15 +17,77 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/miscdevice.h> #include <linux/miscdevice.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/mutex.h>
#include <linux/cma.h>
#include <linux/mm.h>
#include <asm/compat.h> #include <asm/compat.h>
#include <asm/cpcmd.h> #include <asm/cpcmd.h>
#include <asm/debug.h> #include <asm/debug.h>
#include <linux/uaccess.h>
#include "vmcp.h" #include "vmcp.h"
static debug_info_t *vmcp_debug; static debug_info_t *vmcp_debug;
static unsigned long vmcp_cma_size __initdata = CONFIG_VMCP_CMA_SIZE * 1024 * 1024;
static struct cma *vmcp_cma;
static int __init early_parse_vmcp_cma(char *p)
{
vmcp_cma_size = ALIGN(memparse(p, NULL), PAGE_SIZE);
return 0;
}
early_param("vmcp_cma", early_parse_vmcp_cma);
void __init vmcp_cma_reserve(void)
{
if (!MACHINE_IS_VM)
return;
cma_declare_contiguous(0, vmcp_cma_size, 0, 0, 0, false, "vmcp", &vmcp_cma);
}
static void vmcp_response_alloc(struct vmcp_session *session)
{
struct page *page = NULL;
int nr_pages, order;
order = get_order(session->bufsize);
nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
/*
* For anything below order 3 allocations rely on the buddy
* allocator. If such low-order allocations can't be handled
* anymore the system won't work anyway.
*/
if (order > 2)
page = cma_alloc(vmcp_cma, nr_pages, 0, GFP_KERNEL);
if (page) {
session->response = (char *)page_to_phys(page);
session->cma_alloc = 1;
return;
}
session->response = (char *)__get_free_pages(GFP_KERNEL | __GFP_RETRY_MAYFAIL, order);
}
static void vmcp_response_free(struct vmcp_session *session)
{
int nr_pages, order;
struct page *page;
if (!session->response)
return;
order = get_order(session->bufsize);
nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
if (session->cma_alloc) {
page = phys_to_page((unsigned long)session->response);
cma_release(vmcp_cma, page, nr_pages);
session->cma_alloc = 0;
goto out;
}
free_pages((unsigned long)session->response, order);
out:
session->response = NULL;
}
static int vmcp_open(struct inode *inode, struct file *file) static int vmcp_open(struct inode *inode, struct file *file)
{ {
struct vmcp_session *session; struct vmcp_session *session;
@ -51,7 +113,7 @@ static int vmcp_release(struct inode *inode, struct file *file)
session = file->private_data; session = file->private_data;
file->private_data = NULL; file->private_data = NULL;
free_pages((unsigned long)session->response, get_order(session->bufsize)); vmcp_response_free(session);
kfree(session); kfree(session);
return 0; return 0;
} }
@ -97,9 +159,7 @@ vmcp_write(struct file *file, const char __user *buff, size_t count,
return -ERESTARTSYS; return -ERESTARTSYS;
} }
if (!session->response) if (!session->response)
session->response = (char *)__get_free_pages(GFP_KERNEL vmcp_response_alloc(session);
| __GFP_RETRY_MAYFAIL,
get_order(session->bufsize));
if (!session->response) { if (!session->response) {
mutex_unlock(&session->mutex); mutex_unlock(&session->mutex);
kfree(cmd); kfree(cmd);
@ -146,9 +206,7 @@ static long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
mutex_unlock(&session->mutex); mutex_unlock(&session->mutex);
return put_user(temp, argp); return put_user(temp, argp);
case VMCP_SETBUF: case VMCP_SETBUF:
free_pages((unsigned long)session->response, vmcp_response_free(session);
get_order(session->bufsize));
session->response=NULL;
temp = get_user(session->bufsize, argp); temp = get_user(session->bufsize, argp);
if (temp) if (temp)
session->bufsize = PAGE_SIZE; session->bufsize = PAGE_SIZE;

View File

@ -20,8 +20,9 @@
#define VMCP_GETSIZE _IOR(0x10, 3, int) #define VMCP_GETSIZE _IOR(0x10, 3, int)
struct vmcp_session { struct vmcp_session {
unsigned int bufsize;
char *response; char *response;
unsigned int bufsize;
unsigned int cma_alloc : 1;
int resp_size; int resp_size;
int resp_code; int resp_code;
/* As we use copy_from/to_user, which might * /* As we use copy_from/to_user, which might *