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,22 @@
# SPDX-License-Identifier: GPL-2.0-only
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-signal-context-force-tm
tm-signal-sigreturn-nt
tm-signal-pagefault
tm-vmx-unavail
tm-unavailable
tm-trap
tm-sigreturn
tm-poison
@@ -0,0 +1,33 @@
# 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 tm-signal-sigreturn-nt \
tm-signal-context-force-tm tm-poison tm-signal-pagefault
TEST_FILES := settings
top_srcdir = ../../../../..
include ../../lib.mk
include ../flags.mk
$(TEST_GEN_PROGS): ../harness.c ../utils.c
CFLAGS += -mhtm
$(OUTPUT)/tm-syscall: tm-syscall-asm.S
$(OUTPUT)/tm-syscall: CFLAGS += $(KHDR_INCLUDES)
$(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
$(OUTPUT)/tm-signal-context-force-tm: CFLAGS += -pthread -m64
$(OUTPUT)/tm-signal-pagefault: CFLAGS += -pthread -m64
$(OUTPUT)/tm-poison: CFLAGS += -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
@@ -0,0 +1 @@
timeout=0
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2016, Cyril Bur, IBM Corp.
*
* 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());
SKIP_IF(htm_is_synthetic());
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");
}
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Michael Neuling, IBM Corp.
*
* 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());
SKIP_IF(htm_is_synthetic());
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");
}
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2019, Gustavo Romero, Michael Neuling, IBM Corp.
*
* This test will spawn two processes. Both will be attached to the same
* CPU (CPU 0). The child will be in a loop writing to FP register f31 and
* VMX/VEC/Altivec register vr31 a known value, called poison, calling
* sched_yield syscall after to allow the parent to switch on the CPU.
* Parent will set f31 and vr31 to 1 and in a loop will check if f31 and
* vr31 remain 1 as expected until a given timeout (2m). If the issue is
* present child's poison will leak into parent's f31 or vr31 registers,
* otherwise, poison will never leak into parent's f31 and vr31 registers.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <sched.h>
#include <sys/types.h>
#include <signal.h>
#include "tm.h"
int tm_poison_test(void)
{
int cpu, pid;
cpu_set_t cpuset;
uint64_t poison = 0xdeadbeefc0dec0fe;
uint64_t unknown = 0;
bool fail_fp = false;
bool fail_vr = false;
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
cpu = pick_online_cpu();
FAIL_IF(cpu < 0);
// Attach both Child and Parent to the same CPU
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
FAIL_IF(sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0);
pid = fork();
if (!pid) {
/**
* child
*/
while (1) {
sched_yield();
asm (
"mtvsrd 31, %[poison];" // f31 = poison
"mtvsrd 63, %[poison];" // vr31 = poison
: : [poison] "r" (poison) : );
}
}
/**
* parent
*/
asm (
/*
* Set r3, r4, and f31 to known value 1 before entering
* in transaction. They won't be written after that.
*/
" li 3, 0x1 ;"
" li 4, 0x1 ;"
" mtvsrd 31, 4 ;"
/*
* The Time Base (TB) is a 64-bit counter register that is
* independent of the CPU clock and which is incremented
* at a frequency of 512000000 Hz, so every 1.953125ns.
* So it's necessary 120s/0.000000001953125s = 61440000000
* increments to get a 2 minutes timeout. Below we set that
* value in r5 and then use r6 to track initial TB value,
* updating TB values in r7 at every iteration and comparing it
* to r6. When r7 (current) - r6 (initial) > 61440000000 we bail
* out since for sure we spent already 2 minutes in the loop.
* SPR 268 is the TB register.
*/
" lis 5, 14 ;"
" ori 5, 5, 19996 ;"
" sldi 5, 5, 16 ;" // r5 = 61440000000
" mfspr 6, 268 ;" // r6 (TB initial)
"1: mfspr 7, 268 ;" // r7 (TB current)
" subf 7, 6, 7 ;" // r7 - r6 > 61440000000 ?
" cmpd 7, 5 ;"
" bgt 3f ;" // yes, exit
/*
* Main loop to check f31
*/
" tbegin. ;" // no, try again
" beq 1b ;" // restart if no timeout
" mfvsrd 3, 31 ;" // read f31
" cmpd 3, 4 ;" // f31 == 1 ?
" bne 2f ;" // broken :-(
" tabort. 3 ;" // try another transaction
"2: tend. ;" // commit transaction
"3: mr %[unknown], 3 ;" // record r3
: [unknown] "=r" (unknown)
:
: "cr0", "r3", "r4", "r5", "r6", "r7", "vs31"
);
/*
* On leak 'unknown' will contain 'poison' value from child,
* otherwise (no leak) 'unknown' will contain the same value
* as r3 before entering in transactional mode, i.e. 0x1.
*/
fail_fp = unknown != 0x1;
if (fail_fp)
printf("Unknown value %#"PRIx64" leaked into f31!\n", unknown);
else
printf("Good, no poison or leaked value into FP registers\n");
asm (
/*
* Set r3, r4, and vr31 to known value 1 before entering
* in transaction. They won't be written after that.
*/
" li 3, 0x1 ;"
" li 4, 0x1 ;"
" mtvsrd 63, 4 ;"
" lis 5, 14 ;"
" ori 5, 5, 19996 ;"
" sldi 5, 5, 16 ;" // r5 = 61440000000
" mfspr 6, 268 ;" // r6 (TB initial)
"1: mfspr 7, 268 ;" // r7 (TB current)
" subf 7, 6, 7 ;" // r7 - r6 > 61440000000 ?
" cmpd 7, 5 ;"
" bgt 3f ;" // yes, exit
/*
* Main loop to check vr31
*/
" tbegin. ;" // no, try again
" beq 1b ;" // restart if no timeout
" mfvsrd 3, 63 ;" // read vr31
" cmpd 3, 4 ;" // vr31 == 1 ?
" bne 2f ;" // broken :-(
" tabort. 3 ;" // try another transaction
"2: tend. ;" // commit transaction
"3: mr %[unknown], 3 ;" // record r3
: [unknown] "=r" (unknown)
:
: "cr0", "r3", "r4", "r5", "r6", "r7", "vs63"
);
/*
* On leak 'unknown' will contain 'poison' value from child,
* otherwise (no leak) 'unknown' will contain the same value
* as r3 before entering in transactional mode, i.e. 0x1.
*/
fail_vr = unknown != 0x1;
if (fail_vr)
printf("Unknown value %#"PRIx64" leaked into vr31!\n", unknown);
else
printf("Good, no poison or leaked value into VEC registers\n");
kill(pid, SIGKILL);
return (fail_fp | fail_vr);
}
int main(int argc, char *argv[])
{
/* Test completes in about 4m */
test_harness_set_timeout(250);
return test_harness(tm_poison_test, "tm_poison_test");
}
@@ -0,0 +1,101 @@
// 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());
SKIP_IF(htm_is_synthetic());
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");
}
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2016, Cyril Bur, IBM Corp.
*
* 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 (referred too as
* first and second contexts).
* Expected behaviour is that the checkpointed state is in the user
* context passed to the signal handler (first context). The speculated
* state can be accessed with the uc_link pointer (second context).
*
* 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 /* Number of non-volatile FP registers */
#define FPR14 14 /* First non-volatile FP register to check in f14-31 subset */
long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss);
/* Test only non-volatile registers, i.e. 18 fpr registers from f14 to f31 */
static double fps[] = {
/* First context will be set with these values, i.e. non-speculative */
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
/* Second context will be set with these values, i.e. speculative */
-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15,-16,-17,-18
};
static sig_atomic_t fail, broken;
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; i++) {
/* Check first context. Print all mismatches. */
fail = (ucp->uc_mcontext.fp_regs[FPR14 + i] != fps[i]);
if (fail) {
broken = 1;
printf("FPR%d (1st context) == %g instead of %g (expected)\n",
FPR14 + i, ucp->uc_mcontext.fp_regs[FPR14 + i], fps[i]);
}
}
for (i = 0; i < NV_FPU_REGS; i++) {
/* Check second context. Print all mismatches. */
fail = (tm_ucp->uc_mcontext.fp_regs[FPR14 + i] != fps[NV_FPU_REGS + i]);
if (fail) {
broken = 1;
printf("FPR%d (2nd context) == %g instead of %g (expected)\n",
FPR14 + i, tm_ucp->uc_mcontext.fp_regs[FPR14 + i], fps[NV_FPU_REGS + i]);
}
}
}
static int tm_signal_context_chk_fpu()
{
struct sigaction act;
int i;
long rc;
pid_t pid = getpid();
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
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 && !broken) {
/*
* tm_signal_self_context_load will set both first and second
* contexts accordingly to the values passed through non-NULL
* array pointers to it, in that case 'fps', and invoke the
* signal handler installed for SIGUSR1.
*/
rc = tm_signal_self_context_load(pid, NULL, fps, NULL, NULL);
FAIL_IF(rc != pid);
i++;
}
return (broken);
}
int main(void)
{
return test_harness(tm_signal_context_chk_fpu, "tm_signal_context_chk_fpu");
}
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2016, Cyril Bur, IBM Corp.
*
* 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 (referred too as
* first and second contexts).
* Expected behaviour is that the checkpointed state is in the user
* context passed to the signal handler (first context). The speculated
* state can be accessed with the uc_link pointer (second context).
*
* 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 /* Number of non-volatile GPR registers */
#define R14 14 /* First non-volatile register to check in r14-r31 subset */
long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss);
static sig_atomic_t fail, broken;
/* Test only non-volatile general purpose registers, i.e. r14-r31 */
static long gprs[] = {
/* First context will be set with these values, i.e. non-speculative */
/* R14, R15, ... */
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
/* Second context will be set with these values, i.e. speculative */
/* R14, R15, ... */
-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;
/* Check first context. Print all mismatches. */
for (i = 0; i < NV_GPR_REGS; i++) {
fail = (ucp->uc_mcontext.gp_regs[R14 + i] != gprs[i]);
if (fail) {
broken = 1;
printf("GPR%d (1st context) == %lu instead of %lu (expected)\n",
R14 + i, ucp->uc_mcontext.gp_regs[R14 + i], gprs[i]);
}
}
/* Check second context. Print all mismatches. */
for (i = 0; i < NV_GPR_REGS; i++) {
fail = (tm_ucp->uc_mcontext.gp_regs[R14 + i] != gprs[NV_GPR_REGS + i]);
if (fail) {
broken = 1;
printf("GPR%d (2nd context) == %lu instead of %lu (expected)\n",
R14 + i, tm_ucp->uc_mcontext.gp_regs[R14 + i], gprs[NV_GPR_REGS + i]);
}
}
}
static int tm_signal_context_chk_gpr()
{
struct sigaction act;
int i;
long rc;
pid_t pid = getpid();
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
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 && !broken) {
/*
* tm_signal_self_context_load will set both first and second
* contexts accordingly to the values passed through non-NULL
* array pointers to it, in that case 'gprs', and invoke the
* signal handler installed for SIGUSR1.
*/
rc = tm_signal_self_context_load(pid, gprs, NULL, NULL, NULL);
FAIL_IF(rc != pid);
i++;
}
return broken;
}
int main(void)
{
return test_harness(tm_signal_context_chk_gpr, "tm_signal_context_chk_gpr");
}
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2016, Cyril Bur, IBM Corp.
*
* 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 (referred too as
* first and second contexts).
* Expected behaviour is that the checkpointed state is in the user
* context passed to the signal handler (first context). The speculated
* state can be accessed with the uc_link pointer (second context).
*
* 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 /* Number of non-volatile VMX registers */
#define VMX20 20 /* First non-volatile register to check in vr20-31 subset */
long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss);
static sig_atomic_t fail, broken;
/* Test only non-volatile registers, i.e. 12 vmx registers from vr20 to vr31 */
vector int vms[] = {
/* First context will be set with these values, i.e. non-speculative */
/* VMX20 , VMX21 , ... */
{ 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},
/* Second context will be set with these values, i.e. speculative */
/* VMX20 , VMX21 , ... */
{ -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, j;
ucontext_t *ucp = uc;
ucontext_t *tm_ucp = ucp->uc_link;
for (i = 0; i < NV_VMX_REGS; i++) {
/* Check first context. Print all mismatches. */
fail = memcmp(ucp->uc_mcontext.v_regs->vrregs[VMX20 + i],
&vms[i], sizeof(vector int));
if (fail) {
broken = 1;
printf("VMX%d (1st context) == 0x", VMX20 + i);
/* Print actual value in first context. */
for (j = 0; j < 4; j++)
printf("%08x", ucp->uc_mcontext.v_regs->vrregs[VMX20 + i][j]);
printf(" instead of 0x");
/* Print expected value. */
for (j = 0; j < 4; j++)
printf("%08x", vms[i][j]);
printf(" (expected)\n");
}
}
for (i = 0; i < NV_VMX_REGS; i++) {
/* Check second context. Print all mismatches. */
fail = memcmp(tm_ucp->uc_mcontext.v_regs->vrregs[VMX20 + i],
&vms[NV_VMX_REGS + i], sizeof (vector int));
if (fail) {
broken = 1;
printf("VMX%d (2nd context) == 0x", NV_VMX_REGS + i);
/* Print actual value in second context. */
for (j = 0; j < 4; j++)
printf("%08x", tm_ucp->uc_mcontext.v_regs->vrregs[VMX20 + i][j]);
printf(" instead of 0x");
/* Print expected value. */
for (j = 0; j < 4; j++)
printf("%08x", vms[NV_VMX_REGS + i][j]);
printf(" (expected)\n");
}
}
}
static int tm_signal_context_chk()
{
struct sigaction act;
int i;
long rc;
pid_t pid = getpid();
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
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 && !broken) {
/*
* tm_signal_self_context_load will set both first and second
* contexts accordingly to the values passed through non-NULL
* array pointers to it, in that case 'vms', and invoke the
* signal handler installed for SIGUSR1.
*/
rc = tm_signal_self_context_load(pid, NULL, NULL, vms, NULL);
FAIL_IF(rc != pid);
i++;
}
return (broken);
}
int main(void)
{
return test_harness(tm_signal_context_chk, "tm_signal_context_chk_vmx");
}
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2016, Cyril Bur, IBM Corp.
*
* 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 (referred too as
* first and second contexts).
* Expected behaviour is that the checkpointed state is in the user
* context passed to the signal handler (first context). The speculated
* state can be accessed with the uc_link pointer (second context).
*
* 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 /* Number of VSX registers to check. */
#define VSX20 20 /* First VSX register to check in vsr20-vsr31 subset */
#define FPR20 20 /* FPR20 overlaps VSX20 most significant doubleword */
long tm_signal_self_context_load(pid_t pid, long *gprs, double *fps, vector int *vms, vector int *vss);
static sig_atomic_t fail, broken;
/* Test only 12 vsx registers from vsr20 to vsr31 */
vector int vsxs[] = {
/* First context will be set with these values, i.e. non-speculative */
/* VSX20 , VSX21 , ... */
{ 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},
/* Second context will be set with these values, i.e. speculative */
/* VSX20 , VSX21 , ... */
{-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, j;
uint8_t vsx[sizeof(vector int)];
uint8_t vsx_tm[sizeof(vector int)];
ucontext_t *ucp = uc;
ucontext_t *tm_ucp = ucp->uc_link;
/*
* FP registers and VMX registers overlap the VSX registers.
*
* FP registers (f0-31) overlap the most significant 64 bits of VSX
* registers vsr0-31, whilst VMX registers vr0-31, being 128-bit like
* the VSX registers, overlap fully the other half of VSX registers,
* i.e. vr0-31 overlaps fully vsr32-63.
*
* Due to compatibility and historical reasons (VMX/Altivec support
* appeared first on the architecture), VMX registers vr0-31 (so VSX
* half vsr32-63 too) are stored right after the v_regs pointer, in an
* area allocated for 'vmx_reverse' array (please see
* arch/powerpc/include/uapi/asm/sigcontext.h for details about the
* mcontext_t structure on Power).
*
* The other VSX half (vsr0-31) is hence stored below vr0-31/vsr32-63
* registers, but only the least significant 64 bits of vsr0-31. The
* most significant 64 bits of vsr0-31 (f0-31), as it overlaps the FP
* registers, is kept in fp_regs.
*
* 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, so v_regs points to where vr0-31 / vsr32-63 registers are
* fully stored. Since v_regs type is elf_vrregset_t, v_regs + 1
* skips all the slots used to store vr0-31 / vsr32-64 and points to
* part of one VSX half, i.e. v_regs + 1 points to the least significant
* 64 bits of vsr0-31. The other part of this half (the most significant
* part of vsr0-31) is stored in fp_regs.
*
*/
/* Get pointer to least significant doubleword of vsr0-31 */
long *vsx_ptr = (long *)(ucp->uc_mcontext.v_regs + 1);
long *tm_vsx_ptr = (long *)(tm_ucp->uc_mcontext.v_regs + 1);
/* Check first context. Print all mismatches. */
for (i = 0; i < NV_VSX_REGS; i++) {
/*
* Copy VSX most significant doubleword from fp_regs and
* copy VSX least significant one from 64-bit slots below
* saved VMX registers.
*/
memcpy(vsx, &ucp->uc_mcontext.fp_regs[FPR20 + i], 8);
memcpy(vsx + 8, &vsx_ptr[VSX20 + i], 8);
fail = memcmp(vsx, &vsxs[i], sizeof(vector int));
if (fail) {
broken = 1;
printf("VSX%d (1st context) == 0x", VSX20 + i);
for (j = 0; j < 16; j++)
printf("%02x", vsx[j]);
printf(" instead of 0x");
for (j = 0; j < 4; j++)
printf("%08x", vsxs[i][j]);
printf(" (expected)\n");
}
}
/* Check second context. Print all mismatches. */
for (i = 0; i < NV_VSX_REGS; i++) {
/*
* Copy VSX most significant doubleword from fp_regs and
* copy VSX least significant one from 64-bit slots below
* saved VMX registers.
*/
memcpy(vsx_tm, &tm_ucp->uc_mcontext.fp_regs[FPR20 + i], 8);
memcpy(vsx_tm + 8, &tm_vsx_ptr[VSX20 + i], 8);
fail = memcmp(vsx_tm, &vsxs[NV_VSX_REGS + i], sizeof(vector int));
if (fail) {
broken = 1;
printf("VSX%d (2nd context) == 0x", VSX20 + i);
for (j = 0; j < 16; j++)
printf("%02x", vsx_tm[j]);
printf(" instead of 0x");
for (j = 0; j < 4; j++)
printf("%08x", vsxs[NV_VSX_REGS + i][j]);
printf("(expected)\n");
}
}
}
static int tm_signal_context_chk()
{
struct sigaction act;
int i;
long rc;
pid_t pid = getpid();
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
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 && !broken) {
/*
* tm_signal_self_context_load will set both first and second
* contexts accordingly to the values passed through non-NULL
* array pointers to it, in that case 'vsxs', and invoke the
* signal handler installed for SIGUSR1.
*/
rc = tm_signal_self_context_load(pid, NULL, NULL, NULL, vsxs);
FAIL_IF(rc != pid);
i++;
}
return (broken);
}
int main(void)
{
return test_harness(tm_signal_context_chk, "tm_signal_context_chk_vsx");
}
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018, Breno Leitao, Gustavo Romero, IBM Corp.
*
* This test raises a SIGUSR1 signal, and toggle the MSR[TS]
* fields at the signal handler. With MSR[TS] being set, the kernel will
* force a recheckpoint, which may cause a segfault when returning to
* user space. Since the test needs to re-run, the segfault needs to be
* caught and handled.
*
* In order to continue the test even after a segfault, the context is
* saved prior to the signal being raised, and it is restored when there is
* a segmentation fault. This happens for COUNT_MAX times.
*
* This test never fails (as returning EXIT_FAILURE). It either succeeds,
* or crash the kernel (on a buggy kernel).
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>
#include <sys/mman.h>
#include "tm.h"
#include "utils.h"
#include "reg.h"
#define COUNT_MAX 5000 /* Number of interactions */
/*
* This test only runs on 64 bits system. Unsetting MSR_TS_S to avoid
* compilation issue on 32 bits system. There is no side effect, since the
* whole test will be skipped if it is not running on 64 bits system.
*/
#ifndef __powerpc64__
#undef MSR_TS_S
#define MSR_TS_S 0
#endif
/* Setting contexts because the test will crash and we want to recover */
ucontext_t init_context;
/* count is changed in the signal handler, so it must be volatile */
static volatile int count;
void usr_signal_handler(int signo, siginfo_t *si, void *uc)
{
ucontext_t *ucp = uc;
int ret;
/*
* Allocating memory in a signal handler, and never freeing it on
* purpose, forcing the heap increase, so, the memory leak is what
* we want here.
*/
ucp->uc_link = mmap(NULL, sizeof(ucontext_t),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if (ucp->uc_link == (void *)-1) {
perror("Mmap failed");
exit(-1);
}
/* Forcing the page to be allocated in a page fault */
ret = madvise(ucp->uc_link, sizeof(ucontext_t), MADV_DONTNEED);
if (ret) {
perror("madvise failed");
exit(-1);
}
memcpy(&ucp->uc_link->uc_mcontext, &ucp->uc_mcontext,
sizeof(ucp->uc_mcontext));
/* Forcing to enable MSR[TM] */
UCONTEXT_MSR(ucp) |= MSR_TS_S;
/*
* A fork inside a signal handler seems to be more efficient than a
* fork() prior to the signal being raised.
*/
if (fork() == 0) {
/*
* Both child and parent will return, but, child returns
* with count set so it will exit in the next segfault.
* Parent will continue to loop.
*/
count = COUNT_MAX;
}
/*
* If the change above does not hit the bug, it will cause a
* segmentation fault, since the ck structures are NULL.
*/
}
void seg_signal_handler(int signo, siginfo_t *si, void *uc)
{
count++;
/* Reexecute the test */
setcontext(&init_context);
}
void tm_trap_test(void)
{
struct sigaction usr_sa, seg_sa;
stack_t ss;
usr_sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
usr_sa.sa_sigaction = usr_signal_handler;
seg_sa.sa_flags = SA_SIGINFO;
seg_sa.sa_sigaction = seg_signal_handler;
/*
* Set initial context. Will get back here from
* seg_signal_handler()
*/
getcontext(&init_context);
while (count < COUNT_MAX) {
/* Allocated an alternative signal stack area */
ss.ss_sp = mmap(NULL, SIGSTKSZ, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
if (ss.ss_sp == (void *)-1) {
perror("mmap error\n");
exit(-1);
}
/* Force the allocation through a page fault */
if (madvise(ss.ss_sp, SIGSTKSZ, MADV_DONTNEED)) {
perror("madvise\n");
exit(-1);
}
/*
* Setting an alternative stack to generate a page fault when
* the signal is raised.
*/
if (sigaltstack(&ss, NULL)) {
perror("sigaltstack\n");
exit(-1);
}
/* The signal handler will enable MSR_TS */
sigaction(SIGUSR1, &usr_sa, NULL);
/* If it does not crash, it might segfault, avoid it to retest */
sigaction(SIGSEGV, &seg_sa, NULL);
raise(SIGUSR1);
count++;
}
}
int tm_signal_context_force_tm(void)
{
SKIP_IF(!have_htm());
/*
* Skipping if not running on 64 bits system, since I think it is
* not possible to set mcontext's [MSR] with TS, due to it being 32
* bits.
*/
SKIP_IF(!is_ppc64le());
tm_trap_test();
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
return test_harness(tm_signal_context_force_tm, "tm_signal_context_force_tm");
}
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Michael Neuling, IBM Corp.
*
* 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");
}
@@ -0,0 +1,285 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020, Gustavo Luiz Duarte, IBM Corp.
*
* This test starts a transaction and triggers a signal, forcing a pagefault to
* happen when the kernel signal handling code touches the user signal stack.
*
* In order to avoid pre-faulting the signal stack memory and to force the
* pagefault to happen precisely in the kernel signal handling code, the
* pagefault handling is done in userspace using the userfaultfd facility.
*
* Further pagefaults are triggered by crafting the signal handler's ucontext
* to point to additional memory regions managed by the userfaultfd, so using
* the same mechanism used to avoid pre-faulting the signal stack memory.
*
* On failure (bug is present) kernel crashes or never returns control back to
* userspace. If bug is not present, tests completes almost immediately.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include "tm.h"
#define UF_MEM_SIZE 655360 /* 10 x 64k pages */
/* Memory handled by userfaultfd */
static char *uf_mem;
static size_t uf_mem_offset = 0;
/*
* Data that will be copied into the faulting pages (instead of zero-filled
* pages). This is used to make the test more reliable and avoid segfaulting
* when we return from the signal handler. Since we are making the signal
* handler's ucontext point to newly allocated memory, when that memory is
* paged-in it will contain the expected content.
*/
static char backing_mem[UF_MEM_SIZE];
static size_t pagesize;
/*
* Return a chunk of at least 'size' bytes of memory that will be handled by
* userfaultfd. If 'backing_data' is not NULL, its content will be save to
* 'backing_mem' and then copied into the faulting pages when the page fault
* is handled.
*/
void *get_uf_mem(size_t size, void *backing_data)
{
void *ret;
if (uf_mem_offset + size > UF_MEM_SIZE) {
fprintf(stderr, "Requesting more uf_mem than expected!\n");
exit(EXIT_FAILURE);
}
ret = &uf_mem[uf_mem_offset];
/* Save the data that will be copied into the faulting page */
if (backing_data != NULL)
memcpy(&backing_mem[uf_mem_offset], backing_data, size);
/* Reserve the requested amount of uf_mem */
uf_mem_offset += size;
/* Keep uf_mem_offset aligned to the page size (round up) */
uf_mem_offset = (uf_mem_offset + pagesize - 1) & ~(pagesize - 1);
return ret;
}
void *fault_handler_thread(void *arg)
{
struct uffd_msg msg; /* Data read from userfaultfd */
long uffd; /* userfaultfd file descriptor */
struct uffdio_copy uffdio_copy;
struct pollfd pollfd;
ssize_t nread, offset;
uffd = (long) arg;
for (;;) {
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1) {
perror("poll() failed");
exit(EXIT_FAILURE);
}
nread = read(uffd, &msg, sizeof(msg));
if (nread == 0) {
fprintf(stderr, "read(): EOF on userfaultfd\n");
exit(EXIT_FAILURE);
}
if (nread == -1) {
perror("read() failed");
exit(EXIT_FAILURE);
}
/* We expect only one kind of event */
if (msg.event != UFFD_EVENT_PAGEFAULT) {
fprintf(stderr, "Unexpected event on userfaultfd\n");
exit(EXIT_FAILURE);
}
/*
* We need to handle page faults in units of pages(!).
* So, round faulting address down to page boundary.
*/
uffdio_copy.dst = msg.arg.pagefault.address & ~(pagesize-1);
offset = (char *) uffdio_copy.dst - uf_mem;
uffdio_copy.src = (unsigned long) &backing_mem[offset];
uffdio_copy.len = pagesize;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
perror("ioctl-UFFDIO_COPY failed");
exit(EXIT_FAILURE);
}
}
}
void setup_uf_mem(void)
{
long uffd; /* userfaultfd file descriptor */
pthread_t thr;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int ret;
pagesize = sysconf(_SC_PAGE_SIZE);
/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1) {
perror("userfaultfd() failed");
exit(EXIT_FAILURE);
}
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) {
perror("ioctl-UFFDIO_API failed");
exit(EXIT_FAILURE);
}
/*
* Create a private anonymous mapping. The memory will be demand-zero
* paged, that is, not yet allocated. When we actually touch the memory
* the related page will be allocated via the userfaultfd mechanism.
*/
uf_mem = mmap(NULL, UF_MEM_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (uf_mem == MAP_FAILED) {
perror("mmap() failed");
exit(EXIT_FAILURE);
}
/*
* Register the memory range of the mapping we've just mapped to be
* handled by the userfaultfd object. In 'mode' we request to track
* missing pages (i.e. pages that have not yet been faulted-in).
*/
uffdio_register.range.start = (unsigned long) uf_mem;
uffdio_register.range.len = UF_MEM_SIZE;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
perror("ioctl-UFFDIO_REGISTER");
exit(EXIT_FAILURE);
}
/* Create a thread that will process the userfaultfd events */
ret = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
if (ret != 0) {
fprintf(stderr, "pthread_create(): Error. Returned %d\n", ret);
exit(EXIT_FAILURE);
}
}
/*
* Assumption: the signal was delivered while userspace was in transactional or
* suspended state, i.e. uc->uc_link != NULL.
*/
void signal_handler(int signo, siginfo_t *si, void *uc)
{
ucontext_t *ucp = uc;
/* Skip 'trap' after returning, otherwise we get a SIGTRAP again */
ucp->uc_link->uc_mcontext.regs->nip += 4;
ucp->uc_mcontext.v_regs =
get_uf_mem(sizeof(elf_vrreg_t), ucp->uc_mcontext.v_regs);
ucp->uc_link->uc_mcontext.v_regs =
get_uf_mem(sizeof(elf_vrreg_t), ucp->uc_link->uc_mcontext.v_regs);
ucp->uc_link = get_uf_mem(sizeof(ucontext_t), ucp->uc_link);
}
bool have_userfaultfd(void)
{
long rc;
errno = 0;
rc = syscall(__NR_userfaultfd, -1);
return rc == 0 || errno != ENOSYS;
}
int tm_signal_pagefault(void)
{
struct sigaction sa;
stack_t ss;
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
SKIP_IF(!have_userfaultfd());
setup_uf_mem();
/*
* Set an alternative stack that will generate a page fault when the
* signal is raised. The page fault will be treated via userfaultfd,
* i.e. via fault_handler_thread.
*/
ss.ss_sp = get_uf_mem(SIGSTKSZ, NULL);
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) == -1) {
perror("sigaltstack() failed");
exit(EXIT_FAILURE);
}
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sa.sa_sigaction = signal_handler;
if (sigaction(SIGTRAP, &sa, NULL) == -1) {
perror("sigaction() failed");
exit(EXIT_FAILURE);
}
/* Trigger a SIGTRAP in transactional state */
asm __volatile__(
"tbegin.;"
"beq 1f;"
"trap;"
"1: ;"
: : : "memory");
/* Trigger a SIGTRAP in suspended state */
asm __volatile__(
"tbegin.;"
"beq 1f;"
"tsuspend.;"
"trap;"
"tresume.;"
"1: ;"
: : : "memory");
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
/*
* Depending on kernel config, the TM Bad Thing might not result in a
* crash, instead the kernel never returns control back to userspace, so
* set a tight timeout. If the test passes it completes almost
* immediately.
*/
test_harness_set_timeout(2);
return test_harness(tm_signal_pagefault, "tm_signal_pagefault");
}
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018, Breno Leitao, Gustavo Romero, IBM Corp.
*
* A test case that creates a signal and starts a suspended transaction
* inside the signal handler.
*
* It returns from the signal handler with the CPU at suspended state, but
* without setting usercontext MSR Transaction State (TS) fields.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "utils.h"
#include "tm.h"
void trap_signal_handler(int signo, siginfo_t *si, void *uc)
{
ucontext_t *ucp = (ucontext_t *) uc;
asm("tbegin.; tsuspend.;");
/* Skip 'trap' instruction if it succeed */
ucp->uc_mcontext.regs->nip += 4;
}
int tm_signal_sigreturn_nt(void)
{
struct sigaction trap_sa;
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
trap_sa.sa_flags = SA_SIGINFO;
trap_sa.sa_sigaction = trap_signal_handler;
sigaction(SIGTRAP, &trap_sa, NULL);
raise(SIGTRAP);
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
return test_harness(tm_signal_sigreturn_nt, "tm_signal_sigreturn_nt");
}
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Michael Neuling, IBM Corp.
*
* 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());
SKIP_IF(htm_is_synthetic());
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");
}
@@ -0,0 +1,110 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright 2015, Cyril Bur, IBM Corp.
*/
#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)
@@ -0,0 +1,94 @@
// 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(htm_is_synthetic());
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");
}
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <basic_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
.macro scv level
.long (0x44000001 | (\level) << 5)
.endm
FUNC_START(getppid_scv_tm_active)
PUSH_BASIC_STACK(0)
tbegin.
beq 1f
li r0, __NR_getppid
scv 0
tend.
POP_BASIC_STACK(0)
blr
1:
li r3, -1
POP_BASIC_STACK(0)
blr
FUNC_START(getppid_scv_tm_suspended)
PUSH_BASIC_STACK(0)
tbegin.
beq 1f
li r0, __NR_getppid
tsuspend.
scv 0
tresume.
tend.
POP_BASIC_STACK(0)
blr
1:
li r3, -1
POP_BASIC_STACK(0)
blr
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Sam Bobroff, IBM Corp.
*
* 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"
#ifndef PPC_FEATURE2_SCV
#define PPC_FEATURE2_SCV 0x00100000 /* scv syscall */
#endif
extern int getppid_tm_active(void);
extern int getppid_tm_suspended(void);
extern int getppid_scv_tm_active(void);
extern int getppid_scv_tm_suspended(void);
unsigned retries = 0;
#define TEST_DURATION 10 /* seconds */
pid_t getppid_tm(bool scv, bool suspend)
{
int i;
pid_t pid;
for (i = 0; i < TM_RETRIES; i++) {
if (suspend) {
if (scv)
pid = getppid_scv_tm_suspended();
else
pid = getppid_tm_suspended();
} else {
if (scv)
pid = getppid_scv_tm_active();
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());
SKIP_IF(htm_is_synthetic());
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(false, 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, false) != -1); /* Should fail... */
FAIL_IF(!failure_is_persistent()); /* ...persistently... */
FAIL_IF(!failure_is_syscall()); /* ...with code syscall. */
/* Now do it all again with scv if it is available. */
if (have_hwcap2(PPC_FEATURE2_SCV)) {
FAIL_IF(getppid_tm(true, true) == -1); /* Should succeed. */
FAIL_IF(getppid_tm(true, 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");
}
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Michael Neuling, IBM Corp.
* 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(htm_is_synthetic());
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");
}
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Michael Neuling, IBM Corp.
*
* 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 = 1000000;
int passed = 1;
void tfiar_tfhar(void *in)
{
unsigned long tfhar, tfhar_rd, tfiar, tfiar_rd;
int i;
/* 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());
SKIP_IF(htm_is_synthetic());
/* 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");
}
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2017, Gustavo Romero, IBM Corp.
*
* 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 cpu, rc;
pthread_attr_t attr;
cpu_set_t cpuset;
struct sigaction trap_sa;
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
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);
cpu = pick_online_cpu();
FAIL_IF(cpu < 0);
// Set only one CPU in the mask. Both threads will be bound to that CPU.
CPU_ZERO(&cpuset);
CPU_SET(cpu, &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");
}
@@ -0,0 +1,411 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2017, Gustavo Romero, Breno Leitao, Cyril Bur, IBM Corp.
*
* 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 cpu, rc, exception; /* FP = 0, VEC = 1, VSX = 2 */
pthread_t t1;
pthread_attr_t attr;
cpu_set_t cpuset;
SKIP_IF(!have_htm());
SKIP_IF(htm_is_synthetic());
cpu = pick_online_cpu();
FAIL_IF(cpu < 0);
// Set only one CPU in the mask. Both threads will be bound to that CPU.
CPU_ZERO(&cpuset);
CPU_SET(cpu, &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");
}
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2017, Michael Neuling, IBM Corp.
* 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 "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());
SKIP_IF(htm_is_synthetic());
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");
}
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2015, Michael Neuling, IBM Corp.
*
* 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(htm_is_synthetic());
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");
}
@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2015, Michael Ellerman, IBM Corp.
*/
#ifndef _SELFTESTS_POWERPC_TM_TM_H
#define _SELFTESTS_POWERPC_TM_TM_H
#include <stdbool.h>
#include <asm/tm.h>
#include "utils.h"
#include "reg.h"
#define TM_RETRIES 100
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
}
/*
* Transactional Memory was removed in ISA 3.1. A synthetic TM implementation
* is provided on P10 for threads running in P8/P9 compatibility mode. The
* synthetic implementation immediately fails after tbegin. This failure sets
* Bit 7 (Failure Persistent) and Bit 15 (Implementation-specific).
*/
static inline bool htm_is_synthetic(void)
{
int i;
/*
* Per the ISA, the Failure Persistent bit may be incorrect. Try a few
* times in case we got an Implementation-specific failure on a non ISA
* v3.1 system. On these systems the Implementation-specific failure
* should not be persistent.
*/
for (i = 0; i < TM_RETRIES; i++) {
asm volatile(
"tbegin.;"
"beq 1f;"
"tend.;"
"1:"
:
:
: "memory");
if ((__builtin_get_texasr() & (TEXASR_FP | TEXASR_IC)) !=
(TEXASR_FP | TEXASR_IC))
break;
}
return i == TM_RETRIES;
}
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 ||
(failure_code() & TM_CAUSE_KVM_FAC_UNAV) == TM_CAUSE_KVM_FAC_UNAV)
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 */