145 строки
2.8 KiB
C
145 строки
2.8 KiB
C
/*
|
|
* IOCTL interface for SCLP
|
|
*
|
|
* Copyright IBM Corp. 2012
|
|
*
|
|
* Author: Michael Holzheu <holzheu@linux.vnet.ibm.com>
|
|
*/
|
|
|
|
#include <linux/compat.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/fs.h>
|
|
#include <asm/compat.h>
|
|
#include <asm/sclp_ctl.h>
|
|
#include <asm/sclp.h>
|
|
|
|
#include "sclp.h"
|
|
|
|
/*
|
|
* Supported command words
|
|
*/
|
|
static unsigned int sclp_ctl_sccb_wlist[] = {
|
|
0x00400002,
|
|
0x00410002,
|
|
};
|
|
|
|
/*
|
|
* Check if command word is supported
|
|
*/
|
|
static int sclp_ctl_cmdw_supported(unsigned int cmdw)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sclp_ctl_sccb_wlist); i++) {
|
|
if (cmdw == sclp_ctl_sccb_wlist[i])
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void __user *u64_to_uptr(u64 value)
|
|
{
|
|
if (is_compat_task())
|
|
return compat_ptr(value);
|
|
else
|
|
return (void __user *)(unsigned long)value;
|
|
}
|
|
|
|
/*
|
|
* Start SCLP request
|
|
*/
|
|
static int sclp_ctl_ioctl_sccb(void __user *user_area)
|
|
{
|
|
struct sclp_ctl_sccb ctl_sccb;
|
|
struct sccb_header *sccb;
|
|
int rc;
|
|
|
|
if (copy_from_user(&ctl_sccb, user_area, sizeof(ctl_sccb)))
|
|
return -EFAULT;
|
|
if (!sclp_ctl_cmdw_supported(ctl_sccb.cmdw))
|
|
return -EOPNOTSUPP;
|
|
sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
|
|
if (!sccb)
|
|
return -ENOMEM;
|
|
if (copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), sizeof(*sccb))) {
|
|
rc = -EFAULT;
|
|
goto out_free;
|
|
}
|
|
if (sccb->length > PAGE_SIZE || sccb->length < 8)
|
|
return -EINVAL;
|
|
if (copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), sccb->length)) {
|
|
rc = -EFAULT;
|
|
goto out_free;
|
|
}
|
|
rc = sclp_sync_request(ctl_sccb.cmdw, sccb);
|
|
if (rc)
|
|
goto out_free;
|
|
if (copy_to_user(u64_to_uptr(ctl_sccb.sccb), sccb, sccb->length))
|
|
rc = -EFAULT;
|
|
out_free:
|
|
free_page((unsigned long) sccb);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* SCLP SCCB ioctl function
|
|
*/
|
|
static long sclp_ctl_ioctl(struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
void __user *argp;
|
|
|
|
if (is_compat_task())
|
|
argp = compat_ptr(arg);
|
|
else
|
|
argp = (void __user *) arg;
|
|
switch (cmd) {
|
|
case SCLP_CTL_SCCB:
|
|
return sclp_ctl_ioctl_sccb(argp);
|
|
default: /* unknown ioctl number */
|
|
return -ENOTTY;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* File operations
|
|
*/
|
|
static const struct file_operations sclp_ctl_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = nonseekable_open,
|
|
.unlocked_ioctl = sclp_ctl_ioctl,
|
|
.compat_ioctl = sclp_ctl_ioctl,
|
|
.llseek = no_llseek,
|
|
};
|
|
|
|
/*
|
|
* Misc device definition
|
|
*/
|
|
static struct miscdevice sclp_ctl_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "sclp",
|
|
.fops = &sclp_ctl_fops,
|
|
};
|
|
|
|
/*
|
|
* Register sclp_ctl misc device
|
|
*/
|
|
static int __init sclp_ctl_init(void)
|
|
{
|
|
return misc_register(&sclp_ctl_device);
|
|
}
|
|
module_init(sclp_ctl_init);
|
|
|
|
/*
|
|
* Deregister sclp_ctl misc device
|
|
*/
|
|
static void __exit sclp_ctl_exit(void)
|
|
{
|
|
misc_deregister(&sclp_ctl_device);
|
|
}
|
|
module_exit(sclp_ctl_exit);
|