Skip to content

Commit

Permalink
powerpc/selftests: Add test for papr-vpd
Browse files Browse the repository at this point in the history
Add selftests for /dev/papr-vpd, exercising the common expected use
cases:

* Retrieve all VPD by passing an empty location code.
* Retrieve the "system VPD" by passing a location code derived from DT
  root node properties, as done by the vpdupdate command.

The tests also verify that certain intended properties of the driver
hold:

* Passing an unterminated location code to PAPR_VPD_CREATE_HANDLE gets
  EINVAL.
* Passing a NULL location code pointer to PAPR_VPD_CREATE_HANDLE gets
  EFAULT.
* Closing the device node without first issuing a
  PAPR_VPD_CREATE_HANDLE command to it succeeds.
* Releasing a handle without first consuming any data from it
  succeeds.
* Re-reading the contents of a handle returns the same data as the
  first time.

Some minimal validation of the returned data is performed.

The tests are skipped on systems where the papr-vpd driver does not
initialize, making this useful only on PowerVM LPARs at this point.

Signed-off-by: Nathan Lynch <[email protected]>
Signed-off-by: Michael Ellerman <[email protected]>
Link: https://msgid.link/20231207-papr-sys_rtas-vs-lockdown-v5-12-2ce965636a58@linux.ibm.com
  • Loading branch information
nathanlynch authored and mpe committed Dec 11, 2023
1 parent d4b8c60 commit 26a1e27
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 0 deletions.
1 change: 1 addition & 0 deletions tools/testing/selftests/powerpc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ SUB_DIRS = alignment \
vphn \
math \
papr_attributes \
papr_vpd \
ptrace \
security \
mce
Expand Down
1 change: 1 addition & 0 deletions tools/testing/selftests/powerpc/papr_vpd/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/papr_vpd
12 changes: 12 additions & 0 deletions tools/testing/selftests/powerpc/papr_vpd/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
noarg:
$(MAKE) -C ../

TEST_GEN_PROGS := papr_vpd

top_srcdir = ../../../../..
include ../../lib.mk

$(TEST_GEN_PROGS): ../harness.c ../utils.c

$(OUTPUT)/papr_vpd: CFLAGS += $(KHDR_INCLUDES)
352 changes: 352 additions & 0 deletions tools/testing/selftests/powerpc/papr_vpd/papr_vpd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <asm/papr-vpd.h>

#include "utils.h"

#define DEVPATH "/dev/papr-vpd"

static int dev_papr_vpd_open_close(void)
{
const int devfd = open(DEVPATH, O_RDONLY);

SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);
FAIL_IF(close(devfd) != 0);

return 0;
}

static int dev_papr_vpd_get_handle_all(void)
{
const int devfd = open(DEVPATH, O_RDONLY);
struct papr_location_code lc = { .str = "", };
off_t size;
int fd;

SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);

errno = 0;
fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
FAIL_IF(errno != 0);
FAIL_IF(fd < 0);

FAIL_IF(close(devfd) != 0);

size = lseek(fd, 0, SEEK_END);
FAIL_IF(size <= 0);

void *buf = malloc((size_t)size);
FAIL_IF(!buf);

ssize_t consumed = pread(fd, buf, size, 0);
FAIL_IF(consumed != size);

/* Ensure EOF */
FAIL_IF(read(fd, buf, size) != 0);
FAIL_IF(close(fd));

/* Verify that the buffer looks like VPD */
static const char needle[] = "System VPD";
FAIL_IF(!memmem(buf, size, needle, strlen(needle)));

return 0;
}

static int dev_papr_vpd_get_handle_byte_at_a_time(void)
{
const int devfd = open(DEVPATH, O_RDONLY);
struct papr_location_code lc = { .str = "", };
int fd;

SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);

errno = 0;
fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
FAIL_IF(errno != 0);
FAIL_IF(fd < 0);

FAIL_IF(close(devfd) != 0);

size_t consumed = 0;
while (1) {
ssize_t res;
char c;

errno = 0;
res = read(fd, &c, sizeof(c));
FAIL_IF(res > sizeof(c));
FAIL_IF(res < 0);
FAIL_IF(errno != 0);
consumed += res;
if (res == 0)
break;
}

FAIL_IF(consumed != lseek(fd, 0, SEEK_END));

FAIL_IF(close(fd));

return 0;
}


static int dev_papr_vpd_unterm_loc_code(void)
{
const int devfd = open(DEVPATH, O_RDONLY);
struct papr_location_code lc = {};
int fd;

SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);

/*
* Place a non-null byte in every element of loc_code; the
* driver should reject this input.
*/
memset(lc.str, 'x', ARRAY_SIZE(lc.str));

errno = 0;
fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
FAIL_IF(fd != -1);
FAIL_IF(errno != EINVAL);

FAIL_IF(close(devfd) != 0);
return 0;
}

static int dev_papr_vpd_null_handle(void)
{
const int devfd = open(DEVPATH, O_RDONLY);
int rc;

SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);

