forked from Minki/linux
powerpc/selftests: Check endianness on trap in TM
Add a selftest to check if endianness is flipped inadvertently to BE (MSR.LE set to zero) on BE and LE machines when a trap is caught in transactional mode and load_fp and load_vec are zero, i.e. when MSR.FP and MSR.VEC are zeroed (disabled). Signed-off-by: Gustavo Romero <gromero@linux.vnet.ibm.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
This commit is contained in:
parent
1c200e63d0
commit
a08082f8e4
@ -13,3 +13,4 @@ tm-signal-context-chk-vmx
|
||||
tm-signal-context-chk-vsx
|
||||
tm-vmx-unavail
|
||||
tm-unavailable
|
||||
tm-trap
|
||||
|
@ -3,7 +3,7 @@ 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-vmxcopy tm-fork tm-tar tm-tmspr tm-vmx-unavail tm-unavailable tm-trap \
|
||||
$(SIGNAL_CONTEXT_CHK_TESTS)
|
||||
|
||||
include ../../lib.mk
|
||||
@ -18,6 +18,7 @@ $(OUTPUT)/tm-tmspr: CFLAGS += -pthread
|
||||
$(OUTPUT)/tm-vmx-unavail: CFLAGS += -pthread -m64
|
||||
$(OUTPUT)/tm-resched-dscr: ../pmu/lib.o
|
||||
$(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
|
||||
|
329
tools/testing/selftests/powerpc/tm/tm-trap.c
Normal file
329
tools/testing/selftests/powerpc/tm/tm-trap.c
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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");
|
||||
}
|
Loading…
Reference in New Issue
Block a user