Initial commit: Linux kernel 4.19 for Unplugged OS

This commit is contained in:
ziv committed 2025-10-06 13:52:42 +00:00
commit d30235b04c
80519 files changed
+35800681

No files matched your search

@@ -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
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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)
@@ -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");
}
@@ -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
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
@@ -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");
}
+96
View File
@@ -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 */