162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *    Hypervisor filesystem for Linux on s390.
462306a36Sopenharmony_ci *    Set Partition-Resource Parameter interface.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *    Copyright IBM Corp. 2013
762306a36Sopenharmony_ci *    Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/compat.h>
1162306a36Sopenharmony_ci#include <linux/errno.h>
1262306a36Sopenharmony_ci#include <linux/gfp.h>
1362306a36Sopenharmony_ci#include <linux/string.h>
1462306a36Sopenharmony_ci#include <linux/types.h>
1562306a36Sopenharmony_ci#include <linux/uaccess.h>
1662306a36Sopenharmony_ci#include <asm/diag.h>
1762306a36Sopenharmony_ci#include <asm/sclp.h>
1862306a36Sopenharmony_ci#include "hypfs.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define DIAG304_SET_WEIGHTS	0
2162306a36Sopenharmony_ci#define DIAG304_QUERY_PRP	1
2262306a36Sopenharmony_ci#define DIAG304_SET_CAPPING	2
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define DIAG304_CMD_MAX		2
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic inline unsigned long __hypfs_sprp_diag304(void *data, unsigned long cmd)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	union register_pair r1 = { .even = (unsigned long)data, };
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	asm volatile("diag %[r1],%[r3],0x304\n"
3162306a36Sopenharmony_ci		     : [r1] "+&d" (r1.pair)
3262306a36Sopenharmony_ci		     : [r3] "d" (cmd)
3362306a36Sopenharmony_ci		     : "memory");
3462306a36Sopenharmony_ci	return r1.odd;
3562306a36Sopenharmony_ci}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic unsigned long hypfs_sprp_diag304(void *data, unsigned long cmd)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	diag_stat_inc(DIAG_STAT_X304);
4062306a36Sopenharmony_ci	return __hypfs_sprp_diag304(data, cmd);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic void hypfs_sprp_free(const void *data)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	free_page((unsigned long) data);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic int hypfs_sprp_create(void **data_ptr, void **free_ptr, size_t *size)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	unsigned long rc;
5162306a36Sopenharmony_ci	void *data;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	data = (void *) get_zeroed_page(GFP_KERNEL);
5462306a36Sopenharmony_ci	if (!data)
5562306a36Sopenharmony_ci		return -ENOMEM;
5662306a36Sopenharmony_ci	rc = hypfs_sprp_diag304(data, DIAG304_QUERY_PRP);
5762306a36Sopenharmony_ci	if (rc != 1) {
5862306a36Sopenharmony_ci		*data_ptr = *free_ptr = NULL;
5962306a36Sopenharmony_ci		*size = 0;
6062306a36Sopenharmony_ci		free_page((unsigned long) data);
6162306a36Sopenharmony_ci		return -EIO;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci	*data_ptr = *free_ptr = data;
6462306a36Sopenharmony_ci	*size = PAGE_SIZE;
6562306a36Sopenharmony_ci	return 0;
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic int __hypfs_sprp_ioctl(void __user *user_area)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	struct hypfs_diag304 *diag304;
7162306a36Sopenharmony_ci	unsigned long cmd;
7262306a36Sopenharmony_ci	void __user *udata;
7362306a36Sopenharmony_ci	void *data;
7462306a36Sopenharmony_ci	int rc;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	rc = -ENOMEM;
7762306a36Sopenharmony_ci	data = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
7862306a36Sopenharmony_ci	diag304 = kzalloc(sizeof(*diag304), GFP_KERNEL);
7962306a36Sopenharmony_ci	if (!data || !diag304)
8062306a36Sopenharmony_ci		goto out;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	rc = -EFAULT;
8362306a36Sopenharmony_ci	if (copy_from_user(diag304, user_area, sizeof(*diag304)))
8462306a36Sopenharmony_ci		goto out;
8562306a36Sopenharmony_ci	rc = -EINVAL;
8662306a36Sopenharmony_ci	if ((diag304->args[0] >> 8) != 0 || diag304->args[1] > DIAG304_CMD_MAX)
8762306a36Sopenharmony_ci		goto out;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	rc = -EFAULT;
9062306a36Sopenharmony_ci	udata = (void __user *)(unsigned long) diag304->data;
9162306a36Sopenharmony_ci	if (diag304->args[1] == DIAG304_SET_WEIGHTS ||
9262306a36Sopenharmony_ci	    diag304->args[1] == DIAG304_SET_CAPPING)
9362306a36Sopenharmony_ci		if (copy_from_user(data, udata, PAGE_SIZE))
9462306a36Sopenharmony_ci			goto out;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	cmd = *(unsigned long *) &diag304->args[0];
9762306a36Sopenharmony_ci	diag304->rc = hypfs_sprp_diag304(data, cmd);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (diag304->args[1] == DIAG304_QUERY_PRP)
10062306a36Sopenharmony_ci		if (copy_to_user(udata, data, PAGE_SIZE)) {
10162306a36Sopenharmony_ci			rc = -EFAULT;
10262306a36Sopenharmony_ci			goto out;
10362306a36Sopenharmony_ci		}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	rc = copy_to_user(user_area, diag304, sizeof(*diag304)) ? -EFAULT : 0;
10662306a36Sopenharmony_ciout:
10762306a36Sopenharmony_ci	kfree(diag304);
10862306a36Sopenharmony_ci	free_page((unsigned long) data);
10962306a36Sopenharmony_ci	return rc;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic long hypfs_sprp_ioctl(struct file *file, unsigned int cmd,
11362306a36Sopenharmony_ci			       unsigned long arg)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	void __user *argp;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
11862306a36Sopenharmony_ci		return -EACCES;
11962306a36Sopenharmony_ci	if (is_compat_task())
12062306a36Sopenharmony_ci		argp = compat_ptr(arg);
12162306a36Sopenharmony_ci	else
12262306a36Sopenharmony_ci		argp = (void __user *) arg;
12362306a36Sopenharmony_ci	switch (cmd) {
12462306a36Sopenharmony_ci	case HYPFS_DIAG304:
12562306a36Sopenharmony_ci		return __hypfs_sprp_ioctl(argp);
12662306a36Sopenharmony_ci	default: /* unknown ioctl number */
12762306a36Sopenharmony_ci		return -ENOTTY;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci	return 0;
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic struct hypfs_dbfs_file hypfs_sprp_file = {
13362306a36Sopenharmony_ci	.name		= "diag_304",
13462306a36Sopenharmony_ci	.data_create	= hypfs_sprp_create,
13562306a36Sopenharmony_ci	.data_free	= hypfs_sprp_free,
13662306a36Sopenharmony_ci	.unlocked_ioctl = hypfs_sprp_ioctl,
13762306a36Sopenharmony_ci};
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_civoid hypfs_sprp_init(void)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	if (!sclp.has_sprp)
14262306a36Sopenharmony_ci		return;
14362306a36Sopenharmony_ci	hypfs_dbfs_create_file(&hypfs_sprp_file);
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_civoid hypfs_sprp_exit(void)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	if (!sclp.has_sprp)
14962306a36Sopenharmony_ci		return;
15062306a36Sopenharmony_ci	hypfs_dbfs_remove_file(&hypfs_sprp_file);
15162306a36Sopenharmony_ci}
152