cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

vmcp.c (6720B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * Copyright IBM Corp. 2004, 2010
      4 * Interface implementation for communication with the z/VM control program
      5 *
      6 * Author(s): Christian Borntraeger <borntraeger@de.ibm.com>
      7 *
      8 * z/VMs CP offers the possibility to issue commands via the diagnose code 8
      9 * this driver implements a character device that issues these commands and
     10 * returns the answer of CP.
     11 *
     12 * The idea of this driver is based on cpint from Neale Ferguson and #CP in CMS
     13 */
     14
     15#include <linux/fs.h>
     16#include <linux/init.h>
     17#include <linux/compat.h>
     18#include <linux/kernel.h>
     19#include <linux/miscdevice.h>
     20#include <linux/slab.h>
     21#include <linux/uaccess.h>
     22#include <linux/export.h>
     23#include <linux/mutex.h>
     24#include <linux/cma.h>
     25#include <linux/mm.h>
     26#include <asm/cpcmd.h>
     27#include <asm/debug.h>
     28#include <asm/vmcp.h>
     29
     30struct vmcp_session {
     31	char *response;
     32	unsigned int bufsize;
     33	unsigned int cma_alloc : 1;
     34	int resp_size;
     35	int resp_code;
     36	struct mutex mutex;
     37};
     38
     39static debug_info_t *vmcp_debug;
     40
     41static unsigned long vmcp_cma_size __initdata = CONFIG_VMCP_CMA_SIZE * 1024 * 1024;
     42static struct cma *vmcp_cma;
     43
     44static int __init early_parse_vmcp_cma(char *p)
     45{
     46	if (!p)
     47		return 1;
     48	vmcp_cma_size = ALIGN(memparse(p, NULL), PAGE_SIZE);
     49	return 0;
     50}
     51early_param("vmcp_cma", early_parse_vmcp_cma);
     52
     53void __init vmcp_cma_reserve(void)
     54{
     55	if (!MACHINE_IS_VM)
     56		return;
     57	cma_declare_contiguous(0, vmcp_cma_size, 0, 0, 0, false, "vmcp", &vmcp_cma);
     58}
     59
     60static void vmcp_response_alloc(struct vmcp_session *session)
     61{
     62	struct page *page = NULL;
     63	int nr_pages, order;
     64
     65	order = get_order(session->bufsize);
     66	nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
     67	/*
     68	 * For anything below order 3 allocations rely on the buddy
     69	 * allocator. If such low-order allocations can't be handled
     70	 * anymore the system won't work anyway.
     71	 */
     72	if (order > 2)
     73		page = cma_alloc(vmcp_cma, nr_pages, 0, false);
     74	if (page) {
     75		session->response = (char *)page_to_virt(page);
     76		session->cma_alloc = 1;
     77		return;
     78	}
     79	session->response = (char *)__get_free_pages(GFP_KERNEL | __GFP_RETRY_MAYFAIL, order);
     80}
     81
     82static void vmcp_response_free(struct vmcp_session *session)
     83{
     84	int nr_pages, order;
     85	struct page *page;
     86
     87	if (!session->response)
     88		return;
     89	order = get_order(session->bufsize);
     90	nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
     91	if (session->cma_alloc) {
     92		page = virt_to_page((unsigned long)session->response);
     93		cma_release(vmcp_cma, page, nr_pages);
     94		session->cma_alloc = 0;
     95	} else {
     96		free_pages((unsigned long)session->response, order);
     97	}
     98	session->response = NULL;
     99}
    100
    101static int vmcp_open(struct inode *inode, struct file *file)
    102{
    103	struct vmcp_session *session;
    104
    105	if (!capable(CAP_SYS_ADMIN))
    106		return -EPERM;
    107
    108	session = kmalloc(sizeof(*session), GFP_KERNEL);
    109	if (!session)
    110		return -ENOMEM;
    111
    112	session->bufsize = PAGE_SIZE;
    113	session->response = NULL;
    114	session->resp_size = 0;
    115	mutex_init(&session->mutex);
    116	file->private_data = session;
    117	return nonseekable_open(inode, file);
    118}
    119
    120static int vmcp_release(struct inode *inode, struct file *file)
    121{
    122	struct vmcp_session *session;
    123
    124	session = file->private_data;
    125	file->private_data = NULL;
    126	vmcp_response_free(session);
    127	kfree(session);
    128	return 0;
    129}
    130
    131static ssize_t
    132vmcp_read(struct file *file, char __user *buff, size_t count, loff_t *ppos)
    133{
    134	ssize_t ret;
    135	size_t size;
    136	struct vmcp_session *session;
    137
    138	session = file->private_data;
    139	if (mutex_lock_interruptible(&session->mutex))
    140		return -ERESTARTSYS;
    141	if (!session->response) {
    142		mutex_unlock(&session->mutex);
    143		return 0;
    144	}
    145	size = min_t(size_t, session->resp_size, session->bufsize);
    146	ret = simple_read_from_buffer(buff, count, ppos,
    147					session->response, size);
    148
    149	mutex_unlock(&session->mutex);
    150
    151	return ret;
    152}
    153
    154static ssize_t
    155vmcp_write(struct file *file, const char __user *buff, size_t count,
    156	   loff_t *ppos)
    157{
    158	char *cmd;
    159	struct vmcp_session *session;
    160
    161	if (count > 240)
    162		return -EINVAL;
    163	cmd = memdup_user_nul(buff, count);
    164	if (IS_ERR(cmd))
    165		return PTR_ERR(cmd);
    166	session = file->private_data;
    167	if (mutex_lock_interruptible(&session->mutex)) {
    168		kfree(cmd);
    169		return -ERESTARTSYS;
    170	}
    171	if (!session->response)
    172		vmcp_response_alloc(session);
    173	if (!session->response) {
    174		mutex_unlock(&session->mutex);
    175		kfree(cmd);
    176		return -ENOMEM;
    177	}
    178	debug_text_event(vmcp_debug, 1, cmd);
    179	session->resp_size = cpcmd(cmd, session->response, session->bufsize,
    180				   &session->resp_code);
    181	mutex_unlock(&session->mutex);
    182	kfree(cmd);
    183	*ppos = 0;		/* reset the file pointer after a command */
    184	return count;
    185}
    186
    187
    188/*
    189 * These ioctls are available, as the semantics of the diagnose 8 call
    190 * does not fit very well into a Linux call. Diagnose X'08' is described in
    191 * CP Programming Services SC24-6084-00
    192 *
    193 * VMCP_GETCODE: gives the CP return code back to user space
    194 * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8
    195 * expects adjacent pages in real storage and to make matters worse, we
    196 * dont know the size of the response. Therefore we default to PAGESIZE and
    197 * let userspace to change the response size, if userspace expects a bigger
    198 * response
    199 */
    200static long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    201{
    202	struct vmcp_session *session;
    203	int ret = -ENOTTY;
    204	int __user *argp;
    205
    206	session = file->private_data;
    207	if (is_compat_task())
    208		argp = compat_ptr(arg);
    209	else
    210		argp = (int __user *)arg;
    211	if (mutex_lock_interruptible(&session->mutex))
    212		return -ERESTARTSYS;
    213	switch (cmd) {
    214	case VMCP_GETCODE:
    215		ret = put_user(session->resp_code, argp);
    216		break;
    217	case VMCP_SETBUF:
    218		vmcp_response_free(session);
    219		ret = get_user(session->bufsize, argp);
    220		if (ret)
    221			session->bufsize = PAGE_SIZE;
    222		if (!session->bufsize || get_order(session->bufsize) > 8) {
    223			session->bufsize = PAGE_SIZE;
    224			ret = -EINVAL;
    225		}
    226		break;
    227	case VMCP_GETSIZE:
    228		ret = put_user(session->resp_size, argp);
    229		break;
    230	default:
    231		break;
    232	}
    233	mutex_unlock(&session->mutex);
    234	return ret;
    235}
    236
    237static const struct file_operations vmcp_fops = {
    238	.owner		= THIS_MODULE,
    239	.open		= vmcp_open,
    240	.release	= vmcp_release,
    241	.read		= vmcp_read,
    242	.write		= vmcp_write,
    243	.unlocked_ioctl	= vmcp_ioctl,
    244	.compat_ioctl	= vmcp_ioctl,
    245	.llseek		= no_llseek,
    246};
    247
    248static struct miscdevice vmcp_dev = {
    249	.name	= "vmcp",
    250	.minor	= MISC_DYNAMIC_MINOR,
    251	.fops	= &vmcp_fops,
    252};
    253
    254static int __init vmcp_init(void)
    255{
    256	int ret;
    257
    258	if (!MACHINE_IS_VM)
    259		return 0;
    260
    261	vmcp_debug = debug_register("vmcp", 1, 1, 240);
    262	if (!vmcp_debug)
    263		return -ENOMEM;
    264
    265	ret = debug_register_view(vmcp_debug, &debug_hex_ascii_view);
    266	if (ret) {
    267		debug_unregister(vmcp_debug);
    268		return ret;
    269	}
    270
    271	ret = misc_register(&vmcp_dev);
    272	if (ret)
    273		debug_unregister(vmcp_debug);
    274	return ret;
    275}
    276device_initcall(vmcp_init);