| // 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"); | |
| } |