Skip to content

Commit

Permalink
powerpc/pseries/papr-sysparm: expose chardev API to user space
Browse files Browse the repository at this point in the history
Until now the papr_sysparm APIs have been kernel-internal. But user
space needs access to PAPR system parameters too. The only method
available to user space today to get or set system parameters is using
sys_rtas() and /dev/mem to pass RTAS-addressable buffers between user
space and firmware. This is incompatible with lockdown and should be
deprecated.

So provide an alternative ABI to user space in the form of a
/dev/papr-sysparm character device with just two ioctl commands (get
and set). The data payloads involved are small enough to fit in the
ioctl argument buffer, making the code relatively simple.

Exposing the system parameters through sysfs has been considered but
it would be too awkward:

* The kernel currently does not have to contain an exhaustive list of
  defined system parameters. This is a convenient property to maintain
  because we don't have to update the kernel whenever a new parameter
  is added to PAPR. Exporting a named attribute in sysfs for each
  parameter would negate this.

* Some system parameters are text-based and some are not.

* Retrieval of at least one system parameter requires input data,
  which a simple read-oriented interface can't support.

Signed-off-by: Nathan Lynch <[email protected]>
  • Loading branch information
nathanlynch authored and intel-lab-lkp committed Oct 6, 2023
1 parent 36a5129 commit 27c13dc
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 8 deletions.
2 changes: 2 additions & 0 deletions Documentation/userspace-api/ioctl/ioctl-number.rst
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ Code Seq# Include File Comments
<mailto:[email protected]>
0xB2 00 arch/powerpc/include/uapi/asm/papr-vpd.h powerpc/pseries VPD API
<mailto:linuxppc-dev>
0xB2 01-02 arch/powerpc/include/uapi/asm/papr-sysparm.h powerpc/pseries system parameter API
<mailto:linuxppc-dev>
0xB3 00 linux/mmc/ioctl.h
0xB4 00-0F linux/gpio.h <mailto:[email protected]>
0xB5 00-0F uapi/linux/rpmsg.h <mailto:[email protected]>
Expand Down
17 changes: 11 additions & 6 deletions arch/powerpc/include/asm/papr-sysparm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
#ifndef _ASM_POWERPC_PAPR_SYSPARM_H
#define _ASM_POWERPC_PAPR_SYSPARM_H

#include <uapi/asm/papr-sysparm.h>

typedef struct {
const u32 token;
u32 token;
} papr_sysparm_t;

