Skip to content

Commit

Permalink
librtas/sysparm: prefer /dev/papr-sysparm when available
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Tyrel Datwyler <[email protected]>
  • Loading branch information
nathanlynch authored and tyreld committed Feb 1, 2024
1 parent 57d5e1c commit d8d4ee6
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 2 deletions.
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions librtas_src/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 "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_ */
144 changes: 142 additions & 2 deletions librtas_src/sysparm.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "internal.h"
#include "librtas.h"
#include "papr-sysparm.h"

static const char sysparm_devpath[] = "/dev/papr-sysparm";

/**
* rtas_get_sysparm
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

0 comments on commit d8d4ee6

Please sign in to comment.