162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright IBM Corp. 2004, 2010 462306a36Sopenharmony_ci * Interface implementation for communication with the z/VM control program 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Author(s): Christian Borntraeger <borntraeger@de.ibm.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * z/VMs CP offers the possibility to issue commands via the diagnose code 8 962306a36Sopenharmony_ci * this driver implements a character device that issues these commands and 1062306a36Sopenharmony_ci * returns the answer of CP. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * The idea of this driver is based on cpint from Neale Ferguson and #CP in CMS 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/fs.h> 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci#include <linux/compat.h> 1862306a36Sopenharmony_ci#include <linux/kernel.h> 1962306a36Sopenharmony_ci#include <linux/miscdevice.h> 2062306a36Sopenharmony_ci#include <linux/slab.h> 2162306a36Sopenharmony_ci#include <linux/uaccess.h> 2262306a36Sopenharmony_ci#include <linux/export.h> 2362306a36Sopenharmony_ci#include <linux/mutex.h> 2462306a36Sopenharmony_ci#include <linux/cma.h> 2562306a36Sopenharmony_ci#include <linux/mm.h> 2662306a36Sopenharmony_ci#include <asm/cpcmd.h> 2762306a36Sopenharmony_ci#include <asm/debug.h> 2862306a36Sopenharmony_ci#include <asm/vmcp.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistruct vmcp_session { 3162306a36Sopenharmony_ci char *response; 3262306a36Sopenharmony_ci unsigned int bufsize; 3362306a36Sopenharmony_ci unsigned int cma_alloc : 1; 3462306a36Sopenharmony_ci int resp_size; 3562306a36Sopenharmony_ci int resp_code; 3662306a36Sopenharmony_ci struct mutex mutex; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic debug_info_t *vmcp_debug; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic unsigned long vmcp_cma_size __initdata = CONFIG_VMCP_CMA_SIZE * 1024 * 1024; 4262306a36Sopenharmony_cistatic struct cma *vmcp_cma; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int __init early_parse_vmcp_cma(char *p) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci if (!p) 4762306a36Sopenharmony_ci return 1; 4862306a36Sopenharmony_ci vmcp_cma_size = ALIGN(memparse(p, NULL), PAGE_SIZE); 4962306a36Sopenharmony_ci return 0; 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ciearly_param("vmcp_cma", early_parse_vmcp_cma); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_civoid __init vmcp_cma_reserve(void) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci if (!MACHINE_IS_VM) 5662306a36Sopenharmony_ci return; 5762306a36Sopenharmony_ci cma_declare_contiguous(0, vmcp_cma_size, 0, 0, 0, false, "vmcp", &vmcp_cma); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic void vmcp_response_alloc(struct vmcp_session *session) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct page *page = NULL; 6362306a36Sopenharmony_ci int nr_pages, order; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci order = get_order(session->bufsize); 6662306a36Sopenharmony_ci nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT; 6762306a36Sopenharmony_ci /* 6862306a36Sopenharmony_ci * For anything below order 3 allocations rely on the buddy 6962306a36Sopenharmony_ci * allocator. If such low-order allocations can't be handled 7062306a36Sopenharmony_ci * anymore the system won't work anyway. 7162306a36Sopenharmony_ci */ 7262306a36Sopenharmony_ci if (order > 2) 7362306a36Sopenharmony_ci page = cma_alloc(vmcp_cma, nr_pages, 0, false); 7462306a36Sopenharmony_ci if (page) { 7562306a36Sopenharmony_ci session->response = (char *)page_to_virt(page); 7662306a36Sopenharmony_ci session->cma_alloc = 1; 7762306a36Sopenharmony_ci return; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci session->response = (char *)__get_free_pages(GFP_KERNEL | __GFP_RETRY_MAYFAIL, order); 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic void vmcp_response_free(struct vmcp_session *session) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci int nr_pages, order; 8562306a36Sopenharmony_ci struct page *page; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (!session->response) 8862306a36Sopenharmony_ci return; 8962306a36Sopenharmony_ci order = get_order(session->bufsize); 9062306a36Sopenharmony_ci nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT; 9162306a36Sopenharmony_ci if (session->cma_alloc) { 9262306a36Sopenharmony_ci page = virt_to_page(session->response); 9362306a36Sopenharmony_ci cma_release(vmcp_cma, page, nr_pages); 9462306a36Sopenharmony_ci session->cma_alloc = 0; 9562306a36Sopenharmony_ci } else { 9662306a36Sopenharmony_ci free_pages((unsigned long)session->response, order); 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci session->response = NULL; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic int vmcp_open(struct inode *inode, struct file *file) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct vmcp_session *session; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 10662306a36Sopenharmony_ci return -EPERM; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci session = kmalloc(sizeof(*session), GFP_KERNEL); 10962306a36Sopenharmony_ci if (!session) 11062306a36Sopenharmony_ci return -ENOMEM; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci session->bufsize = PAGE_SIZE; 11362306a36Sopenharmony_ci session->response = NULL; 11462306a36Sopenharmony_ci session->resp_size = 0; 11562306a36Sopenharmony_ci mutex_init(&session->mutex); 11662306a36Sopenharmony_ci file->private_data = session; 11762306a36Sopenharmony_ci return nonseekable_open(inode, file); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic int vmcp_release(struct inode *inode, struct file *file) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct vmcp_session *session; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci session = file->private_data; 12562306a36Sopenharmony_ci file->private_data = NULL; 12662306a36Sopenharmony_ci vmcp_response_free(session); 12762306a36Sopenharmony_ci kfree(session); 12862306a36Sopenharmony_ci return 0; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic ssize_t 13262306a36Sopenharmony_civmcp_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci ssize_t ret; 13562306a36Sopenharmony_ci size_t size; 13662306a36Sopenharmony_ci struct vmcp_session *session; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci session = file->private_data; 13962306a36Sopenharmony_ci if (mutex_lock_interruptible(&session->mutex)) 14062306a36Sopenharmony_ci return -ERESTARTSYS; 14162306a36Sopenharmony_ci if (!session->response) { 14262306a36Sopenharmony_ci mutex_unlock(&session->mutex); 14362306a36Sopenharmony_ci return 0; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci size = min_t(size_t, session->resp_size, session->bufsize); 14662306a36Sopenharmony_ci ret = simple_read_from_buffer(buff, count, ppos, 14762306a36Sopenharmony_ci session->response, size); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci mutex_unlock(&session->mutex); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return ret; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic ssize_t 15562306a36Sopenharmony_civmcp_write(struct file *file, const char __user *buff, size_t count, 15662306a36Sopenharmony_ci loff_t *ppos) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci char *cmd; 15962306a36Sopenharmony_ci struct vmcp_session *session; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci if (count > 240) 16262306a36Sopenharmony_ci return -EINVAL; 16362306a36Sopenharmony_ci cmd = memdup_user_nul(buff, count); 16462306a36Sopenharmony_ci if (IS_ERR(cmd)) 16562306a36Sopenharmony_ci return PTR_ERR(cmd); 16662306a36Sopenharmony_ci session = file->private_data; 16762306a36Sopenharmony_ci if (mutex_lock_interruptible(&session->mutex)) { 16862306a36Sopenharmony_ci kfree(cmd); 16962306a36Sopenharmony_ci return -ERESTARTSYS; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci if (!session->response) 17262306a36Sopenharmony_ci vmcp_response_alloc(session); 17362306a36Sopenharmony_ci if (!session->response) { 17462306a36Sopenharmony_ci mutex_unlock(&session->mutex); 17562306a36Sopenharmony_ci kfree(cmd); 17662306a36Sopenharmony_ci return -ENOMEM; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci debug_text_event(vmcp_debug, 1, cmd); 17962306a36Sopenharmony_ci session->resp_size = cpcmd(cmd, session->response, session->bufsize, 18062306a36Sopenharmony_ci &session->resp_code); 18162306a36Sopenharmony_ci mutex_unlock(&session->mutex); 18262306a36Sopenharmony_ci kfree(cmd); 18362306a36Sopenharmony_ci *ppos = 0; /* reset the file pointer after a command */ 18462306a36Sopenharmony_ci return count; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/* 18962306a36Sopenharmony_ci * These ioctls are available, as the semantics of the diagnose 8 call 19062306a36Sopenharmony_ci * does not fit very well into a Linux call. Diagnose X'08' is described in 19162306a36Sopenharmony_ci * CP Programming Services SC24-6084-00 19262306a36Sopenharmony_ci * 19362306a36Sopenharmony_ci * VMCP_GETCODE: gives the CP return code back to user space 19462306a36Sopenharmony_ci * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8 19562306a36Sopenharmony_ci * expects adjacent pages in real storage and to make matters worse, we 19662306a36Sopenharmony_ci * dont know the size of the response. Therefore we default to PAGESIZE and 19762306a36Sopenharmony_ci * let userspace to change the response size, if userspace expects a bigger 19862306a36Sopenharmony_ci * response 19962306a36Sopenharmony_ci */ 20062306a36Sopenharmony_cistatic long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci struct vmcp_session *session; 20362306a36Sopenharmony_ci int ret = -ENOTTY; 20462306a36Sopenharmony_ci int __user *argp; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci session = file->private_data; 20762306a36Sopenharmony_ci if (is_compat_task()) 20862306a36Sopenharmony_ci argp = compat_ptr(arg); 20962306a36Sopenharmony_ci else 21062306a36Sopenharmony_ci argp = (int __user *)arg; 21162306a36Sopenharmony_ci if (mutex_lock_interruptible(&session->mutex)) 21262306a36Sopenharmony_ci return -ERESTARTSYS; 21362306a36Sopenharmony_ci switch (cmd) { 21462306a36Sopenharmony_ci case VMCP_GETCODE: 21562306a36Sopenharmony_ci ret = put_user(session->resp_code, argp); 21662306a36Sopenharmony_ci break; 21762306a36Sopenharmony_ci case VMCP_SETBUF: 21862306a36Sopenharmony_ci vmcp_response_free(session); 21962306a36Sopenharmony_ci ret = get_user(session->bufsize, argp); 22062306a36Sopenharmony_ci if (ret) 22162306a36Sopenharmony_ci session->bufsize = PAGE_SIZE; 22262306a36Sopenharmony_ci if (!session->bufsize || get_order(session->bufsize) > 8) { 22362306a36Sopenharmony_ci session->bufsize = PAGE_SIZE; 22462306a36Sopenharmony_ci ret = -EINVAL; 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci break; 22762306a36Sopenharmony_ci case VMCP_GETSIZE: 22862306a36Sopenharmony_ci ret = put_user(session->resp_size, argp); 22962306a36Sopenharmony_ci break; 23062306a36Sopenharmony_ci default: 23162306a36Sopenharmony_ci break; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci mutex_unlock(&session->mutex); 23462306a36Sopenharmony_ci return ret; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic const struct file_operations vmcp_fops = { 23862306a36Sopenharmony_ci .owner = THIS_MODULE, 23962306a36Sopenharmony_ci .open = vmcp_open, 24062306a36Sopenharmony_ci .release = vmcp_release, 24162306a36Sopenharmony_ci .read = vmcp_read, 24262306a36Sopenharmony_ci .write = vmcp_write, 24362306a36Sopenharmony_ci .unlocked_ioctl = vmcp_ioctl, 24462306a36Sopenharmony_ci .compat_ioctl = vmcp_ioctl, 24562306a36Sopenharmony_ci .llseek = no_llseek, 24662306a36Sopenharmony_ci}; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic struct miscdevice vmcp_dev = { 24962306a36Sopenharmony_ci .name = "vmcp", 25062306a36Sopenharmony_ci .minor = MISC_DYNAMIC_MINOR, 25162306a36Sopenharmony_ci .fops = &vmcp_fops, 25262306a36Sopenharmony_ci}; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int __init vmcp_init(void) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci int ret; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (!MACHINE_IS_VM) 25962306a36Sopenharmony_ci return 0; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci vmcp_debug = debug_register("vmcp", 1, 1, 240); 26262306a36Sopenharmony_ci if (!vmcp_debug) 26362306a36Sopenharmony_ci return -ENOMEM; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci ret = debug_register_view(vmcp_debug, &debug_hex_ascii_view); 26662306a36Sopenharmony_ci if (ret) { 26762306a36Sopenharmony_ci debug_unregister(vmcp_debug); 26862306a36Sopenharmony_ci return ret; 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci ret = misc_register(&vmcp_dev); 27262306a36Sopenharmony_ci if (ret) 27362306a36Sopenharmony_ci debug_unregister(vmcp_debug); 27462306a36Sopenharmony_ci return ret; 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_cidevice_initcall(vmcp_init); 277