From d8d4ee6f5052704ac82bb8aa8d8fe8816dac41cd Mon Sep 17 00:00:00 2001 From: Nathan Lynch Date: Mon, 25 Sep 2023 11:42:26 -0500 Subject: [PATCH 6/6] librtas/sysparm: prefer /dev/papr-sysparm when available Change rtas_get_sysparm() and rtas_set_sysparm() to prefer the /dev/papr-sysparm character device expected in Linux v6.8. On the first invocation of either function, probe for the new ABI and initialize internal function pointers according to the result. Use pthread_once() to ensure that this initialization step is performed atomically and only once. When /dev/papr-sysparm is available, use its PAPR_SYSPARM_IOC_GET and PAPR_SYSPARM_IOC_SET ioctl commands to retrieve and update system parameters. Most of the complexity of calling RTAS is handled internally to the kernel and the ioctl follows Linux conventions, returning 0 on success or -1 w/errno on failure. On failure, back-convert the errno to an RTAS status value that callers will recognize. For now, carry a copy of the kernel uapi header. Signed-off-by: Nathan Lynch Signed-off-by: Tyrel Datwyler --- Makefile.am | 1 + librtas_src/papr-sysparm.h | 58 +++++++++++++++ librtas_src/sysparm.c | 144 ++++++++++++++++++++++++++++++++++++- 3 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 librtas_src/papr-sysparm.h diff --git a/Makefile.am b/Makefile.am index 89a6eaa..67257e3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,7 @@ library_include_HEADERS += librtas_src/librtas.h noinst_HEADERS += \ librtas_src/internal.h \ librtas_src/papr-miscdev.h \ + librtas_src/papr-sysparm.h \ librtas_src/papr-vpd.h # See "Updating library version information" in the libtool manual for diff --git a/librtas_src/papr-sysparm.h b/librtas_src/papr-sysparm.h new file mode 100644 index 0000000..e0c0ebb --- /dev/null +++ b/librtas_src/papr-sysparm.h @@ -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 +#include +#include "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_ */ diff --git a/librtas_src/sysparm.c b/librtas_src/sysparm.c index 40af55e..b4d2054 100644 --- a/librtas_src/sysparm.c +++ b/librtas_src/sysparm.c @@ -1,7 +1,16 @@ +#include +#include +#include +#include #include #include +#include +#include #include "internal.h" #include "librtas.h" +#include "papr-sysparm.h" + +static const char sysparm_devpath[] = "/dev/papr-sysparm"; /** * rtas_get_sysparm @@ -15,7 +24,8 @@ * @param data reference to buffer to return parameter in * @return 0 on success, !0 otherwise */ -int rtas_get_sysparm(unsigned int parameter, unsigned int length, char *data) +static int +get_sysparm_syscall_fallback(unsigned int parameter, unsigned int length, char *data) { uint32_t kernbuf_pa; void *kernbuf; @@ -49,7 +59,8 @@ int rtas_get_sysparm(unsigned int parameter, unsigned int length, char *data) * @param data * @return 0 on success, !0 otherwise */ -int rtas_set_sysparm(unsigned int parameter, char *data) +static int +set_sysparm_syscall_fallback(unsigned int parameter, char *data) { uint32_t kernbuf_pa; void *kernbuf; @@ -86,3 +97,132 @@ int rtas_set_sysparm(unsigned int parameter, char *data) dbg("(%u, %p) = %d\n", parameter, data, rc ? rc : status); return rc ? rc : status; } + +static bool sysparm_can_use_chardev(void) +{ + struct stat statbuf; + + if (stat(sysparm_devpath, &statbuf)) + return false; + + if (!S_ISCHR(statbuf.st_mode)) + return false; + + if (close(open(sysparm_devpath, O_RDONLY))) + return false; + + return true; +} + +/* + * Only to be used when converting an actual error from a syscall. + */ +static int chardev_backconvert_errno(int saved_errno) +{ + const struct { + int linux_errno; + int rtas_status; + } map[] = { +#define errno_to_status(e, s) { .linux_errno = (e), .rtas_status = (s), } + errno_to_status(EINVAL, -9999), + errno_to_status(EPERM, -9002), + errno_to_status(EOPNOTSUPP, -3), + errno_to_status(EIO, -1), + errno_to_status(EFAULT, -1), +#undef errno_to_status + }; + + for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); ++i) + if (map[i].linux_errno == saved_errno) + return map[i].rtas_status; + return -1; +} + +static int get_sysparm_chardev(unsigned int parameter, unsigned int length, char *data) +{ + const int fd = open(sysparm_devpath, O_RDWR); + struct papr_sysparm_io_block buf = { + .parameter = parameter, + }; + + if (fd < 0) { + /* + * Really shouldn't get here without misconfiguration, + * e.g. removal of /dev/papr-sysparm. Synthesize a + * hardware/platform error. + */ + return -1; + } + + /* + * It might make sense to have special handling for parameter + * 60 (OS Service Entitlement Status), which takes input data, + * but librtas has never handled that one correctly. So ignore + * it for now and don't copy incoming data into the block we + * pass to PAPR_SYSPARM_IOC_GET. + */ + + const int res = ioctl(fd, PAPR_SYSPARM_IOC_GET, &buf); + const int saved_errno = errno; + (void)close(fd); + + if (res != 0) + return chardev_backconvert_errno(saved_errno); + + const uint16_t result_size_msb = htobe16(buf.length); + memcpy(data, &result_size_msb, sizeof(result_size_msb)); + length -= sizeof(result_size_msb); + data += sizeof(result_size_msb); + + /* + * Copy no more than min(@length, sizeof(buf.data)). + */ + const size_t copy_size = sizeof(buf.data) < length ? + sizeof(buf.data) : length; + memcpy(data, buf.data, copy_size); + return 0; +} + +static int set_sysparm_chardev(unsigned int parameter, char *data) +{ + const int fd = open(sysparm_devpath, O_RDWR); + struct papr_sysparm_io_block buf = { + .parameter = parameter, + .length = (((unsigned char)data[0] << 8) | (unsigned char)data[1]), + }; + + memcpy(buf.data, data + 2, buf.length); + + const int res = ioctl(fd, PAPR_SYSPARM_IOC_SET, &buf); + const int saved_errno = errno; + (void)close(fd); + + return res == 0 ? 0 : chardev_backconvert_errno(saved_errno); +} + +static int (*get_sysparm_fn)(unsigned int parameter, unsigned int length, char *data); +static int (*set_sysparm_fn)(unsigned int parameter, char *data); + +static void sysparm_fn_setup(void) +{ + const bool use_chardev = sysparm_can_use_chardev(); + + get_sysparm_fn = use_chardev ? + get_sysparm_chardev : get_sysparm_syscall_fallback; + set_sysparm_fn = use_chardev ? + set_sysparm_chardev : set_sysparm_syscall_fallback; +} + +static pthread_once_t sysparm_fn_setup_once = PTHREAD_ONCE_INIT; + +int rtas_get_sysparm(unsigned int parameter, unsigned int length, char *data) +{ + pthread_once(&sysparm_fn_setup_once, sysparm_fn_setup); + return get_sysparm_fn(parameter, length, data); +} + +int rtas_set_sysparm(unsigned int parameter, char *data) +{ + pthread_once(&sysparm_fn_setup_once, sysparm_fn_setup); + return set_sysparm_fn(parameter, data); +} -- 2.43.0