Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,76 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for powerpc selftests
# ARCH can be overridden by the user for cross compiling
ARCH ?= $(shell uname -m)
ARCH := $(shell echo $(ARCH) | sed -e s/ppc.*/powerpc/)
ifeq ($(ARCH),powerpc)
SUB_DIRS = alignment \
benchmarks \
cache_shape \
copyloops \
dexcr \
dscr \
mm \
nx-gzip \
pmu \
signal \
primitives \
stringloops \
switch_endian \
syscalls \
tm \
eeh \
vphn \
math \
papr_attributes \
papr_vpd \
papr_sysparm \
ptrace \
security \
mce
endif
all: $(SUB_DIRS)
$(SUB_DIRS):
BUILD_TARGET=$(OUTPUT)/$@; mkdir -p $$BUILD_TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -k -C $@ all
include ../lib.mk
include ./flags.mk
override define RUN_TESTS
+@for TARGET in $(SUB_DIRS); do \
BUILD_TARGET=$(OUTPUT)/$$TARGET; \
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests;\
done;
endef
override define INSTALL_RULE
+@for TARGET in $(SUB_DIRS); do \
BUILD_TARGET=$(OUTPUT)/$$TARGET; \
$(MAKE) OUTPUT=$$BUILD_TARGET INSTALL_PATH=$$INSTALL_PATH/$$TARGET -C $$TARGET install;\
done;
endef
emit_tests:
+@for TARGET in $(SUB_DIRS); do \
BUILD_TARGET=$(OUTPUT)/$$TARGET; \
$(MAKE) OUTPUT=$$BUILD_TARGET COLLECTION=$(COLLECTION)/$$TARGET -s -C $$TARGET $@;\
done;
override define CLEAN
+@for TARGET in $(SUB_DIRS); do \
BUILD_TARGET=$(OUTPUT)/$$TARGET; \
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean; \
done;
rm -f tags
endef
tags:
find . -name '*.c' -o -name '*.h' | xargs ctags
.PHONY: tags $(SUB_DIRS) emit_tests
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
copy_first_unaligned
alignment_handler
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
TEST_GEN_PROGS := copy_first_unaligned alignment_handler
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
$(TEST_GEN_PROGS): ../harness.c ../utils.c
@@ -0,0 +1,680 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test the powerpc alignment handler on POWER8/POWER9
*
* Copyright (C) 2017 IBM Corporation (Michael Neuling, Andrew Donnellan)
*/
/*
* This selftest exercises the powerpc alignment fault handler.
*
* We create two sets of source and destination buffers, one in regular memory,
* the other cache-inhibited (by default we use /dev/fb0 for this, but an
* alterative path for cache-inhibited memory may be provided, e.g. memtrace).
*
* We initialise the source buffers, then use whichever set of load/store
* instructions is under test to copy bytes from the source buffers to the
* destination buffers. For the regular buffers, these instructions will
* execute normally. For the cache-inhibited buffers, these instructions
* will trap and cause an alignment fault, and the alignment fault handler
* will emulate the particular instruction under test. We then compare the
* destination buffers to ensure that the native and emulated cases give the
* same result.
*
* TODO:
* - Any FIXMEs below
* - Test VSX regs < 32 and > 32
* - Test all loads and stores
* - Check update forms do update register
* - Test alignment faults over page boundary
*
* Some old binutils may not support all the instructions.
*/
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <setjmp.h>
#include <signal.h>
#include "utils.h"
#include "instructions.h"
int bufsize;
int debug;
int testing;
volatile int gotsig;
bool prefixes_enabled;
char *cipath = "/dev/fb0";
long cioffset;
void sighandler(int sig, siginfo_t *info, void *ctx)
{
ucontext_t *ucp = ctx;
if (!testing) {
signal(sig, SIG_DFL);
kill(0, sig);
}
gotsig = sig;
#ifdef __powerpc64__
if (prefixes_enabled) {
u32 inst = *(u32 *)ucp->uc_mcontext.gp_regs[PT_NIP];
ucp->uc_mcontext.gp_regs[PT_NIP] += ((inst >> 26 == 1) ? 8 : 4);
} else {
ucp->uc_mcontext.gp_regs[PT_NIP] += 4;
}
#else
ucp->uc_mcontext.uc_regs->gregs[PT_NIP] += 4;
#endif
}
#define XFORM(reg, n) " " #reg " ,%"#n",%2 ;"
#define DFORM(reg, n) " " #reg " ,0(%"#n") ;"
#define TEST(name, ld_op, st_op, form, ld_reg, st_reg) \
void test_##name(char *s, char *d) \
{ \
asm volatile( \
#ld_op form(ld_reg, 0) \
#st_op form(st_reg, 1) \
:: "r"(s), "r"(d), "r"(0) \
: "memory", "vs0", "vs32", "r31"); \
} \
rc |= do_test(#name, test_##name)
#define TESTP(name, ld_op, st_op, ld_reg, st_reg) \
void test_##name(char *s, char *d) \
{ \
asm volatile( \
ld_op(ld_reg, %0, 0, 0) \
st_op(st_reg, %1, 0, 0) \
:: "r"(s), "r"(d), "r"(0) \
: "memory", "vs0", "vs32", "r31"); \
} \
rc |= do_test(#name, test_##name)
#define LOAD_VSX_XFORM_TEST(op) TEST(op, op, stxvd2x, XFORM, 32, 32)
#define STORE_VSX_XFORM_TEST(op) TEST(op, lxvd2x, op, XFORM, 32, 32)
#define LOAD_VSX_DFORM_TEST(op) TEST(op, op, stxv, DFORM, 32, 32)
#define STORE_VSX_DFORM_TEST(op) TEST(op, lxv, op, DFORM, 32, 32)
#define LOAD_VMX_XFORM_TEST(op) TEST(op, op, stxvd2x, XFORM, 0, 32)
#define STORE_VMX_XFORM_TEST(op) TEST(op, lxvd2x, op, XFORM, 32, 0)
#define LOAD_VMX_DFORM_TEST(op) TEST(op, op, stxv, DFORM, 0, 32)
#define STORE_VMX_DFORM_TEST(op) TEST(op, lxv, op, DFORM, 32, 0)
#define LOAD_XFORM_TEST(op) TEST(op, op, stdx, XFORM, 31, 31)
#define STORE_XFORM_TEST(op) TEST(op, ldx, op, XFORM, 31, 31)
#define LOAD_DFORM_TEST(op) TEST(op, op, std, DFORM, 31, 31)
#define STORE_DFORM_TEST(op) TEST(op, ld, op, DFORM, 31, 31)
#define LOAD_FLOAT_DFORM_TEST(op) TEST(op, op, stfd, DFORM, 0, 0)
#define STORE_FLOAT_DFORM_TEST(op) TEST(op, lfd, op, DFORM, 0, 0)
#define LOAD_FLOAT_XFORM_TEST(op) TEST(op, op, stfdx, XFORM, 0, 0)
#define STORE_FLOAT_XFORM_TEST(op) TEST(op, lfdx, op, XFORM, 0, 0)
#define LOAD_MLS_PREFIX_TEST(op) TESTP(op, op, PSTD, 31, 31)
#define STORE_MLS_PREFIX_TEST(op) TESTP(op, PLD, op, 31, 31)
#define LOAD_8LS_PREFIX_TEST(op) TESTP(op, op, PSTD, 31, 31)
#define STORE_8LS_PREFIX_TEST(op) TESTP(op, PLD, op, 31, 31)
#define LOAD_FLOAT_MLS_PREFIX_TEST(op) TESTP(op, op, PSTFD, 0, 0)
#define STORE_FLOAT_MLS_PREFIX_TEST(op) TESTP(op, PLFD, op, 0, 0)
#define LOAD_VSX_8LS_PREFIX_TEST(op, tail) TESTP(op, op, PSTXV ## tail, 0, 32)
#define STORE_VSX_8LS_PREFIX_TEST(op, tail) TESTP(op, PLXV ## tail, op, 32, 0)
/* FIXME: Unimplemented tests: */
// STORE_DFORM_TEST(stq) /* FIXME: need two registers for quad */
// STORE_DFORM_TEST(stswi) /* FIXME: string instruction */
// STORE_XFORM_TEST(stwat) /* AMO can't emulate or run on CI */
// STORE_XFORM_TEST(stdat) /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
/* preload byte by byte */
void preload_data(void *dst, int offset, int width)
{
char *c = dst;
int i;
c += offset;
for (i = 0 ; i < width ; i++)
c[i] = i;
}
int test_memcpy(void *dst, void *src, int size, int offset,
void (*test_func)(char *, char *))
{
char *s, *d;
s = src;
s += offset;
d = dst;
d += offset;
assert(size == 16);
gotsig = 0;
testing = 1;
test_func(s, d); /* run the actual test */
testing = 0;
if (gotsig) {
if (debug)
printf(" Got signal %i\n", gotsig);
return 1;
}
return 0;
}
void dumpdata(char *s1, char *s2, int n, char *test_name)
{
int i;
printf(" %s: unexpected result:\n", test_name);
printf(" mem:");
for (i = 0; i < n; i++)
printf(" %02x", s1[i]);
printf("\n");
printf(" ci: ");
for (i = 0; i < n; i++)
printf(" %02x", s2[i]);
printf("\n");
}
int test_memcmp(void *s1, void *s2, int n, int offset, char *test_name)
{
char *s1c, *s2c;
s1c = s1;
s1c += offset;
s2c = s2;
s2c += offset;
if (memcmp(s1c, s2c, n)) {
if (debug) {
printf("\n Compare failed. Offset:%i length:%i\n",
offset, n);
dumpdata(s1c, s2c, n, test_name);
}
return 1;
}
return 0;
}
/*
* Do two memcpy tests using the same instructions. One cachable
* memory and the other doesn't.
*/
int do_test(char *test_name, void (*test_func)(char *, char *))
{
int offset, width, fd, rc, r;
void *mem0, *mem1, *ci0, *ci1;
printf("\tDoing %s:\t", test_name);
fd = open(cipath, O_RDWR);
if (fd < 0) {
printf("\n");
perror("Can't open ci file now?");
return 1;
}
ci0 = mmap(NULL, bufsize, PROT_WRITE | PROT_READ, MAP_SHARED,
fd, cioffset);
ci1 = mmap(NULL, bufsize, PROT_WRITE | PROT_READ, MAP_SHARED,
fd, cioffset + bufsize);
if ((ci0 == MAP_FAILED) || (ci1 == MAP_FAILED)) {
printf("\n");
perror("mmap failed");
SKIP_IF(1);
}
rc = posix_memalign(&mem0, bufsize, bufsize);
if (rc) {
printf("\n");
return rc;
}
rc = posix_memalign(&mem1, bufsize, bufsize);
if (rc) {
printf("\n");
free(mem0);
return rc;
}
rc = 0;
/*
* offset = 0 is aligned but tests the workaround for the P9N
* DD2.1 vector CI load issue (see 5080332c2c89 "powerpc/64s:
* Add workaround for P9 vector CI load issue")
*/
for (offset = 0; offset < 16; offset++) {
width = 16; /* vsx == 16 bytes */
r = 0;
/* load pattern into memory byte by byte */
preload_data(ci0, offset, width);
preload_data(mem0, offset, width); // FIXME: remove??
memcpy(ci0, mem0, bufsize);
memcpy(ci1, mem1, bufsize); /* initialise output to the same */
/* sanity check */
test_memcmp(mem0, ci0, width, offset, test_name);
r |= test_memcpy(ci1, ci0, width, offset, test_func);
r |= test_memcpy(mem1, mem0, width, offset, test_func);
if (r && !debug) {
printf("FAILED: Got signal");
rc = 1;
break;
}
r |= test_memcmp(mem1, ci1, width, offset, test_name);
if (r && !debug) {
printf("FAILED: Wrong Data");
rc = 1;
break;
}
}
if (rc == 0)
printf("PASSED");
printf("\n");
munmap(ci0, bufsize);
munmap(ci1, bufsize);
free(mem0);
free(mem1);
close(fd);
return rc;
}
static bool can_open_cifile(void)
{
int fd;
fd = open(cipath, O_RDWR);
if (fd < 0)
return false;
close(fd);
return true;
}
int test_alignment_handler_vsx_206(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
printf("VSX: 2.06B\n");
LOAD_VSX_XFORM_TEST(lxvd2x);
LOAD_VSX_XFORM_TEST(lxvw4x);
LOAD_VSX_XFORM_TEST(lxsdx);
LOAD_VSX_XFORM_TEST(lxvdsx);
STORE_VSX_XFORM_TEST(stxvd2x);
STORE_VSX_XFORM_TEST(stxvw4x);
STORE_VSX_XFORM_TEST(stxsdx);
return rc;
}
int test_alignment_handler_vsx_207(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_2_07));
printf("VSX: 2.07B\n");
LOAD_VSX_XFORM_TEST(lxsspx);
LOAD_VSX_XFORM_TEST(lxsiwax);
LOAD_VSX_XFORM_TEST(lxsiwzx);
STORE_VSX_XFORM_TEST(stxsspx);
STORE_VSX_XFORM_TEST(stxsiwx);
return rc;
}
int test_alignment_handler_vsx_300(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_3_00));
printf("VSX: 3.00B\n");
LOAD_VMX_DFORM_TEST(lxsd);
LOAD_VSX_XFORM_TEST(lxsibzx);
LOAD_VSX_XFORM_TEST(lxsihzx);
LOAD_VMX_DFORM_TEST(lxssp);
LOAD_VSX_DFORM_TEST(lxv);
LOAD_VSX_XFORM_TEST(lxvb16x);
LOAD_VSX_XFORM_TEST(lxvh8x);
LOAD_VSX_XFORM_TEST(lxvx);
LOAD_VSX_XFORM_TEST(lxvwsx);
LOAD_VSX_XFORM_TEST(lxvl);
LOAD_VSX_XFORM_TEST(lxvll);
STORE_VMX_DFORM_TEST(stxsd);
STORE_VSX_XFORM_TEST(stxsibx);
STORE_VSX_XFORM_TEST(stxsihx);
STORE_VMX_DFORM_TEST(stxssp);
STORE_VSX_DFORM_TEST(stxv);
STORE_VSX_XFORM_TEST(stxvb16x);
STORE_VSX_XFORM_TEST(stxvh8x);
STORE_VSX_XFORM_TEST(stxvx);
STORE_VSX_XFORM_TEST(stxvl);
STORE_VSX_XFORM_TEST(stxvll);
return rc;
}
int test_alignment_handler_vsx_prefix(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_3_1));
printf("VSX: PREFIX\n");
LOAD_VSX_8LS_PREFIX_TEST(PLXSD, 0);
LOAD_VSX_8LS_PREFIX_TEST(PLXSSP, 0);
LOAD_VSX_8LS_PREFIX_TEST(PLXV0, 0);
LOAD_VSX_8LS_PREFIX_TEST(PLXV1, 1);
STORE_VSX_8LS_PREFIX_TEST(PSTXSD, 0);
STORE_VSX_8LS_PREFIX_TEST(PSTXSSP, 0);
STORE_VSX_8LS_PREFIX_TEST(PSTXV0, 0);
STORE_VSX_8LS_PREFIX_TEST(PSTXV1, 1);
return rc;
}
int test_alignment_handler_integer(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
printf("Integer\n");
LOAD_DFORM_TEST(lbz);
LOAD_DFORM_TEST(lbzu);
LOAD_XFORM_TEST(lbzx);
LOAD_XFORM_TEST(lbzux);
LOAD_DFORM_TEST(lhz);
LOAD_DFORM_TEST(lhzu);
LOAD_XFORM_TEST(lhzx);
LOAD_XFORM_TEST(lhzux);
LOAD_DFORM_TEST(lha);
LOAD_DFORM_TEST(lhau);
LOAD_XFORM_TEST(lhax);
LOAD_XFORM_TEST(lhaux);
LOAD_XFORM_TEST(lhbrx);
LOAD_DFORM_TEST(lwz);
LOAD_DFORM_TEST(lwzu);
LOAD_XFORM_TEST(lwzx);
LOAD_XFORM_TEST(lwzux);
LOAD_DFORM_TEST(lwa);
LOAD_XFORM_TEST(lwax);
LOAD_XFORM_TEST(lwaux);
LOAD_XFORM_TEST(lwbrx);
LOAD_DFORM_TEST(ld);
LOAD_DFORM_TEST(ldu);
LOAD_XFORM_TEST(ldx);
LOAD_XFORM_TEST(ldux);
STORE_DFORM_TEST(stb);
STORE_XFORM_TEST(stbx);
STORE_DFORM_TEST(stbu);
STORE_XFORM_TEST(stbux);
STORE_DFORM_TEST(sth);
STORE_XFORM_TEST(sthx);
STORE_DFORM_TEST(sthu);
STORE_XFORM_TEST(sthux);
STORE_XFORM_TEST(sthbrx);
STORE_DFORM_TEST(stw);
STORE_XFORM_TEST(stwx);
STORE_DFORM_TEST(stwu);
STORE_XFORM_TEST(stwux);
STORE_XFORM_TEST(stwbrx);
STORE_DFORM_TEST(std);
STORE_XFORM_TEST(stdx);
STORE_DFORM_TEST(stdu);
STORE_XFORM_TEST(stdux);
#ifdef __BIG_ENDIAN__
LOAD_DFORM_TEST(lmw);
STORE_DFORM_TEST(stmw);
#endif
return rc;
}
int test_alignment_handler_integer_206(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
printf("Integer: 2.06\n");
LOAD_XFORM_TEST(ldbrx);
STORE_XFORM_TEST(stdbrx);
return rc;
}
int test_alignment_handler_integer_prefix(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_3_1));
printf("Integer: PREFIX\n");
LOAD_MLS_PREFIX_TEST(PLBZ);
LOAD_MLS_PREFIX_TEST(PLHZ);
LOAD_MLS_PREFIX_TEST(PLHA);
LOAD_MLS_PREFIX_TEST(PLWZ);
LOAD_8LS_PREFIX_TEST(PLWA);
LOAD_8LS_PREFIX_TEST(PLD);
STORE_MLS_PREFIX_TEST(PSTB);
STORE_MLS_PREFIX_TEST(PSTH);
STORE_MLS_PREFIX_TEST(PSTW);
STORE_8LS_PREFIX_TEST(PSTD);
return rc;
}
int test_alignment_handler_vmx(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap(PPC_FEATURE_HAS_ALTIVEC));
printf("VMX\n");
LOAD_VMX_XFORM_TEST(lvx);
/*
* FIXME: These loads only load part of the register, so our
* testing method doesn't work. Also they don't take alignment
* faults, so it's kinda pointless anyway
*
LOAD_VMX_XFORM_TEST(lvebx)
LOAD_VMX_XFORM_TEST(lvehx)
LOAD_VMX_XFORM_TEST(lvewx)
LOAD_VMX_XFORM_TEST(lvxl)
*/
STORE_VMX_XFORM_TEST(stvx);
STORE_VMX_XFORM_TEST(stvebx);
STORE_VMX_XFORM_TEST(stvehx);
STORE_VMX_XFORM_TEST(stvewx);
STORE_VMX_XFORM_TEST(stvxl);
return rc;
}
int test_alignment_handler_fp(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
printf("Floating point\n");
LOAD_FLOAT_DFORM_TEST(lfd);
LOAD_FLOAT_XFORM_TEST(lfdx);
LOAD_FLOAT_DFORM_TEST(lfdu);
LOAD_FLOAT_XFORM_TEST(lfdux);
LOAD_FLOAT_DFORM_TEST(lfs);
LOAD_FLOAT_XFORM_TEST(lfsx);
LOAD_FLOAT_DFORM_TEST(lfsu);
LOAD_FLOAT_XFORM_TEST(lfsux);
STORE_FLOAT_DFORM_TEST(stfd);
STORE_FLOAT_XFORM_TEST(stfdx);
STORE_FLOAT_DFORM_TEST(stfdu);
STORE_FLOAT_XFORM_TEST(stfdux);
STORE_FLOAT_DFORM_TEST(stfs);
STORE_FLOAT_XFORM_TEST(stfsx);
STORE_FLOAT_DFORM_TEST(stfsu);
STORE_FLOAT_XFORM_TEST(stfsux);
STORE_FLOAT_XFORM_TEST(stfiwx);
return rc;
}
int test_alignment_handler_fp_205(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_05));
printf("Floating point: 2.05\n");
LOAD_FLOAT_DFORM_TEST(lfdp);
LOAD_FLOAT_XFORM_TEST(lfdpx);
LOAD_FLOAT_XFORM_TEST(lfiwax);
STORE_FLOAT_DFORM_TEST(stfdp);
STORE_FLOAT_XFORM_TEST(stfdpx);
return rc;
}
int test_alignment_handler_fp_206(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
printf("Floating point: 2.06\n");
LOAD_FLOAT_XFORM_TEST(lfiwzx);
return rc;
}
int test_alignment_handler_fp_prefix(void)
{
int rc = 0;
SKIP_IF(!can_open_cifile());
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_3_1));
printf("Floating point: PREFIX\n");
LOAD_FLOAT_DFORM_TEST(lfs);
LOAD_FLOAT_MLS_PREFIX_TEST(PLFS);
LOAD_FLOAT_MLS_PREFIX_TEST(PLFD);
STORE_FLOAT_MLS_PREFIX_TEST(PSTFS);
STORE_FLOAT_MLS_PREFIX_TEST(PSTFD);
return rc;
}
void usage(char *prog)
{
printf("Usage: %s [options] [path [offset]]\n", prog);
printf(" -d Enable debug error output\n");
printf("\n");
printf("This test requires a POWER8, POWER9 or POWER10 CPU ");
printf("and either a usable framebuffer at /dev/fb0 or ");
printf("the path to usable cache inhibited memory and optional ");
printf("offset to be provided\n");
}
int main(int argc, char *argv[])
{
struct sigaction sa;
int rc = 0;
int option = 0;
while ((option = getopt(argc, argv, "d")) != -1) {
switch (option) {
case 'd':
debug++;
break;
default:
usage(argv[0]);
exit(1);
}
}
argc -= optind;
argv += optind;
if (argc > 0)
cipath = argv[0];
if (argc > 1)
cioffset = strtol(argv[1], 0, 0x10);
bufsize = getpagesize();
sa.sa_sigaction = sighandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGSEGV, &sa, NULL) == -1
|| sigaction(SIGBUS, &sa, NULL) == -1
|| sigaction(SIGILL, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
prefixes_enabled = have_hwcap2(PPC_FEATURE2_ARCH_3_1);
rc |= test_harness(test_alignment_handler_vsx_206,
"test_alignment_handler_vsx_206");
rc |= test_harness(test_alignment_handler_vsx_207,
"test_alignment_handler_vsx_207");
rc |= test_harness(test_alignment_handler_vsx_300,
"test_alignment_handler_vsx_300");
rc |= test_harness(test_alignment_handler_vsx_prefix,
"test_alignment_handler_vsx_prefix");
rc |= test_harness(test_alignment_handler_integer,
"test_alignment_handler_integer");
rc |= test_harness(test_alignment_handler_integer_206,
"test_alignment_handler_integer_206");
rc |= test_harness(test_alignment_handler_integer_prefix,
"test_alignment_handler_integer_prefix");
rc |= test_harness(test_alignment_handler_vmx,
"test_alignment_handler_vmx");
rc |= test_harness(test_alignment_handler_fp,
"test_alignment_handler_fp");
rc |= test_harness(test_alignment_handler_fp_205,
"test_alignment_handler_fp_205");
rc |= test_harness(test_alignment_handler_fp_206,
"test_alignment_handler_fp_206");
rc |= test_harness(test_alignment_handler_fp_prefix,
"test_alignment_handler_fp_prefix");
return rc;
}
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2016, Chris Smart, IBM Corporation.
*
* Calls to copy_first which are not 128-byte aligned should be
* caught and sent a SIGBUS.
*/
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include "utils.h"
#include "instructions.h"
unsigned int expected_instruction = PPC_INST_COPY_FIRST;
unsigned int instruction_mask = 0xfc2007fe;
void signal_action_handler(int signal_num, siginfo_t *info, void *ptr)
{
ucontext_t *ctx = ptr;
#ifdef __powerpc64__
unsigned int *pc = (unsigned int *)ctx->uc_mcontext.gp_regs[PT_NIP];
#else
unsigned int *pc = (unsigned int *)ctx->uc_mcontext.uc_regs->gregs[PT_NIP];
#endif
/*
* Check that the signal was on the correct instruction, using a
* mask because the compiler assigns the register at RB.
*/
if ((*pc & instruction_mask) == expected_instruction)
_exit(0); /* We hit the right instruction */
_exit(1);
}
void setup_signal_handler(void)
{
struct sigaction signal_action;
memset(&signal_action, 0, sizeof(signal_action));
signal_action.sa_sigaction = signal_action_handler;
signal_action.sa_flags = SA_SIGINFO;
sigaction(SIGBUS, &signal_action, NULL);
}
char cacheline_buf[128] __cacheline_aligned;
int test_copy_first_unaligned(void)
{
/* Only run this test on a P9 or later */
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_3_00));
/* Register our signal handler with SIGBUS */
setup_signal_handler();
/* +1 makes buf unaligned */
copy_first(cacheline_buf+1);
/* We should not get here */
return 1;
}
int main(int argc, char *argv[])
{
return test_harness(test_copy_first_unaligned, "test_copy_first_unaligned");
}
@@ -0,0 +1 @@
timeout=130
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
gettimeofday
context_switch
fork
exec_target
mmap_bench
futex_bench
null_syscall
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := gettimeofday context_switch fork mmap_bench futex_bench null_syscall
TEST_GEN_FILES := exec_target
TEST_FILES := settings
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
CFLAGS += -O2
$(TEST_GEN_PROGS): ../harness.c
$(OUTPUT)/context_switch: ../utils.c
$(OUTPUT)/context_switch: CFLAGS += -maltivec -mvsx -mabi=altivec
$(OUTPUT)/context_switch: LDLIBS += -lpthread
$(OUTPUT)/fork: LDLIBS += -lpthread
$(OUTPUT)/exec_target: CFLAGS += -nostartfiles
@@ -0,0 +1,508 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Context switch microbenchmark.
*
* Copyright (C) 2015 Anton Blanchard <anton@au.ibm.com>, IBM
*/
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <signal.h>
#include <assert.h>
#include <pthread.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/syscall.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <linux/futex.h>
#ifdef __powerpc__
#include <altivec.h>
#endif
#include "utils.h"
static unsigned int timeout = 30;
static int touch_vdso;
struct timeval tv;
static int touch_fp = 1;
double fp;
static int touch_vector = 1;
vector int a, b, c;
#ifdef __powerpc__
static int touch_altivec = 1;
/*
* Note: LTO (Link Time Optimisation) doesn't play well with this function
* attribute. Be very careful enabling LTO for this test.
*/
static void __attribute__((__target__("no-vsx"))) altivec_touch_fn(void)
{
c = a + b;
}
#endif
static void touch(void)
{
if (touch_vdso)
gettimeofday(&tv, NULL);
if (touch_fp)
fp += 0.1;
#ifdef __powerpc__
if (touch_altivec)
altivec_touch_fn();
#endif
if (touch_vector)
c = a + b;
asm volatile("# %0 %1 %2": : "r"(&tv), "r"(&fp), "r"(&c));
}
static void start_thread_on(void *(*fn)(void *), void *arg, unsigned long cpu)
{
int rc;
pthread_t tid;
cpu_set_t cpuset;
pthread_attr_t attr;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
rc = pthread_attr_init(&attr);
if (rc) {
errno = rc;
perror("pthread_attr_init");
exit(1);
}
rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
if (rc) {
errno = rc;
perror("pthread_attr_setaffinity_np");
exit(1);
}
rc = pthread_create(&tid, &attr, fn, arg);
if (rc) {
errno = rc;
perror("pthread_create");
exit(1);
}
}
static void start_process_on(void *(*fn)(void *), void *arg, unsigned long cpu)
{
int pid, ncpus;
cpu_set_t *cpuset;
size_t size;
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid)
return;
ncpus = get_nprocs();
size = CPU_ALLOC_SIZE(ncpus);
cpuset = CPU_ALLOC(ncpus);
if (!cpuset) {
perror("malloc");
exit(1);
}
CPU_ZERO_S(size, cpuset);
CPU_SET_S(cpu, size, cpuset);
if (sched_setaffinity(0, size, cpuset)) {
perror("sched_setaffinity");
CPU_FREE(cpuset);
exit(1);
}
CPU_FREE(cpuset);
fn(arg);
exit(0);
}
static unsigned long iterations;
static unsigned long iterations_prev;
static void sigalrm_handler(int junk)
{
unsigned long i = iterations;
printf("%ld\n", i - iterations_prev);
iterations_prev = i;
if (--timeout == 0)
kill(0, SIGUSR1);
alarm(1);
}
static void sigusr1_handler(int junk)
{
exit(0);
}
struct actions {
void (*setup)(int, int);
void *(*thread1)(void *);
void *(*thread2)(void *);
};
#define READ 0
#define WRITE 1
static int pipe_fd1[2];
static int pipe_fd2[2];
static void pipe_setup(int cpu1, int cpu2)
{
if (pipe(pipe_fd1) || pipe(pipe_fd2))
exit(1);
}
static void *pipe_thread1(void *arg)
{
signal(SIGALRM, sigalrm_handler);
alarm(1);
while (1) {
assert(read(pipe_fd1[READ], &c, 1) == 1);
touch();
assert(write(pipe_fd2[WRITE], &c, 1) == 1);
touch();
iterations += 2;
}
return NULL;
}
static void *pipe_thread2(void *arg)
{
while (1) {
assert(write(pipe_fd1[WRITE], &c, 1) == 1);
touch();
assert(read(pipe_fd2[READ], &c, 1) == 1);
touch();
}
return NULL;
}
static struct actions pipe_actions = {
.setup = pipe_setup,
.thread1 = pipe_thread1,
.thread2 = pipe_thread2,
};
static void yield_setup(int cpu1, int cpu2)
{
if (cpu1 != cpu2) {
fprintf(stderr, "Both threads must be on the same CPU for yield test\n");
exit(1);
}
}
static void *yield_thread1(void *arg)
{
signal(SIGALRM, sigalrm_handler);
alarm(1);
while (1) {
sched_yield();
touch();
iterations += 2;
}
return NULL;
}
static void *yield_thread2(void *arg)
{
while (1) {
sched_yield();
touch();
}
return NULL;
}
static struct actions yield_actions = {
.setup = yield_setup,
.thread1 = yield_thread1,
.thread2 = yield_thread2,
};
static long sys_futex(void *addr1, int op, int val1, struct timespec *timeout,
void *addr2, int val3)
{
return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
}
static unsigned long cmpxchg(unsigned long *p, unsigned long expected,
unsigned long desired)
{
unsigned long exp = expected;
__atomic_compare_exchange_n(p, &exp, desired, 0,
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
return exp;
}
static unsigned long xchg(unsigned long *p, unsigned long val)
{
return __atomic_exchange_n(p, val, __ATOMIC_SEQ_CST);
}
static int processes;
static int mutex_lock(unsigned long *m)
{
int c;
int flags = FUTEX_WAIT;
if (!processes)
flags |= FUTEX_PRIVATE_FLAG;
c = cmpxchg(m, 0, 1);
if (!c)
return 0;
if (c == 1)
c = xchg(m, 2);
while (c) {
sys_futex(m, flags, 2, NULL, NULL, 0);
c = xchg(m, 2);
}
return 0;
}
static int mutex_unlock(unsigned long *m)
{
int flags = FUTEX_WAKE;
if (!processes)
flags |= FUTEX_PRIVATE_FLAG;
if (*m == 2)
*m = 0;
else if (xchg(m, 0) == 1)
return 0;
sys_futex(m, flags, 1, NULL, NULL, 0);
return 0;
}
static unsigned long *m1, *m2;
static void futex_setup(int cpu1, int cpu2)
{
if (!processes) {
static unsigned long _m1, _m2;
m1 = &_m1;
m2 = &_m2;
} else {
int shmid;
void *shmaddr;
shmid = shmget(IPC_PRIVATE, getpagesize(), SHM_R | SHM_W);
if (shmid < 0) {
perror("shmget");
exit(1);
}
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
shmctl(shmid, IPC_RMID, NULL);
exit(1);
}
shmctl(shmid, IPC_RMID, NULL);
m1 = shmaddr;
m2 = shmaddr + sizeof(*m1);
}
*m1 = 0;
*m2 = 0;
mutex_lock(m1);
mutex_lock(m2);
}
static void *futex_thread1(void *arg)
{
signal(SIGALRM, sigalrm_handler);
alarm(1);
while (1) {
mutex_lock(m2);
mutex_unlock(m1);
iterations += 2;
}
return NULL;
}
static void *futex_thread2(void *arg)
{
while (1) {
mutex_unlock(m2);
mutex_lock(m1);
}
return NULL;
}
static struct actions futex_actions = {
.setup = futex_setup,
.thread1 = futex_thread1,
.thread2 = futex_thread2,
};
static struct option options[] = {
{ "test", required_argument, 0, 't' },
{ "process", no_argument, &processes, 1 },
{ "timeout", required_argument, 0, 's' },
{ "vdso", no_argument, &touch_vdso, 1 },
{ "no-fp", no_argument, &touch_fp, 0 },
#ifdef __powerpc__
{ "no-altivec", no_argument, &touch_altivec, 0 },
#endif
{ "no-vector", no_argument, &touch_vector, 0 },
{ 0, },
};
static void usage(void)
{
fprintf(stderr, "Usage: context_switch2 <options> CPU1 CPU2\n\n");
fprintf(stderr, "\t\t--test=X\tpipe, futex or yield (default)\n");
fprintf(stderr, "\t\t--process\tUse processes (default threads)\n");
fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n");
fprintf(stderr, "\t\t--vdso\t\ttouch VDSO\n");
fprintf(stderr, "\t\t--no-fp\t\tDon't touch FP\n");
#ifdef __powerpc__
fprintf(stderr, "\t\t--no-altivec\tDon't touch altivec\n");
#endif
fprintf(stderr, "\t\t--no-vector\tDon't touch vector\n");
}
int main(int argc, char *argv[])
{
signed char c;
struct actions *actions = &yield_actions;
int cpu1;
int cpu2;
static void (*start_fn)(void *(*fn)(void *), void *arg, unsigned long cpu);
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "", options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
if (options[option_index].flag != 0)
break;
usage();
exit(1);
break;
case 't':
if (!strcmp(optarg, "pipe")) {
actions = &pipe_actions;
} else if (!strcmp(optarg, "yield")) {
actions = &yield_actions;
} else if (!strcmp(optarg, "futex")) {
actions = &futex_actions;
} else {
usage();
exit(1);
}
break;
case 's':
timeout = atoi(optarg);
break;
default:
usage();
exit(1);
}
}
if (processes)
start_fn = start_process_on;
else
start_fn = start_thread_on;
if (((argc - optind) != 2)) {
cpu1 = cpu2 = pick_online_cpu();
} else {
cpu1 = atoi(argv[optind++]);
cpu2 = atoi(argv[optind++]);
}
printf("Using %s with ", processes ? "processes" : "threads");
if (actions == &pipe_actions)
printf("pipe");
else if (actions == &yield_actions)
printf("yield");
else
printf("futex");
if (!have_hwcap(PPC_FEATURE_HAS_ALTIVEC))
touch_altivec = 0;
if (!have_hwcap(PPC_FEATURE_HAS_VSX))
touch_vector = 0;
printf(" on cpus %d/%d touching FP:%s altivec:%s vector:%s vdso:%s\n",
cpu1, cpu2, touch_fp ? "yes" : "no", touch_altivec ? "yes" : "no",
touch_vector ? "yes" : "no", touch_vdso ? "yes" : "no");
/* Create a new process group so we can signal everyone for exit */
setpgid(getpid(), getpid());
signal(SIGUSR1, sigusr1_handler);
actions->setup(cpu1, cpu2);
start_fn(actions->thread1, NULL, cpu1);
start_fn(actions->thread2, NULL, cpu2);
while (1)
sleep(3600);
return 0;
}
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Part of fork context switch microbenchmark.
*
* Copyright 2018, Anton Blanchard, IBM Corp.
*/
#define _GNU_SOURCE
#include <sys/syscall.h>
void _start(void)
{
asm volatile (
"li %%r0, %[sys_exit];"
"li %%r3, 0;"
"sc;"
:
: [sys_exit] "i" (SYS_exit)
/*
* "sc" will clobber r0, r3-r13, cr0, ctr, xer and memory.
* Even though sys_exit never returns, handle clobber
* registers.
*/
: "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10",
"r11", "r12", "r13", "cr0", "ctr", "xer", "memory"
);
}
@@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Context switch microbenchmark.
*
* Copyright 2018, Anton Blanchard, IBM Corp.
*/
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <linux/futex.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
static unsigned int timeout = 30;
static void set_cpu(int cpu)
{
cpu_set_t cpuset;
if (cpu == -1)
return;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset)) {
perror("sched_setaffinity");
exit(1);
}
}
static void start_process_on(void *(*fn)(void *), void *arg, int cpu)
{
int pid;
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid)
return;
set_cpu(cpu);
fn(arg);
exit(0);
}
static int cpu;
static int do_fork = 0;
static int do_vfork = 0;
static int do_exec = 0;
static char *exec_file;
static int exec_target = 0;
static unsigned long iterations;
static unsigned long iterations_prev;
static void run_exec(void)
{
char *const argv[] = { "./exec_target", NULL };
if (execve("./exec_target", argv, NULL) == -1) {
perror("execve");
exit(1);
}
}
static void bench_fork(void)
{
while (1) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
if (do_exec)
run_exec();
_exit(0);
}
pid = waitpid(pid, NULL, 0);
if (pid == -1) {
perror("waitpid");
exit(1);
}
iterations++;
}
}
static void bench_vfork(void)
{
while (1) {
pid_t pid = vfork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
if (do_exec)
run_exec();
_exit(0);
}
pid = waitpid(pid, NULL, 0);
if (pid == -1) {
perror("waitpid");
exit(1);
}
iterations++;
}
}
static void *null_fn(void *arg)
{
pthread_exit(NULL);
}
static void bench_thread(void)
{
pthread_t tid;
cpu_set_t cpuset;
pthread_attr_t attr;
int rc;
rc = pthread_attr_init(&attr);
if (rc) {
errno = rc;
perror("pthread_attr_init");
exit(1);
}
if (cpu != -1) {
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
if (rc) {
errno = rc;
perror("pthread_attr_setaffinity_np");
exit(1);
}
}
while (1) {
rc = pthread_create(&tid, &attr, null_fn, NULL);
if (rc) {
errno = rc;
perror("pthread_create");
exit(1);
}
rc = pthread_join(tid, NULL);
if (rc) {
errno = rc;
perror("pthread_join");
exit(1);
}
iterations++;
}
}
static void sigalrm_handler(int junk)
{
unsigned long i = iterations;
printf("%ld\n", i - iterations_prev);
iterations_prev = i;
if (--timeout == 0)
kill(0, SIGUSR1);
alarm(1);
}
static void sigusr1_handler(int junk)
{
exit(0);
}
static void *bench_proc(void *arg)
{
signal(SIGALRM, sigalrm_handler);
alarm(1);
if (do_fork)
bench_fork();
else if (do_vfork)
bench_vfork();
else
bench_thread();
return NULL;
}
static struct option options[] = {
{ "fork", no_argument, &do_fork, 1 },
{ "vfork", no_argument, &do_vfork, 1 },
{ "exec", no_argument, &do_exec, 1 },
{ "timeout", required_argument, 0, 's' },
{ "exec-target", no_argument, &exec_target, 1 },
{ NULL },
};
static void usage(void)
{
fprintf(stderr, "Usage: fork <options> CPU\n\n");
fprintf(stderr, "\t\t--fork\tUse fork() (default threads)\n");
fprintf(stderr, "\t\t--vfork\tUse vfork() (default threads)\n");
fprintf(stderr, "\t\t--exec\tAlso exec() (default no exec)\n");
fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n");
fprintf(stderr, "\t\t--exec-target\tInternal option for exec workload\n");
}
int main(int argc, char *argv[])
{
signed char c;
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "", options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
if (options[option_index].flag != 0)
break;
usage();
exit(1);
break;
case 's':
timeout = atoi(optarg);
break;
default:
usage();
exit(1);
}
}
if (do_fork && do_vfork) {
usage();
exit(1);
}
if (do_exec && !do_fork && !do_vfork) {
usage();
exit(1);
}
if (do_exec) {
char *dirname = strdup(argv[0]);
int i;
i = strlen(dirname) - 1;
while (i) {
if (dirname[i] == '/') {
dirname[i] = '\0';
if (chdir(dirname) == -1) {
perror("chdir");
exit(1);
}
break;
}
i--;
}
}
if (exec_target) {
exit(0);
}
if (((argc - optind) != 1)) {
cpu = -1;
} else {
cpu = atoi(argv[optind++]);
}
if (do_exec)
exec_file = argv[0];
set_cpu(cpu);
printf("Using ");
if (do_fork)
printf("fork");
else if (do_vfork)
printf("vfork");
else
printf("clone");
if (do_exec)
printf(" + exec");
printf(" on cpu %d\n", cpu);
/* Create a new process group so we can signal everyone for exit */
setpgid(getpid(), getpid());
signal(SIGUSR1, sigusr1_handler);
start_process_on(bench_proc, NULL, cpu);
while (1)
sleep(3600);
return 0;
}
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2016, Anton Blanchard, Michael Ellerman, IBM Corp.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
#include <linux/futex.h>
#include "utils.h"
#define ITERATIONS 100000000
#define futex(A, B, C, D, E, F) syscall(__NR_futex, A, B, C, D, E, F)
int test_futex(void)
{
struct timespec ts_start, ts_end;
unsigned long i = ITERATIONS;
clock_gettime(CLOCK_MONOTONIC, &ts_start);
while (i--) {
unsigned int addr = 0;
futex(&addr, FUTEX_WAKE, 1, NULL, NULL, 0);
}
clock_gettime(CLOCK_MONOTONIC, &ts_end);
printf("time = %.6f\n", ts_end.tv_sec - ts_start.tv_sec + (ts_end.tv_nsec - ts_start.tv_nsec) / 1e9);
return 0;
}
int main(void)
{
test_harness_set_timeout(300);
return test_harness(test_futex, "futex_bench");
}
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Anton Blanchard, IBM Corp.
*/
#include <sys/time.h>
#include <stdio.h>
#include "utils.h"
static int test_gettimeofday(void)
{
int i;
struct timeval tv_start, tv_end, tv_diff;
gettimeofday(&tv_start, NULL);
for(i = 0; i < 100000000; i++) {
gettimeofday(&tv_end, NULL);
}
timersub(&tv_end, &tv_start, &tv_diff);
printf("time = %.6f\n", tv_diff.tv_sec + (tv_diff.tv_usec) * 1e-6);
return 0;
}
int main(void)
{
return test_harness(test_gettimeofday, "gettimeofday");
}
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2016, Anton Blanchard, Michael Ellerman, IBM Corp.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
#include <getopt.h>
#include "utils.h"
#define ITERATIONS 5000000
#define MEMSIZE (1UL << 27)
#define PAGE_SIZE (1UL << 16)
#define CHUNK_COUNT (MEMSIZE/PAGE_SIZE)
static int pg_fault;
static int iterations = ITERATIONS;
static struct option options[] = {
{ "pgfault", no_argument, &pg_fault, 1 },
{ "iterations", required_argument, 0, 'i' },
{ 0, },
};
static void usage(void)
{
printf("mmap_bench <--pgfault> <--iterations count>\n");
}
int test_mmap(void)
{
struct timespec ts_start, ts_end;
unsigned long i = iterations;
clock_gettime(CLOCK_MONOTONIC, &ts_start);
while (i--) {
char *c = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
FAIL_IF(c == MAP_FAILED);
if (pg_fault) {
int count;
for (count = 0; count < CHUNK_COUNT; count++)
c[count << 16] = 'c';
}
munmap(c, MEMSIZE);
}
clock_gettime(CLOCK_MONOTONIC, &ts_end);
printf("time = %.6f\n", ts_end.tv_sec - ts_start.tv_sec + (ts_end.tv_nsec - ts_start.tv_nsec) / 1e9);
return 0;
}
int main(int argc, char *argv[])
{
signed char c;
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "", options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
if (options[option_index].flag != 0)
break;
usage();
exit(1);
break;
case 'i':
iterations = atoi(optarg);
break;
default:
usage();
exit(1);
}
}
test_harness_set_timeout(300);
return test_harness(test_mmap, "mmap_bench");
}
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test null syscall performance
*
* Copyright (C) 2009-2015 Anton Blanchard, IBM
*/
#define NR_LOOPS 10000000
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/syscall.h>
#include <signal.h>
static volatile int soak_done;
unsigned long long clock_frequency;
unsigned long long timebase_frequency;
double timebase_multiplier;
static inline unsigned long mftb(void)
{
unsigned long low;
asm volatile("mftb %0" : "=r" (low));
return low;
}
static void sigalrm_handler(int unused)
{
soak_done = 1;
}
/*
* Use a timer instead of busy looping on clock_gettime() so we don't
* pollute profiles with glibc and VDSO hits.
*/
static void cpu_soak_usecs(unsigned long usecs)
{
struct itimerval val;
memset(&val, 0, sizeof(val));
val.it_value.tv_usec = usecs;
signal(SIGALRM, sigalrm_handler);
setitimer(ITIMER_REAL, &val, NULL);
while (1) {
if (soak_done)
break;
}
signal(SIGALRM, SIG_DFL);
}
/*
* This only works with recent kernels where cpufreq modifies
* /proc/cpuinfo dynamically.
*/
static void get_proc_frequency(void)
{
FILE *f;
char line[128];
char *p, *end;
unsigned long v;
double d;
char *override;
/* Try to get out of low power/low frequency mode */
cpu_soak_usecs(0.25 * 1000000);
f = fopen("/proc/cpuinfo", "r");
if (f == NULL)
return;
timebase_frequency = 0;
while (fgets(line, sizeof(line), f) != NULL) {
if (strncmp(line, "timebase", 8) == 0) {
p = strchr(line, ':');
if (p != NULL) {
v = strtoull(p + 1, &end, 0);
if (end != p + 1)
timebase_frequency = v;
}
}
if (((strncmp(line, "clock", 5) == 0) ||
(strncmp(line, "cpu MHz", 7) == 0))) {
p = strchr(line, ':');
if (p != NULL) {
d = strtod(p + 1, &end);
if (end != p + 1) {
/* Find fastest clock frequency */
if ((d * 1000000ULL) > clock_frequency)
clock_frequency = d * 1000000ULL;
}
}
}
}
fclose(f);
override = getenv("FREQUENCY");
if (override)
clock_frequency = strtoull(override, NULL, 10);
if (timebase_frequency)
timebase_multiplier = (double)clock_frequency
/ timebase_frequency;
else
timebase_multiplier = 1;
}
static void do_null_syscall(unsigned long nr)
{
unsigned long i;
for (i = 0; i < nr; i++)
syscall(__NR_gettid);
}
#define TIME(A, STR) \
int main(void)
{
unsigned long tb_start, tb_now;
struct timespec tv_start, tv_now;
unsigned long long elapsed_ns, elapsed_tb;
get_proc_frequency();
clock_gettime(CLOCK_MONOTONIC, &tv_start);
tb_start = mftb();
do_null_syscall(NR_LOOPS);
clock_gettime(CLOCK_MONOTONIC, &tv_now);
tb_now = mftb();
elapsed_ns = (tv_now.tv_sec - tv_start.tv_sec) * 1000000000ULL +
(tv_now.tv_nsec - tv_start.tv_nsec);
elapsed_tb = tb_now - tb_start;
printf("%10.2f ns %10.2f cycles\n", (float)elapsed_ns / NR_LOOPS,
(float)elapsed_tb * timebase_multiplier / NR_LOOPS);
return 0;
}
@@ -0,0 +1 @@
timeout=0
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
cache_shape
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := cache_shape
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
$(TEST_GEN_PROGS): ../harness.c ../utils.c
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2017, Michael Ellerman, IBM Corp.
*/
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "utils.h"
#ifndef AT_L1I_CACHESIZE
#define AT_L1I_CACHESIZE 40
#define AT_L1I_CACHEGEOMETRY 41
#define AT_L1D_CACHESIZE 42
#define AT_L1D_CACHEGEOMETRY 43
#define AT_L2_CACHESIZE 44
#define AT_L2_CACHEGEOMETRY 45
#define AT_L3_CACHESIZE 46
#define AT_L3_CACHEGEOMETRY 47
#endif
static void print_size(const char *label, uint32_t val)
{
printf("%s cache size: %#10x %10dB %10dK\n", label, val, val, val / 1024);
}
static void print_geo(const char *label, uint32_t val)
{
uint16_t assoc;
printf("%s line size: %#10x ", label, val & 0xFFFF);
assoc = val >> 16;
if (assoc)
printf("%u-way", assoc);
else
printf("fully");
printf(" associative\n");
}
static int test_cache_shape()
{
static char buffer[4096];
ElfW(auxv_t) *p;
int found;
FAIL_IF(read_auxv(buffer, sizeof(buffer)));
found = 0;
p = find_auxv_entry(AT_L1I_CACHESIZE, buffer);
if (p) {
found++;
print_size("L1I ", (uint32_t)p->a_un.a_val);
}
p = find_auxv_entry(AT_L1I_CACHEGEOMETRY, buffer);
if (p) {
found++;
print_geo("L1I ", (uint32_t)p->a_un.a_val);
}
p = find_auxv_entry(AT_L1D_CACHESIZE, buffer);
if (p) {
found++;
print_size("L1D ", (uint32_t)p->a_un.a_val);
}
p = find_auxv_entry(AT_L1D_CACHEGEOMETRY, buffer);
if (p) {
found++;
print_geo("L1D ", (uint32_t)p->a_un.a_val);
}
p = find_auxv_entry(AT_L2_CACHESIZE, buffer);
if (p) {
found++;
print_size("L2 ", (uint32_t)p->a_un.a_val);
}
p = find_auxv_entry(AT_L2_CACHEGEOMETRY, buffer);
if (p) {
found++;
print_geo("L2 ", (uint32_t)p->a_un.a_val);
}
p = find_auxv_entry(AT_L3_CACHESIZE, buffer);
if (p) {
found++;
print_size("L3 ", (uint32_t)p->a_un.a_val);
}
p = find_auxv_entry(AT_L3_CACHEGEOMETRY, buffer);
if (p) {
found++;
print_geo("L3 ", (uint32_t)p->a_un.a_val);
}
/* If we found none we're probably on a system where they don't exist */
SKIP_IF(found == 0);
/* But if we found any, we expect to find them all */
FAIL_IF(found != 8);
return 0;
}
int main(void)
{
return test_harness(test_cache_shape, "cache_shape");
}
@@ -0,0 +1 @@
timeout=130
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
copyuser_64_t0
copyuser_64_t1
copyuser_64_t2
copyuser_p7
copyuser_p7_vmx
memcpy_64_t0
memcpy_64_t1
memcpy_64_t2
memcpy_p7_t0
memcpy_p7_t1
copyuser_64_exc_t0
copyuser_64_exc_t1
copyuser_64_exc_t2
copy_mc_64
memmove_64
@@ -0,0 +1,71 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := copyuser_64_t0 copyuser_64_t1 copyuser_64_t2 \
copyuser_p7 copyuser_p7_vmx \
memcpy_64_t0 memcpy_64_t1 memcpy_64_t2 \
memcpy_p7_t0 memcpy_p7_t1 copy_mc_64 \
copyuser_64_exc_t0 copyuser_64_exc_t1 copyuser_64_exc_t2 \
memmove_64
EXTRA_SOURCES := validate.c ../harness.c stubs.S
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
# The loops are all 64-bit code
CFLAGS += -m64
CFLAGS += -I$(CURDIR)
CFLAGS += -D SELFTEST
CFLAGS += -maltivec
CFLAGS += -mcpu=power4
# Use our CFLAGS for the implicit .S rule & set the asm machine type
ASFLAGS = $(CFLAGS) -Wa,-mpower4
$(OUTPUT)/copyuser_64_t%: copyuser_64.S $(EXTRA_SOURCES)
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D COPY_LOOP=test___copy_tofrom_user_base \
-D SELFTEST_CASE=$(subst copyuser_64_t,,$(notdir $@)) \
-o $@ $^
$(OUTPUT)/copyuser_p7: copyuser_power7.S $(EXTRA_SOURCES)
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D COPY_LOOP=test___copy_tofrom_user_power7 \
-o $@ $^
$(OUTPUT)/copyuser_p7_vmx: copyuser_power7.S $(EXTRA_SOURCES) ../utils.c
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D COPY_LOOP=test___copy_tofrom_user_power7_vmx \
-D VMX_TEST \
-o $@ $^
# Strictly speaking, we only need the memcpy_64 test cases for big-endian
$(OUTPUT)/memcpy_64_t%: memcpy_64.S $(EXTRA_SOURCES)
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D COPY_LOOP=test_memcpy \
-D SELFTEST_CASE=$(subst memcpy_64_t,,$(notdir $@)) \
-o $@ $^
$(OUTPUT)/memcpy_p7_t%: memcpy_power7.S $(EXTRA_SOURCES)
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D COPY_LOOP=test_memcpy_power7 \
-D SELFTEST_CASE=$(subst memcpy_p7_t,,$(notdir $@)) \
-o $@ $^
$(OUTPUT)/copy_mc_64: copy_mc_64.S $(EXTRA_SOURCES)
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D COPY_LOOP=test_copy_mc_generic \
-o $@ $^
$(OUTPUT)/copyuser_64_exc_t%: copyuser_64.S exc_validate.c ../harness.c \
copy_tofrom_user_reference.S stubs.S
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D COPY_LOOP=test___copy_tofrom_user_base \
-D SELFTEST_CASE=$(subst copyuser_64_exc_t,,$(notdir $@)) \
-o $@ $^
$(OUTPUT)/memmove_64: mem_64.S memcpy_64.S memmove_validate.c ../harness.c \
memcpy_stubs.S
$(CC) $(CPPFLAGS) $(CFLAGS) \
-D TEST_MEMMOVE=test_memmove \
-o $@ $^
@@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __SELFTESTS_POWERPC_PPC_ASM_H
#define __SELFTESTS_POWERPC_PPC_ASM_H
#include <ppc-asm.h>
#define CONFIG_ALTIVEC
#define r1 1
#define R14 r14
#define R15 r15
#define R16 r16
#define R17 r17
#define R18 r18
#define R19 r19
#define R20 r20
#define R21 r21
#define R22 r22
#define R29 r29
#define R30 r30
#define R31 r31
#define STACKFRAMESIZE 256
#define STK_REG(i) (112 + ((i)-14)*8)
#define _GLOBAL(A) FUNC_START(test_ ## A)
#define _GLOBAL_TOC(A) _GLOBAL(A)
#define _GLOBAL_TOC_KASAN(A) _GLOBAL(A)
#define _GLOBAL_KASAN(A) _GLOBAL(A)
#define CFUNC(name) name
#define PPC_MTOCRF(A, B) mtocrf A, B
#define EX_TABLE(x, y) \
.section __ex_table,"a"; \
.8byte x, y; \
.previous
#define BEGIN_FTR_SECTION .if test_feature
#define FTR_SECTION_ELSE .else
#define ALT_FTR_SECTION_END_IFCLR(x) .endif
#define ALT_FTR_SECTION_END_IFSET(x) .endif
#define ALT_FTR_SECTION_END(x, y) .endif
#define END_FTR_SECTION_IFCLR(x) .endif
#define END_FTR_SECTION_IFSET(x) .endif
/* Default to taking the first of any alternative feature sections */
test_feature = 1
#define DCBT_SETUP_STREAMS(from, from_parms, to, to_parms, scratch) \
lis scratch,0x8000; /* GO=1 */ \
clrldi scratch,scratch,32; \
/* setup read stream 0 */ \
dcbt 0,from,0b01000; /* addr from */ \
dcbt 0,from_parms,0b01010; /* length and depth from */ \
/* setup write stream 1 */ \
dcbtst 0,to,0b01000; /* addr to */ \
dcbtst 0,to_parms,0b01010; /* length and depth to */ \
eieio; \
dcbt 0,scratch,0b01010; /* all streams GO */
#endif /* __SELFTESTS_POWERPC_PPC_ASM_H */
@@ -0,0 +1 @@
../../../../../arch/powerpc/lib/copy_mc_64.S
@@ -0,0 +1,24 @@
#include <asm/ppc_asm.h>
_GLOBAL(copy_tofrom_user_reference)
cmpdi r5,0
beq 4f
mtctr r5
1: lbz r6,0(r4)
2: stb r6,0(r3)
addi r3,r3,1
addi r4,r4,1
bdnz 1b
3: mfctr r3
blr
4: mr r3,r5
blr
.section __ex_table,"a"
.llong 1b,3b
.llong 2b,3b
.text
@@ -0,0 +1 @@
../../../../../arch/powerpc/lib/copyuser_64.S
@@ -0,0 +1 @@
../../../../../arch/powerpc/lib/copyuser_power7.S
@@ -0,0 +1,124 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include "utils.h"
extern char __start___ex_table[];
extern char __stop___ex_table[];
#if defined(__powerpc64__)
#define UCONTEXT_NIA(UC) (UC)->uc_mcontext.gp_regs[PT_NIP]
#elif defined(__powerpc__)
#define UCONTEXT_NIA(UC) (UC)->uc_mcontext.uc_regs->gregs[PT_NIP]
#else
#error implement UCONTEXT_NIA
#endif
static void segv_handler(int signr, siginfo_t *info, void *ptr)
{
ucontext_t *uc = (ucontext_t *)ptr;
unsigned long addr = (unsigned long)info->si_addr;
unsigned long *ip = &UCONTEXT_NIA(uc);
unsigned long *ex_p = (unsigned long *)__start___ex_table;
while (ex_p < (unsigned long *)__stop___ex_table) {
unsigned long insn, fixup;
insn = *ex_p++;
fixup = *ex_p++;
if (insn == *ip) {
*ip = fixup;
return;
}
}
printf("No exception table match for NIA %lx ADDR %lx\n", *ip, addr);
abort();
}
static void setup_segv_handler(void)
{
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_sigaction = segv_handler;
action.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &action, NULL);
}
unsigned long COPY_LOOP(void *to, const void *from, unsigned long size);
unsigned long test_copy_tofrom_user_reference(void *to, const void *from, unsigned long size);
static int total_passed;
static int total_failed;
static void do_one_test(char *dstp, char *srcp, unsigned long len)
{
unsigned long got, expected;
got = COPY_LOOP(dstp, srcp, len);
expected = test_copy_tofrom_user_reference(dstp, srcp, len);
if (got != expected) {
total_failed++;
printf("FAIL from=%p to=%p len=%ld returned %ld, expected %ld\n",
srcp, dstp, len, got, expected);
//abort();
} else
total_passed++;
}
//#define MAX_LEN 512
#define MAX_LEN 16
int test_copy_exception(void)
{
int page_size;
static char *p, *q;
unsigned long src, dst, len;
page_size = getpagesize();
p = mmap(NULL, page_size * 2, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) {
perror("mmap");
exit(1);
}
memset(p, 0, page_size);
setup_segv_handler();
if (mprotect(p + page_size, page_size, PROT_NONE)) {
perror("mprotect");
exit(1);
}
q = p + page_size - MAX_LEN;
for (src = 0; src < MAX_LEN; src++) {
for (dst = 0; dst < MAX_LEN; dst++) {
for (len = 0; len < MAX_LEN+1; len++) {
// printf("from=%p to=%p len=%ld\n", q+dst, q+src, len);
do_one_test(q+dst, q+src, len);
}
}
}
printf("Totals:\n");
printf(" Pass: %d\n", total_passed);
printf(" Fail: %d\n", total_failed);
return 0;
}
int main(void)
{
return test_harness(test_copy_exception, str(COPY_LOOP));
}
@@ -0,0 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define EXPORT_SYMBOL(x)
#define EXPORT_SYMBOL_GPL(x)
#define EXPORT_SYMBOL_KASAN(x)
@@ -0,0 +1 @@
../../../../../arch/powerpc/lib/mem_64.S
@@ -0,0 +1 @@
../../../../../arch/powerpc/lib/memcpy_64.S
@@ -0,0 +1 @@
../../../../../arch/powerpc/lib/memcpy_power7.S
@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <asm/ppc_asm.h>
FUNC_START(memcpy)
b test_memcpy
FUNC_START(backwards_memcpy)
b test_backwards_memcpy
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "utils.h"
void *TEST_MEMMOVE(const void *s1, const void *s2, size_t n);
#define BUF_LEN 65536
#define MAX_OFFSET 512
size_t max(size_t a, size_t b)
{
if (a >= b)
return a;
return b;
}
static int testcase_run(void)
{
size_t i, src_off, dst_off, len;
char *usermap = memalign(BUF_LEN, BUF_LEN);
char *kernelmap = memalign(BUF_LEN, BUF_LEN);
assert(usermap != NULL);
assert(kernelmap != NULL);
memset(usermap, 0, BUF_LEN);
memset(kernelmap, 0, BUF_LEN);
for (i = 0; i < BUF_LEN; i++) {
usermap[i] = i & 0xff;
kernelmap[i] = i & 0xff;
}
for (src_off = 0; src_off < MAX_OFFSET; src_off++) {
for (dst_off = 0; dst_off < MAX_OFFSET; dst_off++) {
for (len = 1; len < MAX_OFFSET - max(src_off, dst_off); len++) {
memmove(usermap + dst_off, usermap + src_off, len);
TEST_MEMMOVE(kernelmap + dst_off, kernelmap + src_off, len);
if (memcmp(usermap, kernelmap, MAX_OFFSET) != 0) {
printf("memmove failed at %ld %ld %ld\n",
src_off, dst_off, len);
abort();
}
}
}
}
return 0;
}
int main(void)
{
return test_harness(testcase_run, "memmove");
}
@@ -0,0 +1 @@
timeout=130
@@ -0,0 +1,11 @@
#include <asm/ppc_asm.h>
FUNC_START(enter_vmx_ops)
li r3,1
blr
FUNC_START(exit_vmx_ops)
blr
FUNC_START(__copy_tofrom_user_base)
blr
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "utils.h"
#define MAX_LEN 8192
#define MAX_OFFSET 16
#define MIN_REDZONE 128
#define BUFLEN (MAX_LEN+MAX_OFFSET+2*MIN_REDZONE)
#define POISON 0xa5
#ifdef VMX_TEST
#define VMX_COPY_THRESHOLD 3328
#endif
unsigned long COPY_LOOP(void *to, const void *from, unsigned long size);
static void do_one(char *src, char *dst, unsigned long src_off,
unsigned long dst_off, unsigned long len, void *redzone,
void *fill)
{
char *srcp, *dstp;
unsigned long ret;
unsigned long i;
srcp = src + MIN_REDZONE + src_off;
dstp = dst + MIN_REDZONE + dst_off;
memset(src, POISON, BUFLEN);
memset(dst, POISON, BUFLEN);
memcpy(srcp, fill, len);
ret = COPY_LOOP(dstp, srcp, len);
if (ret && ret != (unsigned long)dstp) {
printf("(%p,%p,%ld) returned %ld\n", dstp, srcp, len, ret);
abort();
}
if (memcmp(dstp, srcp, len)) {
printf("(%p,%p,%ld) miscompare\n", dstp, srcp, len);
printf("src: ");
for (i = 0; i < len; i++)
printf("%02x ", srcp[i]);
printf("\ndst: ");
for (i = 0; i < len; i++)
printf("%02x ", dstp[i]);
printf("\n");
abort();
}
if (memcmp(dst, redzone, dstp - dst)) {
printf("(%p,%p,%ld) redzone before corrupted\n",
dstp, srcp, len);
abort();
}
if (memcmp(dstp+len, redzone, dst+BUFLEN-(dstp+len))) {
printf("(%p,%p,%ld) redzone after corrupted\n",
dstp, srcp, len);
abort();
}
}
int test_copy_loop(void)
{
char *src, *dst, *redzone, *fill;
unsigned long len, src_off, dst_off;
unsigned long i;
src = memalign(BUFLEN, BUFLEN);
dst = memalign(BUFLEN, BUFLEN);
redzone = malloc(BUFLEN);
fill = malloc(BUFLEN);
if (!src || !dst || !redzone || !fill) {
fprintf(stderr, "malloc failed\n");
exit(1);
}
memset(redzone, POISON, BUFLEN);
/* Fill with sequential bytes */
for (i = 0; i < BUFLEN; i++)
fill[i] = i & 0xff;
#ifdef VMX_TEST
/* Force sizes above kernel VMX threshold (3328) */
for (len = VMX_COPY_THRESHOLD + 1; len < MAX_LEN; len++) {
#else
for (len = 1; len < MAX_LEN; len++) {
#endif
for (src_off = 0; src_off < MAX_OFFSET; src_off++) {
for (dst_off = 0; dst_off < MAX_OFFSET; dst_off++) {
do_one(src, dst, src_off, dst_off, len,
redzone, fill);
}
}
}
return 0;
}
int main(void)
{
#ifdef VMX_TEST
/* Skip if Altivec not present */
SKIP_IF_MSG(!have_hwcap(PPC_FEATURE_HAS_ALTIVEC), "ALTIVEC not supported");
#endif
return test_harness(test_copy_loop, str(COPY_LOOP));
}
@@ -0,0 +1,4 @@
dexcr_test
hashchk_test
chdexcr
lsdexcr
@@ -0,0 +1,12 @@
TEST_GEN_PROGS := dexcr_test hashchk_test
TEST_GEN_FILES := lsdexcr chdexcr
include ../../lib.mk
include ../flags.mk
CFLAGS += $(KHDR_INCLUDES)
$(OUTPUT)/hashchk_test: CFLAGS += -fno-pie -no-pie $(call cc-option,-mno-rop-protect)
$(TEST_GEN_PROGS): ../harness.c ../utils.c ./dexcr.c
$(TEST_GEN_FILES): ../utils.c ./dexcr.c
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include "dexcr.h"
#include "utils.h"
static void die(const char *msg)
{
printf("%s\n", msg);
exit(1);
}
static void help(void)
{
printf("Invoke a provided program with a custom DEXCR on-exec reset value\n"
"\n"
"usage: chdexcr [CHDEXCR OPTIONS] -- PROGRAM [ARGS...]\n"
"\n"
"Each configurable DEXCR aspect is exposed as an option.\n"
"\n"
"The normal option sets the aspect in the DEXCR. The --no- variant\n"
"clears that aspect. For example, --ibrtpd sets the IBRTPD aspect bit,\n"
"so indirect branch prediction will be disabled in the provided program.\n"
"Conversely, --no-ibrtpd clears the aspect bit, so indirect branch\n"
"prediction may occur.\n"
"\n"
"CHDEXCR OPTIONS:\n");
for (int i = 0; i < ARRAY_SIZE(aspects); i++) {
const struct dexcr_aspect *aspect = &aspects[i];
if (aspect->prctl == -1)
continue;
printf(" --%-6s / --no-%-6s : %s\n", aspect->opt, aspect->opt, aspect->desc);
}
}
static const struct dexcr_aspect *opt_to_aspect(const char *opt)
{
for (int i = 0; i < ARRAY_SIZE(aspects); i++)
if (aspects[i].prctl != -1 && !strcmp(aspects[i].opt, opt))
return &aspects[i];
return NULL;
}
static int apply_option(const char *option)
{
const struct dexcr_aspect *aspect;
const char *opt = NULL;
const char *set_prefix = "--";
const char *clear_prefix = "--no-";
unsigned long ctrl = 0;
int err;
if (!strcmp(option, "-h") || !strcmp(option, "--help")) {
help();
exit(0);
}
/* Strip out --(no-) prefix and determine ctrl value */
if (!strncmp(option, clear_prefix, strlen(clear_prefix))) {
opt = &option[strlen(clear_prefix)];
ctrl |= PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC;
} else if (!strncmp(option, set_prefix, strlen(set_prefix))) {
opt = &option[strlen(set_prefix)];
ctrl |= PR_PPC_DEXCR_CTRL_SET_ONEXEC;
}
if (!opt || !*opt)
return 1;
aspect = opt_to_aspect(opt);
if (!aspect)
die("unknown aspect");
err = pr_set_dexcr(aspect->prctl, ctrl);
if (err)
die("failed to apply option");
return 0;
}
int main(int argc, char *const argv[])
{
int i;
if (!dexcr_exists())
die("DEXCR not detected on this hardware");
for (i = 1; i < argc; i++)
if (apply_option(argv[i]))
break;
if (i < argc && !strcmp(argv[i], "--"))
i++;
if (i >= argc)
die("missing command");
execvp(argv[i], &argv[i]);
perror("execve");
return errno;
}
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0+
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "dexcr.h"
#include "reg.h"
#include "utils.h"
static jmp_buf generic_signal_jump_buf;
static void generic_signal_handler(int signum, siginfo_t *info, void *context)
{
longjmp(generic_signal_jump_buf, 0);
}
bool dexcr_exists(void)
{
struct sigaction old;
volatile bool exists;
old = push_signal_handler(SIGILL, generic_signal_handler);
if (setjmp(generic_signal_jump_buf))
goto out;
/*
* If the SPR is not recognised by the hardware it triggers
* a hypervisor emulation interrupt. If the kernel does not
* recognise/try to emulate it, we receive a SIGILL signal.
*
* If we do not receive a signal, assume we have the SPR or the
* kernel is trying to emulate it correctly.
*/
exists = false;
mfspr(SPRN_DEXCR_RO);
exists = true;
out:
pop_signal_handler(SIGILL, old);
return exists;
}
unsigned int pr_which_to_aspect(unsigned long which)
{
switch (which) {
case PR_PPC_DEXCR_SBHE:
return DEXCR_PR_SBHE;
case PR_PPC_DEXCR_IBRTPD:
return DEXCR_PR_IBRTPD;
case PR_PPC_DEXCR_SRAPD:
return DEXCR_PR_SRAPD;
case PR_PPC_DEXCR_NPHIE:
return DEXCR_PR_NPHIE;
default:
FAIL_IF_EXIT_MSG(true, "unknown PR aspect");
}
}
int pr_get_dexcr(unsigned long which)
{
return prctl(PR_PPC_GET_DEXCR, which, 0UL, 0UL, 0UL);
}
int pr_set_dexcr(unsigned long which, unsigned long ctrl)
{
return prctl(PR_PPC_SET_DEXCR, which, ctrl, 0UL, 0UL);
}
bool pr_dexcr_aspect_supported(unsigned long which)
{
if (pr_get_dexcr(which) == -1)
return errno == ENODEV;
return true;
}
bool pr_dexcr_aspect_editable(unsigned long which)
{
return pr_get_dexcr(which) & PR_PPC_DEXCR_CTRL_EDITABLE;
}
/*
* Just test if a bad hashchk triggers a signal, without checking
* for support or if the NPHIE aspect is enabled.
*/
bool hashchk_triggers(void)
{
struct sigaction old;
volatile bool triggers;
old = push_signal_handler(SIGILL, generic_signal_handler);
if (setjmp(generic_signal_jump_buf))
goto out;
triggers = true;
do_bad_hashchk();
triggers = false;
out:
pop_signal_handler(SIGILL, old);
return triggers;
}
unsigned int get_dexcr(enum dexcr_source source)
{
switch (source) {
case DEXCR:
return mfspr(SPRN_DEXCR_RO);
case HDEXCR:
return mfspr(SPRN_HDEXCR_RO);
case EFFECTIVE:
return mfspr(SPRN_DEXCR_RO) | mfspr(SPRN_HDEXCR_RO);
default:
FAIL_IF_EXIT_MSG(true, "bad enum dexcr_source");
}
}
void await_child_success(pid_t pid)
{
int wstatus;
FAIL_IF_EXIT_MSG(pid == -1, "fork failed");
FAIL_IF_EXIT_MSG(waitpid(pid, &wstatus, 0) == -1, "wait failed");
FAIL_IF_EXIT_MSG(!WIFEXITED(wstatus), "child did not exit cleanly");
FAIL_IF_EXIT_MSG(WEXITSTATUS(wstatus) != 0, "child exit error");
}
/*
* Perform a hashst instruction. The following components determine the result
*
* 1. The LR value (any register technically)
* 2. The SP value (also any register, but it must be a valid address)
* 3. A secret key managed by the kernel
*
* The result is stored to the address held in SP.
*/
void hashst(unsigned long lr, void *sp)
{
asm volatile ("addi 31, %0, 0;" /* set r31 (pretend LR) to lr */
"addi 30, %1, 8;" /* set r30 (pretend SP) to sp + 8 */
PPC_RAW_HASHST(31, -8, 30) /* compute hash into stack location */
: : "r" (lr), "r" (sp) : "r31", "r30", "memory");
}
/*
* Perform a hashchk instruction. A hash is computed as per hashst(),
* however the result is not stored to memory. Instead the existing
* value is read and compared against the computed hash.
*
* If they match, execution continues.
* If they differ, an interrupt triggers.
*/
void hashchk(unsigned long lr, void *sp)
{
asm volatile ("addi 31, %0, 0;" /* set r31 (pretend LR) to lr */
"addi 30, %1, 8;" /* set r30 (pretend SP) to sp + 8 */
PPC_RAW_HASHCHK(31, -8, 30) /* check hash at stack location */
: : "r" (lr), "r" (sp) : "r31", "r30", "memory");
}
void do_bad_hashchk(void)
{
unsigned long hash = 0;
hashst(0, &hash);
hash += 1;
hashchk(0, &hash);
}
@@ -0,0 +1,106 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* POWER Dynamic Execution Control Facility (DEXCR)
*
* This header file contains helper functions and macros
* required for all the DEXCR related test cases.
*/
#ifndef _SELFTESTS_POWERPC_DEXCR_DEXCR_H
#define _SELFTESTS_POWERPC_DEXCR_DEXCR_H
#include <stdbool.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include "reg.h"
#define DEXCR_PR_BIT(aspect) __MASK(63 - (32 + (aspect)))
#define DEXCR_PR_SBHE DEXCR_PR_BIT(0)
#define DEXCR_PR_IBRTPD DEXCR_PR_BIT(3)
#define DEXCR_PR_SRAPD DEXCR_PR_BIT(4)
#define DEXCR_PR_NPHIE DEXCR_PR_BIT(5)
#define PPC_RAW_HASH_ARGS(b, i, a) \
((((i) >> 3) & 0x1F) << 21 | (a) << 16 | (b) << 11 | (((i) >> 8) & 0x1))
#define PPC_RAW_HASHST(b, i, a) \
str(.long (0x7C0005A4 | PPC_RAW_HASH_ARGS(b, i, a));)
#define PPC_RAW_HASHCHK(b, i, a) \
str(.long (0x7C0005E4 | PPC_RAW_HASH_ARGS(b, i, a));)
struct dexcr_aspect {
const char *name; /* Short display name */
const char *opt; /* Option name for chdexcr */
const char *desc; /* Expanded aspect meaning */
unsigned int index; /* Aspect bit index in DEXCR */
unsigned long prctl; /* 'which' value for get/set prctl */
};
static const struct dexcr_aspect aspects[] = {
{
.name = "SBHE",
.opt = "sbhe",
.desc = "Speculative branch hint enable",
.index = 0,
.prctl = PR_PPC_DEXCR_SBHE,
},
{
.name = "IBRTPD",
.opt = "ibrtpd",
.desc = "Indirect branch recurrent target prediction disable",
.index = 3,
.prctl = PR_PPC_DEXCR_IBRTPD,
},
{
.name = "SRAPD",
.opt = "srapd",
.desc = "Subroutine return address prediction disable",
.index = 4,
.prctl = PR_PPC_DEXCR_SRAPD,
},
{
.name = "NPHIE",
.opt = "nphie",
.desc = "Non-privileged hash instruction enable",
.index = 5,
.prctl = PR_PPC_DEXCR_NPHIE,
},
{
.name = "PHIE",
.opt = "phie",
.desc = "Privileged hash instruction enable",
.index = 6,
.prctl = -1,
},
};
bool dexcr_exists(void);
bool pr_dexcr_aspect_supported(unsigned long which);
bool pr_dexcr_aspect_editable(unsigned long which);
int pr_get_dexcr(unsigned long pr_aspect);
int pr_set_dexcr(unsigned long pr_aspect, unsigned long ctrl);
unsigned int pr_which_to_aspect(unsigned long which);
bool hashchk_triggers(void);
enum dexcr_source {
DEXCR, /* Userspace DEXCR value */
HDEXCR, /* Hypervisor enforced DEXCR value */
EFFECTIVE, /* Bitwise OR of UDEXCR and ENFORCED DEXCR bits */
};
unsigned int get_dexcr(enum dexcr_source source);
void await_child_success(pid_t pid);
void hashst(unsigned long lr, void *sp);
void hashchk(unsigned long lr, void *sp);
void do_bad_hashchk(void);
#endif /* _SELFTESTS_POWERPC_DEXCR_DEXCR_H */
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <unistd.h>
#include "dexcr.h"
#include "utils.h"
/*
* Helper function for testing the behaviour of a newly exec-ed process
*/
static int dexcr_prctl_onexec_test_child(unsigned long which, const char *status)
{
unsigned long dexcr = mfspr(SPRN_DEXCR_RO);
unsigned long aspect = pr_which_to_aspect(which);
int ctrl = pr_get_dexcr(which);
if (!strcmp(status, "set")) {
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET),
"setting aspect across exec not applied");
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC),
"setting aspect across exec not inherited");
FAIL_IF_EXIT_MSG(!(aspect & dexcr), "setting aspect across exec did not take effect");
} else if (!strcmp(status, "clear")) {
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR),
"clearing aspect across exec not applied");
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC),
"clearing aspect across exec not inherited");
FAIL_IF_EXIT_MSG(aspect & dexcr, "clearing aspect across exec did not take effect");
} else {
FAIL_IF_EXIT_MSG(true, "unknown expected status");
}
return 0;
}
/*
* Test that the given prctl value can be manipulated freely
*/
static int dexcr_prctl_aspect_test(unsigned long which)
{
unsigned long aspect = pr_which_to_aspect(which);
pid_t pid;
int ctrl;
int err;
int errno_save;
SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
SKIP_IF_MSG(!pr_dexcr_aspect_supported(which), "DEXCR aspect not supported");
SKIP_IF_MSG(!pr_dexcr_aspect_editable(which), "DEXCR aspect not editable with prctl");
/* We reject invalid combinations of arguments */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR);
errno_save = errno;
FAIL_IF_MSG(err != -1, "simultaneous set and clear should be rejected");
FAIL_IF_MSG(errno_save != EINVAL, "simultaneous set and clear should be rejected with EINVAL");
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET_ONEXEC | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
errno_save = errno;
FAIL_IF_MSG(err != -1, "simultaneous set and clear on exec should be rejected");
FAIL_IF_MSG(errno_save != EINVAL, "simultaneous set and clear on exec should be rejected with EINVAL");
/* We set the aspect */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET), "config value not PR_PPC_DEXCR_CTRL_SET");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_CLEAR, "config value unexpected clear flag");
FAIL_IF_MSG(!(aspect & mfspr(SPRN_DEXCR_RO)), "setting aspect did not take effect");
/* We clear the aspect */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "config value not PR_PPC_DEXCR_CTRL_CLEAR");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_SET, "config value unexpected set flag");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "clearing aspect did not take effect");
/* We make it set on exec (doesn't change our current value) */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "process aspect should still be cleared");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_SET_ONEXEC");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC, "config value unexpected clear on exec flag");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "scheduling aspect to set on exec should not change it now");
/* We make it clear on exec (doesn't change our current value) */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "process aspect config should still be cleared");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC, "config value unexpected set on exec flag");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "process aspect should still be cleared");
/* We allow setting the current and on-exec value in a single call */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET), "config value not PR_PPC_DEXCR_CTRL_SET");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC");
FAIL_IF_MSG(!(aspect & mfspr(SPRN_DEXCR_RO)), "process aspect should be set");
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR | PR_PPC_DEXCR_CTRL_SET_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR | PR_PPC_DEXCR_CTRL_SET_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "config value not PR_PPC_DEXCR_CTRL_CLEAR");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_SET_ONEXEC");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "process aspect should be clear");
/* Verify the onexec value is applied across exec */
pid = fork();
if (!pid) {
char which_str[32] = {};
char *args[] = { "dexcr_prctl_onexec_test_child", which_str, "set", NULL };
unsigned int ctrl = pr_get_dexcr(which);
sprintf(which_str, "%lu", which);
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC),
"setting aspect on exec not copied across fork");
FAIL_IF_EXIT_MSG(mfspr(SPRN_DEXCR_RO) & aspect,
"setting aspect on exec wrongly applied to fork");
execve("/proc/self/exe", args, NULL);
_exit(errno);
}
await_child_success(pid);
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");
pid = fork();
if (!pid) {
char which_str[32] = {};
char *args[] = { "dexcr_prctl_onexec_test_child", which_str, "clear", NULL };
unsigned int ctrl = pr_get_dexcr(which);
sprintf(which_str, "%lu", which);
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC),
"clearing aspect on exec not copied across fork");
FAIL_IF_EXIT_MSG(!(mfspr(SPRN_DEXCR_RO) & aspect),
"clearing aspect on exec wrongly applied to fork");
execve("/proc/self/exe", args, NULL);
_exit(errno);
}
await_child_success(pid);
return 0;
}
static int dexcr_prctl_ibrtpd_test(void)
{
return dexcr_prctl_aspect_test(PR_PPC_DEXCR_IBRTPD);
}
static int dexcr_prctl_srapd_test(void)
{
return dexcr_prctl_aspect_test(PR_PPC_DEXCR_SRAPD);
}
static int dexcr_prctl_nphie_test(void)
{
return dexcr_prctl_aspect_test(PR_PPC_DEXCR_NPHIE);
}
int main(int argc, char *argv[])
{
int err = 0;
/*
* Some tests require checking what happens across exec, so we may be
* invoked as the child of a particular test
*/
if (argc > 1) {
if (argc == 3 && !strcmp(argv[0], "dexcr_prctl_onexec_test_child")) {
unsigned long which;
err = parse_ulong(argv[1], strlen(argv[1]), &which, 10);
FAIL_IF_MSG(err, "failed to parse which value for child");
return dexcr_prctl_onexec_test_child(which, argv[2]);
}
FAIL_IF_MSG(true, "unknown test case");
}
/*
* Otherwise we are the main test invocation and run the full suite
*/
err |= test_harness(dexcr_prctl_ibrtpd_test, "dexcr_prctl_ibrtpd");
err |= test_harness(dexcr_prctl_srapd_test, "dexcr_prctl_srapd");
err |= test_harness(dexcr_prctl_nphie_test, "dexcr_prctl_nphie");
return err;
}
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0+
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sched.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#include "dexcr.h"
#include "utils.h"
static int require_nphie(void)
{
SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
pr_set_dexcr(PR_PPC_DEXCR_NPHIE, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_SET_ONEXEC);
if (get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE)
return 0;
SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE),
"Failed to enable DEXCR[NPHIE]");
return 0;
}
static jmp_buf hashchk_detected_buf;
static const char *hashchk_failure_msg;
static void hashchk_handler(int signum, siginfo_t *info, void *context)
{
if (signum != SIGILL)
hashchk_failure_msg = "wrong signal received";
else if (info->si_code != ILL_ILLOPN)
hashchk_failure_msg = "wrong signal code received";
longjmp(hashchk_detected_buf, 0);
}
/*
* Check that hashchk triggers when DEXCR[NPHIE] is enabled
* and is detected as such by the kernel exception handler
*/
static int hashchk_detected_test(void)
{
struct sigaction old;
int err;
err = require_nphie();
if (err)
return err;
old = push_signal_handler(SIGILL, hashchk_handler);
if (setjmp(hashchk_detected_buf))
goto out;
hashchk_failure_msg = NULL;
do_bad_hashchk();
hashchk_failure_msg = "hashchk failed to trigger";
out:
pop_signal_handler(SIGILL, old);
FAIL_IF_MSG(hashchk_failure_msg, hashchk_failure_msg);
return 0;
}
#define HASH_COUNT 8
static unsigned long hash_values[HASH_COUNT + 1];
static void fill_hash_values(void)
{
for (unsigned long i = 0; i < HASH_COUNT; i++)
hashst(i, &hash_values[i]);
/* Used to ensure the checks uses the same addresses as the hashes */
hash_values[HASH_COUNT] = (unsigned long)&hash_values;
}
static unsigned int count_hash_values_matches(void)
{
unsigned long matches = 0;
for (unsigned long i = 0; i < HASH_COUNT; i++) {
unsigned long orig_hash = hash_values[i];
hash_values[i] = 0;
hashst(i, &hash_values[i]);
if (hash_values[i] == orig_hash)
matches++;
}
return matches;
}
static int hashchk_exec_child(void)
{
ssize_t count;
fill_hash_values();
count = write(STDOUT_FILENO, hash_values, sizeof(hash_values));
return count == sizeof(hash_values) ? 0 : EOVERFLOW;
}
static char *hashchk_exec_child_args[] = { "hashchk_exec_child", NULL };
/*
* Check that new programs get different keys so a malicious process
* can't recreate a victim's hash values.
*/
static int hashchk_exec_random_key_test(void)
{
pid_t pid;
int err;
int pipefd[2];
err = require_nphie();
if (err)
return err;
FAIL_IF_MSG(pipe(pipefd), "failed to create pipe");
pid = fork();
if (pid == 0) {
if (dup2(pipefd[1], STDOUT_FILENO) == -1)
_exit(errno);
execve("/proc/self/exe", hashchk_exec_child_args, NULL);
_exit(errno);
}
await_child_success(pid);
FAIL_IF_MSG(read(pipefd[0], hash_values, sizeof(hash_values)) != sizeof(hash_values),
"missing expected child output");
/* Verify the child used the same hash_values address */
FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)&hash_values,
"bad address check");
/* If all hashes are the same it means (most likely) same key */
FAIL_IF_MSG(count_hash_values_matches() == HASH_COUNT, "shared key detected");
return 0;
}
/*
* Check that forks share the same key so that existing hash values
* remain valid.
*/
static int hashchk_fork_share_key_test(void)
{
pid_t pid;
int err;
err = require_nphie();
if (err)
return err;
fill_hash_values();
pid = fork();
if (pid == 0) {
if (count_hash_values_matches() != HASH_COUNT)
_exit(1);
_exit(0);
}
await_child_success(pid);
return 0;
}
#define STACK_SIZE (1024 * 1024)
static int hashchk_clone_child_fn(void *args)
{
fill_hash_values();
return 0;
}
/*
* Check that threads share the same key so that existing hash values
* remain valid.
*/
static int hashchk_clone_share_key_test(void)
{
void *child_stack;
pid_t pid;
int err;
err = require_nphie();
if (err)
return err;
child_stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
FAIL_IF_MSG(child_stack == MAP_FAILED, "failed to map child stack");
pid = clone(hashchk_clone_child_fn, child_stack + STACK_SIZE,
CLONE_VM | SIGCHLD, NULL);
await_child_success(pid);
FAIL_IF_MSG(count_hash_values_matches() != HASH_COUNT,
"different key detected");
return 0;
}
int main(int argc, char *argv[])
{
int err = 0;
if (argc >= 1 && !strcmp(argv[0], hashchk_exec_child_args[0]))
return hashchk_exec_child();
err |= test_harness(hashchk_detected_test, "hashchk_detected");
err |= test_harness(hashchk_exec_random_key_test, "hashchk_exec_random_key");
err |= test_harness(hashchk_fork_share_key_test, "hashchk_fork_share_key");
err |= test_harness(hashchk_clone_share_key_test, "hashchk_clone_share_key");
return err;
}
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0+
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>
#include "dexcr.h"
#include "utils.h"
static unsigned int dexcr;
static unsigned int hdexcr;
static unsigned int effective;
static void print_list(const char *list[], size_t len)
{
for (size_t i = 0; i < len; i++) {
printf("%s", list[i]);
if (i + 1 < len)
printf(", ");
}
}
static void print_dexcr(char *name, unsigned int bits)
{
const char *enabled_aspects[ARRAY_SIZE(aspects) + 1] = {NULL};
size_t j = 0;
printf("%s: 0x%08x", name, bits);
if (bits == 0) {
printf("\n");
return;
}
for (size_t i = 0; i < ARRAY_SIZE(aspects); i++) {
unsigned int mask = DEXCR_PR_BIT(aspects[i].index);
if (bits & mask) {
enabled_aspects[j++] = aspects[i].name;
bits &= ~mask;
}
}
if (bits)
enabled_aspects[j++] = "unknown";
printf(" (");
print_list(enabled_aspects, j);
printf(")\n");
}
static void print_aspect(const struct dexcr_aspect *aspect)
{
const char *attributes[8] = {NULL};
size_t j = 0;
unsigned long mask;
mask = DEXCR_PR_BIT(aspect->index);
if (dexcr & mask)
attributes[j++] = "set";
if (hdexcr & mask)
attributes[j++] = "set (hypervisor)";
if (!(effective & mask))
attributes[j++] = "clear";
printf("%12s %c (%d): ", aspect->name, effective & mask ? '*' : ' ', aspect->index);
print_list(attributes, j);
printf(" \t(%s)\n", aspect->desc);
}
static void print_aspect_config(const struct dexcr_aspect *aspect)
{
const char *reason = NULL;
const char *reason_hyp = NULL;
const char *reason_prctl = "no prctl";
bool actual = effective & DEXCR_PR_BIT(aspect->index);
bool expected = actual; /* Assume it's fine if we don't expect a specific set/clear value */
if (actual)
reason = "set by unknown";
else
reason = "cleared by unknown";
if (aspect->prctl != -1) {
int ctrl = pr_get_dexcr(aspect->prctl);
if (ctrl < 0) {
reason_prctl = "failed to read prctl";
} else {
if (ctrl & PR_PPC_DEXCR_CTRL_SET) {
reason_prctl = "set by prctl";
expected = true;
} else if (ctrl & PR_PPC_DEXCR_CTRL_CLEAR) {
reason_prctl = "cleared by prctl";
expected = false;
} else {
reason_prctl = "unknown prctl";
}
reason = reason_prctl;
}
}
if (hdexcr & DEXCR_PR_BIT(aspect->index)) {
reason_hyp = "set by hypervisor";
reason = reason_hyp;
expected = true;
} else {
reason_hyp = "not modified by hypervisor";
}
printf("%12s (%d): %-28s (%s, %s)\n",
aspect->name,
aspect->index,
reason,
reason_hyp,
reason_prctl);
/*
* The checks are not atomic, so this can technically trigger if the
* hypervisor makes a change while we are checking each source. It's
* far more likely to be a bug if we see this though.
*/
if (actual != expected)
printf(" : ! actual %s does not match config\n", aspect->name);
}
int main(int argc, char *argv[])
{
if (!dexcr_exists()) {
printf("DEXCR not detected on this hardware\n");
return 1;
}
dexcr = get_dexcr(DEXCR);
hdexcr = get_dexcr(HDEXCR);
effective = dexcr | hdexcr;
printf("current status:\n");
print_dexcr(" DEXCR", dexcr);
print_dexcr(" HDEXCR", hdexcr);
print_dexcr("Effective", effective);
printf("\n");
for (size_t i = 0; i < ARRAY_SIZE(aspects); i++)
print_aspect(&aspects[i]);
printf("\n");
if (effective & DEXCR_PR_NPHIE) {
printf("DEXCR[NPHIE] enabled: hashst/hashchk ");
if (hashchk_triggers())
printf("working\n");
else
printf("failed to trigger\n");
} else {
printf("DEXCR[NPHIE] disabled: hashst/hashchk ");
if (hashchk_triggers())
printf("unexpectedly triggered\n");
else
printf("ignored\n");
}
printf("\n");
printf("configuration:\n");
for (size_t i = 0; i < ARRAY_SIZE(aspects); i++)
print_aspect_config(&aspects[i]);
printf("\n");
return 0;
}
@@ -0,0 +1 @@
timeout=130
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
dscr_default_test
dscr_explicit_test
dscr_inherit_exec_test
dscr_inherit_test
dscr_sysfs_test
dscr_sysfs_thread_test
dscr_user_test
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := dscr_default_test dscr_explicit_test dscr_user_test \
dscr_inherit_test dscr_inherit_exec_test dscr_sysfs_test \
dscr_sysfs_thread_test
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
$(OUTPUT)/dscr_default_test: LDLIBS += -lpthread
$(OUTPUT)/dscr_explicit_test: LDLIBS += -lpthread
$(TEST_GEN_PROGS): ../harness.c ../utils.c
@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* POWER Data Stream Control Register (DSCR)
*
* This header file contains helper functions and macros
* required for all the DSCR related test cases.
*
* Copyright 2012, Anton Blanchard, IBM Corporation.
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#ifndef _SELFTESTS_POWERPC_DSCR_DSCR_H
#define _SELFTESTS_POWERPC_DSCR_DSCR_H
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <pthread.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "reg.h"
#include "utils.h"
#define THREADS 100 /* Max threads */
#define COUNT 100 /* Max iterations */
#define DSCR_MAX 16 /* Max DSCR value */
#define LEN_MAX 100 /* Max name length */
#define DSCR_DEFAULT "/sys/devices/system/cpu/dscr_default"
#define CPU_PATH "/sys/devices/system/cpu/"
#define rmb() asm volatile("lwsync":::"memory")
#define wmb() asm volatile("lwsync":::"memory")
#define READ_ONCE(x) (*(volatile typeof(x) *)&(x))
/* Prilvilege state DSCR access */
inline unsigned long get_dscr(void)
{
return mfspr(SPRN_DSCR_PRIV);
}
inline void set_dscr(unsigned long val)
{
mtspr(SPRN_DSCR_PRIV, val);
}
/* Problem state DSCR access */
inline unsigned long get_dscr_usr(void)
{
return mfspr(SPRN_DSCR);
}
inline void set_dscr_usr(unsigned long val)
{
mtspr(SPRN_DSCR, val);
}
/* Default DSCR access */
unsigned long get_default_dscr(void)
{
int err;
unsigned long val;
err = read_ulong(DSCR_DEFAULT, &val, 16);
if (err) {
perror("read() failed");
exit(1);
}
return val;
}
void set_default_dscr(unsigned long val)
{
int err;
err = write_ulong(DSCR_DEFAULT, val, 16);
if (err) {
perror("write() failed");
exit(1);
}
}
#endif /* _SELFTESTS_POWERPC_DSCR_DSCR_H */
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* POWER Data Stream Control Register (DSCR) default test
*
* This test modifies the system wide default DSCR through
* it's sysfs interface and then verifies that all threads
* see the correct changed DSCR value immediately.
*
* Copyright 2012, Anton Blanchard, IBM Corporation.
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#define _GNU_SOURCE
#include "dscr.h"
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
static void *dscr_default_lockstep_writer(void *arg)
{
sem_t *reader_sem = (sem_t *)arg;
sem_t *writer_sem = (sem_t *)arg + 1;
unsigned long expected_dscr = 0;
for (int i = 0; i < COUNT; i++) {
FAIL_IF_EXIT(sem_wait(writer_sem));
set_default_dscr(expected_dscr);
expected_dscr = (expected_dscr + 1) % DSCR_MAX;
FAIL_IF_EXIT(sem_post(reader_sem));
}
return NULL;
}
int dscr_default_lockstep_test(void)
{
pthread_t writer;
sem_t rw_semaphores[2];
sem_t *reader_sem = &rw_semaphores[0];
sem_t *writer_sem = &rw_semaphores[1];
unsigned long expected_dscr = 0;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
FAIL_IF(sem_init(reader_sem, 0, 0));
FAIL_IF(sem_init(writer_sem, 0, 1)); /* writer starts first */
FAIL_IF(bind_to_cpu(BIND_CPU_ANY) < 0);
FAIL_IF(pthread_create(&writer, NULL, dscr_default_lockstep_writer, (void *)rw_semaphores));
for (int i = 0; i < COUNT ; i++) {
FAIL_IF(sem_wait(reader_sem));
FAIL_IF(get_dscr() != expected_dscr);
FAIL_IF(get_dscr_usr() != expected_dscr);
expected_dscr = (expected_dscr + 1) % DSCR_MAX;
FAIL_IF(sem_post(writer_sem));
}
FAIL_IF(pthread_join(writer, NULL));
FAIL_IF(sem_destroy(reader_sem));
FAIL_IF(sem_destroy(writer_sem));
return 0;
}
struct random_thread_args {
pthread_t thread_id;
unsigned long *expected_system_dscr;
pthread_rwlock_t *rw_lock;
pthread_barrier_t *barrier;
};
static void *dscr_default_random_thread(void *in)
{
struct random_thread_args *args = (struct random_thread_args *)in;
unsigned long *expected_dscr_p = args->expected_system_dscr;
pthread_rwlock_t *rw_lock = args->rw_lock;
int err;
srand(gettid());
err = pthread_barrier_wait(args->barrier);
FAIL_IF_EXIT(err != 0 && err != PTHREAD_BARRIER_SERIAL_THREAD);
for (int i = 0; i < COUNT; i++) {
unsigned long expected_dscr;
unsigned long current_dscr;
unsigned long current_dscr_usr;
FAIL_IF_EXIT(pthread_rwlock_rdlock(rw_lock));
expected_dscr = *expected_dscr_p;
current_dscr = get_dscr();
current_dscr_usr = get_dscr_usr();
FAIL_IF_EXIT(pthread_rwlock_unlock(rw_lock));
FAIL_IF_EXIT(current_dscr != expected_dscr);
FAIL_IF_EXIT(current_dscr_usr != expected_dscr);
if (rand() % 10 == 0) {
unsigned long next_dscr;
FAIL_IF_EXIT(pthread_rwlock_wrlock(rw_lock));
next_dscr = (*expected_dscr_p + 1) % DSCR_MAX;
set_default_dscr(next_dscr);
*expected_dscr_p = next_dscr;
FAIL_IF_EXIT(pthread_rwlock_unlock(rw_lock));
}
}
pthread_exit((void *)0);
}
int dscr_default_random_test(void)
{
struct random_thread_args threads[THREADS];
unsigned long expected_system_dscr = 0;
pthread_rwlockattr_t rwlock_attr;
pthread_rwlock_t rw_lock;
pthread_barrier_t barrier;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
FAIL_IF(pthread_rwlockattr_setkind_np(&rwlock_attr,
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP));
FAIL_IF(pthread_rwlock_init(&rw_lock, &rwlock_attr));
FAIL_IF(pthread_barrier_init(&barrier, NULL, THREADS));
set_default_dscr(expected_system_dscr);
for (int i = 0; i < THREADS; i++) {
threads[i].expected_system_dscr = &expected_system_dscr;
threads[i].rw_lock = &rw_lock;
threads[i].barrier = &barrier;
FAIL_IF(pthread_create(&threads[i].thread_id, NULL,
dscr_default_random_thread, (void *)&threads[i]));
}
for (int i = 0; i < THREADS; i++)
FAIL_IF(pthread_join(threads[i].thread_id, NULL));
FAIL_IF(pthread_barrier_destroy(&barrier));
FAIL_IF(pthread_rwlock_destroy(&rw_lock));
return 0;
}
int main(int argc, char *argv[])
{
unsigned long orig_dscr_default = 0;
int err = 0;
if (have_hwcap2(PPC_FEATURE2_DSCR))
orig_dscr_default = get_default_dscr();
err |= test_harness(dscr_default_lockstep_test, "dscr_default_lockstep_test");
err |= test_harness(dscr_default_random_test, "dscr_default_random_test");
if (have_hwcap2(PPC_FEATURE2_DSCR))
set_default_dscr(orig_dscr_default);
return err;
}
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* POWER Data Stream Control Register (DSCR) explicit test
*
* This test modifies the DSCR value using mtspr instruction and
* verifies the change with mfspr instruction. It uses both the
* privilege state SPR and the problem state SPR for this purpose.
*
* When using the privilege state SPR, the instructions such as
* mfspr or mtspr are privileged and the kernel emulates them
* for us. Instructions using problem state SPR can be executed
* directly without any emulation if the HW supports them. Else
* they also get emulated by the kernel.
*
* Copyright 2012, Anton Blanchard, IBM Corporation.
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#define _GNU_SOURCE
#include "dscr.h"
#include "utils.h"
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
void *dscr_explicit_lockstep_thread(void *args)
{
sem_t *prev = (sem_t *)args;
sem_t *next = (sem_t *)args + 1;
unsigned long expected_dscr = 0;
set_dscr(expected_dscr);
srand(gettid());
for (int i = 0; i < COUNT; i++) {
FAIL_IF_EXIT(sem_wait(prev));
FAIL_IF_EXIT(expected_dscr != get_dscr());
FAIL_IF_EXIT(expected_dscr != get_dscr_usr());
expected_dscr = (expected_dscr + 1) % DSCR_MAX;
set_dscr(expected_dscr);
FAIL_IF_EXIT(sem_post(next));
}
return NULL;
}
int dscr_explicit_lockstep_test(void)
{
pthread_t thread;
sem_t semaphores[2];
sem_t *prev = &semaphores[1]; /* reversed prev/next than for the other thread */
sem_t *next = &semaphores[0];
unsigned long expected_dscr = 0;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
srand(gettid());
set_dscr(expected_dscr);
FAIL_IF(sem_init(prev, 0, 0));
FAIL_IF(sem_init(next, 0, 1)); /* other thread starts first */
FAIL_IF(bind_to_cpu(BIND_CPU_ANY) < 0);
FAIL_IF(pthread_create(&thread, NULL, dscr_explicit_lockstep_thread, (void *)semaphores));
for (int i = 0; i < COUNT; i++) {
FAIL_IF(sem_wait(prev));
FAIL_IF(expected_dscr != get_dscr());
FAIL_IF(expected_dscr != get_dscr_usr());
expected_dscr = (expected_dscr - 1) % DSCR_MAX;
set_dscr(expected_dscr);
FAIL_IF(sem_post(next));
}
FAIL_IF(pthread_join(thread, NULL));
FAIL_IF(sem_destroy(prev));
FAIL_IF(sem_destroy(next));
return 0;
}
struct random_thread_args {
pthread_t thread_id;
bool do_yields;
pthread_barrier_t *barrier;
};
void *dscr_explicit_random_thread(void *in)
{
struct random_thread_args *args = (struct random_thread_args *)in;
unsigned long expected_dscr = 0;
int err;
srand(gettid());
err = pthread_barrier_wait(args->barrier);
FAIL_IF_EXIT(err != 0 && err != PTHREAD_BARRIER_SERIAL_THREAD);
for (int i = 0; i < COUNT; i++) {
expected_dscr = rand() % DSCR_MAX;
set_dscr(expected_dscr);
for (int j = rand() % 5; j > 0; --j) {
FAIL_IF_EXIT(get_dscr() != expected_dscr);
FAIL_IF_EXIT(get_dscr_usr() != expected_dscr);
if (args->do_yields && rand() % 2)
sched_yield();
}
expected_dscr = rand() % DSCR_MAX;
set_dscr_usr(expected_dscr);
for (int j = rand() % 5; j > 0; --j) {
FAIL_IF_EXIT(get_dscr() != expected_dscr);
FAIL_IF_EXIT(get_dscr_usr() != expected_dscr);
if (args->do_yields && rand() % 2)
sched_yield();
}
}
return NULL;
}
int dscr_explicit_random_test(void)
{
struct random_thread_args threads[THREADS];
pthread_barrier_t barrier;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
FAIL_IF(pthread_barrier_init(&barrier, NULL, THREADS));
for (int i = 0; i < THREADS; i++) {
threads[i].do_yields = i % 2 == 0;
threads[i].barrier = &barrier;
FAIL_IF(pthread_create(&threads[i].thread_id, NULL,
dscr_explicit_random_thread, (void *)&threads[i]));
}
for (int i = 0; i < THREADS; i++)
FAIL_IF(pthread_join(threads[i].thread_id, NULL));
FAIL_IF(pthread_barrier_destroy(&barrier));
return 0;
}
int main(int argc, char *argv[])
{
unsigned long orig_dscr_default = 0;
int err = 0;
if (have_hwcap2(PPC_FEATURE2_DSCR))
orig_dscr_default = get_default_dscr();
err |= test_harness(dscr_explicit_lockstep_test, "dscr_explicit_lockstep_test");
err |= test_harness(dscr_explicit_random_test, "dscr_explicit_random_test");
if (have_hwcap2(PPC_FEATURE2_DSCR))
set_default_dscr(orig_dscr_default);
return err;
}
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* POWER Data Stream Control Register (DSCR) fork exec test
*
* This testcase modifies the DSCR using mtspr, forks & execs and
* verifies that the child is using the changed DSCR using mfspr.
*
* When using the privilege state SPR, the instructions such as
* mfspr or mtspr are privileged and the kernel emulates them
* for us. Instructions using problem state SPR can be executed
* directly without any emulation if the HW supports them. Else
* they also get emulated by the kernel.
*
* Copyright 2012, Anton Blanchard, IBM Corporation.
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#include "dscr.h"
static char *prog;
static void do_exec(unsigned long parent_dscr)
{
unsigned long cur_dscr, cur_dscr_usr;
cur_dscr = get_dscr();
cur_dscr_usr = get_dscr_usr();
if (cur_dscr != parent_dscr) {
fprintf(stderr, "Parent DSCR %ld was not inherited "
"over exec (kernel value)\n", parent_dscr);
exit(1);
}
if (cur_dscr_usr != parent_dscr) {
fprintf(stderr, "Parent DSCR %ld was not inherited "
"over exec (user value)\n", parent_dscr);
exit(1);
}
exit(0);
}
int dscr_inherit_exec(void)
{
unsigned long i, dscr = 0;
pid_t pid;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
for (i = 0; i < COUNT; i++) {
dscr++;
if (dscr > DSCR_MAX)
dscr = 0;
if (dscr == get_default_dscr())
continue;
if (i % 2 == 0)
set_dscr_usr(dscr);
else
set_dscr(dscr);
pid = fork();
if (pid == -1) {
perror("fork() failed");
exit(1);
} else if (pid) {
int status;
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid() failed");
exit(1);
}
if (!WIFEXITED(status)) {
fprintf(stderr, "Child didn't exit cleanly\n");
exit(1);
}
if (WEXITSTATUS(status) != 0) {
fprintf(stderr, "Child didn't exit cleanly\n");
return 1;
}
} else {
char dscr_str[16];
sprintf(dscr_str, "%ld", dscr);
execlp(prog, prog, "exec", dscr_str, NULL);
exit(1);
}
}
return 0;
}
int main(int argc, char *argv[])
{
if (argc == 3 && !strcmp(argv[1], "exec")) {
unsigned long parent_dscr;
parent_dscr = atoi(argv[2]);
do_exec(parent_dscr);
} else if (argc != 1) {
fprintf(stderr, "Usage: %s\n", argv[0]);
exit(1);
}
prog = argv[0];
return test_harness(dscr_inherit_exec, "dscr_inherit_exec_test");
}
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* POWER Data Stream Control Register (DSCR) fork test
*
* This testcase modifies the DSCR using mtspr, forks and then
* verifies that the child process has the correct changed DSCR
* value using mfspr.
*
* When using the privilege state SPR, the instructions such as
* mfspr or mtspr are privileged and the kernel emulates them
* for us. Instructions using problem state SPR can be executed
* directly without any emulation if the HW supports them. Else
* they also get emulated by the kernel.
*
* Copyright 2012, Anton Blanchard, IBM Corporation.
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#include "dscr.h"
int dscr_inherit(void)
{
unsigned long i, dscr = 0;
pid_t pid;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
srand(getpid());
set_dscr(dscr);
for (i = 0; i < COUNT; i++) {
unsigned long cur_dscr, cur_dscr_usr;
dscr++;
if (dscr > DSCR_MAX)
dscr = 0;
if (i % 2 == 0)
set_dscr_usr(dscr);
else
set_dscr(dscr);
pid = fork();
if (pid == -1) {
perror("fork() failed");
exit(1);
} else if (pid) {
int status;
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid() failed");
exit(1);
}
if (!WIFEXITED(status)) {
fprintf(stderr, "Child didn't exit cleanly\n");
exit(1);
}
if (WEXITSTATUS(status) != 0) {
fprintf(stderr, "Child didn't exit cleanly\n");
return 1;
}
} else {
cur_dscr = get_dscr();
if (cur_dscr != dscr) {
fprintf(stderr, "Kernel DSCR should be %ld "
"but is %ld\n", dscr, cur_dscr);
exit(1);
}
cur_dscr_usr = get_dscr_usr();
if (cur_dscr_usr != dscr) {
fprintf(stderr, "User DSCR should be %ld "
"but is %ld\n", dscr, cur_dscr_usr);
exit(1);
}
exit(0);
}
}
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(dscr_inherit, "dscr_inherit_test");
}
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* POWER Data Stream Control Register (DSCR) sysfs interface test
*
* This test updates to system wide DSCR default through the sysfs interface
* and then verifies that all the CPU specific DSCR defaults are updated as
* well verified from their sysfs interfaces.
*
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#include "dscr.h"
static int check_cpu_dscr_default(char *file, unsigned long val)
{
unsigned long cpu_dscr;
int err;
err = read_ulong(file, &cpu_dscr, 16);
if (err)
return err;
if (cpu_dscr != val) {
printf("DSCR match failed: %ld (system) %ld (cpu)\n",
val, cpu_dscr);
return 1;
}
return 0;
}
static int check_all_cpu_dscr_defaults(unsigned long val)
{
DIR *sysfs;
struct dirent *dp;
char file[LEN_MAX];
sysfs = opendir(CPU_PATH);
if (!sysfs) {
perror("opendir() failed");
return 1;
}
while ((dp = readdir(sysfs))) {
int len;
if (!(dp->d_type & DT_DIR))
continue;
if (!strcmp(dp->d_name, "cpuidle"))
continue;
if (!strstr(dp->d_name, "cpu"))
continue;
len = snprintf(file, LEN_MAX, "%s%s/dscr", CPU_PATH, dp->d_name);
if (len >= LEN_MAX)
continue;
if (access(file, F_OK))
continue;
if (check_cpu_dscr_default(file, val)) {
closedir(sysfs);
return 1;
}
}
closedir(sysfs);
return 0;
}
int dscr_sysfs(void)
{
unsigned long orig_dscr_default;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
orig_dscr_default = get_default_dscr();
for (int i = 0; i < DSCR_MAX; i++) {
set_default_dscr(i);
if (check_all_cpu_dscr_defaults(i))
goto fail;
}
set_default_dscr(orig_dscr_default);
return 0;
fail:
set_default_dscr(orig_dscr_default);
return 1;
}
int main(int argc, char *argv[])
{
return test_harness(dscr_sysfs, "dscr_sysfs_test");
}
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* POWER Data Stream Control Register (DSCR) sysfs thread test
*
* This test updates the system wide DSCR default value through
* sysfs interface which should then update all the CPU specific
* DSCR default values which must also be then visible to threads
* executing on individual CPUs on the system.
*
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#define _GNU_SOURCE
#include "dscr.h"
static int test_thread_dscr(unsigned long val)
{
unsigned long cur_dscr, cur_dscr_usr;
cur_dscr = get_dscr();
cur_dscr_usr = get_dscr_usr();
if (val != cur_dscr) {
printf("[cpu %d] Kernel DSCR should be %ld but is %ld\n",
sched_getcpu(), val, cur_dscr);
return 1;
}
if (val != cur_dscr_usr) {
printf("[cpu %d] User DSCR should be %ld but is %ld\n",
sched_getcpu(), val, cur_dscr_usr);
return 1;
}
return 0;
}
static int check_cpu_dscr_thread(unsigned long val)
{
cpu_set_t mask;
int cpu;
for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
CPU_ZERO(&mask);
CPU_SET(cpu, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask))
continue;
if (test_thread_dscr(val))
return 1;
}
return 0;
}
int dscr_sysfs_thread(void)
{
unsigned long orig_dscr_default;
int i, j;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
orig_dscr_default = get_default_dscr();
for (i = 0; i < COUNT; i++) {
for (j = 0; j < DSCR_MAX; j++) {
set_default_dscr(j);
if (check_cpu_dscr_thread(j))
goto fail;
}
}
set_default_dscr(orig_dscr_default);
return 0;
fail:
set_default_dscr(orig_dscr_default);
return 1;
}
int main(int argc, char *argv[])
{
return test_harness(dscr_sysfs_thread, "dscr_sysfs_thread_test");
}
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* POWER Data Stream Control Register (DSCR) SPR test
*
* This test modifies the DSCR value through both the SPR number
* based mtspr instruction and then makes sure that the same is
* reflected through mfspr instruction using either of the SPR
* numbers.
*
* When using the privilege state SPR, the instructions such as
* mfspr or mtspr are privileged and the kernel emulates them
* for us. Instructions using problem state SPR can be executed
* directly without any emulation if the HW supports them. Else
* they also get emulated by the kernel.
*
* Copyright 2013, Anton Blanchard, IBM Corporation.
* Copyright 2015, Anshuman Khandual, IBM Corporation.
*/
#include "dscr.h"
static int check_dscr(char *str)
{
unsigned long cur_dscr, cur_dscr_usr;
cur_dscr = get_dscr();
cur_dscr_usr = get_dscr_usr();
if (cur_dscr != cur_dscr_usr) {
printf("%s set, kernel get %lx != user get %lx\n",
str, cur_dscr, cur_dscr_usr);
return 1;
}
return 0;
}
int dscr_user(void)
{
int i;
SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR));
check_dscr("");
for (i = 0; i < COUNT; i++) {
set_dscr(i);
if (check_dscr("kernel"))
return 1;
}
for (i = 0; i < COUNT; i++) {
set_dscr_usr(i);
if (check_dscr("user"))
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(dscr_user, "dscr_user_test");
}
@@ -0,0 +1 @@
timeout=130
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
noarg:
$(MAKE) -C ../
TEST_PROGS := eeh-basic.sh
TEST_FILES := eeh-functions.sh settings
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
@@ -0,0 +1,57 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
. ./eeh-functions.sh
eeh_test_prep # NB: may exit
pre_lspci=`mktemp`
lspci > $pre_lspci
# record the devices that we break in here. Assuming everything
# goes to plan we should get them back once the recover process
# is finished.
devices=""
# Build up a list of candidate devices.
for dev in `ls -1 /sys/bus/pci/devices/ | grep '\.0$'` ; do
if ! eeh_can_break $dev ; then
continue;
fi
# Skip VFs for now since we don't have a reliable way to break them.
if [ -e "/sys/bus/pci/devices/$dev/physfn" ] ; then
echo "$dev, Skipped: virtfn"
continue;
fi
echo "$dev, Added"
# Add to this list of device to check
devices="$devices $dev"
done
dev_count="$(echo $devices | wc -w)"
echo "Found ${dev_count} breakable devices..."
failed=0
for dev in $devices ; do
echo "Breaking $dev..."
if ! pe_ok $dev ; then
echo "Skipping $dev, Initial PE state is not ok"
failed="$((failed + 1))"
continue;
fi
if ! eeh_one_dev $dev ; then
failed="$((failed + 1))"
fi
done
echo "$failed devices failed to recover ($dev_count tested)"
lspci | diff -u $pre_lspci -
rm -f $pre_lspci
test "$failed" -eq 0
exit $?
@@ -0,0 +1,245 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
export KSELFTESTS_SKIP=4
log() {
echo >/dev/stderr $*
}
pe_ok() {
local dev="$1"
local path="/sys/bus/pci/devices/$dev/eeh_pe_state"
# if a driver doesn't support the error handling callbacks then the
# device is recovered by removing and re-probing it. This causes the
# sysfs directory to disappear so read the PE state once and squash
# any potential error messages
local eeh_state="$(cat $path 2>/dev/null)"
if [ -z "$eeh_state" ]; then
return 1;
fi
local fw_state="$(echo $eeh_state | cut -d' ' -f1)"
local sw_state="$(echo $eeh_state | cut -d' ' -f2)"
# If EEH_PE_ISOLATED or EEH_PE_RECOVERING are set then the PE is in an
# error state or being recovered. Either way, not ok.
if [ "$((sw_state & 0x3))" -ne 0 ] ; then
return 1
fi
# A functioning PE should have the EEH_STATE_MMIO_ACTIVE and
# EEH_STATE_DMA_ACTIVE flags set. For some goddamn stupid reason
# the platform backends set these when the PE is in reset. The
# RECOVERING check above should stop any false positives though.
if [ "$((fw_state & 0x18))" -ne "$((0x18))" ] ; then
return 1
fi
return 0;
}
eeh_supported() {
test -e /proc/powerpc/eeh && \
grep -q 'EEH Subsystem is enabled' /proc/powerpc/eeh
}
eeh_test_prep() {
if ! eeh_supported ; then
echo "EEH not supported on this system, skipping"
exit $KSELFTESTS_SKIP;
fi
if [ ! -e "/sys/kernel/debug/powerpc/eeh_dev_check" ] && \
[ ! -e "/sys/kernel/debug/powerpc/eeh_dev_break" ] ; then
log "debugfs EEH testing files are missing. Is debugfs mounted?"
exit $KSELFTESTS_SKIP;
fi
# Bump the max freeze count to something absurd so we don't
# trip over it while breaking things.
echo 5000 > /sys/kernel/debug/powerpc/eeh_max_freezes
}
eeh_can_break() {
# skip bridges since we can't recover them (yet...)
if [ -e "/sys/bus/pci/devices/$dev/pci_bus" ] ; then
log "$dev, Skipped: bridge"
return 1;
fi
# The ahci driver doesn't support error recovery. If the ahci device
# happens to be hosting the root filesystem, and then we go and break
# it the system will generally go down. We should probably fix that
# at some point
if [ "ahci" = "$(basename $(realpath /sys/bus/pci/devices/$dev/driver))" ] ; then
log "$dev, Skipped: ahci doesn't support recovery"
return 1;
fi
# Don't inject errosr into an already-frozen PE. This happens with
# PEs that contain multiple PCI devices (e.g. multi-function cards)
# and injecting new errors during the recovery process will probably
# result in the recovery failing and the device being marked as
# failed.
if ! pe_ok $dev ; then
log "$dev, Skipped: Bad initial PE state"
return 1;
fi
return 0
}
eeh_one_dev() {
local dev="$1"
# Using this function from the command line is sometimes useful for
# testing so check that the argument is a well-formed sysfs device
# name.
if ! test -e /sys/bus/pci/devices/$dev/ ; then
log "Error: '$dev' must be a sysfs device name (DDDD:BB:DD.F)"
return 1;
fi
# Break it
echo $dev >/sys/kernel/debug/powerpc/eeh_dev_break
# Force an EEH device check. If the kernel has already
# noticed the EEH (due to a driver poll or whatever), this
# is a no-op.
echo $dev >/sys/kernel/debug/powerpc/eeh_dev_check
# Default to a 60s timeout when waiting for a device to recover. This
# is an arbitrary default which can be overridden by setting the
# EEH_MAX_WAIT environmental variable when required.
# The current record holder for longest recovery time is:
# "Adaptec Series 8 12G SAS/PCIe 3" at 39 seconds
max_wait=${EEH_MAX_WAIT:=60}
for i in `seq 0 ${max_wait}` ; do
if pe_ok $dev ; then
break;
fi
log "$dev, waited $i/${max_wait}"
sleep 1
done
if ! pe_ok $dev ; then
log "$dev, Failed to recover!"
return 1;
fi
log "$dev, Recovered after $i seconds"
return 0;
}
eeh_has_driver() {
test -e /sys/bus/pci/devices/$1/driver;
return $?
}
eeh_can_recover() {
# we'll get an IO error if the device's current driver doesn't support
# error recovery
echo $1 > '/sys/kernel/debug/powerpc/eeh_dev_can_recover' 2>/dev/null
return $?
}
eeh_find_all_pfs() {
devices=""
# SR-IOV on pseries requires hypervisor support, so check for that
is_pseries=""
if grep -q pSeries /proc/cpuinfo ; then
if [ ! -f /proc/device-tree/rtas/ibm,open-sriov-allow-unfreeze ] ||
[ ! -f /proc/device-tree/rtas/ibm,open-sriov-map-pe-number ] ; then
return 1;
fi
is_pseries="true"
fi
for dev in `ls -1 /sys/bus/pci/devices/` ; do
sysfs="/sys/bus/pci/devices/$dev"
if [ ! -e "$sysfs/sriov_numvfs" ] ; then
continue
fi
# skip unsupported PFs on pseries
if [ -z "$is_pseries" ] &&
[ ! -f "$sysfs/of_node/ibm,is-open-sriov-pf" ] &&
[ ! -f "$sysfs/of_node/ibm,open-sriov-vf-bar-info" ] ; then
continue;
fi
# no driver, no vfs
if ! eeh_has_driver $dev ; then
continue
fi
devices="$devices $dev"
done
if [ -z "$devices" ] ; then
return 1;
fi
echo $devices
return 0;
}
# attempts to enable one VF on each PF so we can do VF specific tests.
# stdout: list of enabled VFs, one per line
# return code: 0 if vfs are found, 1 otherwise
eeh_enable_vfs() {
pf_list="$(eeh_find_all_pfs)"
vfs=0
for dev in $pf_list ; do
pf_sysfs="/sys/bus/pci/devices/$dev"
# make sure we have a single VF
echo 0 > "$pf_sysfs/sriov_numvfs"
echo 1 > "$pf_sysfs/sriov_numvfs"
if [ "$?" != 0 ] ; then
log "Unable to enable VFs on $pf, skipping"
continue;
fi
vf="$(basename $(realpath "$pf_sysfs/virtfn0"))"
if [ $? != 0 ] ; then
log "unable to find enabled vf on $pf"
echo 0 > "$pf_sysfs/sriov_numvfs"
continue;
fi
if ! eeh_can_break $vf ; then
log "skipping "
echo 0 > "$pf_sysfs/sriov_numvfs"
continue;
fi
vfs="$((vfs + 1))"
echo $vf
done
test "$vfs" != 0
return $?
}
eeh_disable_vfs() {
pf_list="$(eeh_find_all_pfs)"
if [ -z "$pf_list" ] ; then
return 1;
fi
for dev in $pf_list ; do
echo 0 > "/sys/bus/pci/devices/$dev/sriov_numvfs"
done
return 0;
}
@@ -0,0 +1,45 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
. ./eeh-functions.sh
eeh_test_prep # NB: may exit
vf_list="$(eeh_enable_vfs)";
if $? != 0 ; then
log "No usable VFs found. Skipping EEH unaware VF test"
exit $KSELFTESTS_SKIP;
fi
log "Enabled VFs: $vf_list"
tested=0
passed=0
for vf in $vf_list ; do
log "Testing $vf"
if ! eeh_can_recover $vf ; then
log "Driver for $vf doesn't support error recovery, skipping"
continue;
fi
tested="$((tested + 1))"
log "Breaking $vf..."
if ! eeh_one_dev $vf ; then
log "$vf failed to recover"
continue;
fi
passed="$((passed + 1))"
done
eeh_disable_vfs
if [ "$tested" == 0 ] ; then
echo "No VFs with EEH aware drivers found, skipping"
exit $KSELFTESTS_SKIP
fi
test "$failed" != 0
exit $?;
@@ -0,0 +1,35 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
. ./eeh-functions.sh
eeh_test_prep # NB: may exit
vf_list="$(eeh_enable_vfs)";
if $? != 0 ; then
log "No usable VFs found. Skipping EEH unaware VF test"
exit $KSELFTESTS_SKIP;
fi
log "Enabled VFs: $vf_list"
failed=0
for vf in $vf_list ; do
log "Testing $vf"
if eeh_can_recover $vf ; then
log "Driver for $vf supports error recovery. Unbinding..."
echo "$vf" > /sys/bus/pci/devices/$vf/driver/unbind
fi
log "Breaking $vf..."
if ! eeh_one_dev $vf ; then
log "$vf failed to recover"
failed="$((failed + 1))"
fi
done
eeh_disable_vfs
test "$failed" != 0
exit $?;
@@ -0,0 +1 @@
timeout=300
@@ -0,0 +1,9 @@
#This checks for any ENV variables and add those.
ifeq ($(GIT_VERSION),)
GIT_VERSION := $(shell git describe --always --long --dirty || echo "unknown")
export GIT_VERSION
endif
CFLAGS := -std=gnu99 -O2 -Wall -Werror -DGIT_VERSION='"$(GIT_VERSION)"' -I$(selfdir)/powerpc/include $(USERCFLAGS)
export CFLAGS
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2013, Michael Ellerman, IBM Corp.
*/
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <elf.h>
#include <fcntl.h>
#include <link.h>
#include <sys/stat.h>
#include "subunit.h"
#include "utils.h"
#define KILL_TIMEOUT 5
/* Setting timeout to -1 disables the alarm */
static uint64_t timeout = 120;
int run_test(int (test_function)(void), const char *name)
{
bool terminated;
int rc, status;
pid_t pid;
/* Make sure output is flushed before forking */
fflush(stdout);
pid = fork();
if (pid == 0) {
setpgid(0, 0);
exit(test_function());
} else if (pid == -1) {
perror("fork");
return 1;
}
setpgid(pid, pid);
if (timeout != -1)
/* Wake us up in timeout seconds */
alarm(timeout);
terminated = false;
wait:
rc = waitpid(pid, &status, 0);
if (rc == -1) {
if (errno != EINTR) {
printf("unknown error from waitpid\n");
return 1;
}
if (terminated) {
printf("!! force killing %s\n", name);
kill(-pid, SIGKILL);
return 1;
} else {
printf("!! killing %s\n", name);
kill(-pid, SIGTERM);
terminated = true;
alarm(KILL_TIMEOUT);
goto wait;
}
}
/* Kill anything else in the process group that is still running */
kill(-pid, SIGTERM);
if (WIFEXITED(status))
status = WEXITSTATUS(status);
else {
if (WIFSIGNALED(status))
printf("!! child died by signal %d\n", WTERMSIG(status));
else
printf("!! child died by unknown cause\n");
status = 1; /* Signal or other */
}
return status;
}
static void sig_handler(int signum)
{
/* Just wake us up from waitpid */
}
static struct sigaction sig_action = {
.sa_handler = sig_handler,
};
void test_harness_set_timeout(uint64_t time)
{
timeout = time;
}
int test_harness(int (test_function)(void), const char *name)
{
int rc;
test_start(name);
test_set_git_version(GIT_VERSION);
if (sigaction(SIGINT, &sig_action, NULL)) {
perror("sigaction (sigint)");
test_error(name);
return 1;
}
if (sigaction(SIGALRM, &sig_action, NULL)) {
perror("sigaction (sigalrm)");
test_error(name);
return 1;
}
rc = run_test(test_function, name);
if (rc == MAGIC_SKIP_RETURN_VALUE) {
test_skip(name);
/* so that skipped test is not marked as failed */
rc = 0;
} else
test_finish(name, rc);
return rc;
}
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _SELFTESTS_POWERPC_BASIC_ASM_H
#define _SELFTESTS_POWERPC_BASIC_ASM_H
#include <ppc-asm.h>
#include <asm/unistd.h>
#ifdef __powerpc64__
#define PPC_LL ld
#define PPC_STL std
#define PPC_STLU stdu
#else
#define PPC_LL lwz
#define PPC_STL stw
#define PPC_STLU stwu
#endif
#define LOAD_REG_IMMEDIATE(reg, expr) \
lis reg, (expr)@highest; \
ori reg, reg, (expr)@higher; \
rldicr reg, reg, 32, 31; \
oris reg, reg, (expr)@high; \
ori reg, reg, (expr)@l;
/*
* Note: These macros assume that variables being stored on the stack are
* sizeof(long), while this is usually the case it may not always be the
* case for each use case.
*/
#ifdef __powerpc64__
// ABIv2
#if defined(_CALL_ELF) && _CALL_ELF == 2
#define STACK_FRAME_MIN_SIZE 32
#define STACK_FRAME_TOC_POS 24
#define __STACK_FRAME_PARAM(_param) (32 + ((_param)*8))
#define __STACK_FRAME_LOCAL(_num_params, _var_num) \
((STACK_FRAME_PARAM(_num_params)) + ((_var_num)*8))
#else // ABIv1 below
#define STACK_FRAME_MIN_SIZE 112
#define STACK_FRAME_TOC_POS 40
#define __STACK_FRAME_PARAM(i) (48 + ((i)*8))
/*
* Caveat: if a function passed more than 8 doublewords, the caller will have
* made more space... which would render the 112 incorrect.
*/
#define __STACK_FRAME_LOCAL(_num_params, _var_num) \
(112 + ((_var_num)*8))
#endif // ABIv2
// Common 64-bit
#define STACK_FRAME_LR_POS 16
#define STACK_FRAME_CR_POS 8
#else // 32-bit below
#define STACK_FRAME_MIN_SIZE 16
#define STACK_FRAME_LR_POS 4
#define __STACK_FRAME_PARAM(_param) (STACK_FRAME_MIN_SIZE + ((_param)*4))
#define __STACK_FRAME_LOCAL(_num_params, _var_num) \
((STACK_FRAME_PARAM(_num_params)) + ((_var_num)*4))
#endif // __powerpc64__
/* Parameter x saved to the stack */
#define STACK_FRAME_PARAM(var) __STACK_FRAME_PARAM(var)
/* Local variable x saved to the stack after x parameters */
#define STACK_FRAME_LOCAL(num_params, var) \
__STACK_FRAME_LOCAL(num_params, var)
/*
* It is very important to note here that _extra is the extra amount of
* stack space needed. This space can be accessed using STACK_FRAME_PARAM()
* or STACK_FRAME_LOCAL() macros.
*
* r1 and r2 are not defined in ppc-asm.h (instead they are defined as sp
* and toc). Kernel programmers tend to prefer rX even for r1 and r2, hence
* %1 and %r2. r0 is defined in ppc-asm.h and therefore %r0 gets
* preprocessed incorrectly, hence r0.
*/
#define PUSH_BASIC_STACK(_extra) \
mflr r0; \
PPC_STL r0, STACK_FRAME_LR_POS(%r1); \
PPC_STLU %r1, -(((_extra + 15) & ~15) + STACK_FRAME_MIN_SIZE)(%r1);
#define POP_BASIC_STACK(_extra) \
addi %r1, %r1, (((_extra + 15) & ~15) + STACK_FRAME_MIN_SIZE); \
PPC_LL r0, STACK_FRAME_LR_POS(%r1); \
mtlr r0;
.macro OP_REGS op, reg_width, start_reg, end_reg, base_reg, base_reg_offset=0, skip=0
.set i, \start_reg
.rept (\end_reg - \start_reg + 1)
\op i, (\reg_width * (i - \skip) + \base_reg_offset)(\base_reg)
.set i, i + 1
.endr
.endm
#endif /* _SELFTESTS_POWERPC_BASIC_ASM_H */
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2016, Cyril Bur, IBM Corp.
*/
#ifndef _SELFTESTS_POWERPC_FPU_ASM_H
#define _SELFTESTS_POWERPC_FPU_ASM_H
#include "basic_asm.h"
#define PUSH_FPU(stack_size) \
stfd f31,(stack_size + STACK_FRAME_MIN_SIZE)(%r1); \
stfd f30,(stack_size + STACK_FRAME_MIN_SIZE - 8)(%r1); \
stfd f29,(stack_size + STACK_FRAME_MIN_SIZE - 16)(%r1); \
stfd f28,(stack_size + STACK_FRAME_MIN_SIZE - 24)(%r1); \
stfd f27,(stack_size + STACK_FRAME_MIN_SIZE - 32)(%r1); \
stfd f26,(stack_size + STACK_FRAME_MIN_SIZE - 40)(%r1); \
stfd f25,(stack_size + STACK_FRAME_MIN_SIZE - 48)(%r1); \
stfd f24,(stack_size + STACK_FRAME_MIN_SIZE - 56)(%r1); \
stfd f23,(stack_size + STACK_FRAME_MIN_SIZE - 64)(%r1); \
stfd f22,(stack_size + STACK_FRAME_MIN_SIZE - 72)(%r1); \
stfd f21,(stack_size + STACK_FRAME_MIN_SIZE - 80)(%r1); \
stfd f20,(stack_size + STACK_FRAME_MIN_SIZE - 88)(%r1); \
stfd f19,(stack_size + STACK_FRAME_MIN_SIZE - 96)(%r1); \
stfd f18,(stack_size + STACK_FRAME_MIN_SIZE - 104)(%r1); \
stfd f17,(stack_size + STACK_FRAME_MIN_SIZE - 112)(%r1); \
stfd f16,(stack_size + STACK_FRAME_MIN_SIZE - 120)(%r1); \
stfd f15,(stack_size + STACK_FRAME_MIN_SIZE - 128)(%r1); \
stfd f14,(stack_size + STACK_FRAME_MIN_SIZE - 136)(%r1);
#define POP_FPU(stack_size) \
lfd f31,(stack_size + STACK_FRAME_MIN_SIZE)(%r1); \
lfd f30,(stack_size + STACK_FRAME_MIN_SIZE - 8)(%r1); \
lfd f29,(stack_size + STACK_FRAME_MIN_SIZE - 16)(%r1); \
lfd f28,(stack_size + STACK_FRAME_MIN_SIZE - 24)(%r1); \
lfd f27,(stack_size + STACK_FRAME_MIN_SIZE - 32)(%r1); \
lfd f26,(stack_size + STACK_FRAME_MIN_SIZE - 40)(%r1); \
lfd f25,(stack_size + STACK_FRAME_MIN_SIZE - 48)(%r1); \
lfd f24,(stack_size + STACK_FRAME_MIN_SIZE - 56)(%r1); \
lfd f23,(stack_size + STACK_FRAME_MIN_SIZE - 64)(%r1); \
lfd f22,(stack_size + STACK_FRAME_MIN_SIZE - 72)(%r1); \
lfd f21,(stack_size + STACK_FRAME_MIN_SIZE - 80)(%r1); \
lfd f20,(stack_size + STACK_FRAME_MIN_SIZE - 88)(%r1); \
lfd f19,(stack_size + STACK_FRAME_MIN_SIZE - 96)(%r1); \
lfd f18,(stack_size + STACK_FRAME_MIN_SIZE - 104)(%r1); \
lfd f17,(stack_size + STACK_FRAME_MIN_SIZE - 112)(%r1); \
lfd f16,(stack_size + STACK_FRAME_MIN_SIZE - 120)(%r1); \
lfd f15,(stack_size + STACK_FRAME_MIN_SIZE - 128)(%r1); \
lfd f14,(stack_size + STACK_FRAME_MIN_SIZE - 136)(%r1);
/*
* Careful calling this, it will 'clobber' fpu (by design)
* Don't call this from C
*/
FUNC_START(load_fpu)
lfd f14,0(r3)
lfd f15,8(r3)
lfd f16,16(r3)
lfd f17,24(r3)
lfd f18,32(r3)
lfd f19,40(r3)
lfd f20,48(r3)
lfd f21,56(r3)
lfd f22,64(r3)
lfd f23,72(r3)
lfd f24,80(r3)
lfd f25,88(r3)
lfd f26,96(r3)
lfd f27,104(r3)
lfd f28,112(r3)
lfd f29,120(r3)
lfd f30,128(r3)
lfd f31,136(r3)
blr
FUNC_END(load_fpu)
#endif /* _SELFTESTS_POWERPC_FPU_ASM_H */
@@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2016, Cyril Bur, IBM Corp.
*/
#ifndef _SELFTESTS_POWERPC_GPR_ASM_H
#define _SELFTESTS_POWERPC_GPR_ASM_H
#include "basic_asm.h"
#define __PUSH_NVREGS(top_pos); \
std r31,(top_pos)(%r1); \
std r30,(top_pos - 8)(%r1); \
std r29,(top_pos - 16)(%r1); \
std r28,(top_pos - 24)(%r1); \
std r27,(top_pos - 32)(%r1); \
std r26,(top_pos - 40)(%r1); \
std r25,(top_pos - 48)(%r1); \
std r24,(top_pos - 56)(%r1); \
std r23,(top_pos - 64)(%r1); \
std r22,(top_pos - 72)(%r1); \
std r21,(top_pos - 80)(%r1); \
std r20,(top_pos - 88)(%r1); \
std r19,(top_pos - 96)(%r1); \
std r18,(top_pos - 104)(%r1); \
std r17,(top_pos - 112)(%r1); \
std r16,(top_pos - 120)(%r1); \
std r15,(top_pos - 128)(%r1); \
std r14,(top_pos - 136)(%r1)
#define __POP_NVREGS(top_pos); \
ld r31,(top_pos)(%r1); \
ld r30,(top_pos - 8)(%r1); \
ld r29,(top_pos - 16)(%r1); \
ld r28,(top_pos - 24)(%r1); \
ld r27,(top_pos - 32)(%r1); \
ld r26,(top_pos - 40)(%r1); \
ld r25,(top_pos - 48)(%r1); \
ld r24,(top_pos - 56)(%r1); \
ld r23,(top_pos - 64)(%r1); \
ld r22,(top_pos - 72)(%r1); \
ld r21,(top_pos - 80)(%r1); \
ld r20,(top_pos - 88)(%r1); \
ld r19,(top_pos - 96)(%r1); \
ld r18,(top_pos - 104)(%r1); \
ld r17,(top_pos - 112)(%r1); \
ld r16,(top_pos - 120)(%r1); \
ld r15,(top_pos - 128)(%r1); \
ld r14,(top_pos - 136)(%r1)
#define PUSH_NVREGS(stack_size) \
__PUSH_NVREGS(stack_size + STACK_FRAME_MIN_SIZE)
/* 18 NV FPU REGS */
#define PUSH_NVREGS_BELOW_FPU(stack_size) \
__PUSH_NVREGS(stack_size + STACK_FRAME_MIN_SIZE - (18 * 8))
#define POP_NVREGS(stack_size) \
__POP_NVREGS(stack_size + STACK_FRAME_MIN_SIZE)
/* 18 NV FPU REGS */
#define POP_NVREGS_BELOW_FPU(stack_size) \
__POP_NVREGS(stack_size + STACK_FRAME_MIN_SIZE - (18 * 8))
/*
* Careful calling this, it will 'clobber' NVGPRs (by design)
* Don't call this from C
*/
FUNC_START(load_gpr)
ld r14,0(r3)
ld r15,8(r3)
ld r16,16(r3)
ld r17,24(r3)
ld r18,32(r3)
ld r19,40(r3)
ld r20,48(r3)
ld r21,56(r3)
ld r22,64(r3)
ld r23,72(r3)
ld r24,80(r3)
ld r25,88(r3)
ld r26,96(r3)
ld r27,104(r3)
ld r28,112(r3)
ld r29,120(r3)
ld r30,128(r3)
ld r31,136(r3)
blr
FUNC_END(load_gpr)
#endif /* _SELFTESTS_POWERPC_GPR_ASM_H */
@@ -0,0 +1,146 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _SELFTESTS_POWERPC_INSTRUCTIONS_H
#define _SELFTESTS_POWERPC_INSTRUCTIONS_H
#include <stdio.h>
#include <stdlib.h>
/* This defines the "copy" instruction from Power ISA 3.0 Book II, section 4.4. */
#define __COPY(RA, RB, L) \
(0x7c00060c | (RA) << (31-15) | (RB) << (31-20) | (L) << (31-10))
#define COPY(RA, RB, L) \
.long __COPY((RA), (RB), (L))
static inline void copy(void *i)
{
asm volatile(str(COPY(0, %0, 0))";"
:
: "b" (i)
: "memory"
);
}
static inline void copy_first(void *i)
{
asm volatile(str(COPY(0, %0, 1))";"
:
: "b" (i)
: "memory"
);
}
/* This defines the "paste" instruction from Power ISA 3.0 Book II, section 4.4. */
#define __PASTE(RA, RB, L, RC) \
(0x7c00070c | (RA) << (31-15) | (RB) << (31-20) | (L) << (31-10) | (RC) << (31-31))
#define PASTE(RA, RB, L, RC) \
.long __PASTE((RA), (RB), (L), (RC))
static inline int paste(void *i)
{
int cr;
asm volatile(str(PASTE(0, %1, 0, 0))";"
"mfcr %0;"
: "=r" (cr)
: "b" (i)
: "memory"
);
return cr;
}
static inline int paste_last(void *i)
{
int cr;
asm volatile(str(PASTE(0, %1, 1, 1))";"
"mfcr %0;"
: "=r" (cr)
: "b" (i)
: "memory"
);
return cr;
}
#define PPC_INST_COPY __COPY(0, 0, 0)
#define PPC_INST_COPY_FIRST __COPY(0, 0, 1)
#define PPC_INST_PASTE __PASTE(0, 0, 0, 0)
#define PPC_INST_PASTE_LAST __PASTE(0, 0, 1, 1)
/* This defines the prefixed load/store instructions */
#ifdef __ASSEMBLER__
# define stringify_in_c(...) __VA_ARGS__
#else
# define __stringify_in_c(...) #__VA_ARGS__
# define stringify_in_c(...) __stringify_in_c(__VA_ARGS__) " "
#endif
#define __PPC_RA(a) (((a) & 0x1f) << 16)
#define __PPC_RS(s) (((s) & 0x1f) << 21)
#define __PPC_RT(t) __PPC_RS(t)
#define __PPC_PREFIX_R(r) (((r) & 0x1) << 20)
#define PPC_PREFIX_MLS 0x06000000
#define PPC_PREFIX_8LS 0x04000000
#define PPC_INST_LBZ 0x88000000
#define PPC_INST_LHZ 0xa0000000
#define PPC_INST_LHA 0xa8000000
#define PPC_INST_LWZ 0x80000000
#define PPC_INST_STB 0x98000000
#define PPC_INST_STH 0xb0000000
#define PPC_INST_STW 0x90000000
#define PPC_INST_STD 0xf8000000
#define PPC_INST_LFS 0xc0000000
#define PPC_INST_LFD 0xc8000000
#define PPC_INST_STFS 0xd0000000
#define PPC_INST_STFD 0xd8000000
#define PREFIX_MLS(instr, t, a, r, d) stringify_in_c(.balign 64, , 4;) \
stringify_in_c(.long PPC_PREFIX_MLS | \
__PPC_PREFIX_R(r) | \
(((d) >> 16) & 0x3ffff);) \
stringify_in_c(.long (instr) | \
__PPC_RT(t) | \
__PPC_RA(a) | \
((d) & 0xffff);\n)
#define PREFIX_8LS(instr, t, a, r, d) stringify_in_c(.balign 64, , 4;) \
stringify_in_c(.long PPC_PREFIX_8LS | \
__PPC_PREFIX_R(r) | \
(((d) >> 16) & 0x3ffff);) \
stringify_in_c(.long (instr) | \
__PPC_RT(t) | \
__PPC_RA(a) | \
((d) & 0xffff);\n)
/* Prefixed Integer Load/Store instructions */
#define PLBZ(t, a, r, d) PREFIX_MLS(PPC_INST_LBZ, t, a, r, d)
#define PLHZ(t, a, r, d) PREFIX_MLS(PPC_INST_LHZ, t, a, r, d)
#define PLHA(t, a, r, d) PREFIX_MLS(PPC_INST_LHA, t, a, r, d)
#define PLWZ(t, a, r, d) PREFIX_MLS(PPC_INST_LWZ, t, a, r, d)
#define PLWA(t, a, r, d) PREFIX_8LS(0xa4000000, t, a, r, d)
#define PLD(t, a, r, d) PREFIX_8LS(0xe4000000, t, a, r, d)
#define PLQ(t, a, r, d) PREFIX_8LS(0xe0000000, t, a, r, d)
#define PSTB(s, a, r, d) PREFIX_MLS(PPC_INST_STB, s, a, r, d)
#define PSTH(s, a, r, d) PREFIX_MLS(PPC_INST_STH, s, a, r, d)
#define PSTW(s, a, r, d) PREFIX_MLS(PPC_INST_STW, s, a, r, d)
#define PSTD(s, a, r, d) PREFIX_8LS(0xf4000000, s, a, r, d)
#define PSTQ(s, a, r, d) PREFIX_8LS(0xf0000000, s, a, r, d)
/* Prefixed Floating-Point Load/Store Instructions */
#define PLFS(frt, a, r, d) PREFIX_MLS(PPC_INST_LFS, frt, a, r, d)
#define PLFD(frt, a, r, d) PREFIX_MLS(PPC_INST_LFD, frt, a, r, d)
#define PSTFS(frs, a, r, d) PREFIX_MLS(PPC_INST_STFS, frs, a, r, d)
#define PSTFD(frs, a, r, d) PREFIX_MLS(PPC_INST_STFD, frs, a, r, d)
/* Prefixed VSX Load/Store Instructions */
#define PLXSD(vrt, a, r, d) PREFIX_8LS(0xa8000000, vrt, a, r, d)
#define PLXSSP(vrt, a, r, d) PREFIX_8LS(0xac000000, vrt, a, r, d)
#define PLXV0(s, a, r, d) PREFIX_8LS(0xc8000000, s, a, r, d)
#define PLXV1(s, a, r, d) PREFIX_8LS(0xcc000000, s, a, r, d)
#define PSTXSD(vrs, a, r, d) PREFIX_8LS(0xb8000000, vrs, a, r, d)
#define PSTXSSP(vrs, a, r, d) PREFIX_8LS(0xbc000000, vrs, a, r, d)
#define PSTXV0(s, a, r, d) PREFIX_8LS(0xd8000000, s, a, r, d)
#define PSTXV1(s, a, r, d) PREFIX_8LS(0xdc000000, s, a, r, d)
#endif /* _SELFTESTS_POWERPC_INSTRUCTIONS_H */
@@ -0,0 +1,147 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020, Sandipan Das, IBM Corp.
*/
#ifndef _SELFTESTS_POWERPC_PKEYS_H
#define _SELFTESTS_POWERPC_PKEYS_H
#include <sys/mman.h>
#include "reg.h"
#include "utils.h"
/*
* Older versions of libc use the Intel-specific access rights.
* Hence, override the definitions as they might be incorrect.
*/
#undef PKEY_DISABLE_ACCESS
#define PKEY_DISABLE_ACCESS 0x3
#undef PKEY_DISABLE_WRITE
#define PKEY_DISABLE_WRITE 0x2
#undef PKEY_DISABLE_EXECUTE
#define PKEY_DISABLE_EXECUTE 0x4
#undef PKEY_UNRESTRICTED
#define PKEY_UNRESTRICTED 0x0
/* Older versions of libc do not define this */
#ifndef SEGV_PKUERR
#define SEGV_PKUERR 4
#endif
#define SI_PKEY_OFFSET 0x20
#define __NR_pkey_mprotect 386
#define __NR_pkey_alloc 384
#define __NR_pkey_free 385
#ifndef NT_PPC_PKEY
#define NT_PPC_PKEY 0x110
#endif
#define PKEY_BITS_PER_PKEY 2
#define NR_PKEYS 32
#define PKEY_BITS_MASK ((1UL << PKEY_BITS_PER_PKEY) - 1)
#define AMR_BITS_PER_PKEY 2
#define PKEY_REG_BITS (sizeof(u64) * 8)
#define pkeyshift(pkey) (PKEY_REG_BITS - ((pkey + 1) * AMR_BITS_PER_PKEY))
inline unsigned long pkeyreg_get(void)
{
return mfspr(SPRN_AMR);
}
inline void pkeyreg_set(unsigned long amr)
{
set_amr(amr);
}
void pkey_set_rights(int pkey, unsigned long rights)
{
unsigned long amr, shift;
shift = (NR_PKEYS - pkey - 1) * PKEY_BITS_PER_PKEY;
amr = pkeyreg_get();
amr &= ~(PKEY_BITS_MASK << shift);
amr |= (rights & PKEY_BITS_MASK) << shift;
pkeyreg_set(amr);
}
int sys_pkey_mprotect(void *addr, size_t len, int prot, int pkey)
{
return syscall(__NR_pkey_mprotect, addr, len, prot, pkey);
}
int sys_pkey_alloc(unsigned long flags, unsigned long rights)
{
return syscall(__NR_pkey_alloc, flags, rights);
}
int sys_pkey_free(int pkey)
{
return syscall(__NR_pkey_free, pkey);
}
int pkeys_unsupported(void)
{
bool hash_mmu = false;
int pkey;
/* Protection keys are currently supported on Hash MMU only */
FAIL_IF(using_hash_mmu(&hash_mmu));
SKIP_IF(!hash_mmu);
/* Check if the system call is supported */
pkey = sys_pkey_alloc(0, PKEY_UNRESTRICTED);
SKIP_IF(pkey < 0);
sys_pkey_free(pkey);
return 0;
}
int siginfo_pkey(siginfo_t *si)
{
/*
* In older versions of libc, siginfo_t does not have si_pkey as
* a member.
*/
#ifdef si_pkey
return si->si_pkey;
#else
return *((int *)(((char *) si) + SI_PKEY_OFFSET));
#endif
}
#define pkey_rights(r) ({ \
static char buf[4] = "rwx"; \
unsigned int amr_bits; \
if ((r) & PKEY_DISABLE_EXECUTE) \
buf[2] = '-'; \
amr_bits = (r) & PKEY_BITS_MASK; \
if (amr_bits & PKEY_DISABLE_WRITE) \
buf[1] = '-'; \
if (amr_bits & PKEY_DISABLE_ACCESS & ~PKEY_DISABLE_WRITE) \
buf[0] = '-'; \
buf; \
})
unsigned long next_pkey_rights(unsigned long rights)
{
if (rights == PKEY_DISABLE_ACCESS)
return PKEY_DISABLE_EXECUTE;
else if (rights == (PKEY_DISABLE_ACCESS | PKEY_DISABLE_EXECUTE))
return 0;
if ((rights & PKEY_BITS_MASK) == 0)
rights |= PKEY_DISABLE_WRITE;
else if ((rights & PKEY_BITS_MASK) == PKEY_DISABLE_WRITE)
rights |= PKEY_DISABLE_ACCESS;
return rights;
}
#endif /* _SELFTESTS_POWERPC_PKEYS_H */
@@ -0,0 +1,174 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2014, Michael Ellerman, IBM Corp.
*/
#ifndef _SELFTESTS_POWERPC_REG_H
#define _SELFTESTS_POWERPC_REG_H
#define __stringify_1(x) #x
#define __stringify(x) __stringify_1(x)
#define mfspr(rn) ({unsigned long rval; \
asm volatile("mfspr %0," _str(rn) \
: "=r" (rval)); rval; })
#define mtspr(rn, v) asm volatile("mtspr " _str(rn) ",%0" : \
: "r" ((unsigned long)(v)) \
: "memory")
#define mb() asm volatile("sync" : : : "memory");
#define barrier() asm volatile("" : : : "memory");
#define SPRN_HDEXCR_RO 455 /* Userspace readonly view of SPRN_HDEXCR (471) */
#define SPRN_MMCR2 769
#define SPRN_MMCRA 770
#define SPRN_MMCR0 779
#define MMCR0_PMAO 0x00000080
#define MMCR0_PMAE 0x04000000
#define MMCR0_FC 0x80000000
#define SPRN_EBBHR 804
#define SPRN_EBBRR 805
#define SPRN_BESCR 806 /* Branch event status & control register */
#define SPRN_BESCRS 800 /* Branch event status & control set (1 bits set to 1) */
#define SPRN_BESCRSU 801 /* Branch event status & control set upper */
#define SPRN_BESCRR 802 /* Branch event status & control REset (1 bits set to 0) */
#define SPRN_BESCRRU 803 /* Branch event status & control REset upper */
#define BESCR_PMEO 0x1 /* PMU Event-based exception Occurred */
#define BESCR_PME (0x1ul << 32) /* PMU Event-based exception Enable */
#define SPRN_PMC1 771
#define SPRN_PMC2 772
#define SPRN_PMC3 773
#define SPRN_PMC4 774
#define SPRN_PMC5 775
#define SPRN_PMC6 776
#define SPRN_SIAR 780
#define SPRN_SDAR 781
#define SPRN_SIER 768
#define SPRN_DEXCR_RO 812 /* Userspace readonly view of SPRN_DEXCR (828) */
#define SPRN_TEXASR 0x82 /* Transaction Exception and Status Register */
#define SPRN_TFIAR 0x81 /* Transaction Failure Inst Addr */
#define SPRN_TFHAR 0x80 /* Transaction Failure Handler Addr */
#define SPRN_TAR 0x32f /* Target Address Register */
#define PVR_VER(pvr) (((pvr) >> 16) & 0xFFFF)
#define SPRN_PVR 0x11F
#define PVR_CFG(pvr) (((pvr) >> 8) & 0xF) /* Configuration field */
#define PVR_MAJ(pvr) (((pvr) >> 4) & 0xF) /* Major revision field */
#define PVR_MIN(pvr) (((pvr) >> 0) & 0xF) /* Minor revision field */
#define SPRN_DSCR_PRIV 0x11 /* Privilege State DSCR */
#define SPRN_DSCR 0x03 /* Data Stream Control Register */
#define SPRN_PPR 896 /* Program Priority Register */
#define SPRN_AMR 13 /* Authority Mask Register - problem state */
#define set_amr(v) asm volatile("isync;" \
"mtspr " __stringify(SPRN_AMR) ",%0;" \
"isync" : \
: "r" ((unsigned long)(v)) \
: "memory")
/* TEXASR register bits */
#define TEXASR_FC 0xFE00000000000000
#define TEXASR_FP 0x0100000000000000
#define TEXASR_DA 0x0080000000000000
#define TEXASR_NO 0x0040000000000000
#define TEXASR_FO 0x0020000000000000
#define TEXASR_SIC 0x0010000000000000
#define TEXASR_NTC 0x0008000000000000
#define TEXASR_TC 0x0004000000000000
#define TEXASR_TIC 0x0002000000000000
#define TEXASR_IC 0x0001000000000000
#define TEXASR_IFC 0x0000800000000000
#define TEXASR_ABT 0x0000000100000000
#define TEXASR_SPD 0x0000000080000000
#define TEXASR_HV 0x0000000020000000
#define TEXASR_PR 0x0000000010000000
#define TEXASR_FS 0x0000000008000000
#define TEXASR_TE 0x0000000004000000
#define TEXASR_ROT 0x0000000002000000
/* MSR register bits */
#define MSR_HV (1ul << 60) /* Hypervisor state */
#define MSR_TS_S_LG 33 /* Trans Mem state: Suspended */
#define MSR_TS_T_LG 34 /* Trans Mem state: Active */
#define __MASK(X) (1UL<<(X))
/* macro to check TM MSR bits */
#define MSR_TS_S __MASK(MSR_TS_S_LG) /* Transaction Suspended */
#define MSR_TS_T __MASK(MSR_TS_T_LG) /* Transaction Transactional */
/* Vector Instructions */
#define VSX_XX1(xs, ra, rb) (((xs) & 0x1f) << 21 | ((ra) << 16) | \
((rb) << 11) | (((xs) >> 5)))
#define STXVD2X(xs, ra, rb) .long (0x7c000798 | VSX_XX1((xs), (ra), (rb)))
#define LXVD2X(xs, ra, rb) .long (0x7c000698 | VSX_XX1((xs), (ra), (rb)))
#define ASM_LOAD_GPR_IMMED(_asm_symbol_name_immed) \
"li 14, %[" #_asm_symbol_name_immed "];" \
"li 15, %[" #_asm_symbol_name_immed "];" \
"li 16, %[" #_asm_symbol_name_immed "];" \
"li 17, %[" #_asm_symbol_name_immed "];" \
"li 18, %[" #_asm_symbol_name_immed "];" \
"li 19, %[" #_asm_symbol_name_immed "];" \
"li 20, %[" #_asm_symbol_name_immed "];" \
"li 21, %[" #_asm_symbol_name_immed "];" \
"li 22, %[" #_asm_symbol_name_immed "];" \
"li 23, %[" #_asm_symbol_name_immed "];" \
"li 24, %[" #_asm_symbol_name_immed "];" \
"li 25, %[" #_asm_symbol_name_immed "];" \
"li 26, %[" #_asm_symbol_name_immed "];" \
"li 27, %[" #_asm_symbol_name_immed "];" \
"li 28, %[" #_asm_symbol_name_immed "];" \
"li 29, %[" #_asm_symbol_name_immed "];" \
"li 30, %[" #_asm_symbol_name_immed "];" \
"li 31, %[" #_asm_symbol_name_immed "];"
#define ASM_LOAD_FPR(_asm_symbol_name_addr) \
"lfd 0, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 1, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 2, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 3, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 4, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 5, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 6, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 7, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 8, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 9, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 10, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 11, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 12, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 13, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 14, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 15, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 16, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 17, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 18, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 19, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 20, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 21, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 22, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 23, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 24, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 25, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 26, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 27, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 28, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 29, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 30, 0(%[" #_asm_symbol_name_addr "]);" \
"lfd 31, 0(%[" #_asm_symbol_name_addr "]);"
#ifndef __ASSEMBLER__
void store_gpr(unsigned long *addr);
void load_gpr(unsigned long *addr);
void store_fpr(double *addr);
#endif /* end of __ASSEMBLER__ */
#endif /* _SELFTESTS_POWERPC_REG_H */
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2013, Michael Ellerman, IBM Corp.
*/
#ifndef _SELFTESTS_POWERPC_SUBUNIT_H
#define _SELFTESTS_POWERPC_SUBUNIT_H
static inline void test_start(const char *name)
{
printf("test: %s\n", name);
}
static inline void test_failure_detail(const char *name, const char *detail)
{
printf("failure: %s [%s]\n", name, detail);
}
static inline void test_failure(const char *name)
{
printf("failure: %s\n", name);
}
static inline void test_error(const char *name)
{
printf("error: %s\n", name);
}
static inline void test_skip(const char *name)
{
printf("skip: %s\n", name);
}
static inline void test_success(const char *name)
{
printf("success: %s\n", name);
}
static inline void test_finish(const char *name, int status)
{
if (status)
test_failure(name);
else
test_success(name);
}
static inline void test_set_git_version(const char *value)
{
printf("tags: git_version:%s\n", value);
}
#endif /* _SELFTESTS_POWERPC_SUBUNIT_H */
@@ -0,0 +1,211 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2013, Michael Ellerman, IBM Corp.
*/
#ifndef _SELFTESTS_POWERPC_UTILS_H
#define _SELFTESTS_POWERPC_UTILS_H
#define __cacheline_aligned __attribute__((aligned(128)))
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/signal.h>
#include <linux/auxvec.h>
#include <linux/perf_event.h>
#include <asm/cputable.h>
#include "reg.h"
#include <unistd.h>
#ifndef ARRAY_SIZE
# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
/* Avoid headaches with PRI?64 - just use %ll? always */
typedef unsigned long long u64;
typedef signed long long s64;
/* Just for familiarity */
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
void test_harness_set_timeout(uint64_t time);
int test_harness(int (test_function)(void), const char *name);
int read_auxv(char *buf, ssize_t buf_size);
void *find_auxv_entry(int type, char *auxv);
void *get_auxv_entry(int type);
#define BIND_CPU_ANY (-1)
int pick_online_cpu(void);
int bind_to_cpu(int cpu);
int parse_intmax(const char *buffer, size_t count, intmax_t *result, int base);
int parse_uintmax(const char *buffer, size_t count, uintmax_t *result, int base);
int parse_int(const char *buffer, size_t count, int *result, int base);
int parse_uint(const char *buffer, size_t count, unsigned int *result, int base);
int parse_long(const char *buffer, size_t count, long *result, int base);
int parse_ulong(const char *buffer, size_t count, unsigned long *result, int base);
int read_file(const char *path, char *buf, size_t count, size_t *len);
int write_file(const char *path, const char *buf, size_t count);
int read_file_alloc(const char *path, char **buf, size_t *len);
int read_long(const char *path, long *result, int base);
int write_long(const char *path, long result, int base);
int read_ulong(const char *path, unsigned long *result, int base);
int write_ulong(const char *path, unsigned long result, int base);
int read_debugfs_file(const char *debugfs_file, char *buf, size_t count);
int write_debugfs_file(const char *debugfs_file, const char *buf, size_t count);
int read_debugfs_int(const char *debugfs_file, int *result);
int write_debugfs_int(const char *debugfs_file, int result);
int read_sysfs_file(char *debugfs_file, char *result, size_t result_size);
int perf_event_open_counter(unsigned int type,
unsigned long config, int group_fd);
int perf_event_enable(int fd);
int perf_event_disable(int fd);
int perf_event_reset(int fd);
struct perf_event_read {
__u64 nr;
__u64 l1d_misses;
};
#if !defined(__GLIBC_PREREQ) || !__GLIBC_PREREQ(2, 30)
#include <sys/syscall.h>
static inline pid_t gettid(void)
{
return syscall(SYS_gettid);
}
#endif
static inline bool have_hwcap(unsigned long ftr)
{
return ((unsigned long)get_auxv_entry(AT_HWCAP) & ftr) == ftr;
}
#ifdef AT_HWCAP2
static inline bool have_hwcap2(unsigned long ftr2)
{
return ((unsigned long)get_auxv_entry(AT_HWCAP2) & ftr2) == ftr2;
}
#else
static inline bool have_hwcap2(unsigned long ftr2)
{
return false;
}
#endif
static inline char *auxv_base_platform(void)
{
return ((char *)get_auxv_entry(AT_BASE_PLATFORM));
}
static inline char *auxv_platform(void)
{
return ((char *)get_auxv_entry(AT_PLATFORM));
}
bool is_ppc64le(void);
int using_hash_mmu(bool *using_hash);
struct sigaction push_signal_handler(int sig, void (*fn)(int, siginfo_t *, void *));
struct sigaction pop_signal_handler(int sig, struct sigaction old_handler);
/* Yes, this is evil */
#define FAIL_IF(x) \
do { \
if ((x)) { \
fprintf(stderr, \
"[FAIL] Test FAILED on line %d\n", __LINE__); \
return 1; \
} \
} while (0)
#define FAIL_IF_MSG(x, msg) \
do { \
if ((x)) { \
fprintf(stderr, \
"[FAIL] Test FAILED on line %d: %s\n", \
__LINE__, msg); \
return 1; \
} \
} while (0)
#define FAIL_IF_EXIT(x) \
do { \
if ((x)) { \
fprintf(stderr, \
"[FAIL] Test FAILED on line %d\n", __LINE__); \
_exit(1); \
} \
} while (0)
#define FAIL_IF_EXIT_MSG(x, msg) \
do { \
if ((x)) { \
fprintf(stderr, \
"[FAIL] Test FAILED on line %d: %s\n", \
__LINE__, msg); \
_exit(1); \
} \
} while (0)
/* The test harness uses this, yes it's gross */
#define MAGIC_SKIP_RETURN_VALUE 99
#define SKIP_IF(x) \
do { \
if ((x)) { \
fprintf(stderr, \
"[SKIP] Test skipped on line %d\n", __LINE__); \
return MAGIC_SKIP_RETURN_VALUE; \
} \
} while (0)
#define SKIP_IF_MSG(x, msg) \
do { \
if ((x)) { \
fprintf(stderr, \
"[SKIP] Test skipped on line %d: %s\n", \
__LINE__, msg); \
return MAGIC_SKIP_RETURN_VALUE; \
} \
} while (0)
#define _str(s) #s
#define str(s) _str(s)
#define sigsafe_err(msg) ({ \
ssize_t nbytes __attribute__((unused)); \
nbytes = write(STDERR_FILENO, msg, strlen(msg)); })
/* POWER9 feature */
#ifndef PPC_FEATURE2_ARCH_3_00
#define PPC_FEATURE2_ARCH_3_00 0x00800000
#endif
/* POWER10 feature */
#ifndef PPC_FEATURE2_ARCH_3_1
#define PPC_FEATURE2_ARCH_3_1 0x00040000
#endif
/* POWER10 features */
#ifndef PPC_FEATURE2_MMA
#define PPC_FEATURE2_MMA 0x00020000
#endif
#if defined(__powerpc64__)
#define UCONTEXT_NIA(UC) (UC)->uc_mcontext.gp_regs[PT_NIP]
#define UCONTEXT_MSR(UC) (UC)->uc_mcontext.gp_regs[PT_MSR]
#elif defined(__powerpc__)
#define UCONTEXT_NIA(UC) (UC)->uc_mcontext.uc_regs->gregs[PT_NIP]
#define UCONTEXT_MSR(UC) (UC)->uc_mcontext.uc_regs->gregs[PT_MSR]
#else
#error implement UCONTEXT_NIA
#endif
#endif /* _SELFTESTS_POWERPC_UTILS_H */
@@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*/
#include "basic_asm.h"
/* POS MUST BE 16 ALIGNED! */
#define PUSH_VMX(pos,reg) \
li reg,pos; \
stvx v20,reg,%r1; \
addi reg,reg,16; \
stvx v21,reg,%r1; \
addi reg,reg,16; \
stvx v22,reg,%r1; \
addi reg,reg,16; \
stvx v23,reg,%r1; \
addi reg,reg,16; \
stvx v24,reg,%r1; \
addi reg,reg,16; \
stvx v25,reg,%r1; \
addi reg,reg,16; \
stvx v26,reg,%r1; \
addi reg,reg,16; \
stvx v27,reg,%r1; \
addi reg,reg,16; \
stvx v28,reg,%r1; \
addi reg,reg,16; \
stvx v29,reg,%r1; \
addi reg,reg,16; \
stvx v30,reg,%r1; \
addi reg,reg,16; \
stvx v31,reg,%r1;
/* POS MUST BE 16 ALIGNED! */
#define POP_VMX(pos,reg) \
li reg,pos; \
lvx v20,reg,%r1; \
addi reg,reg,16; \
lvx v21,reg,%r1; \
addi reg,reg,16; \
lvx v22,reg,%r1; \
addi reg,reg,16; \
lvx v23,reg,%r1; \
addi reg,reg,16; \
lvx v24,reg,%r1; \
addi reg,reg,16; \
lvx v25,reg,%r1; \
addi reg,reg,16; \
lvx v26,reg,%r1; \
addi reg,reg,16; \
lvx v27,reg,%r1; \
addi reg,reg,16; \
lvx v28,reg,%r1; \
addi reg,reg,16; \
lvx v29,reg,%r1; \
addi reg,reg,16; \
lvx v30,reg,%r1; \
addi reg,reg,16; \
lvx v31,reg,%r1;
/*
* Careful this will 'clobber' vmx (by design)
* Don't call this from C
*/
FUNC_START(load_vmx)
li r5,0
lvx v20,r5,r3
addi r5,r5,16
lvx v21,r5,r3
addi r5,r5,16
lvx v22,r5,r3
addi r5,r5,16
lvx v23,r5,r3
addi r5,r5,16
lvx v24,r5,r3
addi r5,r5,16
lvx v25,r5,r3
addi r5,r5,16
lvx v26,r5,r3
addi r5,r5,16
lvx v27,r5,r3
addi r5,r5,16
lvx v28,r5,r3
addi r5,r5,16
lvx v29,r5,r3
addi r5,r5,16
lvx v30,r5,r3
addi r5,r5,16
lvx v31,r5,r3
blr
FUNC_END(load_vmx)
@@ -0,0 +1,67 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*/
#include "basic_asm.h"
/*
* Careful this will 'clobber' vsx (by design), VSX are always
* volatile though so unlike vmx this isn't so much of an issue
* Still should avoid calling from C
*/
FUNC_START(load_vsx)
li r5,0
lxvd2x vs20,r5,r3
addi r5,r5,16
lxvd2x vs21,r5,r3
addi r5,r5,16
lxvd2x vs22,r5,r3
addi r5,r5,16
lxvd2x vs23,r5,r3
addi r5,r5,16
lxvd2x vs24,r5,r3
addi r5,r5,16
lxvd2x vs25,r5,r3
addi r5,r5,16
lxvd2x vs26,r5,r3
addi r5,r5,16
lxvd2x vs27,r5,r3
addi r5,r5,16
lxvd2x vs28,r5,r3
addi r5,r5,16
lxvd2x vs29,r5,r3
addi r5,r5,16
lxvd2x vs30,r5,r3
addi r5,r5,16
lxvd2x vs31,r5,r3
blr
FUNC_END(load_vsx)
FUNC_START(store_vsx)
li r5,0
stxvd2x vs20,r5,r3
addi r5,r5,16
stxvd2x vs21,r5,r3
addi r5,r5,16
stxvd2x vs22,r5,r3
addi r5,r5,16
stxvd2x vs23,r5,r3
addi r5,r5,16
stxvd2x vs24,r5,r3
addi r5,r5,16
stxvd2x vs25,r5,r3
addi r5,r5,16
stxvd2x vs26,r5,r3
addi r5,r5,16
stxvd2x vs27,r5,r3
addi r5,r5,16
stxvd2x vs28,r5,r3
addi r5,r5,16
stxvd2x vs29,r5,r3
addi r5,r5,16
stxvd2x vs30,r5,r3
addi r5,r5,16
stxvd2x vs31,r5,r3
blr
FUNC_END(store_vsx)
@@ -0,0 +1,356 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* test helper assembly functions
*
* Copyright (C) 2016 Simon Guo, IBM Corporation.
*/
#include <ppc-asm.h>
#include "reg.h"
/* Non volatile GPR - unsigned long buf[18] */
FUNC_START(load_gpr)
ld 14, 0*8(3)
ld 15, 1*8(3)
ld 16, 2*8(3)
ld 17, 3*8(3)
ld 18, 4*8(3)
ld 19, 5*8(3)
ld 20, 6*8(3)
ld 21, 7*8(3)
ld 22, 8*8(3)
ld 23, 9*8(3)
ld 24, 10*8(3)
ld 25, 11*8(3)
ld 26, 12*8(3)
ld 27, 13*8(3)
ld 28, 14*8(3)
ld 29, 15*8(3)
ld 30, 16*8(3)
ld 31, 17*8(3)
blr
FUNC_END(load_gpr)
FUNC_START(store_gpr)
std 14, 0*8(3)
std 15, 1*8(3)
std 16, 2*8(3)
std 17, 3*8(3)
std 18, 4*8(3)
std 19, 5*8(3)
std 20, 6*8(3)
std 21, 7*8(3)
std 22, 8*8(3)
std 23, 9*8(3)
std 24, 10*8(3)
std 25, 11*8(3)
std 26, 12*8(3)
std 27, 13*8(3)
std 28, 14*8(3)
std 29, 15*8(3)
std 30, 16*8(3)
std 31, 17*8(3)
blr
FUNC_END(store_gpr)
/* Double Precision Float - double buf[32] */
FUNC_START(store_fpr)
stfd 0, 0*8(3)
stfd 1, 1*8(3)
stfd 2, 2*8(3)
stfd 3, 3*8(3)
stfd 4, 4*8(3)
stfd 5, 5*8(3)
stfd 6, 6*8(3)
stfd 7, 7*8(3)
stfd 8, 8*8(3)
stfd 9, 9*8(3)
stfd 10, 10*8(3)
stfd 11, 11*8(3)
stfd 12, 12*8(3)
stfd 13, 13*8(3)
stfd 14, 14*8(3)
stfd 15, 15*8(3)
stfd 16, 16*8(3)
stfd 17, 17*8(3)
stfd 18, 18*8(3)
stfd 19, 19*8(3)
stfd 20, 20*8(3)
stfd 21, 21*8(3)
stfd 22, 22*8(3)
stfd 23, 23*8(3)
stfd 24, 24*8(3)
stfd 25, 25*8(3)
stfd 26, 26*8(3)
stfd 27, 27*8(3)
stfd 28, 28*8(3)
stfd 29, 29*8(3)
stfd 30, 30*8(3)
stfd 31, 31*8(3)
blr
FUNC_END(store_fpr)
/* VMX/VSX registers - unsigned long buf[128] */
FUNC_START(loadvsx)
lis 4, 0
LXVD2X (0,(4),(3))
addi 4, 4, 16
LXVD2X (1,(4),(3))
addi 4, 4, 16
LXVD2X (2,(4),(3))
addi 4, 4, 16
LXVD2X (3,(4),(3))
addi 4, 4, 16
LXVD2X (4,(4),(3))
addi 4, 4, 16
LXVD2X (5,(4),(3))
addi 4, 4, 16
LXVD2X (6,(4),(3))
addi 4, 4, 16
LXVD2X (7,(4),(3))
addi 4, 4, 16
LXVD2X (8,(4),(3))
addi 4, 4, 16
LXVD2X (9,(4),(3))
addi 4, 4, 16
LXVD2X (10,(4),(3))
addi 4, 4, 16
LXVD2X (11,(4),(3))
addi 4, 4, 16
LXVD2X (12,(4),(3))
addi 4, 4, 16
LXVD2X (13,(4),(3))
addi 4, 4, 16
LXVD2X (14,(4),(3))
addi 4, 4, 16
LXVD2X (15,(4),(3))
addi 4, 4, 16
LXVD2X (16,(4),(3))
addi 4, 4, 16
LXVD2X (17,(4),(3))
addi 4, 4, 16
LXVD2X (18,(4),(3))
addi 4, 4, 16
LXVD2X (19,(4),(3))
addi 4, 4, 16
LXVD2X (20,(4),(3))
addi 4, 4, 16
LXVD2X (21,(4),(3))
addi 4, 4, 16
LXVD2X (22,(4),(3))
addi 4, 4, 16
LXVD2X (23,(4),(3))
addi 4, 4, 16
LXVD2X (24,(4),(3))
addi 4, 4, 16
LXVD2X (25,(4),(3))
addi 4, 4, 16
LXVD2X (26,(4),(3))
addi 4, 4, 16
LXVD2X (27,(4),(3))
addi 4, 4, 16
LXVD2X (28,(4),(3))
addi 4, 4, 16
LXVD2X (29,(4),(3))
addi 4, 4, 16
LXVD2X (30,(4),(3))
addi 4, 4, 16
LXVD2X (31,(4),(3))
addi 4, 4, 16
LXVD2X (32,(4),(3))
addi 4, 4, 16
LXVD2X (33,(4),(3))
addi 4, 4, 16
LXVD2X (34,(4),(3))
addi 4, 4, 16
LXVD2X (35,(4),(3))
addi 4, 4, 16
LXVD2X (36,(4),(3))
addi 4, 4, 16
LXVD2X (37,(4),(3))
addi 4, 4, 16
LXVD2X (38,(4),(3))
addi 4, 4, 16
LXVD2X (39,(4),(3))
addi 4, 4, 16
LXVD2X (40,(4),(3))
addi 4, 4, 16
LXVD2X (41,(4),(3))
addi 4, 4, 16
LXVD2X (42,(4),(3))
addi 4, 4, 16
LXVD2X (43,(4),(3))
addi 4, 4, 16
LXVD2X (44,(4),(3))
addi 4, 4, 16
LXVD2X (45,(4),(3))
addi 4, 4, 16
LXVD2X (46,(4),(3))
addi 4, 4, 16
LXVD2X (47,(4),(3))
addi 4, 4, 16
LXVD2X (48,(4),(3))
addi 4, 4, 16
LXVD2X (49,(4),(3))
addi 4, 4, 16
LXVD2X (50,(4),(3))
addi 4, 4, 16
LXVD2X (51,(4),(3))
addi 4, 4, 16
LXVD2X (52,(4),(3))
addi 4, 4, 16
LXVD2X (53,(4),(3))
addi 4, 4, 16
LXVD2X (54,(4),(3))
addi 4, 4, 16
LXVD2X (55,(4),(3))
addi 4, 4, 16
LXVD2X (56,(4),(3))
addi 4, 4, 16
LXVD2X (57,(4),(3))
addi 4, 4, 16
LXVD2X (58,(4),(3))
addi 4, 4, 16
LXVD2X (59,(4),(3))
addi 4, 4, 16
LXVD2X (60,(4),(3))
addi 4, 4, 16
LXVD2X (61,(4),(3))
addi 4, 4, 16
LXVD2X (62,(4),(3))
addi 4, 4, 16
LXVD2X (63,(4),(3))
blr
FUNC_END(loadvsx)
FUNC_START(storevsx)
lis 4, 0
STXVD2X (0,(4),(3))
addi 4, 4, 16
STXVD2X (1,(4),(3))
addi 4, 4, 16
STXVD2X (2,(4),(3))
addi 4, 4, 16
STXVD2X (3,(4),(3))
addi 4, 4, 16
STXVD2X (4,(4),(3))
addi 4, 4, 16
STXVD2X (5,(4),(3))
addi 4, 4, 16
STXVD2X (6,(4),(3))
addi 4, 4, 16
STXVD2X (7,(4),(3))
addi 4, 4, 16
STXVD2X (8,(4),(3))
addi 4, 4, 16
STXVD2X (9,(4),(3))
addi 4, 4, 16
STXVD2X (10,(4),(3))
addi 4, 4, 16
STXVD2X (11,(4),(3))
addi 4, 4, 16
STXVD2X (12,(4),(3))
addi 4, 4, 16
STXVD2X (13,(4),(3))
addi 4, 4, 16
STXVD2X (14,(4),(3))
addi 4, 4, 16
STXVD2X (15,(4),(3))
addi 4, 4, 16
STXVD2X (16,(4),(3))
addi 4, 4, 16
STXVD2X (17,(4),(3))
addi 4, 4, 16
STXVD2X (18,(4),(3))
addi 4, 4, 16
STXVD2X (19,(4),(3))
addi 4, 4, 16
STXVD2X (20,(4),(3))
addi 4, 4, 16
STXVD2X (21,(4),(3))
addi 4, 4, 16
STXVD2X (22,(4),(3))
addi 4, 4, 16
STXVD2X (23,(4),(3))
addi 4, 4, 16
STXVD2X (24,(4),(3))
addi 4, 4, 16
STXVD2X (25,(4),(3))
addi 4, 4, 16
STXVD2X (26,(4),(3))
addi 4, 4, 16
STXVD2X (27,(4),(3))
addi 4, 4, 16
STXVD2X (28,(4),(3))
addi 4, 4, 16
STXVD2X (29,(4),(3))
addi 4, 4, 16
STXVD2X (30,(4),(3))
addi 4, 4, 16
STXVD2X (31,(4),(3))
addi 4, 4, 16
STXVD2X (32,(4),(3))
addi 4, 4, 16
STXVD2X (33,(4),(3))
addi 4, 4, 16
STXVD2X (34,(4),(3))
addi 4, 4, 16
STXVD2X (35,(4),(3))
addi 4, 4, 16
STXVD2X (36,(4),(3))
addi 4, 4, 16
STXVD2X (37,(4),(3))
addi 4, 4, 16
STXVD2X (38,(4),(3))
addi 4, 4, 16
STXVD2X (39,(4),(3))
addi 4, 4, 16
STXVD2X (40,(4),(3))
addi 4, 4, 16
STXVD2X (41,(4),(3))
addi 4, 4, 16
STXVD2X (42,(4),(3))
addi 4, 4, 16
STXVD2X (43,(4),(3))
addi 4, 4, 16
STXVD2X (44,(4),(3))
addi 4, 4, 16
STXVD2X (45,(4),(3))
addi 4, 4, 16
STXVD2X (46,(4),(3))
addi 4, 4, 16
STXVD2X (47,(4),(3))
addi 4, 4, 16
STXVD2X (48,(4),(3))
addi 4, 4, 16
STXVD2X (49,(4),(3))
addi 4, 4, 16
STXVD2X (50,(4),(3))
addi 4, 4, 16
STXVD2X (51,(4),(3))
addi 4, 4, 16
STXVD2X (52,(4),(3))
addi 4, 4, 16
STXVD2X (53,(4),(3))
addi 4, 4, 16
STXVD2X (54,(4),(3))
addi 4, 4, 16
STXVD2X (55,(4),(3))
addi 4, 4, 16
STXVD2X (56,(4),(3))
addi 4, 4, 16
STXVD2X (57,(4),(3))
addi 4, 4, 16
STXVD2X (58,(4),(3))
addi 4, 4, 16
STXVD2X (59,(4),(3))
addi 4, 4, 16
STXVD2X (60,(4),(3))
addi 4, 4, 16
STXVD2X (61,(4),(3))
addi 4, 4, 16
STXVD2X (62,(4),(3))
addi 4, 4, 16
STXVD2X (63,(4),(3))
blr
FUNC_END(storevsx)
@@ -0,0 +1 @@
timeout=130
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
fpu_syscall
vmx_syscall
fpu_preempt
vmx_preempt
fpu_signal
vmx_signal
vsx_preempt
fpu_denormal
mma
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := fpu_syscall fpu_preempt fpu_signal fpu_denormal vmx_syscall vmx_preempt vmx_signal vsx_preempt mma
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
$(TEST_GEN_PROGS): ../harness.c
$(TEST_GEN_PROGS): CFLAGS += -O2 -g -pthread -m64 -maltivec
$(OUTPUT)/fpu_syscall: fpu_asm.S
$(OUTPUT)/fpu_preempt: fpu_asm.S
$(OUTPUT)/fpu_signal: fpu_asm.S
$(OUTPUT)/vmx_syscall: vmx_asm.S ../utils.c
$(OUTPUT)/vmx_preempt: vmx_asm.S ../utils.c
$(OUTPUT)/vmx_signal: vmx_asm.S ../utils.c
$(OUTPUT)/vsx_preempt: CFLAGS += -mvsx
$(OUTPUT)/vsx_preempt: vsx_asm.S ../utils.c
$(OUTPUT)/mma: mma.c mma.S ../utils.c
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2023, Michael Ellerman, IBM Corporation.
*/
#ifndef _SELFTESTS_POWERPC_FPU_H
#define _SELFTESTS_POWERPC_FPU_H
static inline void randomise_darray(double *darray, int num)
{
long val;
for (int i = 0; i < num; i++) {
val = random();
if (val & 1)
val *= -1;
if (val & 2)
darray[i] = 1.0 / val;
else
darray[i] = val * val;
}
}
#endif /* _SELFTESTS_POWERPC_FPU_H */
@@ -0,0 +1,165 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*/
#include "basic_asm.h"
#include "fpu_asm.h"
FUNC_START(check_fpu)
mr r4,r3
li r3,1 # assume a bad result
lfd f0,0(r4)
fcmpu cr1,f0,f14
bne cr1,1f
lfd f0,8(r4)
fcmpu cr1,f0,f15
bne cr1,1f
lfd f0,16(r4)
fcmpu cr1,f0,f16
bne cr1,1f
lfd f0,24(r4)
fcmpu cr1,f0,f17
bne cr1,1f
lfd f0,32(r4)
fcmpu cr1,f0,f18
bne cr1,1f
lfd f0,40(r4)
fcmpu cr1,f0,f19
bne cr1,1f
lfd f0,48(r4)
fcmpu cr1,f0,f20
bne cr1,1f
lfd f0,56(r4)
fcmpu cr1,f0,f21
bne cr1,1f
lfd f0,64(r4)
fcmpu cr1,f0,f22
bne cr1,1f
lfd f0,72(r4)
fcmpu cr1,f0,f23
bne cr1,1f
lfd f0,80(r4)
fcmpu cr1,f0,f24
bne cr1,1f
lfd f0,88(r4)
fcmpu cr1,f0,f25
bne cr1,1f
lfd f0,96(r4)
fcmpu cr1,f0,f26
bne cr1,1f
lfd f0,104(r4)
fcmpu cr1,f0,f27
bne cr1,1f
lfd f0,112(r4)
fcmpu cr1,f0,f28
bne cr1,1f
lfd f0,120(r4)
fcmpu cr1,f0,f29
bne cr1,1f
lfd f0,128(r4)
fcmpu cr1,f0,f30
bne cr1,1f
lfd f0,136(r4)
fcmpu cr1,f0,f31
bne cr1,1f
li r3,0 # Success!!!
1: blr
// int check_all_fprs(double darray[32])
FUNC_START(check_all_fprs)
PUSH_BASIC_STACK(8)
mr r4, r3 // r4 = darray
li r3, 1 // prepare for failure
stfd f31, STACK_FRAME_LOCAL(0, 0)(sp) // backup f31
// Check regs f0-f30, using f31 as scratch
.set i, 0
.rept 31
lfd f31, (8 * i)(r4) // load expected value
fcmpu cr0, i, f31 // compare
bne cr0, 1f // bail if mismatch
.set i, i + 1
.endr
lfd f31, STACK_FRAME_LOCAL(0, 0)(sp) // reload f31
stfd f30, STACK_FRAME_LOCAL(0, 0)(sp) // backup f30
lfd f30, (8 * 31)(r4) // load expected value of f31
fcmpu cr0, f30, f31 // compare
bne cr0, 1f // bail if mismatch
lfd f30, STACK_FRAME_LOCAL(0, 0)(sp) // reload f30
// Success
li r3, 0
1: POP_BASIC_STACK(8)
blr
FUNC_END(check_all_fprs)
FUNC_START(test_fpu)
# r3 holds pointer to where to put the result of fork
# r4 holds pointer to the pid
# f14-f31 are non volatiles
PUSH_BASIC_STACK(256)
PUSH_FPU(256)
std r3,STACK_FRAME_PARAM(0)(sp) # Address of darray
std r4,STACK_FRAME_PARAM(1)(sp) # Address of pid
// Load FPRs with expected values
OP_REGS lfd, 8, 0, 31, r3
li r0,__NR_fork
sc
# pass the result of the fork to the caller
ld r9,STACK_FRAME_PARAM(1)(sp)
std r3,0(r9)
ld r3,STACK_FRAME_PARAM(0)(sp)
bl check_all_fprs
nop
POP_FPU(256)
POP_BASIC_STACK(256)
blr
FUNC_END(test_fpu)
# int preempt_fpu(double *darray, int *threads_running, int *running)
# On starting will (atomically) decrement not_ready as a signal that the FPU
# has been loaded with darray. Will proceed to check the validity of the FPU
# registers while running is not zero.
FUNC_START(preempt_fpu)
PUSH_BASIC_STACK(256)
PUSH_FPU(256)
std r3,STACK_FRAME_PARAM(0)(sp) # double *darray
std r4,STACK_FRAME_PARAM(1)(sp) # int *threads_starting
std r5,STACK_FRAME_PARAM(2)(sp) # int *running
// Load FPRs with expected values
OP_REGS lfd, 8, 0, 31, r3
sync
# Atomic DEC
ld r3,STACK_FRAME_PARAM(1)(sp)
1: lwarx r4,0,r3
addi r4,r4,-1
stwcx. r4,0,r3
bne- 1b
2: ld r3,STACK_FRAME_PARAM(0)(sp)
bl check_all_fprs
cmpdi r3,0
bne 3f
ld r4,STACK_FRAME_PARAM(2)(sp)
ld r5,0(r4)
cmpwi r5,0
bne 2b
3: POP_FPU(256)
POP_BASIC_STACK(256)
blr
FUNC_END(preempt_fpu)
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright IBM Corp. 2020
*
* This test attempts to cause a FP denormal exception on POWER8 CPUs. Unfortunately
* if the denormal handler is not configured or working properly, this can cause a bad
* crash in kernel mode when the kernel tries to save FP registers when the process
* exits.
*/
#include <stdio.h>
#include <string.h>
#include "utils.h"
static int test_denormal_fpu(void)
{
unsigned int m32;
unsigned long m64;
volatile float f;
volatile double d;
/* try to induce lfs <denormal> ; stfd */
m32 = 0x00715fcf; /* random denormal */
memcpy((float *)&f, &m32, sizeof(f));
d = f;
memcpy(&m64, (double *)&d, sizeof(d));
FAIL_IF((long)(m64 != 0x380c57f3c0000000)); /* renormalised value */
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(test_denormal_fpu, "fpu_denormal");
}
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015, Cyril Bur, IBM Corp.
* Copyright 2023, Michael Ellerman, IBM Corp.
*
* This test attempts to see if the FPU registers change across preemption.
* There is no way to be sure preemption happened so this test just uses many
* threads and a long wait. As such, a successful test doesn't mean much but
* a failure is bad.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <pthread.h>
#include "utils.h"
#include "fpu.h"
/* Time to wait for workers to get preempted (seconds) */
#define PREEMPT_TIME 60
/*
* Factor by which to multiply number of online CPUs for total number of
* worker threads
*/
#define THREAD_FACTOR 8
__thread double darray[32];
int threads_starting;
int running;
extern int preempt_fpu(double *darray, int *threads_starting, int *running);
void *preempt_fpu_c(void *p)
{
long rc;
srand(pthread_self());
randomise_darray(darray, ARRAY_SIZE(darray));
rc = preempt_fpu(darray, &threads_starting, &running);
return (void *)rc;
}
int test_preempt_fpu(void)
{
int i, rc, threads;
pthread_t *tids;
threads = sysconf(_SC_NPROCESSORS_ONLN) * THREAD_FACTOR;
tids = malloc((threads) * sizeof(pthread_t));
FAIL_IF(!tids);
running = true;
threads_starting = threads;
for (i = 0; i < threads; i++) {
rc = pthread_create(&tids[i], NULL, preempt_fpu_c, NULL);
FAIL_IF(rc);
}
setbuf(stdout, NULL);
/* Not really necessary but nice to wait for every thread to start */
printf("\tWaiting for all workers to start...");
while(threads_starting)
asm volatile("": : :"memory");
printf("done\n");
printf("\tWaiting for %d seconds to let some workers get preempted...", PREEMPT_TIME);
sleep(PREEMPT_TIME);
printf("done\n");
printf("\tStopping workers...");
/*
* Working are checking this value every loop. In preempt_fpu 'cmpwi r5,0; bne 2b'.
* r5 will have loaded the value of running.
*/
running = 0;
for (i = 0; i < threads; i++) {
void *rc_p;
pthread_join(tids[i], &rc_p);
/*
* Harness will say the fail was here, look at why preempt_fpu
* returned
*/
if ((long) rc_p)
printf("oops\n");
FAIL_IF((long) rc_p);
}
printf("done\n");
free(tids);
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(test_preempt_fpu, "fpu_preempt");
}
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*
* This test attempts to see if the FPU registers are correctly reported in a
* signal context. Each worker just spins checking its FPU registers, at some
* point a signal will interrupt it and C code will check the signal context
* ensuring it is also the same.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <pthread.h>
#include "utils.h"
#include "fpu.h"
/* Number of times each thread should receive the signal */
#define ITERATIONS 10
/*
* Factor by which to multiply number of online CPUs for total number of
* worker threads
*/
#define THREAD_FACTOR 8
__thread double darray[32];
bool bad_context;
int threads_starting;
int running;
extern long preempt_fpu(double *darray, int *threads_starting, int *running);
void signal_fpu_sig(int sig, siginfo_t *info, void *context)
{
int i;
ucontext_t *uc = context;
mcontext_t *mc = &uc->uc_mcontext;
// Don't check f30/f31, they're used as scratches in check_all_fprs()
for (i = 0; i < 30; i++) {
if (mc->fp_regs[i] != darray[i]) {
bad_context = true;
break;
}
}
}
void *signal_fpu_c(void *p)
{
long rc;
struct sigaction act;
act.sa_sigaction = signal_fpu_sig;
act.sa_flags = SA_SIGINFO;
rc = sigaction(SIGUSR1, &act, NULL);
if (rc)
return p;
srand(pthread_self());
randomise_darray(darray, ARRAY_SIZE(darray));
rc = preempt_fpu(darray, &threads_starting, &running);
return (void *) rc;
}
int test_signal_fpu(void)
{
int i, j, rc, threads;
void *rc_p;
pthread_t *tids;
threads = sysconf(_SC_NPROCESSORS_ONLN) * THREAD_FACTOR;
tids = malloc(threads * sizeof(pthread_t));
FAIL_IF(!tids);
running = true;
threads_starting = threads;
for (i = 0; i < threads; i++) {
rc = pthread_create(&tids[i], NULL, signal_fpu_c, NULL);
FAIL_IF(rc);
}
setbuf(stdout, NULL);
printf("\tWaiting for all workers to start...");
while (threads_starting)
asm volatile("": : :"memory");
printf("done\n");
printf("\tSending signals to all threads %d times...", ITERATIONS);
for (i = 0; i < ITERATIONS; i++) {
for (j = 0; j < threads; j++) {
pthread_kill(tids[j], SIGUSR1);
}
sleep(1);
}
printf("done\n");
printf("\tStopping workers...");
running = 0;
for (i = 0; i < threads; i++) {
pthread_join(tids[i], &rc_p);
/*
* Harness will say the fail was here, look at why signal_fpu
* returned
*/
if ((long) rc_p || bad_context)
printf("oops\n");
if (bad_context)
fprintf(stderr, "\t!! bad_context is true\n");
FAIL_IF((long) rc_p || bad_context);
}
printf("done\n");
free(tids);
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(test_signal_fpu, "fpu_signal");
}
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*
* This test attempts to see if the FPU registers change across a syscall (fork).
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include "utils.h"
#include "fpu.h"
extern int test_fpu(double *darray, pid_t *pid);
double darray[32];
int syscall_fpu(void)
{
pid_t fork_pid;
int i;
int ret;
int child_ret;
randomise_darray(darray, ARRAY_SIZE(darray));
for (i = 0; i < 1000; i++) {
/* test_fpu will fork() */
ret = test_fpu(darray, &fork_pid);
if (fork_pid == -1)
return -1;
if (fork_pid == 0)
exit(ret);
waitpid(fork_pid, &child_ret, 0);
if (ret || child_ret)
return 1;
}
return 0;
}
int test_syscall_fpu(void)
{
/*
* Setup an environment with much context switching
*/
pid_t pid2;
pid_t pid = fork();
int ret;
int child_ret;
FAIL_IF(pid == -1);
pid2 = fork();
/* Can't FAIL_IF(pid2 == -1); because already forked once */
if (pid2 == -1) {
/*
* Couldn't fork, ensure test is a fail
*/
child_ret = ret = 1;
} else {
ret = syscall_fpu();
if (pid2)
waitpid(pid2, &child_ret, 0);
else
exit(ret);
}
ret |= child_ret;
if (pid)
waitpid(pid, &child_ret, 0);
else
exit(ret);
FAIL_IF(ret || child_ret);
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(test_syscall_fpu, "syscall_fpu");
}
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
*
* Test basic matrix multiply assist (MMA) functionality if available.
*
* Copyright 2020, Alistair Popple, IBM Corp.
*/
.global test_mma
test_mma:
/* Load accumulator via VSX registers from image passed in r3 */
lxvh8x 4,0,3
lxvh8x 5,0,4
/* Clear and prime the accumulator (xxsetaccz) */
.long 0x7c030162
/* Prime the accumulator with MMA VSX move to accumulator
* X-form (xxmtacc) (not needed due to above zeroing) */
//.long 0x7c010162
/* xvi16ger2s */
.long 0xec042958
/* Deprime the accumulator - xxmfacc 0 */
.long 0x7c000162
/* Store result in image passed in r5 */
stxvw4x 0,0,5
addi 5,5,16
stxvw4x 1,0,5
addi 5,5,16
stxvw4x 2,0,5
addi 5,5,16
stxvw4x 3,0,5
addi 5,5,16
blr
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test basic matrix multiply assist (MMA) functionality if available.
*
* Copyright 2020, Alistair Popple, IBM Corp.
*/
#include <stdio.h>
#include <stdint.h>
#include "utils.h"
extern void test_mma(uint16_t (*)[8], uint16_t (*)[8], uint32_t (*)[4*4]);
static int mma(void)
{
int i;
int rc = 0;
uint16_t x[] = {1, 0, 2, 0, 3, 0, 4, 0};
uint16_t y[] = {1, 0, 2, 0, 3, 0, 4, 0};
uint32_t z[4*4];
uint32_t exp[4*4] = {1, 2, 3, 4,
2, 4, 6, 8,
3, 6, 9, 12,
4, 8, 12, 16};
SKIP_IF_MSG(!have_hwcap2(PPC_FEATURE2_ARCH_3_1), "Need ISAv3.1");
SKIP_IF_MSG(!have_hwcap2(PPC_FEATURE2_MMA), "Need MMA");
test_mma(&x, &y, &z);
for (i = 0; i < 16; i++) {
printf("MMA[%d] = %d ", i, z[i]);
if (z[i] == exp[i]) {
printf(" (Correct)\n");
} else {
printf(" (Incorrect)\n");
rc = 1;
}
}
return rc;
}
int main(int argc, char *argv[])
{
return test_harness(mma, "mma");
}
@@ -0,0 +1 @@
timeout=130
@@ -0,0 +1,148 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*/
#include "basic_asm.h"
#include "vmx_asm.h"
# Should be safe from C, only touches r4, r5 and v0,v1,v2
FUNC_START(check_vmx)
PUSH_BASIC_STACK(32)
mr r4,r3
li r3,1 # assume a bad result
li r5,0
lvx v0,r5,r4
vcmpequd. v1,v0,v20
vmr v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v21
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v22
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v23
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v24
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v25
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v26
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v27
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v28
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v29
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v30
vand v2,v2,v1
addi r5,r5,16
lvx v0,r5,r4
vcmpequd. v1,v0,v31
vand v2,v2,v1
li r5,STACK_FRAME_LOCAL(0,0)
stvx v2,r5,sp
ldx r0,r5,sp
cmpdi r0,0xffffffffffffffff
bne 1f
li r3,0
1: POP_BASIC_STACK(32)
blr
FUNC_END(check_vmx)
# Safe from C
FUNC_START(test_vmx)
# r3 holds pointer to where to put the result of fork
# r4 holds pointer to the pid
# v20-v31 are non-volatile
PUSH_BASIC_STACK(512)
std r3,STACK_FRAME_PARAM(0)(sp) # Address of varray
std r4,STACK_FRAME_PARAM(1)(sp) # address of pid
PUSH_VMX(STACK_FRAME_LOCAL(2,0),r4)
bl load_vmx
nop
li r0,__NR_fork
sc
# Pass the result of fork back to the caller
ld r9,STACK_FRAME_PARAM(1)(sp)
std r3,0(r9)
ld r3,STACK_FRAME_PARAM(0)(sp)
bl check_vmx
nop
POP_VMX(STACK_FRAME_LOCAL(2,0),r4)
POP_BASIC_STACK(512)
blr
FUNC_END(test_vmx)
# int preempt_vmx(vector int *varray, int *threads_starting, int *running)
# On starting will (atomically) decrement threads_starting as a signal that
# the VMX have been loaded with varray. Will proceed to check the validity of
# the VMX registers while running is not zero.
FUNC_START(preempt_vmx)
PUSH_BASIC_STACK(512)
std r3,STACK_FRAME_PARAM(0)(sp) # vector int *varray
std r4,STACK_FRAME_PARAM(1)(sp) # int *threads_starting
std r5,STACK_FRAME_PARAM(2)(sp) # int *running
# VMX need to write to 16 byte aligned addresses, skip STACK_FRAME_LOCAL(3,0)
PUSH_VMX(STACK_FRAME_LOCAL(4,0),r4)
bl load_vmx
nop
sync
# Atomic DEC
ld r3,STACK_FRAME_PARAM(1)(sp)
1: lwarx r4,0,r3
addi r4,r4,-1
stwcx. r4,0,r3
bne- 1b
2: ld r3,STACK_FRAME_PARAM(0)(sp)
bl check_vmx
nop
cmpdi r3,0
bne 3f
ld r4,STACK_FRAME_PARAM(2)(sp)
ld r5,0(r4)
cmpwi r5,0
bne 2b
3: POP_VMX(STACK_FRAME_LOCAL(4,0),r4)
POP_BASIC_STACK(512)
blr
FUNC_END(preempt_vmx)
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*
* This test attempts to see if the VMX registers change across preemption.
* Two things should be noted here a) The check_vmx function in asm only checks
* the non volatile registers as it is reused from the syscall test b) There is
* no way to be sure preemption happened so this test just uses many threads
* and a long wait. As such, a successful test doesn't mean much but a failure
* is bad.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <pthread.h>
#include "utils.h"
/* Time to wait for workers to get preempted (seconds) */
#define PREEMPT_TIME 20
/*
* Factor by which to multiply number of online CPUs for total number of
* worker threads
*/
#define THREAD_FACTOR 8
__thread vector int varray[] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10,11,12},
{13,14,15,16},{17,18,19,20},{21,22,23,24},
{25,26,27,28},{29,30,31,32},{33,34,35,36},
{37,38,39,40},{41,42,43,44},{45,46,47,48}};
int threads_starting;
int running;
extern int preempt_vmx(vector int *varray, int *threads_starting, int *running);
void *preempt_vmx_c(void *p)
{
int i, j;
long rc;
srand(pthread_self());
for (i = 0; i < 12; i++)
for (j = 0; j < 4; j++)
varray[i][j] = rand();
rc = preempt_vmx(varray, &threads_starting, &running);
return (void *)rc;
}
int test_preempt_vmx(void)
{
int i, rc, threads;
pthread_t *tids;
// vcmpequd used in vmx_asm.S is v2.07
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_2_07));
threads = sysconf(_SC_NPROCESSORS_ONLN) * THREAD_FACTOR;
tids = malloc(threads * sizeof(pthread_t));
FAIL_IF(!tids);
running = true;
threads_starting = threads;
for (i = 0; i < threads; i++) {
rc = pthread_create(&tids[i], NULL, preempt_vmx_c, NULL);
FAIL_IF(rc);
}
setbuf(stdout, NULL);
/* Not really nessesary but nice to wait for every thread to start */
printf("\tWaiting for all workers to start...");
while(threads_starting)
asm volatile("": : :"memory");
printf("done\n");
printf("\tWaiting for %d seconds to let some workers get preempted...", PREEMPT_TIME);
sleep(PREEMPT_TIME);
printf("done\n");
printf("\tStopping workers...");
/*
* Working are checking this value every loop. In preempt_vmx 'cmpwi r5,0; bne 2b'.
* r5 will have loaded the value of running.
*/
running = 0;
for (i = 0; i < threads; i++) {
void *rc_p;
pthread_join(tids[i], &rc_p);
/*
* Harness will say the fail was here, look at why preempt_vmx
* returned
*/
if ((long) rc_p)
printf("oops\n");
FAIL_IF((long) rc_p);
}
printf("done\n");
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(test_preempt_vmx, "vmx_preempt");
}
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*
* This test attempts to see if the VMX registers are correctly reported in a
* signal context. Each worker just spins checking its VMX registers, at some
* point a signal will interrupt it and C code will check the signal context
* ensuring it is also the same.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <altivec.h>
#include "utils.h"
/* Number of times each thread should receive the signal */
#define ITERATIONS 10
/*
* Factor by which to multiply number of online CPUs for total number of
* worker threads
*/
#define THREAD_FACTOR 8
__thread vector int varray[] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10,11,12},
{13,14,15,16},{17,18,19,20},{21,22,23,24},
{25,26,27,28},{29,30,31,32},{33,34,35,36},
{37,38,39,40},{41,42,43,44},{45,46,47,48}};
bool bad_context;
int running;
int threads_starting;
extern int preempt_vmx(vector int *varray, int *threads_starting, int *sentinal);
void signal_vmx_sig(int sig, siginfo_t *info, void *context)
{
int i;
ucontext_t *uc = context;
mcontext_t *mc = &uc->uc_mcontext;
/* Only the non volatiles were loaded up */
for (i = 20; i < 32; i++) {
if (memcmp(mc->v_regs->vrregs[i], &varray[i - 20], 16)) {
int j;
/*
* Shouldn't printf() in a signal handler, however, this is a
* test and we've detected failure. Understanding what failed
* is paramount. All that happens after this is tests exit with
* failure.
*/
printf("VMX mismatch at reg %d!\n", i);
printf("Reg | Actual | Expected\n");
for (j = 20; j < 32; j++) {
printf("%d | 0x%04x%04x%04x%04x | 0x%04x%04x%04x%04x\n", j, mc->v_regs->vrregs[j][0],
mc->v_regs->vrregs[j][1], mc->v_regs->vrregs[j][2], mc->v_regs->vrregs[j][3],
varray[j - 20][0], varray[j - 20][1], varray[j - 20][2], varray[j - 20][3]);
}
bad_context = true;
break;
}
}
}
void *signal_vmx_c(void *p)
{
int i, j;
long rc;
struct sigaction act;
act.sa_sigaction = signal_vmx_sig;
act.sa_flags = SA_SIGINFO;
rc = sigaction(SIGUSR1, &act, NULL);
if (rc)
return p;
srand(pthread_self());
for (i = 0; i < 12; i++)
for (j = 0; j < 4; j++)
varray[i][j] = rand();
rc = preempt_vmx(varray, &threads_starting, &running);
return (void *) rc;
}
int test_signal_vmx(void)
{
int i, j, rc, threads;
void *rc_p;
pthread_t *tids;
// vcmpequd used in vmx_asm.S is v2.07
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_2_07));
threads = sysconf(_SC_NPROCESSORS_ONLN) * THREAD_FACTOR;
tids = malloc(threads * sizeof(pthread_t));
FAIL_IF(!tids);
running = true;
threads_starting = threads;
for (i = 0; i < threads; i++) {
rc = pthread_create(&tids[i], NULL, signal_vmx_c, NULL);
FAIL_IF(rc);
}
setbuf(stdout, NULL);
printf("\tWaiting for %d workers to start... %d", threads, threads_starting);
while (threads_starting) {
asm volatile("": : :"memory");
usleep(1000);
printf(", %d", threads_starting);
}
printf(" ...done\n");
printf("\tSending signals to all threads %d times...", ITERATIONS);
for (i = 0; i < ITERATIONS; i++) {
for (j = 0; j < threads; j++) {
pthread_kill(tids[j], SIGUSR1);
}
sleep(1);
}
printf("done\n");
printf("\tKilling workers...");
running = 0;
for (i = 0; i < threads; i++) {
pthread_join(tids[i], &rc_p);
/*
* Harness will say the fail was here, look at why signal_vmx
* returned
*/
if ((long) rc_p || bad_context)
printf("oops\n");
if (bad_context)
fprintf(stderr, "\t!! bad_context is true\n");
FAIL_IF((long) rc_p || bad_context);
}
printf("done\n");
free(tids);
return 0;
}
int main(int argc, char *argv[])
{
test_harness_set_timeout(360);
return test_harness(test_signal_vmx, "vmx_signal");
}
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*
* This test attempts to see if the VMX registers change across a syscall (fork).
*/
#include <altivec.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "utils.h"
vector int varray[] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10,11,12},
{13,14,15,16},{17,18,19,20},{21,22,23,24},
{25,26,27,28},{29,30,31,32},{33,34,35,36},
{37,38,39,40},{41,42,43,44},{45,46,47,48}};
extern int test_vmx(vector int *varray, pid_t *pid);
int vmx_syscall(void)
{
pid_t fork_pid;
int i;
int ret;
int child_ret;
for (i = 0; i < 1000; i++) {
/* test_vmx will fork() */
ret = test_vmx(varray, &fork_pid);
if (fork_pid == -1)
return -1;
if (fork_pid == 0)
exit(ret);
waitpid(fork_pid, &child_ret, 0);
if (ret || child_ret)
return 1;
}
return 0;
}
int test_vmx_syscall(void)
{
/*
* Setup an environment with much context switching
*/
pid_t pid2;
pid_t pid;
int ret;
int child_ret;
// vcmpequd used in vmx_asm.S is v2.07
SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_2_07));
pid = fork();
FAIL_IF(pid == -1);
pid2 = fork();
ret = vmx_syscall();
/* Can't FAIL_IF(pid2 == -1); because we've already forked */
if (pid2 == -1) {
/*
* Couldn't fork, ensure child_ret is set and is a fail
*/
ret = child_ret = 1;
} else {
if (pid2)
waitpid(pid2, &child_ret, 0);
else
exit(ret);
}
ret |= child_ret;
if (pid)
waitpid(pid, &child_ret, 0);
else
exit(ret);
FAIL_IF(ret || child_ret);
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(test_vmx_syscall, "vmx_syscall");
}
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*/
#include "basic_asm.h"
#include "vsx_asm.h"
#long check_vsx(vector int *r3);
#This function wraps storeing VSX regs to the end of an array and a
#call to a comparison function in C which boils down to a memcmp()
FUNC_START(check_vsx)
PUSH_BASIC_STACK(32)
std r3,STACK_FRAME_PARAM(0)(sp)
addi r3, r3, 16 * 12 #Second half of array
bl store_vsx
ld r3,STACK_FRAME_PARAM(0)(sp)
bl vsx_memcmp
POP_BASIC_STACK(32)
blr
FUNC_END(check_vsx)
# int preempt_vmx(vector int *varray, int *threads_starting,
# int *running);
# On starting will (atomically) decrement threads_starting as a signal
# that the VMX have been loaded with varray. Will proceed to check the
# validity of the VMX registers while running is not zero.
FUNC_START(preempt_vsx)
PUSH_BASIC_STACK(512)
std r3,STACK_FRAME_PARAM(0)(sp) # vector int *varray
std r4,STACK_FRAME_PARAM(1)(sp) # int *threads_starting
std r5,STACK_FRAME_PARAM(2)(sp) # int *running
bl load_vsx
nop
sync
# Atomic DEC
ld r3,STACK_FRAME_PARAM(1)(sp)
1: lwarx r4,0,r3
addi r4,r4,-1
stwcx. r4,0,r3
bne- 1b
2: ld r3,STACK_FRAME_PARAM(0)(sp)
bl check_vsx
nop
cmpdi r3,0
bne 3f
ld r4,STACK_FRAME_PARAM(2)(sp)
ld r5,0(r4)
cmpwi r5,0
bne 2b
3: POP_BASIC_STACK(512)
blr
FUNC_END(preempt_vsx)
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*
* This test attempts to see if the VSX registers change across preemption.
* There is no way to be sure preemption happened so this test just
* uses many threads and a long wait. As such, a successful test
* doesn't mean much but a failure is bad.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <pthread.h>
#include "utils.h"
/* Time to wait for workers to get preempted (seconds) */
#define PREEMPT_TIME 20
/*
* Factor by which to multiply number of online CPUs for total number of
* worker threads
*/
#define THREAD_FACTOR 8
/*
* Ensure there is twice the number of non-volatile VMX regs!
* check_vmx() is going to use the other half as space to put the live
* registers before calling vsx_memcmp()
*/
__thread vector int varray[24] = {
{1, 2, 3, 4 }, {5, 6, 7, 8 }, {9, 10,11,12},
{13,14,15,16}, {17,18,19,20}, {21,22,23,24},
{25,26,27,28}, {29,30,31,32}, {33,34,35,36},
{37,38,39,40}, {41,42,43,44}, {45,46,47,48}
};
int threads_starting;
int running;
extern long preempt_vsx(vector int *varray, int *threads_starting, int *running);
long vsx_memcmp(vector int *a) {
vector int zero = {0, 0, 0, 0};
int i;
FAIL_IF(a != varray);
for(i = 0; i < 12; i++) {
if (memcmp(&a[i + 12], &zero, sizeof(vector int)) == 0) {
fprintf(stderr, "Detected zero from the VSX reg %d\n", i + 12);
return 2;
}
}
if (memcmp(a, &a[12], 12 * sizeof(vector int))) {
long *p = (long *)a;
fprintf(stderr, "VSX mismatch\n");
for (i = 0; i < 24; i=i+2)
fprintf(stderr, "%d: 0x%08lx%08lx | 0x%08lx%08lx\n",
i/2 + i%2 + 20, p[i], p[i + 1], p[i + 24], p[i + 25]);
return 1;
}
return 0;
}
void *preempt_vsx_c(void *p)
{
int i, j;
long rc;
srand(pthread_self());
for (i = 0; i < 12; i++)
for (j = 0; j < 4; j++) {
varray[i][j] = rand();
/* Don't want zero because it hides kernel problems */
if (varray[i][j] == 0)
j--;
}
rc = preempt_vsx(varray, &threads_starting, &running);
if (rc == 2)
fprintf(stderr, "Caught zeros in VSX compares\n");
return (void *)rc;
}
int test_preempt_vsx(void)
{
int i, rc, threads;
pthread_t *tids;
SKIP_IF(!have_hwcap(PPC_FEATURE_HAS_VSX));
threads = sysconf(_SC_NPROCESSORS_ONLN) * THREAD_FACTOR;
tids = malloc(threads * sizeof(pthread_t));
FAIL_IF(!tids);
running = true;
threads_starting = threads;
for (i = 0; i < threads; i++) {
rc = pthread_create(&tids[i], NULL, preempt_vsx_c, NULL);
FAIL_IF(rc);
}
setbuf(stdout, NULL);
/* Not really nessesary but nice to wait for every thread to start */
printf("\tWaiting for %d workers to start...", threads_starting);
while(threads_starting)
asm volatile("": : :"memory");
printf("done\n");
printf("\tWaiting for %d seconds to let some workers get preempted...", PREEMPT_TIME);
sleep(PREEMPT_TIME);
printf("done\n");
printf("\tStopping workers...");
/*
* Working are checking this value every loop. In preempt_vsx 'cmpwi r5,0; bne 2b'.
* r5 will have loaded the value of running.
*/
running = 0;
for (i = 0; i < threads; i++) {
void *rc_p;
pthread_join(tids[i], &rc_p);
/*
* Harness will say the fail was here, look at why preempt_vsx
* returned
*/
if ((long) rc_p)
printf("oops\n");
FAIL_IF((long) rc_p);
}
printf("done\n");
return 0;
}
int main(int argc, char *argv[])
{
return test_harness(test_preempt_vsx, "vsx_preempt");
}
@@ -0,0 +1 @@
inject-ra-err
@@ -0,0 +1,8 @@
#SPDX-License-Identifier: GPL-2.0-or-later
TEST_GEN_PROGS := inject-ra-err
include ../../lib.mk
include ../flags.mk
$(TEST_GEN_PROGS): ../harness.c

Some files were not shown because too many files have changed in this diff Show More