Skip to content

Commit d8d4ee6

Browse files
nathanlynchtyreld
authored andcommitted
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 <[email protected]> Signed-off-by: Tyrel Datwyler <[email protected]>
1 parent 57d5e1c commit d8d4ee6

File tree

3 files changed

+201
-2
lines changed

3 files changed

+201
-2
lines changed

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ library_include_HEADERS += librtas_src/librtas.h
3838
noinst_HEADERS += \
3939
librtas_src/internal.h \
4040
librtas_src/papr-miscdev.h \
41+
librtas_src/papr-sysparm.h \
4142
librtas_src/papr-vpd.h
4243

4344
# See "Updating library version information" in the libtool manual for

librtas_src/papr-sysparm.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 "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_ */

librtas_src/sysparm.c

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1+
#include <errno.h>
2+
#include <fcntl.h>
3+
#include <pthread.h>
4+
#include <stdbool.h>
15
#include <stdint.h>
26
#include <string.h>
7+
#include <sys/ioctl.h>
8+
#include <sys/stat.h>
39
#include "internal.h"
410
#include "librtas.h"
11+
#include "papr-sysparm.h"
12+
13+
static const char sysparm_devpath[] = "/dev/papr-sysparm";
514

615
/**
716
* rtas_get_sysparm
@@ -15,7 +24,8 @@
1524
* @param data reference to buffer to return parameter in
1625
* @return 0 on success, !0 otherwise
1726
*/
18-
int rtas_get_sysparm(unsigned int parameter, unsigned int length, char *data)
27+
static int
28+
get_sysparm_syscall_fallback(unsigned int parameter, unsigned int length, char *data)
1929
{
2030
uint32_t kernbuf_pa;
2131
void *kernbuf;
@@ -49,7 +59,8 @@ int rtas_get_sysparm(unsigned int parameter, unsigned int length, char *data)
4959
* @param data
5060
* @return 0 on success, !0 otherwise
5161
*/
52-
int rtas_set_sysparm(unsigned int parameter, char *data)
62+
static int
63+
set_sysparm_syscall_fallback(unsigned int parameter, char *data)
5364
{
5465
uint32_t kernbuf_pa;
5566
void *kernbuf;
@@ -86,3 +97,132 @@ int rtas_set_sysparm(unsigned int parameter, char *data)
8697
dbg("(%u, %p) = %d\n", parameter, data, rc ? rc : status);
8798
return rc ? rc : status;
8899
}
100+
101+
static bool sysparm_can_use_chardev(void)
102+
{
103+
struct stat statbuf;
104+
105+
if (stat(sysparm_devpath, &statbuf))
106+
return false;
107+
108+
if (!S_ISCHR(statbuf.st_mode))
109+
return false;
110+
111+
if (close(open(sysparm_devpath, O_RDONLY)))
112+
return false;
113+
114+
return true;
115+
}
116+
117+
/*
118+
* Only to be used when converting an actual error from a syscall.
119+
*/
120+
static int chardev_backconvert_errno(int saved_errno)
121+
{
122+
const struct {
123+
int linux_errno;
124+
int rtas_status;
125+
} map[] = {
126+
#define errno_to_status(e, s) { .linux_errno = (e), .rtas_status = (s), }
127+
errno_to_status(EINVAL, -9999),
128+
errno_to_status(EPERM, -9002),
129+
errno_to_status(EOPNOTSUPP, -3),
130+
errno_to_status(EIO, -1),
131+
errno_to_status(EFAULT, -1),
132+
#undef errno_to_status
133+
};
134+
135+
for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); ++i)
136+
if (map[i].linux_errno == saved_errno)
137+
return map[i].rtas_status;
138+
return -1;
139+
}
140+
141+
static int get_sysparm_chardev(unsigned int parameter, unsigned int length, char *data)
142+
{
143+
const int fd = open(sysparm_devpath, O_RDWR);
144+
struct papr_sysparm_io_block buf = {
145+
.parameter = parameter,
146+
};
147+
148+
if (fd < 0) {
149+
/*
150+
* Really shouldn't get here without misconfiguration,
151+
* e.g. removal of /dev/papr-sysparm. Synthesize a
152+
* hardware/platform error.
153+
*/
154+
return -1;
155+
}
156+
157+
/*
158+
* It might make sense to have special handling for parameter
159+
* 60 (OS Service Entitlement Status), which takes input data,
160+
* but librtas has never handled that one correctly. So ignore
161+
* it for now and don't copy incoming data into the block we
162+
* pass to PAPR_SYSPARM_IOC_GET.
163+
*/
164+
165+
const int res = ioctl(fd, PAPR_SYSPARM_IOC_GET, &buf);
166+
const int saved_errno = errno;
167+
(void)close(fd);
168+
169+
if (res != 0)
170+
return chardev_backconvert_errno(saved_errno);
171+
172+
const uint16_t result_size_msb = htobe16(buf.length);
173+
memcpy(data, &result_size_msb, sizeof(result_size_msb));
174+
length -= sizeof(result_size_msb);
175+
data += sizeof(result_size_msb);
176+
177+
/*
178+
* Copy no more than min(@length, sizeof(buf.data)).
179+
*/
180+
const size_t copy_size = sizeof(buf.data) < length ?
181+
sizeof(buf.data) : length;
182+
memcpy(data, buf.data, copy_size);
183+
return 0;
184+
}
185+
186+
static int set_sysparm_chardev(unsigned int parameter, char *data)
187+
{
188+
const int fd = open(sysparm_devpath, O_RDWR);
189+
struct papr_sysparm_io_block buf = {
190+
.parameter = parameter,
191+
.length = (((unsigned char)data[0] << 8) | (unsigned char)data[1]),
192+
};
193+
194+
memcpy(buf.data, data + 2, buf.length);
195+
196+
const int res = ioctl(fd, PAPR_SYSPARM_IOC_SET, &buf);
197+
const int saved_errno = errno;
198+
(void)close(fd);
199+
200+
return res == 0 ? 0 : chardev_backconvert_errno(saved_errno);
201+
}
202+
203+
static int (*get_sysparm_fn)(unsigned int parameter, unsigned int length, char *data);
204+
static int (*set_sysparm_fn)(unsigned int parameter, char *data);
205+
206+
static void sysparm_fn_setup(void)
207+
{
208+
const bool use_chardev = sysparm_can_use_chardev();
209+
210+
get_sysparm_fn = use_chardev ?
211+
get_sysparm_chardev : get_sysparm_syscall_fallback;
212+
set_sysparm_fn = use_chardev ?
213+
set_sysparm_chardev : set_sysparm_syscall_fallback;
214+
}
215+
216+
static pthread_once_t sysparm_fn_setup_once = PTHREAD_ONCE_INIT;
217+
218+
int rtas_get_sysparm(unsigned int parameter, unsigned int length, char *data)
219+
{
220+
pthread_once(&sysparm_fn_setup_once, sysparm_fn_setup);
221+
return get_sysparm_fn(parameter, length, data);
222+
}
223+
224+
int rtas_set_sysparm(unsigned int parameter, char *data)
225+
{
226+
pthread_once(&sysparm_fn_setup_once, sysparm_fn_setup);
227+
return set_sysparm_fn(parameter, data);
228+
}

0 commit comments

Comments
 (0)