diff options
Diffstat (limited to 'tools/testing/selftests/powerpc/tm')
22 files changed, 2460 insertions, 0 deletions
diff --git a/tools/testing/selftests/powerpc/tm/.gitignore b/tools/testing/selftests/powerpc/tm/.gitignore new file mode 100644 index 000000000..c3ee8393d --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/.gitignore @@ -0,0 +1,17 @@ +tm-resched-dscr +tm-syscall +tm-signal-msr-resv +tm-signal-stack +tm-vmxcopy +tm-fork +tm-tar +tm-tmspr +tm-exec +tm-signal-context-chk-fpu +tm-signal-context-chk-gpr +tm-signal-context-chk-vmx +tm-signal-context-chk-vsx +tm-vmx-unavail +tm-unavailable +tm-trap +tm-sigreturn diff --git a/tools/testing/selftests/powerpc/tm/Makefile b/tools/testing/selftests/powerpc/tm/Makefile new file mode 100644 index 000000000..9fc2cf6fb --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +SIGNAL_CONTEXT_CHK_TESTS := tm-signal-context-chk-gpr tm-signal-context-chk-fpu \ + tm-signal-context-chk-vmx tm-signal-context-chk-vsx + +TEST_GEN_PROGS := tm-resched-dscr tm-syscall tm-signal-msr-resv tm-signal-stack \ + tm-vmxcopy tm-fork tm-tar tm-tmspr tm-vmx-unavail tm-unavailable tm-trap \ + $(SIGNAL_CONTEXT_CHK_TESTS) tm-sigreturn + +top_srcdir = ../../../../.. +include ../../lib.mk + +$(TEST_GEN_PROGS): ../harness.c ../utils.c + +CFLAGS += -mhtm + +$(OUTPUT)/tm-syscall: tm-syscall-asm.S +$(OUTPUT)/tm-syscall: CFLAGS += -I../../../../../usr/include +$(OUTPUT)/tm-tmspr: CFLAGS += -pthread +$(OUTPUT)/tm-vmx-unavail: CFLAGS += -pthread -m64 +$(OUTPUT)/tm-resched-dscr: ../pmu/lib.c +$(OUTPUT)/tm-unavailable: CFLAGS += -O0 -pthread -m64 -Wno-error=uninitialized -mvsx +$(OUTPUT)/tm-trap: CFLAGS += -O0 -pthread -m64 + +SIGNAL_CONTEXT_CHK_TESTS := $(patsubst %,$(OUTPUT)/%,$(SIGNAL_CONTEXT_CHK_TESTS)) +$(SIGNAL_CONTEXT_CHK_TESTS): tm-signal.S +$(SIGNAL_CONTEXT_CHK_TESTS): CFLAGS += -mhtm -m64 -mvsx diff --git a/tools/testing/selftests/powerpc/tm/tm-exec.c b/tools/testing/selftests/powerpc/tm/tm-exec.c new file mode 100644 index 000000000..3d27fa0ec --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-exec.c @@ -0,0 +1,70 @@ +/* + * Copyright 2016, Cyril Bur, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Syscalls can be performed provided the transactions are suspended. + * The exec() class of syscall is unique as a new process is loaded. + * + * It makes little sense for after an exec() call for the previously + * suspended transaction to still exist. + */ + +#define _GNU_SOURCE +#include <errno.h> +#include <inttypes.h> +#include <libgen.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "utils.h" +#include "tm.h" + +static char *path; + +static int test_exec(void) +{ + SKIP_IF(!have_htm()); + + asm __volatile__( + "tbegin.;" + "blt 1f; " + "tsuspend.;" + "1: ;" + : : : "memory"); + + execl(path, "tm-exec", "--child", NULL); + + /* Shouldn't get here */ + perror("execl() failed"); + return 1; +} + +static int after_exec(void) +{ + asm __volatile__( + "tbegin.;" + "blt 1f;" + "tsuspend.;" + "1: ;" + : : : "memory"); + + FAIL_IF(failure_is_nesting()); + return 0; +} + +int main(int argc, char *argv[]) +{ + path = argv[0]; + + if (argc > 1 && strcmp(argv[1], "--child") == 0) + return after_exec(); + + return test_harness(test_exec, "tm_exec"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-fork.c b/tools/testing/selftests/powerpc/tm/tm-fork.c new file mode 100644 index 000000000..8d48579b7 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-fork.c @@ -0,0 +1,42 @@ +/* + * Copyright 2015, Michael Neuling, IBM Corp. + * Licensed under GPLv2. + * + * Edited: Rashmica Gupta, Nov 2015 + * + * This test does a fork syscall inside a transaction. Basic sniff test + * to see if we can enter the kernel during a transaction. + */ + +#include <errno.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "utils.h" +#include "tm.h" + +int test_fork(void) +{ + SKIP_IF(!have_htm()); + + asm __volatile__( + "tbegin.;" + "blt 1f; " + "li 0, 2;" /* fork syscall */ + "sc ;" + "tend.;" + "1: ;" + : : : "memory", "r0"); + /* If we reach here, we've passed. Otherwise we've probably crashed + * the kernel */ + + return 0; +} + +int main(int argc, char *argv[]) +{ + return test_harness(test_fork, "tm_fork"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c b/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c new file mode 100644 index 000000000..4cdb83964 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Test context switching to see if the DSCR SPR is correctly preserved + * when within a transaction. + * + * Note: We assume that the DSCR has been left at the default value (0) + * for all CPUs. + * + * Method: + * + * Set a value into the DSCR. + * + * Start a transaction, and suspend it (*). + * + * Hard loop checking to see if the transaction has become doomed. + * + * Now that we *may* have been preempted, record the DSCR and TEXASR SPRS. + * + * If the abort was because of a context switch, check the DSCR value. + * Otherwise, try again. + * + * (*) If the transaction is not suspended we can't see the problem because + * the transaction abort handler will restore the DSCR to it's checkpointed + * value before we regain control. + */ + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <asm/tm.h> + +#include "utils.h" +#include "tm.h" +#include "../pmu/lib.h" + +#define SPRN_DSCR 0x03 + +int test_body(void) +{ + uint64_t rv, dscr1 = 1, dscr2, texasr; + + SKIP_IF(!have_htm()); + + printf("Check DSCR TM context switch: "); + fflush(stdout); + for (;;) { + asm __volatile__ ( + /* set a known value into the DSCR */ + "ld 3, %[dscr1];" + "mtspr %[sprn_dscr], 3;" + + "li %[rv], 1;" + /* start and suspend a transaction */ + "tbegin.;" + "beq 1f;" + "tsuspend.;" + + /* hard loop until the transaction becomes doomed */ + "2: ;" + "tcheck 0;" + "bc 4, 0, 2b;" + + /* record DSCR and TEXASR */ + "mfspr 3, %[sprn_dscr];" + "std 3, %[dscr2];" + "mfspr 3, %[sprn_texasr];" + "std 3, %[texasr];" + + "tresume.;" + "tend.;" + "li %[rv], 0;" + "1: ;" + : [rv]"=r"(rv), [dscr2]"=m"(dscr2), [texasr]"=m"(texasr) + : [dscr1]"m"(dscr1) + , [sprn_dscr]"i"(SPRN_DSCR), [sprn_texasr]"i"(SPRN_TEXASR) + : "memory", "r3" + ); + assert(rv); /* make sure the transaction aborted */ + if ((texasr >> 56) != TM_CAUSE_RESCHED) { + continue; + } + if (dscr2 != dscr1) { + printf(" FAIL\n"); + return 1; + } else { + printf(" OK\n"); + return 0; + } + } +} + +static int tm_resched_dscr(void) +{ + return eat_cpu(test_body); +} + +int main(int argc, const char *argv[]) +{ + return test_harness(tm_resched_dscr, "tm_resched_dscr"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-fpu.c b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-fpu.c new file mode 100644 index 000000000..c760debbd --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-fpu.c @@ -0,0 +1,92 @@ +/* + * Copyright 2016, Cyril Bur, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * + * Test the kernel's signal frame code. + * + * The kernel sets up two sets of ucontexts if the signal was to be + * delivered while the thread was in a transaction. + * Expected behaviour is that the checkpointed state is in the user + * context passed to the signal handler. The speculated state can be + * accessed with the uc_link pointer. + * + * The rationale for this is that if TM unaware code (which linked + * against TM libs) installs a signal handler it will not know of the + * speculative nature of the 'live' registers and may infer the wrong + * thing. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <unistd.h> + +#include <altivec.h> + +#include "utils.h" +#include "tm.h" + +#define MAX_ATTEMPT 500000 + +#define NV_FPU_REGS 18 + +long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss); + +/* Be sure there are 2x as many as there are NV FPU regs (2x18) */ +static double fps[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + -1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15,-16,-17,-18 +}; + +static sig_atomic_t fail; + +static void signal_usr1(int signum, siginfo_t *info, void *uc) +{ + int i; + ucontext_t *ucp = uc; + ucontext_t *tm_ucp = ucp->uc_link; + + for (i = 0; i < NV_FPU_REGS && !fail; i++) { + fail = (ucp->uc_mcontext.fp_regs[i + 14] != fps[i]); + fail |= (tm_ucp->uc_mcontext.fp_regs[i + 14] != fps[i + NV_FPU_REGS]); + if (fail) + printf("Failed on %d FP %g or %g\n", i, ucp->uc_mcontext.fp_regs[i + 14], tm_ucp->uc_mcontext.fp_regs[i + 14]); + } +} + +static int tm_signal_context_chk_fpu() +{ + struct sigaction act; + int i; + long rc; + pid_t pid = getpid(); + + SKIP_IF(!have_htm()); + + act.sa_sigaction = signal_usr1; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGUSR1, &act, NULL) < 0) { + perror("sigaction sigusr1"); + exit(1); + } + + i = 0; + while (i < MAX_ATTEMPT && !fail) { + rc = tm_signal_self_context_load(pid, NULL, fps, NULL, NULL); + FAIL_IF(rc != pid); + i++; + } + + return fail; +} + +int main(void) +{ + return test_harness(tm_signal_context_chk_fpu, "tm_signal_context_chk_fpu"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-gpr.c b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-gpr.c new file mode 100644 index 000000000..df91330a0 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-gpr.c @@ -0,0 +1,90 @@ +/* + * Copyright 2016, Cyril Bur, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * + * Test the kernel's signal frame code. + * + * The kernel sets up two sets of ucontexts if the signal was to be + * delivered while the thread was in a transaction. + * Expected behaviour is that the checkpointed state is in the user + * context passed to the signal handler. The speculated state can be + * accessed with the uc_link pointer. + * + * The rationale for this is that if TM unaware code (which linked + * against TM libs) installs a signal handler it will not know of the + * speculative nature of the 'live' registers and may infer the wrong + * thing. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <unistd.h> + +#include <altivec.h> + +#include "utils.h" +#include "tm.h" + +#define MAX_ATTEMPT 500000 + +#define NV_GPR_REGS 18 + +long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss); + +static sig_atomic_t fail; + +static long gps[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + -1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15,-16,-17,-18}; + +static void signal_usr1(int signum, siginfo_t *info, void *uc) +{ + int i; + ucontext_t *ucp = uc; + ucontext_t *tm_ucp = ucp->uc_link; + + for (i = 0; i < NV_GPR_REGS && !fail; i++) { + fail = (ucp->uc_mcontext.gp_regs[i + 14] != gps[i]); + fail |= (tm_ucp->uc_mcontext.gp_regs[i + 14] != gps[i + NV_GPR_REGS]); + if (fail) + printf("Failed on %d GPR %lu or %lu\n", i, + ucp->uc_mcontext.gp_regs[i + 14], tm_ucp->uc_mcontext.gp_regs[i + 14]); + } +} + +static int tm_signal_context_chk_gpr() +{ + struct sigaction act; + int i; + long rc; + pid_t pid = getpid(); + + SKIP_IF(!have_htm()); + + act.sa_sigaction = signal_usr1; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGUSR1, &act, NULL) < 0) { + perror("sigaction sigusr1"); + exit(1); + } + + i = 0; + while (i < MAX_ATTEMPT && !fail) { + rc = tm_signal_self_context_load(pid, gps, NULL, NULL, NULL); + FAIL_IF(rc != pid); + i++; + } + + return fail; +} + +int main(void) +{ + return test_harness(tm_signal_context_chk_gpr, "tm_signal_context_chk_gpr"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-vmx.c b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-vmx.c new file mode 100644 index 000000000..f0ee55fd5 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-vmx.c @@ -0,0 +1,110 @@ +/* + * Copyright 2016, Cyril Bur, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * + * Test the kernel's signal frame code. + * + * The kernel sets up two sets of ucontexts if the signal was to be + * delivered while the thread was in a transaction. + * Expected behaviour is that the checkpointed state is in the user + * context passed to the signal handler. The speculated state can be + * accessed with the uc_link pointer. + * + * The rationale for this is that if TM unaware code (which linked + * against TM libs) installs a signal handler it will not know of the + * speculative nature of the 'live' registers and may infer the wrong + * thing. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> + +#include <altivec.h> + +#include "utils.h" +#include "tm.h" + +#define MAX_ATTEMPT 500000 + +#define NV_VMX_REGS 12 + +long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss); + +static sig_atomic_t fail; + +vector int vms[] = { + {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}, + {-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} +}; + +static void signal_usr1(int signum, siginfo_t *info, void *uc) +{ + int i; + ucontext_t *ucp = uc; + ucontext_t *tm_ucp = ucp->uc_link; + + for (i = 0; i < NV_VMX_REGS && !fail; i++) { + fail = memcmp(ucp->uc_mcontext.v_regs->vrregs[i + 20], + &vms[i], sizeof(vector int)); + fail |= memcmp(tm_ucp->uc_mcontext.v_regs->vrregs[i + 20], + &vms[i + NV_VMX_REGS], sizeof (vector int)); + + if (fail) { + int j; + + fprintf(stderr, "Failed on %d vmx 0x", i); + for (j = 0; j < 4; j++) + fprintf(stderr, "%04x", ucp->uc_mcontext.v_regs->vrregs[i + 20][j]); + fprintf(stderr, " vs 0x"); + for (j = 0 ; j < 4; j++) + fprintf(stderr, "%04x", tm_ucp->uc_mcontext.v_regs->vrregs[i + 20][j]); + fprintf(stderr, "\n"); + } + } +} + +static int tm_signal_context_chk() +{ + struct sigaction act; + int i; + long rc; + pid_t pid = getpid(); + + SKIP_IF(!have_htm()); + + act.sa_sigaction = signal_usr1; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGUSR1, &act, NULL) < 0) { + perror("sigaction sigusr1"); + exit(1); + } + + i = 0; + while (i < MAX_ATTEMPT && !fail) { + rc = tm_signal_self_context_load(pid, NULL, NULL, vms, NULL); + FAIL_IF(rc != pid); + i++; + } + + return fail; +} + +int main(void) +{ + return test_harness(tm_signal_context_chk, "tm_signal_context_chk_vmx"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-vsx.c b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-vsx.c new file mode 100644 index 000000000..b99c3d835 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal-context-chk-vsx.c @@ -0,0 +1,125 @@ +/* + * Copyright 2016, Cyril Bur, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * + * Test the kernel's signal frame code. + * + * The kernel sets up two sets of ucontexts if the signal was to be + * delivered while the thread was in a transaction. + * Expected behaviour is that the checkpointed state is in the user + * context passed to the signal handler. The speculated state can be + * accessed with the uc_link pointer. + * + * The rationale for this is that if TM unaware code (which linked + * against TM libs) installs a signal handler it will not know of the + * speculative nature of the 'live' registers and may infer the wrong + * thing. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> + +#include <altivec.h> + +#include "utils.h" +#include "tm.h" + +#define MAX_ATTEMPT 500000 + +#define NV_VSX_REGS 12 + +long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss); + +static sig_atomic_t fail; + +vector int vss[] = { + {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}, + {-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} +}; + +static void signal_usr1(int signum, siginfo_t *info, void *uc) +{ + int i; + uint8_t vsc[sizeof(vector int)]; + uint8_t vst[sizeof(vector int)]; + ucontext_t *ucp = uc; + ucontext_t *tm_ucp = ucp->uc_link; + + /* + * The other half of the VSX regs will be after v_regs. + * + * In short, vmx_reserve array holds everything. v_regs is a 16 + * byte aligned pointer at the start of vmx_reserve (vmx_reserve + * may or may not be 16 aligned) where the v_regs structure exists. + * (half of) The VSX regsters are directly after v_regs so the + * easiest way to find them below. + */ + long *vsx_ptr = (long *)(ucp->uc_mcontext.v_regs + 1); + long *tm_vsx_ptr = (long *)(tm_ucp->uc_mcontext.v_regs + 1); + for (i = 0; i < NV_VSX_REGS && !fail; i++) { + memcpy(vsc, &ucp->uc_mcontext.fp_regs[i + 20], 8); + memcpy(vsc + 8, &vsx_ptr[20 + i], 8); + fail = memcmp(vsc, &vss[i], sizeof(vector int)); + memcpy(vst, &tm_ucp->uc_mcontext.fp_regs[i + 20], 8); + memcpy(vst + 8, &tm_vsx_ptr[20 + i], 8); + fail |= memcmp(vst, &vss[i + NV_VSX_REGS], sizeof(vector int)); + + if (fail) { + int j; + + fprintf(stderr, "Failed on %d vsx 0x", i); + for (j = 0; j < 16; j++) + fprintf(stderr, "%02x", vsc[j]); + fprintf(stderr, " vs 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%02x", vst[j]); + fprintf(stderr, "\n"); + } + } +} + +static int tm_signal_context_chk() +{ + struct sigaction act; + int i; + long rc; + pid_t pid = getpid(); + + SKIP_IF(!have_htm()); + + act.sa_sigaction = signal_usr1; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGUSR1, &act, NULL) < 0) { + perror("sigaction sigusr1"); + exit(1); + } + + i = 0; + while (i < MAX_ATTEMPT && !fail) { + rc = tm_signal_self_context_load(pid, NULL, NULL, NULL, vss); + FAIL_IF(rc != pid); + i++; + } + + return fail; +} + +int main(void) +{ + return test_harness(tm_signal_context_chk, "tm_signal_context_chk_vsx"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-msr-resv.c b/tools/testing/selftests/powerpc/tm/tm-signal-msr-resv.c new file mode 100644 index 000000000..8c54d18b3 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal-msr-resv.c @@ -0,0 +1,74 @@ +/* + * Copyright 2015, Michael Neuling, IBM Corp. + * Licensed under GPLv2. + * + * Test the kernel's signal return code to ensure that it doesn't + * crash when both the transactional and suspend MSR bits are set in + * the signal context. + * + * For this test, we send ourselves a SIGUSR1. In the SIGUSR1 handler + * we modify the signal context to set both MSR TM S and T bits (which + * is "reserved" by the PowerISA). When we return from the signal + * handler (implicit sigreturn), the kernel should detect reserved MSR + * value and send us with a SIGSEGV. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <unistd.h> + +#include "utils.h" +#include "tm.h" + +int segv_expected = 0; + +void signal_segv(int signum) +{ + if (segv_expected && (signum == SIGSEGV)) + _exit(0); + _exit(1); +} + +void signal_usr1(int signum, siginfo_t *info, void *uc) +{ + ucontext_t *ucp = uc; + + /* Link tm checkpointed context to normal context */ + ucp->uc_link = ucp; + /* Set all TM bits so that the context is now invalid */ +#ifdef __powerpc64__ + ucp->uc_mcontext.gp_regs[PT_MSR] |= (7ULL << 32); +#else + ucp->uc_mcontext.uc_regs->gregs[PT_MSR] |= (7ULL); +#endif + /* Should segv on return becuase of invalid context */ + segv_expected = 1; +} + +int tm_signal_msr_resv() +{ + struct sigaction act; + + SKIP_IF(!have_htm()); + + act.sa_sigaction = signal_usr1; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGUSR1, &act, NULL) < 0) { + perror("sigaction sigusr1"); + exit(1); + } + if (signal(SIGSEGV, signal_segv) == SIG_ERR) + exit(1); + + raise(SIGUSR1); + + /* We shouldn't get here as we exit in the segv handler */ + return 1; +} + +int main(void) +{ + return test_harness(tm_signal_msr_resv, "tm_signal_msr_resv"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-stack.c b/tools/testing/selftests/powerpc/tm/tm-signal-stack.c new file mode 100644 index 000000000..1f0eb5674 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal-stack.c @@ -0,0 +1,76 @@ +/* + * Copyright 2015, Michael Neuling, IBM Corp. + * Licensed under GPLv2. + * + * Test the kernel's signal delievery code to ensure that we don't + * trelaim twice in the kernel signal delivery code. This can happen + * if we trigger a signal when in a transaction and the stack pointer + * is bogus. + * + * This test case registers a SEGV handler, sets the stack pointer + * (r1) to NULL, starts a transaction and then generates a SEGV. The + * SEGV should be handled but we exit here as the stack pointer is + * invalid and hance we can't sigreturn. We only need to check that + * this flow doesn't crash the kernel. + */ + +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#include "utils.h" +#include "tm.h" + +void signal_segv(int signum) +{ + /* This should never actually run since stack is foobar */ + exit(1); +} + +int tm_signal_stack() +{ + int pid; + + SKIP_IF(!have_htm()); + + pid = fork(); + if (pid < 0) + exit(1); + + if (pid) { /* Parent */ + /* + * It's likely the whole machine will crash here so if + * the child ever exits, we are good. + */ + wait(NULL); + return 0; + } + + /* + * The flow here is: + * 1) register a signal handler (so signal delievery occurs) + * 2) make stack pointer (r1) = NULL + * 3) start transaction + * 4) cause segv + */ + if (signal(SIGSEGV, signal_segv) == SIG_ERR) + exit(1); + asm volatile("li 1, 0 ;" /* stack ptr == NULL */ + "1:" + "tbegin.;" + "beq 1b ;" /* retry forever */ + "tsuspend.;" + "ld 2, 0(1) ;" /* trigger segv" */ + : : : "memory"); + + /* This should never get here due to above segv */ + return 1; +} + +int main(void) +{ + return test_harness(tm_signal_stack, "tm_signal_stack"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-signal.S b/tools/testing/selftests/powerpc/tm/tm-signal.S new file mode 100644 index 000000000..506a4ebaf --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-signal.S @@ -0,0 +1,114 @@ +/* + * Copyright 2015, Cyril Bur, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include "basic_asm.h" +#include "gpr_asm.h" +#include "fpu_asm.h" +#include "vmx_asm.h" +#include "vsx_asm.h" + +/* + * Large caveat here being that the caller cannot expect the + * signal to always be sent! The hardware can (AND WILL!) abort + * the transaction between the tbegin and the tsuspend (however + * unlikely it seems or infrequently it actually happens). + * You have been warned. + */ +/* long tm_signal_self(pid_t pid, long *gprs, double *fps, vector *vms, vector *vss); */ +FUNC_START(tm_signal_self_context_load) + PUSH_BASIC_STACK(512) + /* + * Don't strictly need to save and restore as it depends on if + * we're going to use them, however this reduces messy logic + */ + PUSH_VMX(STACK_FRAME_LOCAL(5,0),r8) + PUSH_FPU(512) + PUSH_NVREGS_BELOW_FPU(512) + std r3, STACK_FRAME_PARAM(0)(sp) /* pid */ + std r4, STACK_FRAME_PARAM(1)(sp) /* gps */ + std r5, STACK_FRAME_PARAM(2)(sp) /* fps */ + std r6, STACK_FRAME_PARAM(3)(sp) /* vms */ + std r7, STACK_FRAME_PARAM(4)(sp) /* vss */ + + ld r3, STACK_FRAME_PARAM(1)(sp) + cmpdi r3, 0 + beq skip_gpr_lc + bl load_gpr +skip_gpr_lc: + ld r3, STACK_FRAME_PARAM(2)(sp) + cmpdi r3, 0 + beq skip_fpu_lc + bl load_fpu +skip_fpu_lc: + ld r3, STACK_FRAME_PARAM(3)(sp) + cmpdi r3, 0 + beq skip_vmx_lc + bl load_vmx +skip_vmx_lc: + ld r3, STACK_FRAME_PARAM(4)(sp) + cmpdi r3, 0 + beq skip_vsx_lc + bl load_vsx +skip_vsx_lc: + /* + * Set r3 (return value) before tbegin. Use the pid as a known + * 'all good' return value, zero is used to indicate a non-doomed + * transaction. + */ + ld r3, STACK_FRAME_PARAM(0)(sp) + tbegin. + beq 1f + tsuspend. /* Can't enter a syscall transactionally */ + ld r3, STACK_FRAME_PARAM(1)(sp) + cmpdi r3, 0 + beq skip_gpr_lt + /* Get the second half of the array */ + addi r3, r3, 8 * 18 + bl load_gpr +skip_gpr_lt: + ld r3, STACK_FRAME_PARAM(2)(sp) + cmpdi r3, 0 + beq skip_fpu_lt + /* Get the second half of the array */ + addi r3, r3, 8 * 18 + bl load_fpu +skip_fpu_lt: + ld r3, STACK_FRAME_PARAM(3)(sp) + cmpdi r3, 0 + beq skip_vmx_lt + /* Get the second half of the array */ + addi r3, r3, 16 * 12 + bl load_vmx +skip_vmx_lt: + ld r3, STACK_FRAME_PARAM(4)(sp) + cmpdi r3, 0 + beq skip_vsx_lt + /* Get the second half of the array */ + addi r3, r3, 16 * 12 + bl load_vsx +skip_vsx_lt: + li r0, 37 /* sys_kill */ + ld r3, STACK_FRAME_PARAM(0)(sp) /* pid */ + li r4, 10 /* SIGUSR1 */ + sc /* Taking the signal will doom the transaction */ + tabort. 0 + tresume. /* Be super sure we abort */ + /* + * This will cause us to resume doomed transaction and cause + * hardware to cleanup, we'll end up at 1: anything between + * tresume. and 1: shouldn't ever run. + */ + li r3, 0 + 1: + POP_VMX(STACK_FRAME_LOCAL(5,0),r4) + POP_FPU(512) + POP_NVREGS_BELOW_FPU(512) + POP_BASIC_STACK(512) + blr +FUNC_END(tm_signal_self_context_load) diff --git a/tools/testing/selftests/powerpc/tm/tm-sigreturn.c b/tools/testing/selftests/powerpc/tm/tm-sigreturn.c new file mode 100644 index 000000000..9a6017a1d --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-sigreturn.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright 2015, Laurent Dufour, IBM Corp. + * + * Test the kernel's signal returning code to check reclaim is done if the + * sigreturn() is called while in a transaction (suspended since active is + * already dropped trough the system call path). + * + * The kernel must discard the transaction when entering sigreturn, since + * restoring the potential TM SPRS from the signal frame is requiring to not be + * in a transaction. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "tm.h" +#include "utils.h" + + +void handler(int sig) +{ + uint64_t ret; + + asm __volatile__( + "li 3,1 ;" + "tbegin. ;" + "beq 1f ;" + "li 3,0 ;" + "tsuspend. ;" + "1: ;" + "std%X[ret] 3, %[ret] ;" + : [ret] "=m"(ret) + : + : "memory", "3", "cr0"); + + if (ret) + exit(1); + + /* + * We return from the signal handle while in a suspended transaction + */ +} + + +int tm_sigreturn(void) +{ + struct sigaction sa; + uint64_t ret = 0; + + SKIP_IF(!have_htm()); + SKIP_IF(!is_ppc64le()); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + + if (sigaction(SIGSEGV, &sa, NULL)) + exit(1); + + asm __volatile__( + "tbegin. ;" + "beq 1f ;" + "li 3,0 ;" + "std 3,0(3) ;" /* trigger SEGV */ + "li 3,1 ;" + "std%X[ret] 3,%[ret] ;" + "tend. ;" + "b 2f ;" + "1: ;" + "li 3,2 ;" + "std%X[ret] 3,%[ret] ;" + "2: ;" + : [ret] "=m"(ret) + : + : "memory", "3", "cr0"); + + if (ret != 2) + exit(1); + + exit(0); +} + +int main(void) +{ + return test_harness(tm_sigreturn, "tm_sigreturn"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-syscall-asm.S b/tools/testing/selftests/powerpc/tm/tm-syscall-asm.S new file mode 100644 index 000000000..bd1ca25fe --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-syscall-asm.S @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <ppc-asm.h> +#include <asm/unistd.h> + + .text +FUNC_START(getppid_tm_active) + tbegin. + beq 1f + li r0, __NR_getppid + sc + tend. + blr +1: + li r3, -1 + blr + +FUNC_START(getppid_tm_suspended) + tbegin. + beq 1f + li r0, __NR_getppid + tsuspend. + sc + tresume. + tend. + blr +1: + li r3, -1 + blr diff --git a/tools/testing/selftests/powerpc/tm/tm-syscall.c b/tools/testing/selftests/powerpc/tm/tm-syscall.c new file mode 100644 index 000000000..454b965a2 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-syscall.c @@ -0,0 +1,106 @@ +/* + * Copyright 2015, Sam Bobroff, IBM Corp. + * Licensed under GPLv2. + * + * Test the kernel's system call code to ensure that a system call + * made from within an active HTM transaction is aborted with the + * correct failure code. + * Conversely, ensure that a system call made from within a + * suspended transaction can succeed. + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <asm/tm.h> +#include <sys/time.h> +#include <stdlib.h> + +#include "utils.h" +#include "tm.h" + +extern int getppid_tm_active(void); +extern int getppid_tm_suspended(void); + +unsigned retries = 0; + +#define TEST_DURATION 10 /* seconds */ +#define TM_RETRIES 100 + +pid_t getppid_tm(bool suspend) +{ + int i; + pid_t pid; + + for (i = 0; i < TM_RETRIES; i++) { + if (suspend) + pid = getppid_tm_suspended(); + else + pid = getppid_tm_active(); + + if (pid >= 0) + return pid; + + if (failure_is_persistent()) { + if (failure_is_syscall()) + return -1; + + printf("Unexpected persistent transaction failure.\n"); + printf("TEXASR 0x%016lx, TFIAR 0x%016lx.\n", + __builtin_get_texasr(), __builtin_get_tfiar()); + exit(-1); + } + + retries++; + } + + printf("Exceeded limit of %d temporary transaction failures.\n", TM_RETRIES); + printf("TEXASR 0x%016lx, TFIAR 0x%016lx.\n", + __builtin_get_texasr(), __builtin_get_tfiar()); + + exit(-1); +} + +int tm_syscall(void) +{ + unsigned count = 0; + struct timeval end, now; + + SKIP_IF(!have_htm_nosc()); + + setbuf(stdout, NULL); + + printf("Testing transactional syscalls for %d seconds...\n", TEST_DURATION); + + gettimeofday(&end, NULL); + now.tv_sec = TEST_DURATION; + now.tv_usec = 0; + timeradd(&end, &now, &end); + + for (count = 0; timercmp(&now, &end, <); count++) { + /* + * Test a syscall within a suspended transaction and verify + * that it succeeds. + */ + FAIL_IF(getppid_tm(true) == -1); /* Should succeed. */ + + /* + * Test a syscall within an active transaction and verify that + * it fails with the correct failure code. + */ + FAIL_IF(getppid_tm(false) != -1); /* Should fail... */ + FAIL_IF(!failure_is_persistent()); /* ...persistently... */ + FAIL_IF(!failure_is_syscall()); /* ...with code syscall. */ + gettimeofday(&now, 0); + } + + printf("%d active and suspended transactions behaved correctly.\n", count); + printf("(There were %d transaction retries.)\n", retries); + + return 0; +} + +int main(void) +{ + return test_harness(tm_syscall, "tm_syscall"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-tar.c b/tools/testing/selftests/powerpc/tm/tm-tar.c new file mode 100644 index 000000000..f31fe5a28 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-tar.c @@ -0,0 +1,91 @@ +/* + * Copyright 2015, Michael Neuling, IBM Corp. + * Licensed under GPLv2. + * Original: Michael Neuling 19/7/2013 + * Edited: Rashmica Gupta 01/12/2015 + * + * Do some transactions, see if the tar is corrupted. + * If the transaction is aborted, the TAR should be rolled back to the + * checkpointed value before the transaction began. The value written to + * TAR in suspended mode should only remain in TAR if the transaction + * completes. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "tm.h" +#include "utils.h" + +int num_loops = 10000; + +int test_tar(void) +{ + int i; + + SKIP_IF(!have_htm()); + SKIP_IF(!is_ppc64le()); + + for (i = 0; i < num_loops; i++) + { + uint64_t result = 0; + asm __volatile__( + "li 7, 1;" + "mtspr %[tar], 7;" /* tar = 1 */ + "tbegin.;" + "beq 3f;" + "li 4, 0x7000;" /* Loop lots, to use time */ + "2:;" /* Start loop */ + "li 7, 2;" + "mtspr %[tar], 7;" /* tar = 2 */ + "tsuspend.;" + "li 7, 3;" + "mtspr %[tar], 7;" /* tar = 3 */ + "tresume.;" + "subi 4, 4, 1;" + "cmpdi 4, 0;" + "bne 2b;" + "tend.;" + + /* Transaction sucess! TAR should be 3 */ + "mfspr 7, %[tar];" + "ori %[res], 7, 4;" // res = 3|4 = 7 + "b 4f;" + + /* Abort handler. TAR should be rolled back to 1 */ + "3:;" + "mfspr 7, %[tar];" + "ori %[res], 7, 8;" // res = 1|8 = 9 + "4:;" + + : [res]"=r"(result) + : [tar]"i"(SPRN_TAR) + : "memory", "r0", "r4", "r7"); + + /* If result is anything else other than 7 or 9, the tar + * value must have been corrupted. */ + if ((result != 7) && (result != 9)) + return 1; + } + return 0; +} + +int main(int argc, char *argv[]) +{ + /* A low number of iterations (eg 100) can cause a false pass */ + if (argc > 1) { + if (strcmp(argv[1], "-h") == 0) { + printf("Syntax:\n\t%s [<num loops>]\n", + argv[0]); + return 1; + } else { + num_loops = atoi(argv[1]); + } + } + + printf("Starting, %d loops\n", num_loops); + + return test_harness(test_tar, "tm_tar"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-tmspr.c b/tools/testing/selftests/powerpc/tm/tm-tmspr.c new file mode 100644 index 000000000..df1d7d4b1 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-tmspr.c @@ -0,0 +1,150 @@ +/* + * Copyright 2015, Michael Neuling, IBM Corp. + * Licensed under GPLv2. + * + * Original: Michael Neuling 3/4/2014 + * Modified: Rashmica Gupta 8/12/2015 + * + * Check if any of the Transaction Memory SPRs get corrupted. + * - TFIAR - stores address of location of transaction failure + * - TFHAR - stores address of software failure handler (if transaction + * fails) + * - TEXASR - lots of info about the transacion(s) + * + * (1) create more threads than cpus + * (2) in each thread: + * (a) set TFIAR and TFHAR a unique value + * (b) loop for awhile, continually checking to see if + * either register has been corrupted. + * + * (3) Loop: + * (a) begin transaction + * (b) abort transaction + * (c) check TEXASR to see if FS has been corrupted + * + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <string.h> + +#include "utils.h" +#include "tm.h" + +int num_loops = 10000; +int passed = 1; + +void tfiar_tfhar(void *in) +{ + int i, cpu; + unsigned long tfhar, tfhar_rd, tfiar, tfiar_rd; + cpu_set_t cpuset; + + CPU_ZERO(&cpuset); + cpu = (unsigned long)in >> 1; + CPU_SET(cpu, &cpuset); + sched_setaffinity(0, sizeof(cpuset), &cpuset); + + /* TFIAR: Last bit has to be high so userspace can read register */ + tfiar = ((unsigned long)in) + 1; + tfiar += 2; + mtspr(SPRN_TFIAR, tfiar); + + /* TFHAR: Last two bits are reserved */ + tfhar = ((unsigned long)in); + tfhar &= ~0x3UL; + tfhar += 4; + mtspr(SPRN_TFHAR, tfhar); + + for (i = 0; i < num_loops; i++) { + tfhar_rd = mfspr(SPRN_TFHAR); + tfiar_rd = mfspr(SPRN_TFIAR); + if ( (tfhar != tfhar_rd) || (tfiar != tfiar_rd) ) { + passed = 0; + return; + } + } + return; +} + +void texasr(void *in) +{ + unsigned long i; + uint64_t result = 0; + + for (i = 0; i < num_loops; i++) { + asm __volatile__( + "tbegin.;" + "beq 3f ;" + "tabort. 0 ;" + "tend.;" + + /* Abort handler */ + "3: ;" + ::: "memory"); + + /* Check the TEXASR */ + result = mfspr(SPRN_TEXASR); + if ((result & TEXASR_FS) == 0) { + passed = 0; + return; + } + } + return; +} + +int test_tmspr() +{ + pthread_t *thread; + int thread_num; + unsigned long i; + + SKIP_IF(!have_htm()); + + /* To cause some context switching */ + thread_num = 10 * sysconf(_SC_NPROCESSORS_ONLN); + + thread = malloc(thread_num * sizeof(pthread_t)); + if (thread == NULL) + return EXIT_FAILURE; + + /* Test TFIAR and TFHAR */ + for (i = 0; i < thread_num; i += 2) { + if (pthread_create(&thread[i], NULL, (void *)tfiar_tfhar, + (void *)i)) + return EXIT_FAILURE; + } + /* Test TEXASR */ + for (i = 1; i < thread_num; i += 2) { + if (pthread_create(&thread[i], NULL, (void *)texasr, (void *)i)) + return EXIT_FAILURE; + } + + for (i = 0; i < thread_num; i++) { + if (pthread_join(thread[i], NULL) != 0) + return EXIT_FAILURE; + } + + free(thread); + + if (passed) + return 0; + else + return 1; +} + +int main(int argc, char *argv[]) +{ + if (argc > 1) { + if (strcmp(argv[1], "-h") == 0) { + printf("Syntax:\t [<num loops>]\n"); + return 0; + } else { + num_loops = atoi(argv[1]); + } + } + return test_harness(test_tmspr, "tm_tmspr"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-trap.c b/tools/testing/selftests/powerpc/tm/tm-trap.c new file mode 100644 index 000000000..179d592f0 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-trap.c @@ -0,0 +1,331 @@ +/* + * Copyright 2017, Gustavo Romero, IBM Corp. + * Licensed under GPLv2. + * + * Check if thread endianness is flipped inadvertently to BE on trap + * caught in TM whilst MSR.FP and MSR.VEC are zero (i.e. just after + * load_fp and load_vec overflowed). + * + * The issue can be checked on LE machines simply by zeroing load_fp + * and load_vec and then causing a trap in TM. Since the endianness + * changes to BE on return from the signal handler, 'nop' is + * thread as an illegal instruction in following sequence: + * tbegin. + * beq 1f + * trap + * tend. + * 1: nop + * + * However, although the issue is also present on BE machines, it's a + * bit trickier to check it on BE machines because MSR.LE bit is set + * to zero which determines a BE endianness that is the native + * endianness on BE machines, so nothing notably critical happens, + * i.e. no illegal instruction is observed immediately after returning + * from the signal handler (as it happens on LE machines). Thus to test + * it on BE machines LE endianness is forced after a first trap and then + * the endianness is verified on subsequent traps to determine if the + * endianness "flipped back" to the native endianness (BE). + */ + +#define _GNU_SOURCE +#include <error.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <htmintrin.h> +#include <inttypes.h> +#include <pthread.h> +#include <sched.h> +#include <signal.h> +#include <stdbool.h> + +#include "tm.h" +#include "utils.h" + +#define pr_error(error_code, format, ...) \ + error_at_line(1, error_code, __FILE__, __LINE__, format, ##__VA_ARGS__) + +#define MSR_LE 1UL +#define LE 1UL + +pthread_t t0_ping; +pthread_t t1_pong; + +int exit_from_pong; + +int trap_event; +int le; + +bool success; + +void trap_signal_handler(int signo, siginfo_t *si, void *uc) +{ + ucontext_t *ucp = uc; + uint64_t thread_endianness; + + /* Get thread endianness: extract bit LE from MSR */ + thread_endianness = MSR_LE & ucp->uc_mcontext.gp_regs[PT_MSR]; + + /*** + * Little-Endian Machine + */ + + if (le) { + /* First trap event */ + if (trap_event == 0) { + /* Do nothing. Since it is returning from this trap + * event that endianness is flipped by the bug, so just + * let the process return from the signal handler and + * check on the second trap event if endianness is + * flipped or not. + */ + } + /* Second trap event */ + else if (trap_event == 1) { + /* + * Since trap was caught in TM on first trap event, if + * endianness was still LE (not flipped inadvertently) + * after returning from the signal handler instruction + * (1) is executed (basically a 'nop'), as it's located + * at address of tbegin. +4 (rollback addr). As (1) on + * LE endianness does in effect nothing, instruction (2) + * is then executed again as 'trap', generating a second + * trap event (note that in that case 'trap' is caught + * not in transacional mode). On te other hand, if after + * the return from the signal handler the endianness in- + * advertently flipped, instruction (1) is tread as a + * branch instruction, i.e. b .+8, hence instruction (3) + * and (4) are executed (tbegin.; trap;) and we get sim- + * ilaly on the trap signal handler, but now in TM mode. + * Either way, it's now possible to check the MSR LE bit + * once in the trap handler to verify if endianness was + * flipped or not after the return from the second trap + * event. If endianness is flipped, the bug is present. + * Finally, getting a trap in TM mode or not is just + * worth noting because it affects the math to determine + * the offset added to the NIP on return: the NIP for a + * trap caught in TM is the rollback address, i.e. the + * next instruction after 'tbegin.', whilst the NIP for + * a trap caught in non-transactional mode is the very + * same address of the 'trap' instruction that generated + * the trap event. + */ + + if (thread_endianness == LE) { + /* Go to 'success', i.e. instruction (6) */ + ucp->uc_mcontext.gp_regs[PT_NIP] += 16; + } else { + /* + * Thread endianness is BE, so it flipped + * inadvertently. Thus we flip back to LE and + * set NIP to go to 'failure', instruction (5). + */ + ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL; + ucp->uc_mcontext.gp_regs[PT_NIP] += 4; + } + } + } + + /*** + * Big-Endian Machine + */ + + else { + /* First trap event */ + if (trap_event == 0) { + /* + * Force thread endianness to be LE. Instructions (1), + * (3), and (4) will be executed, generating a second + * trap in TM mode. + */ + ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL; + } + /* Second trap event */ + else if (trap_event == 1) { + /* + * Do nothing. If bug is present on return from this + * second trap event endianness will flip back "automat- + * ically" to BE, otherwise thread endianness will + * continue to be LE, just as it was set above. + */ + } + /* A third trap event */ + else { + /* + * Once here it means that after returning from the sec- + * ond trap event instruction (4) (trap) was executed + * as LE, generating a third trap event. In that case + * endianness is still LE as set on return from the + * first trap event, hence no bug. Otherwise, bug + * flipped back to BE on return from the second trap + * event and instruction (4) was executed as 'tdi' (so + * basically a 'nop') and branch to 'failure' in + * instruction (5) was taken to indicate failure and we + * never get here. + */ + + /* + * Flip back to BE and go to instruction (6), i.e. go to + * 'success'. + */ + ucp->uc_mcontext.gp_regs[PT_MSR] &= ~1UL; + ucp->uc_mcontext.gp_regs[PT_NIP] += 8; + } + } + + trap_event++; +} + +void usr1_signal_handler(int signo, siginfo_t *si, void *not_used) +{ + /* Got a USR1 signal from ping(), so just tell pong() to exit */ + exit_from_pong = 1; +} + +void *ping(void *not_used) +{ + uint64_t i; + + trap_event = 0; + + /* + * Wait an amount of context switches so load_fp and load_vec overflows + * and MSR_[FP|VEC|V] is 0. + */ + for (i = 0; i < 1024*1024*512; i++) + ; + + asm goto( + /* + * [NA] means "Native Endianness", i.e. it tells how a + * instruction is executed on machine's native endianness (in + * other words, native endianness matches kernel endianness). + * [OP] means "Opposite Endianness", i.e. on a BE machine, it + * tells how a instruction is executed as a LE instruction; con- + * versely, on a LE machine, it tells how a instruction is + * executed as a BE instruction. When [NA] is omitted, it means + * that the native interpretation of a given instruction is not + * relevant for the test. Likewise when [OP] is omitted. + */ + + " tbegin. ;" /* (0) tbegin. [NA] */ + " tdi 0, 0, 0x48;" /* (1) nop [NA]; b (3) [OP] */ + " trap ;" /* (2) trap [NA] */ + ".long 0x1D05007C;" /* (3) tbegin. [OP] */ + ".long 0x0800E07F;" /* (4) trap [OP]; nop [NA] */ + " b %l[failure] ;" /* (5) b [NA]; MSR.LE flipped (bug) */ + " b %l[success] ;" /* (6) b [NA]; MSR.LE did not flip (ok)*/ + + : : : : failure, success); + +failure: + success = false; + goto exit_from_ping; + +success: + success = true; + +exit_from_ping: + /* Tell pong() to exit before leaving */ + pthread_kill(t1_pong, SIGUSR1); + return NULL; +} + +void *pong(void *not_used) +{ + while (!exit_from_pong) + /* + * Induce context switches on ping() thread + * until ping() finishes its job and signs + * to exit from this loop. + */ + sched_yield(); + + return NULL; +} + +int tm_trap_test(void) +{ + uint16_t k = 1; + + int rc; + + pthread_attr_t attr; + cpu_set_t cpuset; + + struct sigaction trap_sa; + + SKIP_IF(!have_htm()); + + trap_sa.sa_flags = SA_SIGINFO; + trap_sa.sa_sigaction = trap_signal_handler; + sigaction(SIGTRAP, &trap_sa, NULL); + + struct sigaction usr1_sa; + + usr1_sa.sa_flags = SA_SIGINFO; + usr1_sa.sa_sigaction = usr1_signal_handler; + sigaction(SIGUSR1, &usr1_sa, NULL); + + /* Set only CPU 0 in the mask. Both threads will be bound to cpu 0. */ + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + + /* Init pthread attribute */ + rc = pthread_attr_init(&attr); + if (rc) + pr_error(rc, "pthread_attr_init()"); + + /* + * Bind thread ping() and pong() both to CPU 0 so they ping-pong and + * speed up context switches on ping() thread, speeding up the load_fp + * and load_vec overflow. + */ + rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); + if (rc) + pr_error(rc, "pthread_attr_setaffinity()"); + + /* Figure out the machine endianness */ + le = (int) *(uint8_t *)&k; + + printf("%s machine detected. Checking if endianness flips %s", + le ? "Little-Endian" : "Big-Endian", + "inadvertently on trap in TM... "); + + rc = fflush(0); + if (rc) + pr_error(rc, "fflush()"); + + /* Launch ping() */ + rc = pthread_create(&t0_ping, &attr, ping, NULL); + if (rc) + pr_error(rc, "pthread_create()"); + + exit_from_pong = 0; + + /* Launch pong() */ + rc = pthread_create(&t1_pong, &attr, pong, NULL); + if (rc) + pr_error(rc, "pthread_create()"); + + rc = pthread_join(t0_ping, NULL); + if (rc) + pr_error(rc, "pthread_join()"); + + rc = pthread_join(t1_pong, NULL); + if (rc) + pr_error(rc, "pthread_join()"); + + if (success) { + printf("no.\n"); /* no, endianness did not flip inadvertently */ + return EXIT_SUCCESS; + } + + printf("yes!\n"); /* yes, endianness did flip inadvertently */ + return EXIT_FAILURE; +} + +int main(int argc, char **argv) +{ + return test_harness(tm_trap_test, "tm_trap_test"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-unavailable.c b/tools/testing/selftests/powerpc/tm/tm-unavailable.c new file mode 100644 index 000000000..09894f4ff --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-unavailable.c @@ -0,0 +1,407 @@ +/* + * Copyright 2017, Gustavo Romero, Breno Leitao, Cyril Bur, IBM Corp. + * Licensed under GPLv2. + * + * Force FP, VEC and VSX unavailable exception during transaction in all + * possible scenarios regarding the MSR.FP and MSR.VEC state, e.g. when FP + * is enable and VEC is disable, when FP is disable and VEC is enable, and + * so on. Then we check if the restored state is correctly set for the + * FP and VEC registers to the previous state we set just before we entered + * in TM, i.e. we check if it corrupts somehow the recheckpointed FP and + * VEC/Altivec registers on abortion due to an unavailable exception in TM. + * N.B. In this test we do not test all the FP/Altivec/VSX registers for + * corruption, but only for registers vs0 and vs32, which are respectively + * representatives of FP and VEC/Altivec reg sets. + */ + +#define _GNU_SOURCE +#include <error.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <stdbool.h> +#include <pthread.h> +#include <sched.h> + +#include "tm.h" + +#define DEBUG 0 + +/* Unavailable exceptions to test in HTM */ +#define FP_UNA_EXCEPTION 0 +#define VEC_UNA_EXCEPTION 1 +#define VSX_UNA_EXCEPTION 2 + +#define NUM_EXCEPTIONS 3 +#define err_at_line(status, errnum, format, ...) \ + error_at_line(status, errnum, __FILE__, __LINE__, format ##__VA_ARGS__) + +#define pr_warn(code, format, ...) err_at_line(0, code, format, ##__VA_ARGS__) +#define pr_err(code, format, ...) err_at_line(1, code, format, ##__VA_ARGS__) + +struct Flags { + int touch_fp; + int touch_vec; + int result; + int exception; +} flags; + +bool expecting_failure(void) +{ + if (flags.touch_fp && flags.exception == FP_UNA_EXCEPTION) + return false; + + if (flags.touch_vec && flags.exception == VEC_UNA_EXCEPTION) + return false; + + /* + * If both FP and VEC are touched it does not mean that touching VSX + * won't raise an exception. However since FP and VEC state are already + * correctly loaded, the transaction is not aborted (i.e. + * treclaimed/trecheckpointed) and MSR.VSX is just set as 1, so a TM + * failure is not expected also in this case. + */ + if ((flags.touch_fp && flags.touch_vec) && + flags.exception == VSX_UNA_EXCEPTION) + return false; + + return true; +} + +/* Check if failure occurred whilst in transaction. */ +bool is_failure(uint64_t condition_reg) +{ + /* + * When failure handling occurs, CR0 is set to 0b1010 (0xa). Otherwise + * transaction completes without failure and hence reaches out 'tend.' + * that sets CR0 to 0b0100 (0x4). + */ + return ((condition_reg >> 28) & 0xa) == 0xa; +} + +void *tm_una_ping(void *input) +{ + + /* + * Expected values for vs0 and vs32 after a TM failure. They must never + * change, otherwise they got corrupted. + */ + uint64_t high_vs0 = 0x5555555555555555; + uint64_t low_vs0 = 0xffffffffffffffff; + uint64_t high_vs32 = 0x5555555555555555; + uint64_t low_vs32 = 0xffffffffffffffff; + + /* Counter for busy wait */ + uint64_t counter = 0x1ff000000; + + /* + * Variable to keep a copy of CR register content taken just after we + * leave the transactional state. + */ + uint64_t cr_ = 0; + + /* + * Wait a bit so thread can get its name "ping". This is not important + * to reproduce the issue but it's nice to have for systemtap debugging. + */ + if (DEBUG) + sleep(1); + + printf("If MSR.FP=%d MSR.VEC=%d: ", flags.touch_fp, flags.touch_vec); + + if (flags.exception != FP_UNA_EXCEPTION && + flags.exception != VEC_UNA_EXCEPTION && + flags.exception != VSX_UNA_EXCEPTION) { + printf("No valid exception specified to test.\n"); + return NULL; + } + + asm ( + /* Prepare to merge low and high. */ + " mtvsrd 33, %[high_vs0] ;" + " mtvsrd 34, %[low_vs0] ;" + + /* + * Adjust VS0 expected value after an TM failure, + * i.e. vs0 = 0x5555555555555555555FFFFFFFFFFFFFFFF + */ + " xxmrghd 0, 33, 34 ;" + + /* + * Adjust VS32 expected value after an TM failure, + * i.e. vs32 = 0x5555555555555555555FFFFFFFFFFFFFFFF + */ + " xxmrghd 32, 33, 34 ;" + + /* + * Wait an amount of context switches so load_fp and load_vec + * overflow and MSR.FP, MSR.VEC, and MSR.VSX become zero (off). + */ + " mtctr %[counter] ;" + + /* Decrement CTR branch if CTR non zero. */ + "1: bdnz 1b ;" + + /* + * Check if we want to touch FP prior to the test in order + * to set MSR.FP = 1 before provoking an unavailable + * exception in TM. + */ + " cmpldi %[touch_fp], 0 ;" + " beq no_fp ;" + " fadd 10, 10, 10 ;" + "no_fp: ;" + + /* + * Check if we want to touch VEC prior to the test in order + * to set MSR.VEC = 1 before provoking an unavailable + * exception in TM. + */ + " cmpldi %[touch_vec], 0 ;" + " beq no_vec ;" + " vaddcuw 10, 10, 10 ;" + "no_vec: ;" + + /* + * Perhaps it would be a better idea to do the + * compares outside transactional context and simply + * duplicate code. + */ + " tbegin. ;" + " beq trans_fail ;" + + /* Do we do FP Unavailable? */ + " cmpldi %[exception], %[ex_fp] ;" + " bne 1f ;" + " fadd 10, 10, 10 ;" + " b done ;" + + /* Do we do VEC Unavailable? */ + "1: cmpldi %[exception], %[ex_vec] ;" + " bne 2f ;" + " vaddcuw 10, 10, 10 ;" + " b done ;" + + /* + * Not FP or VEC, therefore VSX. Ensure this + * instruction always generates a VSX Unavailable. + * ISA 3.0 is tricky here. + * (xxmrghd will on ISA 2.07 and ISA 3.0) + */ + "2: xxmrghd 10, 10, 10 ;" + + "done: tend. ;" + + "trans_fail: ;" + + /* Give values back to C. */ + " mfvsrd %[high_vs0], 0 ;" + " xxsldwi 3, 0, 0, 2 ;" + " mfvsrd %[low_vs0], 3 ;" + " mfvsrd %[high_vs32], 32 ;" + " xxsldwi 3, 32, 32, 2 ;" + " mfvsrd %[low_vs32], 3 ;" + + /* Give CR back to C so that it can check what happened. */ + " mfcr %[cr_] ;" + + : [high_vs0] "+r" (high_vs0), + [low_vs0] "+r" (low_vs0), + [high_vs32] "=r" (high_vs32), + [low_vs32] "=r" (low_vs32), + [cr_] "+r" (cr_) + : [touch_fp] "r" (flags.touch_fp), + [touch_vec] "r" (flags.touch_vec), + [exception] "r" (flags.exception), + [ex_fp] "i" (FP_UNA_EXCEPTION), + [ex_vec] "i" (VEC_UNA_EXCEPTION), + [ex_vsx] "i" (VSX_UNA_EXCEPTION), + [counter] "r" (counter) + + : "cr0", "ctr", "v10", "vs0", "vs10", "vs3", "vs32", "vs33", + "vs34", "fr10" + + ); + + /* + * Check if we were expecting a failure and it did not occur by checking + * CR0 state just after we leave the transaction. Either way we check if + * vs0 or vs32 got corrupted. + */ + if (expecting_failure() && !is_failure(cr_)) { + printf("\n\tExpecting the transaction to fail, %s", + "but it didn't\n\t"); + flags.result++; + } + + /* Check if we were not expecting a failure and a it occurred. */ + if (!expecting_failure() && is_failure(cr_) && + !failure_is_reschedule()) { + printf("\n\tUnexpected transaction failure 0x%02lx\n\t", + failure_code()); + return (void *) -1; + } + + /* + * Check if TM failed due to the cause we were expecting. 0xda is a + * TM_CAUSE_FAC_UNAV cause, otherwise it's an unexpected cause, unless + * it was caused by a reschedule. + */ + if (is_failure(cr_) && !failure_is_unavailable() && + !failure_is_reschedule()) { + printf("\n\tUnexpected failure cause 0x%02lx\n\t", + failure_code()); + return (void *) -1; + } + + /* 0x4 is a success and 0xa is a fail. See comment in is_failure(). */ + if (DEBUG) + printf("CR0: 0x%1lx ", cr_ >> 28); + + /* Check FP (vs0) for the expected value. */ + if (high_vs0 != 0x5555555555555555 || low_vs0 != 0xFFFFFFFFFFFFFFFF) { + printf("FP corrupted!"); + printf(" high = %#16" PRIx64 " low = %#16" PRIx64 " ", + high_vs0, low_vs0); + flags.result++; + } else + printf("FP ok "); + + /* Check VEC (vs32) for the expected value. */ + if (high_vs32 != 0x5555555555555555 || low_vs32 != 0xFFFFFFFFFFFFFFFF) { + printf("VEC corrupted!"); + printf(" high = %#16" PRIx64 " low = %#16" PRIx64, + high_vs32, low_vs32); + flags.result++; + } else + printf("VEC ok"); + + putchar('\n'); + + return NULL; +} + +/* Thread to force context switch */ +void *tm_una_pong(void *not_used) +{ + /* Wait thread get its name "pong". */ + if (DEBUG) + sleep(1); + + /* Classed as an interactive-like thread. */ + while (1) + sched_yield(); +} + +/* Function that creates a thread and launches the "ping" task. */ +void test_fp_vec(int fp, int vec, pthread_attr_t *attr) +{ + int retries = 2; + void *ret_value; + pthread_t t0; + + flags.touch_fp = fp; + flags.touch_vec = vec; + + /* + * Without luck it's possible that the transaction is aborted not due to + * the unavailable exception caught in the middle as we expect but also, + * for instance, due to a context switch or due to a KVM reschedule (if + * it's running on a VM). Thus we try a few times before giving up, + * checking if the failure cause is the one we expect. + */ + do { + int rc; + + /* Bind to CPU 0, as specified in 'attr'. */ + rc = pthread_create(&t0, attr, tm_una_ping, (void *) &flags); + if (rc) + pr_err(rc, "pthread_create()"); + rc = pthread_setname_np(t0, "tm_una_ping"); + if (rc) + pr_warn(rc, "pthread_setname_np"); + rc = pthread_join(t0, &ret_value); + if (rc) + pr_err(rc, "pthread_join"); + + retries--; + } while (ret_value != NULL && retries); + + if (!retries) { + flags.result = 1; + if (DEBUG) + printf("All transactions failed unexpectedly\n"); + + } +} + +int tm_unavailable_test(void) +{ + int rc, exception; /* FP = 0, VEC = 1, VSX = 2 */ + pthread_t t1; + pthread_attr_t attr; + cpu_set_t cpuset; + + SKIP_IF(!have_htm()); + + /* Set only CPU 0 in the mask. Both threads will be bound to CPU 0. */ + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + + /* Init pthread attribute. */ + rc = pthread_attr_init(&attr); + if (rc) + pr_err(rc, "pthread_attr_init()"); + + /* Set CPU 0 mask into the pthread attribute. */ + rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); + if (rc) + pr_err(rc, "pthread_attr_setaffinity_np()"); + + rc = pthread_create(&t1, &attr /* Bind to CPU 0 */, tm_una_pong, NULL); + if (rc) + pr_err(rc, "pthread_create()"); + + /* Name it for systemtap convenience */ + rc = pthread_setname_np(t1, "tm_una_pong"); + if (rc) + pr_warn(rc, "pthread_create()"); + + flags.result = 0; + + for (exception = 0; exception < NUM_EXCEPTIONS; exception++) { + printf("Checking if FP/VEC registers are sane after"); + + if (exception == FP_UNA_EXCEPTION) + printf(" a FP unavailable exception...\n"); + + else if (exception == VEC_UNA_EXCEPTION) + printf(" a VEC unavailable exception...\n"); + + else + printf(" a VSX unavailable exception...\n"); + + flags.exception = exception; + + test_fp_vec(0, 0, &attr); + test_fp_vec(1, 0, &attr); + test_fp_vec(0, 1, &attr); + test_fp_vec(1, 1, &attr); + + } + + if (flags.result > 0) { + printf("result: failed!\n"); + exit(1); + } else { + printf("result: success\n"); + exit(0); + } +} + +int main(int argc, char **argv) +{ + test_harness_set_timeout(220); + return test_harness(tm_unavailable_test, "tm_unavailable_test"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-vmx-unavail.c b/tools/testing/selftests/powerpc/tm/tm-vmx-unavail.c new file mode 100644 index 000000000..137185ba4 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-vmx-unavail.c @@ -0,0 +1,118 @@ +/* + * Copyright 2017, Michael Neuling, IBM Corp. + * Licensed under GPLv2. + * Original: Breno Leitao <brenohl@br.ibm.com> & + * Gustavo Bueno Romero <gromero@br.ibm.com> + * Edited: Michael Neuling + * + * Force VMX unavailable during a transaction and see if it corrupts + * the checkpointed VMX register state after the abort. + */ + +#include <inttypes.h> +#include <htmintrin.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <pthread.h> +#include <sys/mman.h> +#include <unistd.h> +#include <pthread.h> + +#include "tm.h" +#include "utils.h" + +int passed; + +void *worker(void *unused) +{ + __int128 vmx0; + uint64_t texasr; + + asm goto ( + "li 3, 1;" /* Stick non-zero value in VMX0 */ + "std 3, 0(%[vmx0_ptr]);" + "lvx 0, 0, %[vmx0_ptr];" + + /* Wait here a bit so we get scheduled out 255 times */ + "lis 3, 0x3fff;" + "1: ;" + "addi 3, 3, -1;" + "cmpdi 3, 0;" + "bne 1b;" + + /* Kernel will hopefully turn VMX off now */ + + "tbegin. ;" + "beq failure;" + + /* Cause VMX unavail. Any VMX instruction */ + "vaddcuw 0,0,0;" + + "tend. ;" + "b %l[success];" + + /* Check VMX0 sanity after abort */ + "failure: ;" + "lvx 1, 0, %[vmx0_ptr];" + "vcmpequb. 2, 0, 1;" + "bc 4, 24, %l[value_mismatch];" + "b %l[value_match];" + : + : [vmx0_ptr] "r"(&vmx0) + : "r3" + : success, value_match, value_mismatch + ); + + /* HTM aborted and VMX0 is corrupted */ +value_mismatch: + texasr = __builtin_get_texasr(); + + printf("\n\n==============\n\n"); + printf("Failure with error: %lx\n", _TEXASR_FAILURE_CODE(texasr)); + printf("Summary error : %lx\n", _TEXASR_FAILURE_SUMMARY(texasr)); + printf("TFIAR exact : %lx\n\n", _TEXASR_TFIAR_EXACT(texasr)); + + passed = 0; + return NULL; + + /* HTM aborted but VMX0 is correct */ +value_match: +// printf("!"); + return NULL; + +success: +// printf("."); + return NULL; +} + +int tm_vmx_unavail_test() +{ + int threads; + pthread_t *thread; + + SKIP_IF(!have_htm()); + + passed = 1; + + threads = sysconf(_SC_NPROCESSORS_ONLN) * 4; + thread = malloc(sizeof(pthread_t)*threads); + if (!thread) + return EXIT_FAILURE; + + for (uint64_t i = 0; i < threads; i++) + pthread_create(&thread[i], NULL, &worker, NULL); + + for (uint64_t i = 0; i < threads; i++) + pthread_join(thread[i], NULL); + + free(thread); + + return passed ? EXIT_SUCCESS : EXIT_FAILURE; +} + + +int main(int argc, char **argv) +{ + return test_harness(tm_vmx_unavail_test, "tm_vmx_unavail_test"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm-vmxcopy.c b/tools/testing/selftests/powerpc/tm/tm-vmxcopy.c new file mode 100644 index 000000000..fe5281158 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-vmxcopy.c @@ -0,0 +1,104 @@ +/* + * Copyright 2015, Michael Neuling, IBM Corp. + * Licensed under GPLv2. + * + * Original: Michael Neuling 4/12/2013 + * Edited: Rashmica Gupta 4/12/2015 + * + * See if the altivec state is leaked out of an aborted transaction due to + * kernel vmx copy loops. + * + * When the transaction aborts, VSR values should rollback to the values + * they held before the transaction commenced. Using VSRs while transaction + * is suspended should not affect the checkpointed values. + * + * (1) write A to a VSR + * (2) start transaction + * (3) suspend transaction + * (4) change the VSR to B + * (5) trigger kernel vmx copy loop + * (6) abort transaction + * (7) check that the VSR value is A + */ + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/mman.h> +#include <string.h> +#include <assert.h> + +#include "tm.h" +#include "utils.h" + +int test_vmxcopy() +{ + long double vecin = 1.3; + long double vecout; + unsigned long pgsize = getpagesize(); + int i; + int fd; + int size = pgsize*16; + char tmpfile[] = "/tmp/page_faultXXXXXX"; + char buf[pgsize]; + char *a; + uint64_t aborted = 0; + + SKIP_IF(!have_htm()); + SKIP_IF(!is_ppc64le()); + + fd = mkstemp(tmpfile); + assert(fd >= 0); + + memset(buf, 0, pgsize); + for (i = 0; i < size; i += pgsize) + assert(write(fd, buf, pgsize) == pgsize); + + unlink(tmpfile); + + a = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); + assert(a != MAP_FAILED); + + asm __volatile__( + "lxvd2x 40,0,%[vecinptr];" /* set 40 to initial value*/ + "tbegin.;" + "beq 3f;" + "tsuspend.;" + "xxlxor 40,40,40;" /* set 40 to 0 */ + "std 5, 0(%[map]);" /* cause kernel vmx copy page */ + "tabort. 0;" + "tresume.;" + "tend.;" + "li %[res], 0;" + "b 5f;" + + /* Abort handler */ + "3:;" + "li %[res], 1;" + + "5:;" + "stxvd2x 40,0,%[vecoutptr];" + : [res]"=r"(aborted) + : [vecinptr]"r"(&vecin), + [vecoutptr]"r"(&vecout), + [map]"r"(a) + : "memory", "r0", "r3", "r4", "r5", "r6", "r7"); + + if (aborted && (vecin != vecout)){ + printf("FAILED: vector state leaked on abort %f != %f\n", + (double)vecin, (double)vecout); + return 1; + } + + munmap(a, size); + + close(fd); + + return 0; +} + +int main(void) +{ + return test_harness(test_vmxcopy, "tm_vmxcopy"); +} diff --git a/tools/testing/selftests/powerpc/tm/tm.h b/tools/testing/selftests/powerpc/tm/tm.h new file mode 100644 index 000000000..5518b1d4e --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm.h @@ -0,0 +1,96 @@ +/* + * Copyright 2015, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef _SELFTESTS_POWERPC_TM_TM_H +#define _SELFTESTS_POWERPC_TM_TM_H + +#include <asm/tm.h> +#include <asm/cputable.h> +#include <stdbool.h> + +#include "utils.h" + +static inline bool have_htm(void) +{ +#ifdef PPC_FEATURE2_HTM + return have_hwcap2(PPC_FEATURE2_HTM); +#else + printf("PPC_FEATURE2_HTM not defined, can't check AT_HWCAP2\n"); + return false; +#endif +} + +static inline bool have_htm_nosc(void) +{ +#ifdef PPC_FEATURE2_HTM_NOSC + return have_hwcap2(PPC_FEATURE2_HTM_NOSC); +#else + printf("PPC_FEATURE2_HTM_NOSC not defined, can't check AT_HWCAP2\n"); + return false; +#endif +} + +static inline long failure_code(void) +{ + return __builtin_get_texasru() >> 24; +} + +static inline bool failure_is_persistent(void) +{ + return (failure_code() & TM_CAUSE_PERSISTENT) == TM_CAUSE_PERSISTENT; +} + +static inline bool failure_is_syscall(void) +{ + return (failure_code() & TM_CAUSE_SYSCALL) == TM_CAUSE_SYSCALL; +} + +static inline bool failure_is_unavailable(void) +{ + return (failure_code() & TM_CAUSE_FAC_UNAV) == TM_CAUSE_FAC_UNAV; +} + +static inline bool failure_is_reschedule(void) +{ + if ((failure_code() & TM_CAUSE_RESCHED) == TM_CAUSE_RESCHED || + (failure_code() & TM_CAUSE_KVM_RESCHED) == TM_CAUSE_KVM_RESCHED) + return true; + + return false; +} + +static inline bool failure_is_nesting(void) +{ + return (__builtin_get_texasru() & 0x400000); +} + +static inline int tcheck(void) +{ + long cr; + asm volatile ("tcheck 0" : "=r"(cr) : : "cr0"); + return (cr >> 28) & 4; +} + +static inline bool tcheck_doomed(void) +{ + return tcheck() & 8; +} + +static inline bool tcheck_active(void) +{ + return tcheck() & 4; +} + +static inline bool tcheck_suspended(void) +{ + return tcheck() & 2; +} + +static inline bool tcheck_transactional(void) +{ + return tcheck() & 6; +} + +#endif /* _SELFTESTS_POWERPC_TM_TM_H */ |