Skip to content

Commit

Permalink
librtas/vpd: prefer /dev/papr-vpd when available
Browse files Browse the repository at this point in the history
Change rtas_get_vpd() to prefer the /dev/papr-vpd character device
expected in Linux v6.8.

On the first invocation of rtas_get_vpd(), probe for the new ABI and
initialize an internal function pointer according to the result. Use
pthread_once() to ensure that this probe step is performed atomically
and only once. It would not make sense to re-probe (and potentially
update the implementation) on every call, since the implementations
differ in the way state for the call sequence is maintained.

Assuming the new ABI is available: upon first call to rtas_get_vpd(),
open /dev/papr-vpd and obtain a file descriptor using the
PAPR_VPD_CREATE_HANDLE ioctl() command. This file descriptor is a
reference to the VPD returned from a complete ibm,get-vpd call
sequence. Subsequent calls sequentially read the VPD from the fd,
which is closed once EOF is reached.

To existing users of rtas_get_vpd(), the new implementation is
indistinguishable from the old one, with one exception:

* When using the rtas() syscall on current systems, RTAS advances the
  sequence number by a fixed interval on each call in a sequence. This
  is visible in the values returned in the 'seq_next' out parameter.

* When using /dev/papr-vpd, the value returned in 'seq_next' does not
  change between calls; librtas treats it as a "handle" for a sequence
  in progress.

In PAPR there is no meaning attached to the value returned in the
'seq_next' out parameter, and there is no requirement that it must
change or increment on each call. No user should be relying on the
behavior of this aspect of the interface to judge the status of a
sequence.

I have verified that an unmodified vpdupdate command from a distro
lsvpd package works correctly when linked to a librtas.so with this
change. This is the primary (and perhaps only) user of rtas_get_vpd().

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 c914a1a commit 57d5e1c
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 20 deletions.
5 changes: 3 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ LIBRTAS_AGE = 0
LIBRTAS_LIBRARY_VERSION = $(LIBRTAS_CURRENT):$(LIBRTAS_REVISION):$(LIBRTAS_AGE)

lib_LTLIBRARIES += librtas.la
librtas_la_LDFLAGS = -version-info $(LIBRTAS_LIBRARY_VERSION)
librtas_la_LDFLAGS = -version-info $(LIBRTAS_LIBRARY_VERSION) -lpthread
librtas_la_SOURCES = \
librtas_src/vpd.c \
librtas_src/ofdt.c \
Expand All @@ -37,7 +37,8 @@ librtas_la_SOURCES = \
library_include_HEADERS += librtas_src/librtas.h
noinst_HEADERS += \
librtas_src/internal.h \
librtas_src/papr-miscdev.h
librtas_src/papr-miscdev.h \
librtas_src/papr-vpd.h

# See "Updating library version information" in the libtool manual for
# how to maintain these values. They are *not* tied to the release
Expand Down
22 changes: 22 additions & 0 deletions librtas_src/papr-vpd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#ifndef _UAPI_PAPR_VPD_H_
#define _UAPI_PAPR_VPD_H_

#include <asm/ioctl.h>
#include "papr-miscdev.h"

struct papr_location_code {
/*
* PAPR+ 12.3.2.4 Converged Location Code Rules - Length
* Restrictions. 79 characters plus nul.
*/
char str[80];
};

/*
* ioctl for /dev/papr-vpd. Returns a VPD handle fd corresponding to
* the location code.
*/
#define PAPR_VPD_IOC_CREATE_HANDLE _IOW(PAPR_MISCDEV_IOC_ID, 0, struct papr_location_code)

#endif /* _UAPI_PAPR_VPD_H_ */
155 changes: 137 additions & 18 deletions librtas_src/vpd.c
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
// SPDX-License-Identifier: LGPL-2.1-or-later

// Support for accessing IBM Power systems Vital Product Data (VPD)
// via the rtas() syscall.
// via /dev/papr-vpd or the legacy rtas() syscall.

#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <stdint.h>
#include <pthread.h>
#include <search.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

#include "internal.h"
#include "librtas.h"
#include "papr-vpd.h"

