1/*
2 *    Hypervisor filesystem for Linux on s390.
3 *    Set Partition-Resource Parameter interface.
4 *
5 *    Copyright IBM Corp. 2013
6 *    Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
7 */
8
9#include <linux/compat.h>
10#include <linux/errno.h>
11#include <linux/gfp.h>
12#include <linux/string.h>
13#include <linux/types.h>
14#include <linux/uaccess.h>
15#include <asm/compat.h>
16#include <asm/sclp.h>
17#include "hypfs.h"
18
19#define DIAG304_SET_WEIGHTS	0
20#define DIAG304_QUERY_PRP	1
21#define DIAG304_SET_CAPPING	2
22
23#define DIAG304_CMD_MAX		2
24
25static unsigned long hypfs_sprp_diag304(void *data, unsigned long cmd)
26{
27	register unsigned long _data asm("2") = (unsigned long) data;
28	register unsigned long _rc asm("3");
29	register unsigned long _cmd asm("4") = cmd;
30
31	asm volatile("diag %1,%2,0x304\n"
32		     : "=d" (_rc) : "d" (_data), "d" (_cmd) : "memory");
33
34	return _rc;
35}
36
37static void hypfs_sprp_free(const void *data)
38{
39	free_page((unsigned long) data);
40}
41
42static int hypfs_sprp_create(void **data_ptr, void **free_ptr, size_t *size)
43{
44	unsigned long rc;
45	void *data;
46
47	data = (void *) get_zeroed_page(GFP_KERNEL);
48	if (!data)
49		return -ENOMEM;
50	rc = hypfs_sprp_diag304(data, DIAG304_QUERY_PRP);
51	if (rc != 1) {
52		*data_ptr = *free_ptr = NULL;
53		*size = 0;
54		free_page((unsigned long) data);
55		return -EIO;
56	}
57	*data_ptr = *free_ptr = data;
58	*size = PAGE_SIZE;
59	return 0;
60}
61
62static int __hypfs_sprp_ioctl(void __user *user_area)
63{
64	struct hypfs_diag304 diag304;
65	unsigned long cmd;
66	void __user *udata;
67	void *data;
68	int rc;
69
70	if (copy_from_user(&diag304, user_area, sizeof(diag304)))
71		return -EFAULT;
72	if ((diag304.args[0] >> 8) != 0 || diag304.args[1] > DIAG304_CMD_MAX)
73		return -EINVAL;
74
75	data = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
76	if (!data)
77		return -ENOMEM;
78
79	udata = (void __user *)(unsigned long) diag304.data;
80	if (diag304.args[1] == DIAG304_SET_WEIGHTS ||
81	    diag304.args[1] == DIAG304_SET_CAPPING)
82		if (copy_from_user(data, udata, PAGE_SIZE)) {
83			rc = -EFAULT;
84			goto out;
85		}
86
87	cmd = *(unsigned long *) &diag304.args[0];
88	diag304.rc = hypfs_sprp_diag304(data, cmd);
89
90	if (diag304.args[1] == DIAG304_QUERY_PRP)
91		if (copy_to_user(udata, data, PAGE_SIZE)) {
92			rc = -EFAULT;
93			goto out;
94		}
95
96	rc = copy_to_user(user_area, &diag304, sizeof(diag304)) ? -EFAULT : 0;
97out:
98	free_page((unsigned long) data);
99	return rc;
100}
101
102static long hypfs_sprp_ioctl(struct file *file, unsigned int cmd,
103			       unsigned long arg)
104{
105	void __user *argp;
106
107	if (!capable(CAP_SYS_ADMIN))
108		return -EACCES;
109	if (is_compat_task())
110		argp = compat_ptr(arg);
111	else
112		argp = (void __user *) arg;
113	switch (cmd) {
114	case HYPFS_DIAG304:
115		return __hypfs_sprp_ioctl(argp);
116	default: /* unknown ioctl number */
117		return -ENOTTY;
118	}
119	return 0;
120}
121
122static struct hypfs_dbfs_file hypfs_sprp_file = {
123	.name		= "diag_304",
124	.data_create	= hypfs_sprp_create,
125	.data_free	= hypfs_sprp_free,
126	.unlocked_ioctl = hypfs_sprp_ioctl,
127};
128
129int hypfs_sprp_init(void)
130{
131	if (!sclp_has_sprp())
132		return 0;
133	return hypfs_dbfs_create_file(&hypfs_sprp_file);
134}
135
136void hypfs_sprp_exit(void)
137{
138	if (!sclp_has_sprp())
139		return;
140	hypfs_dbfs_remove_file(&hypfs_sprp_file);
141}
142