#define mk_papr_sysparm(x_) ((papr_sysparm_t){ .token = x_, })
Expand All @@ -20,11 +22,14 @@ typedef struct {
#define PAPR_SYSPARM_TLB_BLOCK_INVALIDATE_ATTRS mk_papr_sysparm(50)
#define PAPR_SYSPARM_LPAR_NAME mk_papr_sysparm(55)

enum {
PAPR_SYSPARM_MAX_INPUT = 1024,
PAPR_SYSPARM_MAX_OUTPUT = 4000,
};

/**
* struct papr_sysparm_buf - RTAS work area layout for system parameter functions.
*
* This is the memory layout of the buffers passed to/from
* ibm,get-system-parameter and ibm,set-system-parameter. It is
* distinct from the papr_sysparm_io_block structure that is passed
* between user space and the kernel.
*/
struct papr_sysparm_buf {
__be16 len;
char val[PAPR_SYSPARM_MAX_OUTPUT];
Expand Down
58 changes: 58 additions & 0 deletions arch/powerpc/include/uapi/asm/papr-sysparm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _UAPI_PAPR_SYSPARM_H_
#define _UAPI_PAPR_SYSPARM_H_

#include <linux/types.h>
#include <asm/ioctl.h>
#include <asm/papr-miscdev.h>

enum {
PAPR_SYSPARM_MAX_INPUT = 1024,
PAPR_SYSPARM_MAX_OUTPUT = 4000,
};

struct papr_sysparm_io_block {
__u32 parameter;
__u16 length;
char data[PAPR_SYSPARM_MAX_OUTPUT];
};

/**
* PAPR_SYSPARM_IOC_GET - Retrieve the value of a PAPR system parameter.
*
* Uses _IOWR because of one corner case: Retrieving the value of the
* "OS Service Entitlement Status" parameter (60) requires the caller
* to supply input data (a date string) in the buffer passed to
* firmware. So the @length and @data of the incoming
* papr_sysparm_io_block are always used to initialize the work area
* supplied to ibm,get-system-parameter. No other parameters are known
* to parameterize the result this way, and callers are encouraged
* (but not required) to zero-initialize @length and @data in the
* common case.
*
* On error the contents of the ioblock are indeterminate.
*
* Return:
* 0: Success; @length is the length of valid data in @data, not to exceed @PAPR_SYSPARM_MAX_OUTPUT.
* -EIO: Platform error. (-1)
* -EINVAL: Incorrect data length or format. (-9999)
* -EPERM: The calling partition is not allowed to access this parameter. (-9002)
* -EOPNOTSUPP: Parameter not supported on this platform (-3)
*/
#define PAPR_SYSPARM_IOC_GET _IOWR(PAPR_MISCDEV_IOC_ID, 1, struct papr_sysparm_io_block)

/**
* PAPR_SYSPARM_IOC_SET - Update the value of a PAPR system parameter.
*
* The contents of the ioblock are unchanged regardless of success.
*
* Return:
* 0: Success; the parameter has been updated.
* -EIO: Platform error. (-1)
* -EINVAL: Incorrect data length or format. (-9999)
* -EPERM: The calling partition is not allowed to access this parameter. (-9002)
* -EOPNOTSUPP: Parameter not supported on this platform (-3)
*/
#define PAPR_SYSPARM_IOC_SET _IOW(PAPR_MISCDEV_IOC_ID, 2, struct papr_sysparm_io_block)

#endif /* _UAPI_PAPR_SYSPARM_H_ */
160 changes: 158 additions & 2 deletions arch/powerpc/platforms/pseries/papr-sysparm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

#define pr_fmt(fmt) "papr-sysparm: " fmt

#include <linux/anon_inodes.h>
#include <linux/bug.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <asm/rtas.h>
#include <asm/machdep.h>
#include <asm/papr-sysparm.h>
#include <asm/rtas-work-area.h>
#include <asm/rtas.h>

struct papr_sysparm_buf *papr_sysparm_buf_alloc(void)
{
Expand Down Expand Up @@ -87,7 +92,6 @@ static bool papr_sysparm_buf_can_submit(const struct papr_sysparm_buf *buf)
*
* Return: 0 on success, -errno otherwise. @buf is unmodified on error.
*/

int papr_sysparm_get(papr_sysparm_t param, struct papr_sysparm_buf *buf)
{
const s32 token = rtas_function_token(RTAS_FN_IBM_GET_SYSTEM_PARAMETER);
Expand Down Expand Up @@ -196,3 +200,155 @@ int papr_sysparm_set(papr_sysparm_t param, const struct papr_sysparm_buf *buf)

return ret;
}

static struct papr_sysparm_buf *
papr_sysparm_buf_from_user(const struct papr_sysparm_io_block __user *user_iob)
{
struct papr_sysparm_buf *kern_spbuf;
papr_sysparm_t param;
long err;
u16 len;

/*
* The parameter index that userspace wants to query or update.
*/
if (get_user(param.token, &user_iob->parameter))
return ERR_PTR(-EFAULT);
/*
* The length of valid data that userspace claims to be in
* user_iob->data[].
*/
if (get_user(len, &user_iob->length))
return ERR_PTR(-EFAULT);

static_assert(sizeof(user_iob->data) >= PAPR_SYSPARM_MAX_INPUT);
static_assert(sizeof(kern_spbuf->val) >= PAPR_SYSPARM_MAX_INPUT);

if (len > PAPR_SYSPARM_MAX_INPUT)
return ERR_PTR(-EINVAL);

kern_spbuf = papr_sysparm_buf_alloc();
if (!kern_spbuf)
return ERR_PTR(-ENOMEM);

papr_sysparm_buf_set_length(kern_spbuf, len);

if (len > 0 && copy_from_user(kern_spbuf->val, user_iob->data, len)) {
err = -EFAULT;
goto free_sysparm_buf;
}

return kern_spbuf;

free_sysparm_buf:
papr_sysparm_buf_free(kern_spbuf);
return ERR_PTR(err);
}

static int papr_sysparm_buf_to_user(const struct papr_sysparm_buf *kern_spbuf,
struct papr_sysparm_io_block __user *user_iob)
{
u16 len_out = papr_sysparm_buf_get_length(kern_spbuf);

if (put_user(len_out, &user_iob->length))
return -EFAULT;

static_assert(sizeof(user_iob->data) >= PAPR_SYSPARM_MAX_OUTPUT);
static_assert(sizeof(kern_spbuf->val) >= PAPR_SYSPARM_MAX_OUTPUT);

if (copy_to_user(user_iob->data, kern_spbuf->val, PAPR_SYSPARM_MAX_OUTPUT))
return -EFAULT;

return 0;
}

static long papr_sysparm_ioctl_get(struct papr_sysparm_io_block __user *user_iob)
{
struct papr_sysparm_buf *kern_spbuf;
papr_sysparm_t param;
long ret;

if (get_user(param.token, &user_iob->parameter))
return -EFAULT;

kern_spbuf = papr_sysparm_buf_from_user(user_iob);
if (IS_ERR(kern_spbuf))
return PTR_ERR(kern_spbuf);

ret = papr_sysparm_get(param, kern_spbuf);
if (ret)
goto free_sysparm_buf;

ret = papr_sysparm_buf_to_user(kern_spbuf, user_iob);
if (ret)
goto free_sysparm_buf;

ret = 0;

free_sysparm_buf:
papr_sysparm_buf_free(kern_spbuf);
return ret;
}


static long papr_sysparm_ioctl_set(struct papr_sysparm_io_block __user *user_iob)
{
struct papr_sysparm_buf *kern_spbuf;
papr_sysparm_t param;
long ret;

if (get_user(param.token, &user_iob->parameter))
return -EFAULT;

kern_spbuf = papr_sysparm_buf_from_user(user_iob);
if (IS_ERR(kern_spbuf))
return PTR_ERR(kern_spbuf);

ret = papr_sysparm_set(param, kern_spbuf);
if (ret)
goto free_sysparm_buf;

ret = 0;

free_sysparm_buf:
papr_sysparm_buf_free(kern_spbuf);
return ret;
}

static long papr_sysparm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
void __user *argp = (__force void __user *)arg;
long ret;

switch (ioctl) {
case PAPR_SYSPARM_IOC_GET:
ret = papr_sysparm_ioctl_get(argp);
break;
case PAPR_SYSPARM_IOC_SET:
ret = papr_sysparm_ioctl_set(argp);
break;
default:
ret = -ENOIOCTLCMD;
break;
}
return ret;
}

static const struct file_operations papr_sysparm_ops = {
.unlocked_ioctl = papr_sysparm_ioctl,
};

static struct miscdevice papr_sysparm_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "papr-sysparm",
.fops = &papr_sysparm_ops,
};

static __init int papr_sysparm_init(void)
{
if (!rtas_function_implemented(RTAS_FN_IBM_GET_SYSTEM_PARAMETER))
return -ENODEV;

return misc_register(&papr_sysparm_dev);
}
machine_device_initcall(pseries, papr_sysparm_init);

0 comments on commit 27c13dc

Please sign in to comment.