linux/drivers/s390/char/vmcp.c
<<
>>
Prefs
   1/*
   2 * Copyright IBM Corp. 2004,2007
   3 * Interface implementation for communication with the z/VM control program
   4 * Author(s): Christian Borntraeger <borntraeger@de.ibm.com>
   5 *
   6 *
   7 * z/VMs CP offers the possibility to issue commands via the diagnose code 8
   8 * this driver implements a character device that issues these commands and
   9 * returns the answer of CP.
  10
  11 * The idea of this driver is based on cpint from Neale Ferguson and #CP in CMS
  12 */
  13
  14#define KMSG_COMPONENT "vmcp"
  15#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
  16
  17#include <linux/fs.h>
  18#include <linux/init.h>
  19#include <linux/kernel.h>
  20#include <linux/miscdevice.h>
  21#include <linux/module.h>
  22#include <asm/cpcmd.h>
  23#include <asm/debug.h>
  24#include <asm/uaccess.h>
  25#include "vmcp.h"
  26
  27MODULE_LICENSE("GPL");
  28MODULE_AUTHOR("Christian Borntraeger <borntraeger@de.ibm.com>");
  29MODULE_DESCRIPTION("z/VM CP interface");
  30
  31static debug_info_t *vmcp_debug;
  32
  33static int vmcp_open(struct inode *inode, struct file *file)
  34{
  35        struct vmcp_session *session;
  36
  37        if (!capable(CAP_SYS_ADMIN))
  38                return -EPERM;
  39
  40        session = kmalloc(sizeof(*session), GFP_KERNEL);
  41        if (!session)
  42                return -ENOMEM;
  43
  44        session->bufsize = PAGE_SIZE;
  45        session->response = NULL;
  46        session->resp_size = 0;
  47        mutex_init(&session->mutex);
  48        file->private_data = session;
  49        return nonseekable_open(inode, file);
  50}
  51
  52static int vmcp_release(struct inode *inode, struct file *file)
  53{
  54        struct vmcp_session *session;
  55
  56        session = (struct vmcp_session *)file->private_data;
  57        file->private_data = NULL;
  58        free_pages((unsigned long)session->response, get_order(session->bufsize));
  59        kfree(session);
  60        return 0;
  61}
  62
  63static ssize_t
  64vmcp_read(struct file *file, char __user *buff, size_t count, loff_t *ppos)
  65{
  66        ssize_t ret;
  67        size_t size;
  68        struct vmcp_session *session;
  69
  70        session = file->private_data;
  71        if (mutex_lock_interruptible(&session->mutex))
  72                return -ERESTARTSYS;
  73        if (!session->response) {
  74                mutex_unlock(&session->mutex);
  75                return 0;
  76        }
  77        size = min_t(size_t, session->resp_size, session->bufsize);
  78        ret = simple_read_from_buffer(buff, count, ppos,
  79                                        session->response, size);
  80
  81        mutex_unlock(&session->mutex);
  82
  83        return ret;
  84}
  85
  86static ssize_t
  87vmcp_write(struct file *file, const char __user *buff, size_t count,
  88           loff_t *ppos)
  89{
  90        char *cmd;
  91        struct vmcp_session *session;
  92
  93        if (count > 240)
  94                return -EINVAL;
  95        cmd = kmalloc(count + 1, GFP_KERNEL);
  96        if (!cmd)
  97                return -ENOMEM;
  98        if (copy_from_user(cmd, buff, count)) {
  99                kfree(cmd);
 100                return -EFAULT;
 101        }
 102        cmd[count] = '\0';
 103        session = (struct vmcp_session *)file->private_data;
 104        if (mutex_lock_interruptible(&session->mutex)) {
 105                kfree(cmd);
 106                return -ERESTARTSYS;
 107        }
 108        if (!session->response)
 109                session->response = (char *)__get_free_pages(GFP_KERNEL
 110                                                | __GFP_REPEAT | GFP_DMA,
 111                                                get_order(session->bufsize));
 112        if (!session->response) {
 113                mutex_unlock(&session->mutex);
 114                kfree(cmd);
 115                return -ENOMEM;
 116        }
 117        debug_text_event(vmcp_debug, 1, cmd);
 118        session->resp_size = cpcmd(cmd, session->response, session->bufsize,
 119                                   &session->resp_code);
 120        mutex_unlock(&session->mutex);
 121        kfree(cmd);
 122        *ppos = 0;              /* reset the file pointer after a command */
 123        return count;
 124}
 125
 126
 127/*
 128 * These ioctls are available, as the semantics of the diagnose 8 call
 129 * does not fit very well into a Linux call. Diagnose X'08' is described in
 130 * CP Programming Services SC24-6084-00
 131 *
 132 * VMCP_GETCODE: gives the CP return code back to user space
 133 * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8
 134 * expects adjacent pages in real storage and to make matters worse, we
 135 * dont know the size of the response. Therefore we default to PAGESIZE and
 136 * let userspace to change the response size, if userspace expects a bigger
 137 * response
 138 */
 139static long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 140{
 141        struct vmcp_session *session;
 142        int temp;
 143
 144        session = (struct vmcp_session *)file->private_data;
 145        if (mutex_lock_interruptible(&session->mutex))
 146                return -ERESTARTSYS;
 147        switch (cmd) {
 148        case VMCP_GETCODE:
 149                temp = session->resp_code;
 150                mutex_unlock(&session->mutex);
 151                return put_user(temp, (int __user *)arg);
 152        case VMCP_SETBUF:
 153                free_pages((unsigned long)session->response,
 154                                get_order(session->bufsize));
 155                session->response=NULL;
 156                temp = get_user(session->bufsize, (int __user *)arg);
 157                if (get_order(session->bufsize) > 8) {
 158                        session->bufsize = PAGE_SIZE;
 159                        temp = -EINVAL;
 160                }
 161                mutex_unlock(&session->mutex);
 162                return temp;
 163        case VMCP_GETSIZE:
 164                temp = session->resp_size;
 165                mutex_unlock(&session->mutex);
 166                return put_user(temp, (int __user *)arg);
 167        default:
 168                mutex_unlock(&session->mutex);
 169                return -ENOIOCTLCMD;
 170        }
 171}
 172
 173static const struct file_operations vmcp_fops = {
 174        .owner          = THIS_MODULE,
 175        .open           = vmcp_open,
 176        .release        = vmcp_release,
 177        .read           = vmcp_read,
 178        .write          = vmcp_write,
 179        .unlocked_ioctl = vmcp_ioctl,
 180        .compat_ioctl   = vmcp_ioctl,
 181};
 182
 183static struct miscdevice vmcp_dev = {
 184        .name   = "vmcp",
 185        .minor  = MISC_DYNAMIC_MINOR,
 186        .fops   = &vmcp_fops,
 187};
 188
 189static int __init vmcp_init(void)
 190{
 191        int ret;
 192
 193        if (!MACHINE_IS_VM) {
 194                pr_warning("The z/VM CP interface device driver cannot be "
 195                           "loaded without z/VM\n");
 196                return -ENODEV;
 197        }
 198
 199        vmcp_debug = debug_register("vmcp", 1, 1, 240);
 200        if (!vmcp_debug)
 201                return -ENOMEM;
 202
 203        ret = debug_register_view(vmcp_debug, &debug_hex_ascii_view);
 204        if (ret) {
 205                debug_unregister(vmcp_debug);
 206                return ret;
 207        }
 208
 209        ret = misc_register(&vmcp_dev);
 210        if (ret) {
 211                debug_unregister(vmcp_debug);
 212                return ret;
 213        }
 214
 215        return 0;
 216}
 217
 218static void __exit vmcp_exit(void)
 219{
 220        misc_deregister(&vmcp_dev);
 221        debug_unregister(vmcp_debug);
 222}
 223
 224module_init(vmcp_init);
 225module_exit(vmcp_exit);
 226