powerpc/selftests: Check endianness on trap in TM
authorGustavo Romero <gromero@linux.vnet.ibm.com>
Sun, 31 Dec 2017 23:20:46 +0000 (18:20 -0500)
committerMichael Ellerman <mpe@ellerman.id.au>
Sun, 21 Jan 2018 18:48:37 +0000 (05:48 +1100)
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>
tools/testing/selftests/powerpc/tm/.gitignore
tools/testing/selftests/powerpc/tm/Makefile
tools/testing/selftests/powerpc/tm/tm-trap.c [new file with mode: 0644]

index 241a4a4ee0e4f43dc447eb98887e07b9c2868e07..bb90d4b79524eff3c50d9037814749a44cc8061c 100644 (file)
@@ -13,3 +13,4 @@ tm-signal-context-chk-vmx
 tm-signal-context-chk-vsx
 tm-vmx-unavail
 tm-unavailable
+tm-trap
index 8ed6f8c5723075d2dbc56e8fa058186fcc319ece..a23453943ad2b95538571015c066eaa2c2deb711 100644 (file)
@@ -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
diff --git a/tools/testing/selftests/powerpc/tm/tm-trap.c b/tools/testing/selftests/powerpc/tm/tm-trap.c
new file mode 100644 (file)
index 0000000..5d92c23
--- /dev/null
@@ -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");
+}