errno = 0;
rc = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, NULL);
FAIL_IF(rc != -1);
FAIL_IF(errno != EFAULT);

FAIL_IF(close(devfd) != 0);
return 0;
}

static int papr_vpd_close_handle_without_reading(void)
{
const int devfd = open(DEVPATH, O_RDONLY);
struct papr_location_code lc;
int fd;

SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);

errno = 0;
fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
FAIL_IF(errno != 0);
FAIL_IF(fd < 0);

/* close the handle without reading it */
FAIL_IF(close(fd) != 0);

FAIL_IF(close(devfd) != 0);
return 0;
}

static int papr_vpd_reread(void)
{
const int devfd = open(DEVPATH, O_RDONLY);
struct papr_location_code lc = { .str = "", };
int fd;

SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);

errno = 0;
fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
FAIL_IF(errno != 0);
FAIL_IF(fd < 0);

FAIL_IF(close(devfd) != 0);

const off_t size = lseek(fd, 0, SEEK_END);
FAIL_IF(size <= 0);

char *bufs[2];

for (size_t i = 0; i < ARRAY_SIZE(bufs); ++i) {
bufs[i] = malloc(size);
FAIL_IF(!bufs[i]);
ssize_t consumed = pread(fd, bufs[i], size, 0);
FAIL_IF(consumed != size);
}

FAIL_IF(memcmp(bufs[0], bufs[1], size));

FAIL_IF(close(fd) != 0);

return 0;
}

static int get_system_loc_code(struct papr_location_code *lc)
{
static const char system_id_path[] = "/sys/firmware/devicetree/base/system-id";
static const char model_path[] = "/sys/firmware/devicetree/base/model";
char *system_id;
char *model;
int err = -1;

if (read_file_alloc(model_path, &model, NULL))
return err;

if (read_file_alloc(system_id_path, &system_id, NULL))
goto free_model;

char *mtm;
int sscanf_ret = sscanf(model, "IBM,%ms", &mtm);
if (sscanf_ret != 1)
goto free_system_id;

char *plant_and_seq;
if (sscanf(system_id, "IBM,%*c%*c%ms", &plant_and_seq) != 1)
goto free_mtm;
/*
* Replace - with . to build location code.
*/
char *sep = strchr(mtm, '-');
if (!sep)
goto free_mtm;
else
*sep = '.';

snprintf(lc->str, sizeof(lc->str),
"U%s.%s", mtm, plant_and_seq);
err = 0;

free(plant_and_seq);
free_mtm:
free(mtm);
free_system_id:
free(system_id);
free_model:
free(model);
return err;
}

static int papr_vpd_system_loc_code(void)
{
struct papr_location_code lc;
const int devfd = open(DEVPATH, O_RDONLY);
off_t size;
int fd;

SKIP_IF_MSG(get_system_loc_code(&lc),
"Cannot determine system location code");
SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
DEVPATH " not present");

FAIL_IF(devfd < 0);

errno = 0;
fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
FAIL_IF(errno != 0);
FAIL_IF(fd < 0);

FAIL_IF(close(devfd) != 0);

size = lseek(fd, 0, SEEK_END);
FAIL_IF(size <= 0);

void *buf = malloc((size_t)size);
FAIL_IF(!buf);

ssize_t consumed = pread(fd, buf, size, 0);
FAIL_IF(consumed != size);

/* Ensure EOF */
FAIL_IF(read(fd, buf, size) != 0);
FAIL_IF(close(fd));

/* Verify that the buffer looks like VPD */
static const char needle[] = "System VPD";
FAIL_IF(!memmem(buf, size, needle, strlen(needle)));

return 0;
}

struct vpd_test {
int (*function)(void);
const char *description;
};

static const struct vpd_test vpd_tests[] = {
{
.function = dev_papr_vpd_open_close,
.description = "open/close " DEVPATH,
},
{
.function = dev_papr_vpd_unterm_loc_code,
.description = "ensure EINVAL on unterminated location code",
},
{
.function = dev_papr_vpd_null_handle,
.description = "ensure EFAULT on bad handle addr",
},
{
.function = dev_papr_vpd_get_handle_all,
.description = "get handle for all VPD"
},
{
.function = papr_vpd_close_handle_without_reading,
.description = "close handle without consuming VPD"
},
{
.function = dev_papr_vpd_get_handle_byte_at_a_time,
.description = "read all VPD one byte at a time"
},
{
.function = papr_vpd_reread,
.description = "ensure re-read yields same results"
},
{
.function = papr_vpd_system_loc_code,
.description = "get handle for system VPD"
},
};

int main(void)
{
size_t fails = 0;

for (size_t i = 0; i < ARRAY_SIZE(vpd_tests); ++i) {
const struct vpd_test *t = &vpd_tests[i];

if (test_harness(t->function, t->description))
++fails;
}

return fails == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

0 comments on commit 26a1e27

Please sign in to comment.