/**
* rtas_get_vpd
* @brief Interface to the ibm,get-vpd rtas call
*
* @param loc_code location code
* @param workarea additional args to rtas call
* @param size
* @param sequence
* @param seq_next
* @param bytes_ret
* @return 0 on success, !0 otherwise
*/
int rtas_get_vpd(char *loc_code, char *workarea, size_t size,
unsigned int sequence, unsigned int *seq_next,
unsigned int *bytes_ret)
static int
get_vpd_syscall_fallback(const char *loc_code, char *workarea, size_t size,
unsigned int sequence, unsigned int *seq_next,
unsigned int *bytes_ret)
{
uint32_t kernbuf_pa;
uint32_t loc_pa = 0;
Expand Down Expand Up @@ -79,3 +79,122 @@ int rtas_get_vpd(char *loc_code, char *workarea, size_t size,
workarea, size, sequence, status, *seq_next, *bytes_ret);
return rc ? rc : status;
}

static bool get_vpd_can_use_chardev(void)
{
struct stat statbuf;

if (stat("/dev/papr-vpd", &statbuf))
return false;

if (!S_ISCHR(statbuf.st_mode))
return false;

if (close(open("/dev/papr-vpd", O_RDONLY)))
return false;

return true;
}

#define DEVPATH "/dev/papr-vpd"

static int vpd_fd_new(const char *loc_code)
{
const int devfd = open(DEVPATH, O_WRONLY);
struct papr_location_code lc = {};
int fd = -1;

if (devfd < 0)
return -1;

if (!loc_code)
loc_code = "";

if (sizeof(lc.str) < strlen(loc_code))
goto close_devfd;

strncpy(lc.str, loc_code, sizeof(lc.str));
fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
close_devfd:
close(devfd);
return fd;
}

static int
get_vpd_chardev(const char *loc_code, char *workarea, size_t size,
unsigned int sequence, unsigned int *seq_next,
unsigned int *bytes_ret)
{
int fd = (sequence == 1) ? vpd_fd_new(loc_code) : (int)sequence;

/*
* Ensure we return a fd > 0 in seq_next.
*/
if (fd == 1) {
int newfd = dup(fd);
close(fd);
fd = newfd;
}

if (fd < 0)
return -3; /* Synthesize ibm,get-vpd "parameter error" */

int rtas_status = 0;
ssize_t res = read(fd, workarea, size);
if (res < 0) {
rtas_status = -1; /* Synthesize ibm,get-vpd "hardware error" */
close(fd);
} else if (res == 0 || res < (ssize_t)size) {
rtas_status = 0; /* Done with sequence, no more data */
close(fd);
if (seq_next)
*seq_next = 1;
if (bytes_ret)
*bytes_ret = res;
} else {
rtas_status = 1; /* More data available, call again */
if (seq_next)
*seq_next = fd;
if (bytes_ret)
*bytes_ret = res;
}

return rtas_status;
}

static int (*get_vpd_fn)(const char *loc_code,
char *workarea,
size_t size,
unsigned int sequence,
unsigned int *seq_next,
unsigned int *bytes_ret);

static void get_vpd_fn_setup(void)
{
get_vpd_fn = get_vpd_can_use_chardev() ?
get_vpd_chardev : get_vpd_syscall_fallback;
}

/**
* rtas_get_vpd
* @brief Interface to the ibm,get-vpd rtas call
*
* @param loc_code location code
* @param workarea additional args to rtas call
* @param size
* @param sequence
* @param seq_next
* @param bytes_ret
* @return 0 on success, !0 otherwise
*/
int rtas_get_vpd(char *loc_code, char *workarea, size_t size,
unsigned int sequence, unsigned int *seq_next,
unsigned int *bytes_ret)
{
static pthread_once_t get_vpd_fn_once = PTHREAD_ONCE_INIT;

pthread_once(&get_vpd_fn_once, get_vpd_fn_setup);

return get_vpd_fn(loc_code, workarea, size, sequence,
seq_next, bytes_ret);
}

0 comments on commit 57d5e1c

Please sign in to comment.