162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2017, Gustavo Romero, IBM Corp.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Check if thread endianness is flipped inadvertently to BE on trap
662306a36Sopenharmony_ci * caught in TM whilst MSR.FP and MSR.VEC are zero (i.e. just after
762306a36Sopenharmony_ci * load_fp and load_vec overflowed).
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * The issue can be checked on LE machines simply by zeroing load_fp
1062306a36Sopenharmony_ci * and load_vec and then causing a trap in TM. Since the endianness
1162306a36Sopenharmony_ci * changes to BE on return from the signal handler, 'nop' is
1262306a36Sopenharmony_ci * thread as an illegal instruction in following sequence:
1362306a36Sopenharmony_ci *	tbegin.
1462306a36Sopenharmony_ci *	beq 1f
1562306a36Sopenharmony_ci *	trap
1662306a36Sopenharmony_ci *	tend.
1762306a36Sopenharmony_ci * 1:	nop
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * However, although the issue is also present on BE machines, it's a
2062306a36Sopenharmony_ci * bit trickier to check it on BE machines because MSR.LE bit is set
2162306a36Sopenharmony_ci * to zero which determines a BE endianness that is the native
2262306a36Sopenharmony_ci * endianness on BE machines, so nothing notably critical happens,
2362306a36Sopenharmony_ci * i.e. no illegal instruction is observed immediately after returning
2462306a36Sopenharmony_ci * from the signal handler (as it happens on LE machines). Thus to test
2562306a36Sopenharmony_ci * it on BE machines LE endianness is forced after a first trap and then
2662306a36Sopenharmony_ci * the endianness is verified on subsequent traps to determine if the
2762306a36Sopenharmony_ci * endianness "flipped back" to the native endianness (BE).
2862306a36Sopenharmony_ci */
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define _GNU_SOURCE
3162306a36Sopenharmony_ci#include <error.h>
3262306a36Sopenharmony_ci#include <stdio.h>
3362306a36Sopenharmony_ci#include <stdlib.h>
3462306a36Sopenharmony_ci#include <unistd.h>
3562306a36Sopenharmony_ci#include <htmintrin.h>
3662306a36Sopenharmony_ci#include <inttypes.h>
3762306a36Sopenharmony_ci#include <pthread.h>
3862306a36Sopenharmony_ci#include <sched.h>
3962306a36Sopenharmony_ci#include <signal.h>
4062306a36Sopenharmony_ci#include <stdbool.h>
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#include "tm.h"
4362306a36Sopenharmony_ci#include "utils.h"
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define pr_error(error_code, format, ...) \
4662306a36Sopenharmony_ci	error_at_line(1, error_code, __FILE__, __LINE__, format, ##__VA_ARGS__)
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci#define MSR_LE 1UL
4962306a36Sopenharmony_ci#define LE     1UL
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cipthread_t t0_ping;
5262306a36Sopenharmony_cipthread_t t1_pong;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ciint exit_from_pong;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ciint trap_event;
5762306a36Sopenharmony_ciint le;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cibool success;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_civoid trap_signal_handler(int signo, siginfo_t *si, void *uc)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	ucontext_t *ucp = uc;
6462306a36Sopenharmony_ci	uint64_t thread_endianness;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	/* Get thread endianness: extract bit LE from MSR */
6762306a36Sopenharmony_ci	thread_endianness = MSR_LE & ucp->uc_mcontext.gp_regs[PT_MSR];
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/*
7062306a36Sopenharmony_ci	 * Little-Endian Machine
7162306a36Sopenharmony_ci	 */
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (le) {
7462306a36Sopenharmony_ci		/* First trap event */
7562306a36Sopenharmony_ci		if (trap_event == 0) {
7662306a36Sopenharmony_ci			/* Do nothing. Since it is returning from this trap
7762306a36Sopenharmony_ci			 * event that endianness is flipped by the bug, so just
7862306a36Sopenharmony_ci			 * let the process return from the signal handler and
7962306a36Sopenharmony_ci			 * check on the second trap event if endianness is
8062306a36Sopenharmony_ci			 * flipped or not.
8162306a36Sopenharmony_ci			 */
8262306a36Sopenharmony_ci		}
8362306a36Sopenharmony_ci		/* Second trap event */
8462306a36Sopenharmony_ci		else if (trap_event == 1) {
8562306a36Sopenharmony_ci			/*
8662306a36Sopenharmony_ci			 * Since trap was caught in TM on first trap event, if
8762306a36Sopenharmony_ci			 * endianness was still LE (not flipped inadvertently)
8862306a36Sopenharmony_ci			 * after returning from the signal handler instruction
8962306a36Sopenharmony_ci			 * (1) is executed (basically a 'nop'), as it's located
9062306a36Sopenharmony_ci			 * at address of tbegin. +4 (rollback addr). As (1) on
9162306a36Sopenharmony_ci			 * LE endianness does in effect nothing, instruction (2)
9262306a36Sopenharmony_ci			 * is then executed again as 'trap', generating a second
9362306a36Sopenharmony_ci			 * trap event (note that in that case 'trap' is caught
9462306a36Sopenharmony_ci			 * not in transacional mode). On te other hand, if after
9562306a36Sopenharmony_ci			 * the return from the signal handler the endianness in-
9662306a36Sopenharmony_ci			 * advertently flipped, instruction (1) is tread as a
9762306a36Sopenharmony_ci			 * branch instruction, i.e. b .+8, hence instruction (3)
9862306a36Sopenharmony_ci			 * and (4) are executed (tbegin.; trap;) and we get sim-
9962306a36Sopenharmony_ci			 * ilaly on the trap signal handler, but now in TM mode.
10062306a36Sopenharmony_ci			 * Either way, it's now possible to check the MSR LE bit
10162306a36Sopenharmony_ci			 * once in the trap handler to verify if endianness was
10262306a36Sopenharmony_ci			 * flipped or not after the return from the second trap
10362306a36Sopenharmony_ci			 * event. If endianness is flipped, the bug is present.
10462306a36Sopenharmony_ci			 * Finally, getting a trap in TM mode or not is just
10562306a36Sopenharmony_ci			 * worth noting because it affects the math to determine
10662306a36Sopenharmony_ci			 * the offset added to the NIP on return: the NIP for a
10762306a36Sopenharmony_ci			 * trap caught in TM is the rollback address, i.e. the
10862306a36Sopenharmony_ci			 * next instruction after 'tbegin.', whilst the NIP for
10962306a36Sopenharmony_ci			 * a trap caught in non-transactional mode is the very
11062306a36Sopenharmony_ci			 * same address of the 'trap' instruction that generated
11162306a36Sopenharmony_ci			 * the trap event.
11262306a36Sopenharmony_ci			 */
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci			if (thread_endianness == LE) {
11562306a36Sopenharmony_ci				/* Go to 'success', i.e. instruction (6) */
11662306a36Sopenharmony_ci				ucp->uc_mcontext.gp_regs[PT_NIP] += 16;
11762306a36Sopenharmony_ci			} else {
11862306a36Sopenharmony_ci				/*
11962306a36Sopenharmony_ci				 * Thread endianness is BE, so it flipped
12062306a36Sopenharmony_ci				 * inadvertently. Thus we flip back to LE and
12162306a36Sopenharmony_ci				 * set NIP to go to 'failure', instruction (5).
12262306a36Sopenharmony_ci				 */
12362306a36Sopenharmony_ci				ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL;
12462306a36Sopenharmony_ci				ucp->uc_mcontext.gp_regs[PT_NIP] += 4;
12562306a36Sopenharmony_ci			}
12662306a36Sopenharmony_ci		}
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/*
13062306a36Sopenharmony_ci	 * Big-Endian Machine
13162306a36Sopenharmony_ci	 */
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	else {
13462306a36Sopenharmony_ci		/* First trap event */
13562306a36Sopenharmony_ci		if (trap_event == 0) {
13662306a36Sopenharmony_ci			/*
13762306a36Sopenharmony_ci			 * Force thread endianness to be LE. Instructions (1),
13862306a36Sopenharmony_ci			 * (3), and (4) will be executed, generating a second
13962306a36Sopenharmony_ci			 * trap in TM mode.
14062306a36Sopenharmony_ci			 */
14162306a36Sopenharmony_ci			ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL;
14262306a36Sopenharmony_ci		}
14362306a36Sopenharmony_ci		/* Second trap event */
14462306a36Sopenharmony_ci		else if (trap_event == 1) {
14562306a36Sopenharmony_ci			/*
14662306a36Sopenharmony_ci			 * Do nothing. If bug is present on return from this
14762306a36Sopenharmony_ci			 * second trap event endianness will flip back "automat-
14862306a36Sopenharmony_ci			 * ically" to BE, otherwise thread endianness will
14962306a36Sopenharmony_ci			 * continue to be LE, just as it was set above.
15062306a36Sopenharmony_ci			 */
15162306a36Sopenharmony_ci		}
15262306a36Sopenharmony_ci		/* A third trap event */
15362306a36Sopenharmony_ci		else {
15462306a36Sopenharmony_ci			/*
15562306a36Sopenharmony_ci			 * Once here it means that after returning from the sec-
15662306a36Sopenharmony_ci			 * ond trap event instruction (4) (trap) was executed
15762306a36Sopenharmony_ci			 * as LE, generating a third trap event. In that case
15862306a36Sopenharmony_ci			 * endianness is still LE as set on return from the
15962306a36Sopenharmony_ci			 * first trap event, hence no bug. Otherwise, bug
16062306a36Sopenharmony_ci			 * flipped back to BE on return from the second trap
16162306a36Sopenharmony_ci			 * event and instruction (4) was executed as 'tdi' (so
16262306a36Sopenharmony_ci			 * basically a 'nop') and branch to 'failure' in
16362306a36Sopenharmony_ci			 * instruction (5) was taken to indicate failure and we
16462306a36Sopenharmony_ci			 * never get here.
16562306a36Sopenharmony_ci			 */
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci			/*
16862306a36Sopenharmony_ci			 * Flip back to BE and go to instruction (6), i.e. go to
16962306a36Sopenharmony_ci			 * 'success'.
17062306a36Sopenharmony_ci			 */
17162306a36Sopenharmony_ci			ucp->uc_mcontext.gp_regs[PT_MSR] &= ~1UL;
17262306a36Sopenharmony_ci			ucp->uc_mcontext.gp_regs[PT_NIP] += 8;
17362306a36Sopenharmony_ci		}
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	trap_event++;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_civoid usr1_signal_handler(int signo, siginfo_t *si, void *not_used)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	/* Got a USR1 signal from ping(), so just tell pong() to exit */
18262306a36Sopenharmony_ci	exit_from_pong = 1;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_civoid *ping(void *not_used)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	uint64_t i;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	trap_event = 0;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	/*
19262306a36Sopenharmony_ci	 * Wait an amount of context switches so load_fp and load_vec overflows
19362306a36Sopenharmony_ci	 * and MSR_[FP|VEC|V] is 0.
19462306a36Sopenharmony_ci	 */
19562306a36Sopenharmony_ci	for (i = 0; i < 1024*1024*512; i++)
19662306a36Sopenharmony_ci		;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	asm goto(
19962306a36Sopenharmony_ci		/*
20062306a36Sopenharmony_ci		 * [NA] means "Native Endianness", i.e. it tells how a
20162306a36Sopenharmony_ci		 * instruction is executed on machine's native endianness (in
20262306a36Sopenharmony_ci		 * other words, native endianness matches kernel endianness).
20362306a36Sopenharmony_ci		 * [OP] means "Opposite Endianness", i.e. on a BE machine, it
20462306a36Sopenharmony_ci		 * tells how a instruction is executed as a LE instruction; con-
20562306a36Sopenharmony_ci		 * versely, on a LE machine, it tells how a instruction is
20662306a36Sopenharmony_ci		 * executed as a BE instruction. When [NA] is omitted, it means
20762306a36Sopenharmony_ci		 * that the native interpretation of a given instruction is not
20862306a36Sopenharmony_ci		 * relevant for the test. Likewise when [OP] is omitted.
20962306a36Sopenharmony_ci		 */
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci		" tbegin.        ;" /* (0) tbegin. [NA]                    */
21262306a36Sopenharmony_ci		" tdi  0, 0, 0x48;" /* (1) nop     [NA]; b (3) [OP]        */
21362306a36Sopenharmony_ci		" trap           ;" /* (2) trap    [NA]                    */
21462306a36Sopenharmony_ci		".long 0x1D05007C;" /* (3) tbegin. [OP]                    */
21562306a36Sopenharmony_ci		".long 0x0800E07F;" /* (4) trap    [OP]; nop   [NA]        */
21662306a36Sopenharmony_ci		" b %l[failure]  ;" /* (5) b [NA]; MSR.LE flipped (bug)    */
21762306a36Sopenharmony_ci		" b %l[success]  ;" /* (6) b [NA]; MSR.LE did not flip (ok)*/
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		: : : : failure, success);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cifailure:
22262306a36Sopenharmony_ci	success = false;
22362306a36Sopenharmony_ci	goto exit_from_ping;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cisuccess:
22662306a36Sopenharmony_ci	success = true;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ciexit_from_ping:
22962306a36Sopenharmony_ci	/* Tell pong() to exit before leaving */
23062306a36Sopenharmony_ci	pthread_kill(t1_pong, SIGUSR1);
23162306a36Sopenharmony_ci	return NULL;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_civoid *pong(void *not_used)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	while (!exit_from_pong)
23762306a36Sopenharmony_ci		/*
23862306a36Sopenharmony_ci		 * Induce context switches on ping() thread
23962306a36Sopenharmony_ci		 * until ping() finishes its job and signs
24062306a36Sopenharmony_ci		 * to exit from this loop.
24162306a36Sopenharmony_ci		 */
24262306a36Sopenharmony_ci		sched_yield();
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	return NULL;
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ciint tm_trap_test(void)
24862306a36Sopenharmony_ci{
24962306a36Sopenharmony_ci	uint16_t k = 1;
25062306a36Sopenharmony_ci	int cpu, rc;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	pthread_attr_t attr;
25362306a36Sopenharmony_ci	cpu_set_t cpuset;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	struct sigaction trap_sa;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	SKIP_IF(!have_htm());
25862306a36Sopenharmony_ci	SKIP_IF(htm_is_synthetic());
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	trap_sa.sa_flags = SA_SIGINFO;
26162306a36Sopenharmony_ci	trap_sa.sa_sigaction = trap_signal_handler;
26262306a36Sopenharmony_ci	sigaction(SIGTRAP, &trap_sa, NULL);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	struct sigaction usr1_sa;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	usr1_sa.sa_flags = SA_SIGINFO;
26762306a36Sopenharmony_ci	usr1_sa.sa_sigaction = usr1_signal_handler;
26862306a36Sopenharmony_ci	sigaction(SIGUSR1, &usr1_sa, NULL);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	cpu = pick_online_cpu();
27162306a36Sopenharmony_ci	FAIL_IF(cpu < 0);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	// Set only one CPU in the mask. Both threads will be bound to that CPU.
27462306a36Sopenharmony_ci	CPU_ZERO(&cpuset);
27562306a36Sopenharmony_ci	CPU_SET(cpu, &cpuset);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	/* Init pthread attribute */
27862306a36Sopenharmony_ci	rc = pthread_attr_init(&attr);
27962306a36Sopenharmony_ci	if (rc)
28062306a36Sopenharmony_ci		pr_error(rc, "pthread_attr_init()");
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	/*
28362306a36Sopenharmony_ci	 * Bind thread ping() and pong() both to CPU 0 so they ping-pong and
28462306a36Sopenharmony_ci	 * speed up context switches on ping() thread, speeding up the load_fp
28562306a36Sopenharmony_ci	 * and load_vec overflow.
28662306a36Sopenharmony_ci	 */
28762306a36Sopenharmony_ci	rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
28862306a36Sopenharmony_ci	if (rc)
28962306a36Sopenharmony_ci		pr_error(rc, "pthread_attr_setaffinity()");
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	/* Figure out the machine endianness */
29262306a36Sopenharmony_ci	le = (int) *(uint8_t *)&k;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	printf("%s machine detected. Checking if endianness flips %s",
29562306a36Sopenharmony_ci		le ? "Little-Endian" : "Big-Endian",
29662306a36Sopenharmony_ci		"inadvertently on trap in TM... ");
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	rc = fflush(0);
29962306a36Sopenharmony_ci	if (rc)
30062306a36Sopenharmony_ci		pr_error(rc, "fflush()");
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	/* Launch ping() */
30362306a36Sopenharmony_ci	rc = pthread_create(&t0_ping, &attr, ping, NULL);
30462306a36Sopenharmony_ci	if (rc)
30562306a36Sopenharmony_ci		pr_error(rc, "pthread_create()");
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	exit_from_pong = 0;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	/* Launch pong() */
31062306a36Sopenharmony_ci	rc = pthread_create(&t1_pong, &attr, pong, NULL);
31162306a36Sopenharmony_ci	if (rc)
31262306a36Sopenharmony_ci		pr_error(rc, "pthread_create()");
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	rc = pthread_join(t0_ping, NULL);
31562306a36Sopenharmony_ci	if (rc)
31662306a36Sopenharmony_ci		pr_error(rc, "pthread_join()");
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	rc = pthread_join(t1_pong, NULL);
31962306a36Sopenharmony_ci	if (rc)
32062306a36Sopenharmony_ci		pr_error(rc, "pthread_join()");
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	if (success) {
32362306a36Sopenharmony_ci		printf("no.\n"); /* no, endianness did not flip inadvertently */
32462306a36Sopenharmony_ci		return EXIT_SUCCESS;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	printf("yes!\n"); /* yes, endianness did flip inadvertently */
32862306a36Sopenharmony_ci	return EXIT_FAILURE;
32962306a36Sopenharmony_ci}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ciint main(int argc, char **argv)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	return test_harness(tm_trap_test, "tm_trap_test");
33462306a36Sopenharmony_ci}
335