Path: blob/master/arch/powerpc/platforms/pseries/papr-sysparm.c
26481 views
// SPDX-License-Identifier: GPL-2.0-only12#define pr_fmt(fmt) "papr-sysparm: " fmt34#include <linux/anon_inodes.h>5#include <linux/bug.h>6#include <linux/file.h>7#include <linux/fs.h>8#include <linux/init.h>9#include <linux/kernel.h>10#include <linux/miscdevice.h>11#include <linux/printk.h>12#include <linux/slab.h>13#include <linux/uaccess.h>14#include <asm/machdep.h>15#include <asm/papr-sysparm.h>16#include <asm/rtas-work-area.h>17#include <asm/rtas.h>1819struct papr_sysparm_buf *papr_sysparm_buf_alloc(void)20{21struct papr_sysparm_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL);2223return buf;24}2526void papr_sysparm_buf_free(struct papr_sysparm_buf *buf)27{28kfree(buf);29}3031static size_t papr_sysparm_buf_get_length(const struct papr_sysparm_buf *buf)32{33return be16_to_cpu(buf->len);34}3536static void papr_sysparm_buf_set_length(struct papr_sysparm_buf *buf, size_t length)37{38WARN_ONCE(length > sizeof(buf->val),39"bogus length %zu, clamping to safe value", length);40length = min(sizeof(buf->val), length);41buf->len = cpu_to_be16(length);42}4344/*45* For use on buffers returned from ibm,get-system-parameter before46* returning them to callers. Ensures the encoded length of valid data47* cannot overrun buf->val[].48*/49static void papr_sysparm_buf_clamp_length(struct papr_sysparm_buf *buf)50{51papr_sysparm_buf_set_length(buf, papr_sysparm_buf_get_length(buf));52}5354/*55* Perform some basic diligence on the system parameter buffer before56* submitting it to RTAS.57*/58static bool papr_sysparm_buf_can_submit(const struct papr_sysparm_buf *buf)59{60/*61* Firmware ought to reject buffer lengths that exceed the62* maximum specified in PAPR, but there's no reason for the63* kernel to allow them either.64*/65if (papr_sysparm_buf_get_length(buf) > sizeof(buf->val))66return false;6768return true;69}7071/**72* papr_sysparm_get() - Retrieve the value of a PAPR system parameter.73* @param: PAPR system parameter token as described in74* 7.3.16 "System Parameters Option".75* @buf: A &struct papr_sysparm_buf as returned from papr_sysparm_buf_alloc().76*77* Place the result of querying the specified parameter, if available,78* in @buf. The result includes a be16 length header followed by the79* value, which may be a string or binary data. See &struct papr_sysparm_buf.80*81* Since there is at least one parameter (60, OS Service Entitlement82* Status) where the results depend on the incoming contents of the83* work area, the caller-supplied buffer is copied unmodified into the84* work area before calling ibm,get-system-parameter.85*86* A defined parameter may not be implemented on a given system, and87* some implemented parameters may not be available to all partitions88* on a system. A parameter's disposition may change at any time due89* to system configuration changes or partition migration.90*91* Context: This function may sleep.92*93* Return: 0 on success, -errno otherwise. @buf is unmodified on error.94*/95int papr_sysparm_get(papr_sysparm_t param, struct papr_sysparm_buf *buf)96{97const s32 token = rtas_function_token(RTAS_FN_IBM_GET_SYSTEM_PARAMETER);98struct rtas_work_area *work_area;99s32 fwrc;100int ret;101102might_sleep();103104if (WARN_ON(!buf))105return -EFAULT;106107if (token == RTAS_UNKNOWN_SERVICE)108return -ENOENT;109110if (!papr_sysparm_buf_can_submit(buf))111return -EINVAL;112113work_area = rtas_work_area_alloc(sizeof(*buf));114115memcpy(rtas_work_area_raw_buf(work_area), buf, sizeof(*buf));116117do {118fwrc = rtas_call(token, 3, 1, NULL, param.token,119rtas_work_area_phys(work_area),120rtas_work_area_size(work_area));121} while (rtas_busy_delay(fwrc));122123switch (fwrc) {124case 0:125ret = 0;126memcpy(buf, rtas_work_area_raw_buf(work_area), sizeof(*buf));127papr_sysparm_buf_clamp_length(buf);128break;129case -3: /* parameter not implemented */130ret = -EOPNOTSUPP;131break;132case -9002: /* this partition not authorized to retrieve this parameter */133ret = -EPERM;134break;135case -9999: /* "parameter error" e.g. the buffer is too small */136ret = -EINVAL;137break;138default:139pr_err("unexpected ibm,get-system-parameter result %d\n", fwrc);140fallthrough;141case -1: /* Hardware/platform error */142ret = -EIO;143break;144}145146rtas_work_area_free(work_area);147148return ret;149}150151int papr_sysparm_set(papr_sysparm_t param, const struct papr_sysparm_buf *buf)152{153const s32 token = rtas_function_token(RTAS_FN_IBM_SET_SYSTEM_PARAMETER);154struct rtas_work_area *work_area;155s32 fwrc;156int ret;157158might_sleep();159160if (WARN_ON(!buf))161return -EFAULT;162163if (token == RTAS_UNKNOWN_SERVICE)164return -ENOENT;165166if (!papr_sysparm_buf_can_submit(buf))167return -EINVAL;168169work_area = rtas_work_area_alloc(sizeof(*buf));170171memcpy(rtas_work_area_raw_buf(work_area), buf, sizeof(*buf));172173do {174fwrc = rtas_call(token, 2, 1, NULL, param.token,175rtas_work_area_phys(work_area));176} while (rtas_busy_delay(fwrc));177178switch (fwrc) {179case 0:180ret = 0;181break;182case -3: /* parameter not supported */183ret = -EOPNOTSUPP;184break;185case -9002: /* this partition not authorized to modify this parameter */186ret = -EPERM;187break;188case -9999: /* "parameter error" e.g. invalid input data */189ret = -EINVAL;190break;191default:192pr_err("unexpected ibm,set-system-parameter result %d\n", fwrc);193fallthrough;194case -1: /* Hardware/platform error */195ret = -EIO;196break;197}198199rtas_work_area_free(work_area);200201return ret;202}203204static struct papr_sysparm_buf *205papr_sysparm_buf_from_user(const struct papr_sysparm_io_block __user *user_iob)206{207struct papr_sysparm_buf *kern_spbuf;208long err;209u16 len;210211/*212* The length of valid data that userspace claims to be in213* user_iob->data[].214*/215if (get_user(len, &user_iob->length))216return ERR_PTR(-EFAULT);217218static_assert(sizeof(user_iob->data) >= PAPR_SYSPARM_MAX_INPUT);219static_assert(sizeof(kern_spbuf->val) >= PAPR_SYSPARM_MAX_INPUT);220221if (len > PAPR_SYSPARM_MAX_INPUT)222return ERR_PTR(-EINVAL);223224kern_spbuf = papr_sysparm_buf_alloc();225if (!kern_spbuf)226return ERR_PTR(-ENOMEM);227228papr_sysparm_buf_set_length(kern_spbuf, len);229230if (len > 0 && copy_from_user(kern_spbuf->val, user_iob->data, len)) {231err = -EFAULT;232goto free_sysparm_buf;233}234235return kern_spbuf;236237free_sysparm_buf:238papr_sysparm_buf_free(kern_spbuf);239return ERR_PTR(err);240}241242static int papr_sysparm_buf_to_user(const struct papr_sysparm_buf *kern_spbuf,243struct papr_sysparm_io_block __user *user_iob)244{245u16 len_out = papr_sysparm_buf_get_length(kern_spbuf);246247if (put_user(len_out, &user_iob->length))248return -EFAULT;249250static_assert(sizeof(user_iob->data) >= PAPR_SYSPARM_MAX_OUTPUT);251static_assert(sizeof(kern_spbuf->val) >= PAPR_SYSPARM_MAX_OUTPUT);252253if (copy_to_user(user_iob->data, kern_spbuf->val, PAPR_SYSPARM_MAX_OUTPUT))254return -EFAULT;255256return 0;257}258259static long papr_sysparm_ioctl_get(struct papr_sysparm_io_block __user *user_iob)260{261struct papr_sysparm_buf *kern_spbuf;262papr_sysparm_t param;263long ret;264265if (get_user(param.token, &user_iob->parameter))266return -EFAULT;267268kern_spbuf = papr_sysparm_buf_from_user(user_iob);269if (IS_ERR(kern_spbuf))270return PTR_ERR(kern_spbuf);271272ret = papr_sysparm_get(param, kern_spbuf);273if (ret)274goto free_sysparm_buf;275276ret = papr_sysparm_buf_to_user(kern_spbuf, user_iob);277if (ret)278goto free_sysparm_buf;279280ret = 0;281282free_sysparm_buf:283papr_sysparm_buf_free(kern_spbuf);284return ret;285}286287288static long papr_sysparm_ioctl_set(struct papr_sysparm_io_block __user *user_iob)289{290struct papr_sysparm_buf *kern_spbuf;291papr_sysparm_t param;292long ret;293294if (get_user(param.token, &user_iob->parameter))295return -EFAULT;296297kern_spbuf = papr_sysparm_buf_from_user(user_iob);298if (IS_ERR(kern_spbuf))299return PTR_ERR(kern_spbuf);300301ret = papr_sysparm_set(param, kern_spbuf);302if (ret)303goto free_sysparm_buf;304305ret = 0;306307free_sysparm_buf:308papr_sysparm_buf_free(kern_spbuf);309return ret;310}311312static long papr_sysparm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)313{314void __user *argp = (__force void __user *)arg;315long ret;316317switch (ioctl) {318case PAPR_SYSPARM_IOC_GET:319ret = papr_sysparm_ioctl_get(argp);320break;321case PAPR_SYSPARM_IOC_SET:322if (filp->f_mode & FMODE_WRITE)323ret = papr_sysparm_ioctl_set(argp);324else325ret = -EBADF;326break;327default:328ret = -ENOIOCTLCMD;329break;330}331return ret;332}333334static const struct file_operations papr_sysparm_ops = {335.unlocked_ioctl = papr_sysparm_ioctl,336};337338static struct miscdevice papr_sysparm_dev = {339.minor = MISC_DYNAMIC_MINOR,340.name = "papr-sysparm",341.fops = &papr_sysparm_ops,342};343344static __init int papr_sysparm_init(void)345{346if (!rtas_function_implemented(RTAS_FN_IBM_GET_SYSTEM_PARAMETER))347return -ENODEV;348349return misc_register(&papr_sysparm_dev);350}351machine_device_initcall(pseries, papr_sysparm_init);352353354