Add a set of selftests for pSeries/PowerVM-specific kernel interfaces: - lparcfg_test: validates /proc/ppc64/lparcfg fields (partition_id, shared_processor_mode, processor counts, partition_entitled_capacity, partition_name, system_type) - dtl_test: validates the vpa_dtl perf PMU by running perf record against the DTL event source and confirming samples are captured; skips gracefully if the PMU is absent or perf is not installed - pseries_energy_test: validates CPU frequency from /proc/cpuinfo and power management fields from /proc/ppc64/lparcfg (partition_entitled_capacity, partition_max_entitled_capacity, power_mode_data as a 16-character hex string) - papr_rtas_common_test: exercises RTAS work-area serialisation by issuing 1000 concurrent get-time-of-day calls across 10 threads via syscall(__NR_rtas) and asserting every call returns status 0
Signed-off-by: Pavithra <[email protected]> --- tools/testing/selftests/powerpc/Makefile | 1 + .../selftests/powerpc/papr_pseries/Makefile | 19 ++ .../selftests/powerpc/papr_pseries/dtl_test.c | 93 ++++++++++ .../powerpc/papr_pseries/lparcfg_test.c | 125 +++++++++++++ .../papr_pseries/papr_rtas_common_test.c | 171 ++++++++++++++++++ .../papr_pseries/pseries_energy_test.c | 155 ++++++++++++++++ .../selftests/powerpc/papr_pseries/utils.h | 19 ++ 7 files changed, 583 insertions(+) create mode 100644 tools/testing/selftests/powerpc/papr_pseries/Makefile create mode 100644 tools/testing/selftests/powerpc/papr_pseries/dtl_test.c create mode 100644 tools/testing/selftests/powerpc/papr_pseries/lparcfg_test.c create mode 100644 tools/testing/selftests/powerpc/papr_pseries/papr_rtas_common_test.c create mode 100644 tools/testing/selftests/powerpc/papr_pseries/pseries_energy_test.c create mode 100644 tools/testing/selftests/powerpc/papr_pseries/utils.h diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile index b175e94e1901..93aaa7a90a1b 100644 --- a/tools/testing/selftests/powerpc/Makefile +++ b/tools/testing/selftests/powerpc/Makefile @@ -26,6 +26,7 @@ SUB_DIRS = alignment \ vphn \ math \ papr_attributes \ + papr_pseries \ papr_vpd \ papr_sysparm \ ptrace \ diff --git a/tools/testing/selftests/powerpc/papr_pseries/Makefile b/tools/testing/selftests/powerpc/papr_pseries/Makefile new file mode 100644 index 000000000000..2016a6e3a8e7 --- /dev/null +++ b/tools/testing/selftests/powerpc/papr_pseries/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +noarg: + $(MAKE) -C ../ + +TEST_GEN_PROGS := lparcfg_test +TEST_GEN_PROGS += dtl_test +TEST_GEN_PROGS += pseries_energy_test +TEST_GEN_PROGS += papr_rtas_common_test + +top_srcdir = ../../../../.. +include ../../lib.mk +include ../flags.mk + +$(TEST_GEN_PROGS): ../harness.c ../utils.c + +$(OUTPUT)/papr_rtas_common_test: CFLAGS += $(KHDR_INCLUDES) +$(OUTPUT)/papr_rtas_common_test: LDFLAGS += -lpthread + +$(OUTPUT)/dtl_test: CFLAGS += $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/powerpc/papr_pseries/dtl_test.c b/tools/testing/selftests/powerpc/papr_pseries/dtl_test.c new file mode 100644 index 000000000000..097c80def4c9 --- /dev/null +++ b/tools/testing/selftests/powerpc/papr_pseries/dtl_test.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Dispatch Trace Log (DTL) Test + * Copyright (C) 2025 IBM Corporation + * + * Validates the vpa_dtl perf PMU on PowerVM LPARs. The PMU sysfs directory + * is created by the DTL subsystem when FW_FEATURE_SPLPAR is set. + * + * The test SKIPs when the vpa_dtl PMU is absent or perf is not installed. + * The test FAILs when the PMU is present and perf is installed but the + * event cannot be opened or produces no samples. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include "utils.h" + +#define VPA_DTL_PMU_PATH "/sys/bus/event_source/devices/vpa_dtl" + +static int perf_is_installed(void) +{ + return system("perf version >/dev/null 2>&1") == 0; +} + +static int test_dtl_via_perf(void) +{ + char outfile[] = "/tmp/dtl_test_XXXXXX"; + char cmd[256]; + struct stat st; + int fd, ret; + + fd = mkstemp(outfile); + if (fd < 0) { + fprintf(stderr, "[FAIL] mkstemp failed\n"); + return -1; + } + close(fd); + + snprintf(cmd, sizeof(cmd), + "perf record -e vpa_dtl/dtl_all/ -a -o %s sleep 1 2>/dev/null", + outfile); + + printf("Running: %s\n", cmd); + ret = system(cmd); + + if (ret != 0) { + fprintf(stderr, + "[FAIL] perf record failed (exit %d) -- vpa_dtl PMU present but perf could not open the event\n", + WEXITSTATUS(ret)); + unlink(outfile); + return -1; + } + + if (stat(outfile, &st) != 0) { + fprintf(stderr, + "[FAIL] perf record succeeded but output file was not created\n"); + unlink(outfile); + return -1; + } + + if (st.st_size < 1024) { + fprintf(stderr, + "[FAIL] perf.data is only %ld bytes -- vpa_dtl PMU opened but delivered no samples\n", + (long)st.st_size); + unlink(outfile); + return -1; + } + + printf("vpa_dtl PMU: %ld bytes of samples captured\n", + (long)st.st_size); + unlink(outfile); + return 0; +} + +int main(void) +{ + test_harness_set_timeout(30); + + SKIP_IF_MSG(access(VPA_DTL_PMU_PATH, F_OK) != 0, + "vpa_dtl PMU not present (" VPA_DTL_PMU_PATH " missing)"); + + SKIP_IF_MSG(!perf_is_installed(), + "perf binary not found -- install perf to run this test"); + + printf("vpa_dtl PMU present, perf available\n"); + FAIL_IF(test_dtl_via_perf() != 0); + + printf("dtl_test passed\n"); + return 0; +} diff --git a/tools/testing/selftests/powerpc/papr_pseries/lparcfg_test.c b/tools/testing/selftests/powerpc/papr_pseries/lparcfg_test.c new file mode 100644 index 000000000000..b43af7e1c4e7 --- /dev/null +++ b/tools/testing/selftests/powerpc/papr_pseries/lparcfg_test.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LPAR Configuration Test + * Copyright (C) 2026 IBM Corporation + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "utils.h" + +#define LPARCFG_PATH "/proc/ppc64/lparcfg" + +struct lparcfg_data { + int partition_id; + char partition_name[256]; + char system_type[256]; + int shared_processor_mode; + int system_active_processors; + int partition_active_processors; + int partition_potential_processors; + int partition_entitled_capacity; + int found_fields; +}; + +static int parse_lparcfg(struct lparcfg_data *data) +{ + FILE *fp; + char line[512]; + char key[128], value[256]; + + memset(data, 0, sizeof(*data)); + + fp = fopen(LPARCFG_PATH, "r"); + if (!fp) + return -1; + + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "%127[^=]=%255[^\n]", key, value) == 2) { + if (strcmp(key, "partition_id") == 0) { + data->partition_id = atoi(value); + data->found_fields++; + } else if (strcmp(key, "partition_name") == 0) { + snprintf(data->partition_name, + sizeof(data->partition_name), + "%s", value); + data->found_fields++; + } else if (strcmp(key, "system_type") == 0) { + snprintf(data->system_type, + sizeof(data->system_type), + "%s", value); + data->found_fields++; + } else if (strcmp(key, "shared_processor_mode") == 0) { + data->shared_processor_mode = atoi(value); + data->found_fields++; + } else if (strcmp(key, "system_active_processors") == 0) { + data->system_active_processors = atoi(value); + data->found_fields++; + } else if (strcmp(key, "partition_active_processors") == 0) { + data->partition_active_processors = atoi(value); + data->found_fields++; + } else if (strcmp(key, "partition_potential_processors") == 0) { + data->partition_potential_processors = atoi(value); + data->found_fields++; + } else if (strcmp(key, "partition_entitled_capacity") == 0) { + data->partition_entitled_capacity = atoi(value); + data->found_fields++; + } + } + } + + fclose(fp); + return 0; +} + +int main(void) +{ + struct lparcfg_data data; + + test_harness_set_timeout(10); + + SKIP_IF(access(LPARCFG_PATH, R_OK) != 0); + + FAIL_IF(parse_lparcfg(&data) != 0); + + FAIL_IF_MSG(data.found_fields < 5, + "Only found %d fields (expected at least 5)", data.found_fields); + + FAIL_IF_MSG(data.partition_id == 0, "partition_id is 0 (invalid)"); + printf("partition_id: %d\n", data.partition_id); + + FAIL_IF_MSG(data.shared_processor_mode != 0 && data.shared_processor_mode != 1, + "shared_processor_mode is %d (expected 0 or 1)", + data.shared_processor_mode); + printf("shared_processor_mode: %d\n", data.shared_processor_mode); + + FAIL_IF_MSG(data.system_active_processors <= 0, + "system_active_processors is %d (expected > 0)", + data.system_active_processors); + printf("system_active_processors: %d\n", data.system_active_processors); + + FAIL_IF_MSG(data.partition_active_processors <= 0, + "partition_active_processors is %d (expected > 0)", + data.partition_active_processors); + printf("partition_active_processors: %d\n", data.partition_active_processors); + + FAIL_IF_MSG(data.partition_potential_processors > 0 && + data.partition_active_processors > data.partition_potential_processors, + "partition_active_processors (%d) > partition_potential_processors (%d)", + data.partition_active_processors, data.partition_potential_processors); + + FAIL_IF_MSG(data.partition_entitled_capacity == 0, + "partition_entitled_capacity is 0 (invalid)"); + printf("partition_entitled_capacity: %d\n", data.partition_entitled_capacity); + + FAIL_IF_MSG(strlen(data.partition_name) == 0, "partition_name is empty"); + printf("partition_name: %s\n", data.partition_name); + + FAIL_IF_MSG(strlen(data.system_type) == 0, "system_type is empty"); + printf("system_type: %s\n", data.system_type); + + printf("lparcfg test passed\n"); + return 0; +} diff --git a/tools/testing/selftests/powerpc/papr_pseries/papr_rtas_common_test.c b/tools/testing/selftests/powerpc/papr_pseries/papr_rtas_common_test.c new file mode 100644 index 000000000000..56e4ec6aa0fd --- /dev/null +++ b/tools/testing/selftests/powerpc/papr_pseries/papr_rtas_common_test.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PAPR RTAS Work-Area Locking Test + * Copyright (C) 2026 IBM Corporation + * + * Exercises the kernel's RTAS work-area serialisation under concurrent load + * by spawning multiple threads that simultaneously issue the get-time-of-day + * RTAS call via __NR_rtas. Every call must return status 0; a non-zero + * status indicates work-area corruption caused by a locking failure. + */ + +#include <byteswap.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <sys/syscall.h> +#include "utils.h" + +#ifndef __NR_rtas +#error "This test requires a pSeries kernel (__NR_rtas is not defined)" +#endif + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define cpu_to_be32(x) bswap_32(x) +#define be32_to_cpu(x) bswap_32(x) +#else +#define cpu_to_be32(x) (x) +#define be32_to_cpu(x) (x) +#endif + +#define OFDT_RTAS_PATH "/proc/device-tree/rtas" +#define RTAS_UNKNOWN_OP (-1099) + +#define NUM_THREADS 10 +#define ITERATIONS 100 + +struct rtas_args { + __be32 token; + __be32 nargs; + __be32 nret; + __be32 args[16]; + __be32 *rets; +}; + +static int gtod_token = RTAS_UNKNOWN_OP; + +static int rtas_get_token(const char *call_name) +{ + char path[256]; + __be32 raw; + ssize_t n; + int fd; + + snprintf(path, sizeof(path), "%s/%s", OFDT_RTAS_PATH, call_name); + + fd = open(path, O_RDONLY); + if (fd < 0) + return RTAS_UNKNOWN_OP; + + n = read(fd, &raw, sizeof(raw)); + close(fd); + + if (n != (ssize_t)sizeof(raw)) + return RTAS_UNKNOWN_OP; + + return (int)be32_to_cpu(raw); +} + +static int rtas_call_gtod(void) +{ + struct rtas_args args; + + memset(&args, 0, sizeof(args)); + args.token = cpu_to_be32(gtod_token); + args.nargs = cpu_to_be32(0); + args.nret = cpu_to_be32(1); + args.rets = &args.args[0]; + + if (syscall(__NR_rtas, &args) != 0) + return -errno; + + return (int)be32_to_cpu(args.args[0]); +} + +struct thread_data { + int thread_id; + int success_count; + int error_count; +}; + +static void *rtas_worker(void *arg) +{ + struct thread_data *data = (struct thread_data *)arg; + int i, rc; + + for (i = 0; i < ITERATIONS; i++) { + rc = rtas_call_gtod(); + if (rc != 0) { + fprintf(stderr, + "[FAIL] thread %d iteration %d: get-time-of-day returned %d\n", + data->thread_id, i, rc); + data->error_count++; + } else { + data->success_count++; + } + } + + return NULL; +} + +int main(void) +{ + pthread_t threads[NUM_THREADS]; + struct thread_data thread_data[NUM_THREADS]; + int total_success = 0; + int total_errors = 0; + int i, rc; + + test_harness_set_timeout(60); + + SKIP_IF(access(OFDT_RTAS_PATH, R_OK) != 0); + + gtod_token = rtas_get_token("get-time-of-day"); + SKIP_IF(gtod_token == RTAS_UNKNOWN_OP); + printf("get-time-of-day token: %d\n", gtod_token); + + rc = rtas_call_gtod(); + FAIL_IF_MSG(rc != 0, + "Baseline get-time-of-day failed with status %d", rc); + printf("Baseline get-time-of-day: OK\n"); + + printf("Spawning %d threads, %d RTAS calls each...\n", + NUM_THREADS, ITERATIONS); + + for (i = 0; i < NUM_THREADS; i++) { + thread_data[i].thread_id = i; + thread_data[i].success_count = 0; + thread_data[i].error_count = 0; + + FAIL_IF(pthread_create(&threads[i], NULL, rtas_worker, + &thread_data[i]) != 0); + } + + for (i = 0; i < NUM_THREADS; i++) { + FAIL_IF(pthread_join(threads[i], NULL) != 0); + total_success += thread_data[i].success_count; + total_errors += thread_data[i].error_count; + printf("Thread %2d: %d ok, %d errors\n", + i, thread_data[i].success_count, + thread_data[i].error_count); + } + + printf("Total: %d successful RTAS calls, %d errors\n", + total_success, total_errors); + + FAIL_IF_MSG(total_errors != 0, + "%d of %d concurrent RTAS calls returned non-zero status; " + "possible work-area lock corruption", + total_errors, NUM_THREADS * ITERATIONS); + + FAIL_IF_MSG(total_success != NUM_THREADS * ITERATIONS, + "Expected %d successful calls, got %d", + NUM_THREADS * ITERATIONS, total_success); + + printf("papr_rtas_common_test passed\n"); + return 0; +} diff --git a/tools/testing/selftests/powerpc/papr_pseries/pseries_energy_test.c b/tools/testing/selftests/powerpc/papr_pseries/pseries_energy_test.c new file mode 100644 index 000000000000..db6eb86d4647 --- /dev/null +++ b/tools/testing/selftests/powerpc/papr_pseries/pseries_energy_test.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * pseries Energy/Power Management Test + * Copyright (C) 2026 IBM Corporation + * + * Validates CPU frequency and power management on pSeries LPARs by reading + * /proc/cpuinfo and /proc/ppc64/lparcfg directly. + * + * Tests performed: + * 1. CPU clock frequency is readable from /proc/cpuinfo and is > 0. + * 2. partition_entitled_capacity is present in lparcfg and is > 0. + * 3. partition_max_entitled_capacity is present and >= entitled_capacity. + * 4. power_mode_data is present and is a 16-character hex string. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include "utils.h" + +#define PROC_CPUINFO "/proc/cpuinfo" +#define LPARCFG "/proc/ppc64/lparcfg" + +#define POWER_MODE_DATA_LEN 16 + +static double read_cpuinfo_freq(void) +{ + FILE *fp; + char line[256]; + double mhz = 0.0; + + fp = fopen(PROC_CPUINFO, "r"); + if (!fp) + return 0.0; + + while (fgets(line, sizeof(line), fp)) { + char *colon; + + /* "clock : <value>MHz" ??? pSeries dedicated-mode LPAR */ + if (strncmp(line, "clock", 5) == 0) { + colon = strchr(line, ':'); + if (colon) + mhz = strtod(colon + 1, NULL); + break; + } + + /* "cpu MHz : <value>" ??? other Power configurations */ + if (strncmp(line, "cpu MHz", 7) == 0) { + colon = strchr(line, ':'); + if (colon) + mhz = strtod(colon + 1, NULL); + break; + } + } + + fclose(fp); + return mhz; +} + +static int read_lparcfg_ulong(FILE *fp, const char *key, unsigned long *out) +{ + char line[256]; + size_t keylen = strlen(key); + char *endptr; + + rewind(fp); + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, key, keylen) == 0 && line[keylen] == '=') { + *out = strtoul(line + keylen + 1, &endptr, 10); + if (endptr == line + keylen + 1) + return -1; + return 0; + } + } + return -1; +} + +static int read_lparcfg_str(FILE *fp, const char *key, char *buf, size_t size) +{ + char line[256]; + size_t keylen = strlen(key); + + rewind(fp); + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, key, keylen) == 0 && line[keylen] == '=') { + char *val = line + keylen + 1; + size_t vlen; + + val[strcspn(val, "\n")] = 0; + vlen = strlen(val); + if (vlen == 0 || vlen >= size) + return -1; + memcpy(buf, val, vlen + 1); + return 0; + } + } + return -1; +} + +/* ------------------------------------------------------------------ */ + +int main(void) +{ + double mhz; + FILE *fp; + unsigned long entitled, max_entitled; + char power_mode[32]; + size_t i; + + test_harness_set_timeout(10); + + SKIP_IF(access(LPARCFG, R_OK) != 0); + + mhz = read_cpuinfo_freq(); + FAIL_IF_MSG(mhz <= 0.0, + "Could not read a valid CPU frequency from " PROC_CPUINFO); + printf("CPU frequency: %.3f GHz\n", mhz / 1000.0); + + fp = fopen(LPARCFG, "r"); + FAIL_IF_MSG(!fp, "Cannot open " LPARCFG); + + FAIL_IF_MSG(read_lparcfg_ulong(fp, "partition_entitled_capacity", + &entitled) != 0, + "partition_entitled_capacity missing from " LPARCFG); + FAIL_IF_MSG(entitled == 0, "partition_entitled_capacity is 0"); + printf("partition_entitled_capacity: %lu\n", entitled); + + FAIL_IF_MSG(read_lparcfg_ulong(fp, "partition_max_entitled_capacity", + &max_entitled) != 0, + "partition_max_entitled_capacity missing from " LPARCFG); + printf("partition_max_entitled_capacity: %lu\n", max_entitled); + FAIL_IF_MSG(max_entitled < entitled, + "partition_max_entitled_capacity (%lu) < partition_entitled_capacity (%lu)", + max_entitled, entitled); + + FAIL_IF_MSG(read_lparcfg_str(fp, "power_mode_data", + power_mode, sizeof(power_mode)) != 0, + "power_mode_data missing from " LPARCFG); + fclose(fp); + fp = NULL; + printf("power_mode_data: %s\n", power_mode); + FAIL_IF_MSG(strlen(power_mode) != POWER_MODE_DATA_LEN, + "power_mode_data '%s' is %zu chars, expected %d", + power_mode, strlen(power_mode), POWER_MODE_DATA_LEN); + for (i = 0; i < POWER_MODE_DATA_LEN; i++) { + FAIL_IF_MSG(!isxdigit((unsigned char)power_mode[i]), + "power_mode_data '%s' has non-hex char '%c' at position %zu", + power_mode, power_mode[i], i); + } + + printf("pseries_energy test passed\n"); + return 0; +} diff --git a/tools/testing/selftests/powerpc/papr_pseries/utils.h b/tools/testing/selftests/powerpc/papr_pseries/utils.h new file mode 100644 index 000000000000..6eb88958a0ba --- /dev/null +++ b/tools/testing/selftests/powerpc/papr_pseries/utils.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Local test utilities for papr_pseries selftests. + */ +#ifndef _PAPR_PSERIES_TEST_UTILS_H +#define _PAPR_PSERIES_TEST_UTILS_H + +#include "../include/utils.h" + +#undef FAIL_IF_MSG +#define FAIL_IF_MSG(x, fmt, ...) \ +do { \ + if ((x)) { \ + fprintf(stderr, "[FAIL] " fmt "\n", ##__VA_ARGS__); \ + return 1; \ + } \ +} while (0) + +#endif /* _PAPR_PSERIES_TEST_UTILS_H */ -- 2.55.0
