Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

powerpc/pseries/papr-sysparm: Expose character device to user space

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 <nathanl@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20231212-papr-sys_rtas-vs-lockdown-v6-11-e9eafd0c8c6c@linux.ibm.com

authored by

Nathan Lynch and committed by
Michael Ellerman
905b9e48 35aae182

+227 -8
+2
Documentation/userspace-api/ioctl/ioctl-number.rst
··· 351 351 <mailto:mostrows@styx.uwaterloo.ca> 352 352 0xB2 00 arch/powerpc/include/uapi/asm/papr-vpd.h powerpc/pseries VPD API 353 353 <mailto:linuxppc-dev> 354 + 0xB2 01-02 arch/powerpc/include/uapi/asm/papr-sysparm.h powerpc/pseries system parameter API 355 + <mailto:linuxppc-dev> 354 356 0xB3 00 linux/mmc/ioctl.h 355 357 0xB4 00-0F linux/gpio.h <mailto:linux-gpio@vger.kernel.org> 356 358 0xB5 00-0F uapi/linux/rpmsg.h <mailto:linux-remoteproc@vger.kernel.org>
+11 -6
arch/powerpc/include/asm/papr-sysparm.h
··· 2 2 #ifndef _ASM_POWERPC_PAPR_SYSPARM_H 3 3 #define _ASM_POWERPC_PAPR_SYSPARM_H 4 4 5 + #include <uapi/asm/papr-sysparm.h> 6 + 5 7 typedef struct { 6 - const u32 token; 8 + u32 token; 7 9 } papr_sysparm_t; 8 10 9 11 #define mk_papr_sysparm(x_) ((papr_sysparm_t){ .token = x_, }) ··· 22 20 #define PAPR_SYSPARM_TLB_BLOCK_INVALIDATE_ATTRS mk_papr_sysparm(50) 23 21 #define PAPR_SYSPARM_LPAR_NAME mk_papr_sysparm(55) 24 22 25 - enum { 26 - PAPR_SYSPARM_MAX_INPUT = 1024, 27 - PAPR_SYSPARM_MAX_OUTPUT = 4000, 28 - }; 29 - 23 + /** 24 + * struct papr_sysparm_buf - RTAS work area layout for system parameter functions. 25 + * 26 + * This is the memory layout of the buffers passed to/from 27 + * ibm,get-system-parameter and ibm,set-system-parameter. It is 28 + * distinct from the papr_sysparm_io_block structure that is passed 29 + * between user space and the kernel. 30 + */ 30 31 struct papr_sysparm_buf { 31 32 __be16 len; 32 33 char val[PAPR_SYSPARM_MAX_OUTPUT];
+58
arch/powerpc/include/uapi/asm/papr-sysparm.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 + #ifndef _UAPI_PAPR_SYSPARM_H_ 3 + #define _UAPI_PAPR_SYSPARM_H_ 4 + 5 + #include <linux/types.h> 6 + #include <asm/ioctl.h> 7 + #include <asm/papr-miscdev.h> 8 + 9 + enum { 10 + PAPR_SYSPARM_MAX_INPUT = 1024, 11 + PAPR_SYSPARM_MAX_OUTPUT = 4000, 12 + }; 13 + 14 + struct papr_sysparm_io_block { 15 + __u32 parameter; 16 + __u16 length; 17 + char data[PAPR_SYSPARM_MAX_OUTPUT]; 18 + }; 19 + 20 + /** 21 + * PAPR_SYSPARM_IOC_GET - Retrieve the value of a PAPR system parameter. 22 + * 23 + * Uses _IOWR because of one corner case: Retrieving the value of the 24 + * "OS Service Entitlement Status" parameter (60) requires the caller 25 + * to supply input data (a date string) in the buffer passed to 26 + * firmware. So the @length and @data of the incoming 27 + * papr_sysparm_io_block are always used to initialize the work area 28 + * supplied to ibm,get-system-parameter. No other parameters are known 29 + * to parameterize the result this way, and callers are encouraged 30 + * (but not required) to zero-initialize @length and @data in the 31 + * common case. 32 + * 33 + * On error the contents of the ioblock are indeterminate. 34 + * 35 + * Return: 36 + * 0: Success; @length is the length of valid data in @data, not to exceed @PAPR_SYSPARM_MAX_OUTPUT. 37 + * -EIO: Platform error. (-1) 38 + * -EINVAL: Incorrect data length or format. (-9999) 39 + * -EPERM: The calling partition is not allowed to access this parameter. (-9002) 40 + * -EOPNOTSUPP: Parameter not supported on this platform (-3) 41 + */ 42 + #define PAPR_SYSPARM_IOC_GET _IOWR(PAPR_MISCDEV_IOC_ID, 1, struct papr_sysparm_io_block) 43 + 44 + /** 45 + * PAPR_SYSPARM_IOC_SET - Update the value of a PAPR system parameter. 46 + * 47 + * The contents of the ioblock are unchanged regardless of success. 48 + * 49 + * Return: 50 + * 0: Success; the parameter has been updated. 51 + * -EIO: Platform error. (-1) 52 + * -EINVAL: Incorrect data length or format. (-9999) 53 + * -EPERM: The calling partition is not allowed to access this parameter. (-9002) 54 + * -EOPNOTSUPP: Parameter not supported on this platform (-3) 55 + */ 56 + #define PAPR_SYSPARM_IOC_SET _IOW(PAPR_MISCDEV_IOC_ID, 2, struct papr_sysparm_io_block) 57 + 58 + #endif /* _UAPI_PAPR_SYSPARM_H_ */
+156 -2
arch/powerpc/platforms/pseries/papr-sysparm.c
··· 2 2 3 3 #define pr_fmt(fmt) "papr-sysparm: " fmt 4 4 5 + #include <linux/anon_inodes.h> 5 6 #include <linux/bug.h> 7 + #include <linux/file.h> 8 + #include <linux/fs.h> 6 9 #include <linux/init.h> 7 10 #include <linux/kernel.h> 11 + #include <linux/miscdevice.h> 8 12 #include <linux/printk.h> 9 13 #include <linux/slab.h> 10 - #include <asm/rtas.h> 14 + #include <linux/uaccess.h> 15 + #include <asm/machdep.h> 11 16 #include <asm/papr-sysparm.h> 12 17 #include <asm/rtas-work-area.h> 18 + #include <asm/rtas.h> 13 19 14 20 struct papr_sysparm_buf *papr_sysparm_buf_alloc(void) 15 21 { ··· 93 87 * 94 88 * Return: 0 on success, -errno otherwise. @buf is unmodified on error. 95 89 */ 96 - 97 90 int papr_sysparm_get(papr_sysparm_t param, struct papr_sysparm_buf *buf) 98 91 { 99 92 const s32 token = rtas_function_token(RTAS_FN_IBM_GET_SYSTEM_PARAMETER); ··· 201 196 202 197 return ret; 203 198 } 199 + 200 + static struct papr_sysparm_buf * 201 + papr_sysparm_buf_from_user(const struct papr_sysparm_io_block __user *user_iob) 202 + { 203 + struct papr_sysparm_buf *kern_spbuf; 204 + long err; 205 + u16 len; 206 + 207 + /* 208 + * The length of valid data that userspace claims to be in 209 + * user_iob->data[]. 210 + */ 211 + if (get_user(len, &user_iob->length)) 212 + return ERR_PTR(-EFAULT); 213 + 214 + static_assert(sizeof(user_iob->data) >= PAPR_SYSPARM_MAX_INPUT); 215 + static_assert(sizeof(kern_spbuf->val) >= PAPR_SYSPARM_MAX_INPUT); 216 + 217 + if (len > PAPR_SYSPARM_MAX_INPUT) 218 + return ERR_PTR(-EINVAL); 219 + 220 + kern_spbuf = papr_sysparm_buf_alloc(); 221 + if (!kern_spbuf) 222 + return ERR_PTR(-ENOMEM); 223 + 224 + papr_sysparm_buf_set_length(kern_spbuf, len); 225 + 226 + if (len > 0 && copy_from_user(kern_spbuf->val, user_iob->data, len)) { 227 + err = -EFAULT; 228 + goto free_sysparm_buf; 229 + } 230 + 231 + return kern_spbuf; 232 + 233 + free_sysparm_buf: 234 + papr_sysparm_buf_free(kern_spbuf); 235 + return ERR_PTR(err); 236 + } 237 + 238 + static int papr_sysparm_buf_to_user(const struct papr_sysparm_buf *kern_spbuf, 239 + struct papr_sysparm_io_block __user *user_iob) 240 + { 241 + u16 len_out = papr_sysparm_buf_get_length(kern_spbuf); 242 + 243 + if (put_user(len_out, &user_iob->length)) 244 + return -EFAULT; 245 + 246 + static_assert(sizeof(user_iob->data) >= PAPR_SYSPARM_MAX_OUTPUT); 247 + static_assert(sizeof(kern_spbuf->val) >= PAPR_SYSPARM_MAX_OUTPUT); 248 + 249 + if (copy_to_user(user_iob->data, kern_spbuf->val, PAPR_SYSPARM_MAX_OUTPUT)) 250 + return -EFAULT; 251 + 252 + return 0; 253 + } 254 + 255 + static long papr_sysparm_ioctl_get(struct papr_sysparm_io_block __user *user_iob) 256 + { 257 + struct papr_sysparm_buf *kern_spbuf; 258 + papr_sysparm_t param; 259 + long ret; 260 + 261 + if (get_user(param.token, &user_iob->parameter)) 262 + return -EFAULT; 263 + 264 + kern_spbuf = papr_sysparm_buf_from_user(user_iob); 265 + if (IS_ERR(kern_spbuf)) 266 + return PTR_ERR(kern_spbuf); 267 + 268 + ret = papr_sysparm_get(param, kern_spbuf); 269 + if (ret) 270 + goto free_sysparm_buf; 271 + 272 + ret = papr_sysparm_buf_to_user(kern_spbuf, user_iob); 273 + if (ret) 274 + goto free_sysparm_buf; 275 + 276 + ret = 0; 277 + 278 + free_sysparm_buf: 279 + papr_sysparm_buf_free(kern_spbuf); 280 + return ret; 281 + } 282 + 283 + 284 + static long papr_sysparm_ioctl_set(struct papr_sysparm_io_block __user *user_iob) 285 + { 286 + struct papr_sysparm_buf *kern_spbuf; 287 + papr_sysparm_t param; 288 + long ret; 289 + 290 + if (get_user(param.token, &user_iob->parameter)) 291 + return -EFAULT; 292 + 293 + kern_spbuf = papr_sysparm_buf_from_user(user_iob); 294 + if (IS_ERR(kern_spbuf)) 295 + return PTR_ERR(kern_spbuf); 296 + 297 + ret = papr_sysparm_set(param, kern_spbuf); 298 + if (ret) 299 + goto free_sysparm_buf; 300 + 301 + ret = 0; 302 + 303 + free_sysparm_buf: 304 + papr_sysparm_buf_free(kern_spbuf); 305 + return ret; 306 + } 307 + 308 + static long papr_sysparm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) 309 + { 310 + void __user *argp = (__force void __user *)arg; 311 + long ret; 312 + 313 + switch (ioctl) { 314 + case PAPR_SYSPARM_IOC_GET: 315 + ret = papr_sysparm_ioctl_get(argp); 316 + break; 317 + case PAPR_SYSPARM_IOC_SET: 318 + if (filp->f_mode & FMODE_WRITE) 319 + ret = papr_sysparm_ioctl_set(argp); 320 + else 321 + ret = -EBADF; 322 + break; 323 + default: 324 + ret = -ENOIOCTLCMD; 325 + break; 326 + } 327 + return ret; 328 + } 329 + 330 + static const struct file_operations papr_sysparm_ops = { 331 + .unlocked_ioctl = papr_sysparm_ioctl, 332 + }; 333 + 334 + static struct miscdevice papr_sysparm_dev = { 335 + .minor = MISC_DYNAMIC_MINOR, 336 + .name = "papr-sysparm", 337 + .fops = &papr_sysparm_ops, 338 + }; 339 + 340 + static __init int papr_sysparm_init(void) 341 + { 342 + if (!rtas_function_implemented(RTAS_FN_IBM_GET_SYSTEM_PARAMETER)) 343 + return -ENODEV; 344 + 345 + return misc_register(&papr_sysparm_dev); 346 + } 347 + machine_device_initcall(pseries, papr_sysparm_init);