18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright IBM Corp. 2004, 2010 48c2ecf20Sopenharmony_ci * Interface implementation for communication with the z/VM control program 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Author(s): Christian Borntraeger <borntraeger@de.ibm.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * z/VMs CP offers the possibility to issue commands via the diagnose code 8 98c2ecf20Sopenharmony_ci * this driver implements a character device that issues these commands and 108c2ecf20Sopenharmony_ci * returns the answer of CP. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * The idea of this driver is based on cpint from Neale Ferguson and #CP in CMS 138c2ecf20Sopenharmony_ci */ 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/fs.h> 168c2ecf20Sopenharmony_ci#include <linux/init.h> 178c2ecf20Sopenharmony_ci#include <linux/compat.h> 188c2ecf20Sopenharmony_ci#include <linux/kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 208c2ecf20Sopenharmony_ci#include <linux/slab.h> 218c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 228c2ecf20Sopenharmony_ci#include <linux/export.h> 238c2ecf20Sopenharmony_ci#include <linux/mutex.h> 248c2ecf20Sopenharmony_ci#include <linux/cma.h> 258c2ecf20Sopenharmony_ci#include <linux/mm.h> 268c2ecf20Sopenharmony_ci#include <asm/cpcmd.h> 278c2ecf20Sopenharmony_ci#include <asm/debug.h> 288c2ecf20Sopenharmony_ci#include <asm/vmcp.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct vmcp_session { 318c2ecf20Sopenharmony_ci char *response; 328c2ecf20Sopenharmony_ci unsigned int bufsize; 338c2ecf20Sopenharmony_ci unsigned int cma_alloc : 1; 348c2ecf20Sopenharmony_ci int resp_size; 358c2ecf20Sopenharmony_ci int resp_code; 368c2ecf20Sopenharmony_ci struct mutex mutex; 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic debug_info_t *vmcp_debug; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic unsigned long vmcp_cma_size __initdata = CONFIG_VMCP_CMA_SIZE * 1024 * 1024; 428c2ecf20Sopenharmony_cistatic struct cma *vmcp_cma; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic int __init early_parse_vmcp_cma(char *p) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci if (!p) 478c2ecf20Sopenharmony_ci return 1; 488c2ecf20Sopenharmony_ci vmcp_cma_size = ALIGN(memparse(p, NULL), PAGE_SIZE); 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ciearly_param("vmcp_cma", early_parse_vmcp_cma); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_civoid __init vmcp_cma_reserve(void) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci if (!MACHINE_IS_VM) 568c2ecf20Sopenharmony_ci return; 578c2ecf20Sopenharmony_ci cma_declare_contiguous(0, vmcp_cma_size, 0, 0, 0, false, "vmcp", &vmcp_cma); 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic void vmcp_response_alloc(struct vmcp_session *session) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct page *page = NULL; 638c2ecf20Sopenharmony_ci int nr_pages, order; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci order = get_order(session->bufsize); 668c2ecf20Sopenharmony_ci nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT; 678c2ecf20Sopenharmony_ci /* 688c2ecf20Sopenharmony_ci * For anything below order 3 allocations rely on the buddy 698c2ecf20Sopenharmony_ci * allocator. If such low-order allocations can't be handled 708c2ecf20Sopenharmony_ci * anymore the system won't work anyway. 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_ci if (order > 2) 738c2ecf20Sopenharmony_ci page = cma_alloc(vmcp_cma, nr_pages, 0, false); 748c2ecf20Sopenharmony_ci if (page) { 758c2ecf20Sopenharmony_ci session->response = (char *)page_to_phys(page); 768c2ecf20Sopenharmony_ci session->cma_alloc = 1; 778c2ecf20Sopenharmony_ci return; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci session->response = (char *)__get_free_pages(GFP_KERNEL | __GFP_RETRY_MAYFAIL, order); 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic void vmcp_response_free(struct vmcp_session *session) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci int nr_pages, order; 858c2ecf20Sopenharmony_ci struct page *page; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (!session->response) 888c2ecf20Sopenharmony_ci return; 898c2ecf20Sopenharmony_ci order = get_order(session->bufsize); 908c2ecf20Sopenharmony_ci nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT; 918c2ecf20Sopenharmony_ci if (session->cma_alloc) { 928c2ecf20Sopenharmony_ci page = phys_to_page((unsigned long)session->response); 938c2ecf20Sopenharmony_ci cma_release(vmcp_cma, page, nr_pages); 948c2ecf20Sopenharmony_ci session->cma_alloc = 0; 958c2ecf20Sopenharmony_ci } else { 968c2ecf20Sopenharmony_ci free_pages((unsigned long)session->response, order); 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci session->response = NULL; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic int vmcp_open(struct inode *inode, struct file *file) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct vmcp_session *session; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 1068c2ecf20Sopenharmony_ci return -EPERM; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci session = kmalloc(sizeof(*session), GFP_KERNEL); 1098c2ecf20Sopenharmony_ci if (!session) 1108c2ecf20Sopenharmony_ci return -ENOMEM; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci session->bufsize = PAGE_SIZE; 1138c2ecf20Sopenharmony_ci session->response = NULL; 1148c2ecf20Sopenharmony_ci session->resp_size = 0; 1158c2ecf20Sopenharmony_ci mutex_init(&session->mutex); 1168c2ecf20Sopenharmony_ci file->private_data = session; 1178c2ecf20Sopenharmony_ci return nonseekable_open(inode, file); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic int vmcp_release(struct inode *inode, struct file *file) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci struct vmcp_session *session; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci session = file->private_data; 1258c2ecf20Sopenharmony_ci file->private_data = NULL; 1268c2ecf20Sopenharmony_ci vmcp_response_free(session); 1278c2ecf20Sopenharmony_ci kfree(session); 1288c2ecf20Sopenharmony_ci return 0; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic ssize_t 1328c2ecf20Sopenharmony_civmcp_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci ssize_t ret; 1358c2ecf20Sopenharmony_ci size_t size; 1368c2ecf20Sopenharmony_ci struct vmcp_session *session; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci session = file->private_data; 1398c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&session->mutex)) 1408c2ecf20Sopenharmony_ci return -ERESTARTSYS; 1418c2ecf20Sopenharmony_ci if (!session->response) { 1428c2ecf20Sopenharmony_ci mutex_unlock(&session->mutex); 1438c2ecf20Sopenharmony_ci return 0; 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci size = min_t(size_t, session->resp_size, session->bufsize); 1468c2ecf20Sopenharmony_ci ret = simple_read_from_buffer(buff, count, ppos, 1478c2ecf20Sopenharmony_ci session->response, size); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci mutex_unlock(&session->mutex); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return ret; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic ssize_t 1558c2ecf20Sopenharmony_civmcp_write(struct file *file, const char __user *buff, size_t count, 1568c2ecf20Sopenharmony_ci loff_t *ppos) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci char *cmd; 1598c2ecf20Sopenharmony_ci struct vmcp_session *session; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci if (count > 240) 1628c2ecf20Sopenharmony_ci return -EINVAL; 1638c2ecf20Sopenharmony_ci cmd = memdup_user_nul(buff, count); 1648c2ecf20Sopenharmony_ci if (IS_ERR(cmd)) 1658c2ecf20Sopenharmony_ci return PTR_ERR(cmd); 1668c2ecf20Sopenharmony_ci session = file->private_data; 1678c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&session->mutex)) { 1688c2ecf20Sopenharmony_ci kfree(cmd); 1698c2ecf20Sopenharmony_ci return -ERESTARTSYS; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci if (!session->response) 1728c2ecf20Sopenharmony_ci vmcp_response_alloc(session); 1738c2ecf20Sopenharmony_ci if (!session->response) { 1748c2ecf20Sopenharmony_ci mutex_unlock(&session->mutex); 1758c2ecf20Sopenharmony_ci kfree(cmd); 1768c2ecf20Sopenharmony_ci return -ENOMEM; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci debug_text_event(vmcp_debug, 1, cmd); 1798c2ecf20Sopenharmony_ci session->resp_size = cpcmd(cmd, session->response, session->bufsize, 1808c2ecf20Sopenharmony_ci &session->resp_code); 1818c2ecf20Sopenharmony_ci mutex_unlock(&session->mutex); 1828c2ecf20Sopenharmony_ci kfree(cmd); 1838c2ecf20Sopenharmony_ci *ppos = 0; /* reset the file pointer after a command */ 1848c2ecf20Sopenharmony_ci return count; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci/* 1898c2ecf20Sopenharmony_ci * These ioctls are available, as the semantics of the diagnose 8 call 1908c2ecf20Sopenharmony_ci * does not fit very well into a Linux call. Diagnose X'08' is described in 1918c2ecf20Sopenharmony_ci * CP Programming Services SC24-6084-00 1928c2ecf20Sopenharmony_ci * 1938c2ecf20Sopenharmony_ci * VMCP_GETCODE: gives the CP return code back to user space 1948c2ecf20Sopenharmony_ci * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8 1958c2ecf20Sopenharmony_ci * expects adjacent pages in real storage and to make matters worse, we 1968c2ecf20Sopenharmony_ci * dont know the size of the response. Therefore we default to PAGESIZE and 1978c2ecf20Sopenharmony_ci * let userspace to change the response size, if userspace expects a bigger 1988c2ecf20Sopenharmony_ci * response 1998c2ecf20Sopenharmony_ci */ 2008c2ecf20Sopenharmony_cistatic long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 2018c2ecf20Sopenharmony_ci{ 2028c2ecf20Sopenharmony_ci struct vmcp_session *session; 2038c2ecf20Sopenharmony_ci int ret = -ENOTTY; 2048c2ecf20Sopenharmony_ci int __user *argp; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci session = file->private_data; 2078c2ecf20Sopenharmony_ci if (is_compat_task()) 2088c2ecf20Sopenharmony_ci argp = compat_ptr(arg); 2098c2ecf20Sopenharmony_ci else 2108c2ecf20Sopenharmony_ci argp = (int __user *)arg; 2118c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&session->mutex)) 2128c2ecf20Sopenharmony_ci return -ERESTARTSYS; 2138c2ecf20Sopenharmony_ci switch (cmd) { 2148c2ecf20Sopenharmony_ci case VMCP_GETCODE: 2158c2ecf20Sopenharmony_ci ret = put_user(session->resp_code, argp); 2168c2ecf20Sopenharmony_ci break; 2178c2ecf20Sopenharmony_ci case VMCP_SETBUF: 2188c2ecf20Sopenharmony_ci vmcp_response_free(session); 2198c2ecf20Sopenharmony_ci ret = get_user(session->bufsize, argp); 2208c2ecf20Sopenharmony_ci if (ret) 2218c2ecf20Sopenharmony_ci session->bufsize = PAGE_SIZE; 2228c2ecf20Sopenharmony_ci if (!session->bufsize || get_order(session->bufsize) > 8) { 2238c2ecf20Sopenharmony_ci session->bufsize = PAGE_SIZE; 2248c2ecf20Sopenharmony_ci ret = -EINVAL; 2258c2ecf20Sopenharmony_ci } 2268c2ecf20Sopenharmony_ci break; 2278c2ecf20Sopenharmony_ci case VMCP_GETSIZE: 2288c2ecf20Sopenharmony_ci ret = put_user(session->resp_size, argp); 2298c2ecf20Sopenharmony_ci break; 2308c2ecf20Sopenharmony_ci default: 2318c2ecf20Sopenharmony_ci break; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci mutex_unlock(&session->mutex); 2348c2ecf20Sopenharmony_ci return ret; 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic const struct file_operations vmcp_fops = { 2388c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2398c2ecf20Sopenharmony_ci .open = vmcp_open, 2408c2ecf20Sopenharmony_ci .release = vmcp_release, 2418c2ecf20Sopenharmony_ci .read = vmcp_read, 2428c2ecf20Sopenharmony_ci .write = vmcp_write, 2438c2ecf20Sopenharmony_ci .unlocked_ioctl = vmcp_ioctl, 2448c2ecf20Sopenharmony_ci .compat_ioctl = vmcp_ioctl, 2458c2ecf20Sopenharmony_ci .llseek = no_llseek, 2468c2ecf20Sopenharmony_ci}; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cistatic struct miscdevice vmcp_dev = { 2498c2ecf20Sopenharmony_ci .name = "vmcp", 2508c2ecf20Sopenharmony_ci .minor = MISC_DYNAMIC_MINOR, 2518c2ecf20Sopenharmony_ci .fops = &vmcp_fops, 2528c2ecf20Sopenharmony_ci}; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int __init vmcp_init(void) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci int ret; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci if (!MACHINE_IS_VM) 2598c2ecf20Sopenharmony_ci return 0; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci vmcp_debug = debug_register("vmcp", 1, 1, 240); 2628c2ecf20Sopenharmony_ci if (!vmcp_debug) 2638c2ecf20Sopenharmony_ci return -ENOMEM; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci ret = debug_register_view(vmcp_debug, &debug_hex_ascii_view); 2668c2ecf20Sopenharmony_ci if (ret) { 2678c2ecf20Sopenharmony_ci debug_unregister(vmcp_debug); 2688c2ecf20Sopenharmony_ci return ret; 2698c2ecf20Sopenharmony_ci } 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci ret = misc_register(&vmcp_dev); 2728c2ecf20Sopenharmony_ci if (ret) 2738c2ecf20Sopenharmony_ci debug_unregister(vmcp_debug); 2748c2ecf20Sopenharmony_ci return ret; 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_cidevice_initcall(vmcp_init); 277