18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *    Hypervisor filesystem for Linux on s390.
48c2ecf20Sopenharmony_ci *    Set Partition-Resource Parameter interface.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci *    Copyright IBM Corp. 2013
78c2ecf20Sopenharmony_ci *    Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/compat.h>
118c2ecf20Sopenharmony_ci#include <linux/errno.h>
128c2ecf20Sopenharmony_ci#include <linux/gfp.h>
138c2ecf20Sopenharmony_ci#include <linux/string.h>
148c2ecf20Sopenharmony_ci#include <linux/types.h>
158c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
168c2ecf20Sopenharmony_ci#include <asm/diag.h>
178c2ecf20Sopenharmony_ci#include <asm/sclp.h>
188c2ecf20Sopenharmony_ci#include "hypfs.h"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define DIAG304_SET_WEIGHTS	0
218c2ecf20Sopenharmony_ci#define DIAG304_QUERY_PRP	1
228c2ecf20Sopenharmony_ci#define DIAG304_SET_CAPPING	2
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define DIAG304_CMD_MAX		2
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic inline unsigned long __hypfs_sprp_diag304(void *data, unsigned long cmd)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	register unsigned long _data asm("2") = (unsigned long) data;
298c2ecf20Sopenharmony_ci	register unsigned long _rc asm("3");
308c2ecf20Sopenharmony_ci	register unsigned long _cmd asm("4") = cmd;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	asm volatile("diag %1,%2,0x304\n"
338c2ecf20Sopenharmony_ci		     : "=d" (_rc) : "d" (_data), "d" (_cmd) : "memory");
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	return _rc;
368c2ecf20Sopenharmony_ci}
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic unsigned long hypfs_sprp_diag304(void *data, unsigned long cmd)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	diag_stat_inc(DIAG_STAT_X304);
418c2ecf20Sopenharmony_ci	return __hypfs_sprp_diag304(data, cmd);
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic void hypfs_sprp_free(const void *data)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	free_page((unsigned long) data);
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic int hypfs_sprp_create(void **data_ptr, void **free_ptr, size_t *size)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	unsigned long rc;
528c2ecf20Sopenharmony_ci	void *data;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	data = (void *) get_zeroed_page(GFP_KERNEL);
558c2ecf20Sopenharmony_ci	if (!data)
568c2ecf20Sopenharmony_ci		return -ENOMEM;
578c2ecf20Sopenharmony_ci	rc = hypfs_sprp_diag304(data, DIAG304_QUERY_PRP);
588c2ecf20Sopenharmony_ci	if (rc != 1) {
598c2ecf20Sopenharmony_ci		*data_ptr = *free_ptr = NULL;
608c2ecf20Sopenharmony_ci		*size = 0;
618c2ecf20Sopenharmony_ci		free_page((unsigned long) data);
628c2ecf20Sopenharmony_ci		return -EIO;
638c2ecf20Sopenharmony_ci	}
648c2ecf20Sopenharmony_ci	*data_ptr = *free_ptr = data;
658c2ecf20Sopenharmony_ci	*size = PAGE_SIZE;
668c2ecf20Sopenharmony_ci	return 0;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic int __hypfs_sprp_ioctl(void __user *user_area)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct hypfs_diag304 *diag304;
728c2ecf20Sopenharmony_ci	unsigned long cmd;
738c2ecf20Sopenharmony_ci	void __user *udata;
748c2ecf20Sopenharmony_ci	void *data;
758c2ecf20Sopenharmony_ci	int rc;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	rc = -ENOMEM;
788c2ecf20Sopenharmony_ci	data = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
798c2ecf20Sopenharmony_ci	diag304 = kzalloc(sizeof(*diag304), GFP_KERNEL);
808c2ecf20Sopenharmony_ci	if (!data || !diag304)
818c2ecf20Sopenharmony_ci		goto out;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	rc = -EFAULT;
848c2ecf20Sopenharmony_ci	if (copy_from_user(diag304, user_area, sizeof(*diag304)))
858c2ecf20Sopenharmony_ci		goto out;
868c2ecf20Sopenharmony_ci	rc = -EINVAL;
878c2ecf20Sopenharmony_ci	if ((diag304->args[0] >> 8) != 0 || diag304->args[1] > DIAG304_CMD_MAX)
888c2ecf20Sopenharmony_ci		goto out;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	rc = -EFAULT;
918c2ecf20Sopenharmony_ci	udata = (void __user *)(unsigned long) diag304->data;
928c2ecf20Sopenharmony_ci	if (diag304->args[1] == DIAG304_SET_WEIGHTS ||
938c2ecf20Sopenharmony_ci	    diag304->args[1] == DIAG304_SET_CAPPING)
948c2ecf20Sopenharmony_ci		if (copy_from_user(data, udata, PAGE_SIZE))
958c2ecf20Sopenharmony_ci			goto out;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	cmd = *(unsigned long *) &diag304->args[0];
988c2ecf20Sopenharmony_ci	diag304->rc = hypfs_sprp_diag304(data, cmd);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	if (diag304->args[1] == DIAG304_QUERY_PRP)
1018c2ecf20Sopenharmony_ci		if (copy_to_user(udata, data, PAGE_SIZE)) {
1028c2ecf20Sopenharmony_ci			rc = -EFAULT;
1038c2ecf20Sopenharmony_ci			goto out;
1048c2ecf20Sopenharmony_ci		}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	rc = copy_to_user(user_area, diag304, sizeof(*diag304)) ? -EFAULT : 0;
1078c2ecf20Sopenharmony_ciout:
1088c2ecf20Sopenharmony_ci	kfree(diag304);
1098c2ecf20Sopenharmony_ci	free_page((unsigned long) data);
1108c2ecf20Sopenharmony_ci	return rc;
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic long hypfs_sprp_ioctl(struct file *file, unsigned int cmd,
1148c2ecf20Sopenharmony_ci			       unsigned long arg)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	void __user *argp;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
1198c2ecf20Sopenharmony_ci		return -EACCES;
1208c2ecf20Sopenharmony_ci	if (is_compat_task())
1218c2ecf20Sopenharmony_ci		argp = compat_ptr(arg);
1228c2ecf20Sopenharmony_ci	else
1238c2ecf20Sopenharmony_ci		argp = (void __user *) arg;
1248c2ecf20Sopenharmony_ci	switch (cmd) {
1258c2ecf20Sopenharmony_ci	case HYPFS_DIAG304:
1268c2ecf20Sopenharmony_ci		return __hypfs_sprp_ioctl(argp);
1278c2ecf20Sopenharmony_ci	default: /* unknown ioctl number */
1288c2ecf20Sopenharmony_ci		return -ENOTTY;
1298c2ecf20Sopenharmony_ci	}
1308c2ecf20Sopenharmony_ci	return 0;
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic struct hypfs_dbfs_file hypfs_sprp_file = {
1348c2ecf20Sopenharmony_ci	.name		= "diag_304",
1358c2ecf20Sopenharmony_ci	.data_create	= hypfs_sprp_create,
1368c2ecf20Sopenharmony_ci	.data_free	= hypfs_sprp_free,
1378c2ecf20Sopenharmony_ci	.unlocked_ioctl = hypfs_sprp_ioctl,
1388c2ecf20Sopenharmony_ci};
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_civoid hypfs_sprp_init(void)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	if (!sclp.has_sprp)
1438c2ecf20Sopenharmony_ci		return;
1448c2ecf20Sopenharmony_ci	hypfs_dbfs_create_file(&hypfs_sprp_file);
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_civoid hypfs_sprp_exit(void)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	if (!sclp.has_sprp)
1508c2ecf20Sopenharmony_ci		return;
1518c2ecf20Sopenharmony_ci	hypfs_dbfs_remove_file(&hypfs_sprp_file);
1528c2ecf20Sopenharmony_ci}
153