1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright 2017, Gustavo Romero, IBM Corp.
4 *
5 * Check if thread endianness is flipped inadvertently to BE on trap
6 * caught in TM whilst MSR.FP and MSR.VEC are zero (i.e. just after
7 * load_fp and load_vec overflowed).
8 *
9 * The issue can be checked on LE machines simply by zeroing load_fp
10 * and load_vec and then causing a trap in TM. Since the endianness
11 * changes to BE on return from the signal handler, 'nop' is
12 * thread as an illegal instruction in following sequence:
13 *	tbegin.
14 *	beq 1f
15 *	trap
16 *	tend.
17 * 1:	nop
18 *
19 * However, although the issue is also present on BE machines, it's a
20 * bit trickier to check it on BE machines because MSR.LE bit is set
21 * to zero which determines a BE endianness that is the native
22 * endianness on BE machines, so nothing notably critical happens,
23 * i.e. no illegal instruction is observed immediately after returning
24 * from the signal handler (as it happens on LE machines). Thus to test
25 * it on BE machines LE endianness is forced after a first trap and then
26 * the endianness is verified on subsequent traps to determine if the
27 * endianness "flipped back" to the native endianness (BE).
28 */
29
30#define _GNU_SOURCE
31#include <error.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <unistd.h>
35#include <htmintrin.h>
36#include <inttypes.h>
37#include <pthread.h>
38#include <sched.h>
39#include <signal.h>
40#include <stdbool.h>
41
42#include "tm.h"
43#include "utils.h"
44
45#define pr_error(error_code, format, ...) \
46	error_at_line(1, error_code, __FILE__, __LINE__, format, ##__VA_ARGS__)
47
48#define MSR_LE 1UL
49#define LE     1UL
50
51pthread_t t0_ping;
52pthread_t t1_pong;
53
54int exit_from_pong;
55
56int trap_event;
57int le;
58
59bool success;
60
61void trap_signal_handler(int signo, siginfo_t *si, void *uc)
62{
63	ucontext_t *ucp = uc;
64	uint64_t thread_endianness;
65
66	/* Get thread endianness: extract bit LE from MSR */
67	thread_endianness = MSR_LE & ucp->uc_mcontext.gp_regs[PT_MSR];
68
69	/*
70	 * Little-Endian Machine
71	 */
72
73	if (le) {
74		/* First trap event */
75		if (trap_event == 0) {
76			/* Do nothing. Since it is returning from this trap
77			 * event that endianness is flipped by the bug, so just
78			 * let the process return from the signal handler and
79			 * check on the second trap event if endianness is
80			 * flipped or not.
81			 */
82		}
83		/* Second trap event */
84		else if (trap_event == 1) {
85			/*
86			 * Since trap was caught in TM on first trap event, if
87			 * endianness was still LE (not flipped inadvertently)
88			 * after returning from the signal handler instruction
89			 * (1) is executed (basically a 'nop'), as it's located
90			 * at address of tbegin. +4 (rollback addr). As (1) on
91			 * LE endianness does in effect nothing, instruction (2)
92			 * is then executed again as 'trap', generating a second
93			 * trap event (note that in that case 'trap' is caught
94			 * not in transacional mode). On te other hand, if after
95			 * the return from the signal handler the endianness in-
96			 * advertently flipped, instruction (1) is tread as a
97			 * branch instruction, i.e. b .+8, hence instruction (3)
98			 * and (4) are executed (tbegin.; trap;) and we get sim-
99			 * ilaly on the trap signal handler, but now in TM mode.
100			 * Either way, it's now possible to check the MSR LE bit
101			 * once in the trap handler to verify if endianness was
102			 * flipped or not after the return from the second trap
103			 * event. If endianness is flipped, the bug is present.
104			 * Finally, getting a trap in TM mode or not is just
105			 * worth noting because it affects the math to determine
106			 * the offset added to the NIP on return: the NIP for a
107			 * trap caught in TM is the rollback address, i.e. the
108			 * next instruction after 'tbegin.', whilst the NIP for
109			 * a trap caught in non-transactional mode is the very
110			 * same address of the 'trap' instruction that generated
111			 * the trap event.
112			 */
113
114			if (thread_endianness == LE) {
115				/* Go to 'success', i.e. instruction (6) */
116				ucp->uc_mcontext.gp_regs[PT_NIP] += 16;
117			} else {
118				/*
119				 * Thread endianness is BE, so it flipped
120				 * inadvertently. Thus we flip back to LE and
121				 * set NIP to go to 'failure', instruction (5).
122				 */
123				ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL;
124				ucp->uc_mcontext.gp_regs[PT_NIP] += 4;
125			}
126		}
127	}
128
129	/*
130	 * Big-Endian Machine
131	 */
132
133	else {
134		/* First trap event */
135		if (trap_event == 0) {
136			/*
137			 * Force thread endianness to be LE. Instructions (1),
138			 * (3), and (4) will be executed, generating a second
139			 * trap in TM mode.
140			 */
141			ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL;
142		}
143		/* Second trap event */
144		else if (trap_event == 1) {
145			/*
146			 * Do nothing. If bug is present on return from this
147			 * second trap event endianness will flip back "automat-
148			 * ically" to BE, otherwise thread endianness will
149			 * continue to be LE, just as it was set above.
150			 */
151		}
152		/* A third trap event */
153		else {
154			/*
155			 * Once here it means that after returning from the sec-
156			 * ond trap event instruction (4) (trap) was executed
157			 * as LE, generating a third trap event. In that case
158			 * endianness is still LE as set on return from the
159			 * first trap event, hence no bug. Otherwise, bug
160			 * flipped back to BE on return from the second trap
161			 * event and instruction (4) was executed as 'tdi' (so
162			 * basically a 'nop') and branch to 'failure' in
163			 * instruction (5) was taken to indicate failure and we
164			 * never get here.
165			 */
166
167			/*
168			 * Flip back to BE and go to instruction (6), i.e. go to
169			 * 'success'.
170			 */
171			ucp->uc_mcontext.gp_regs[PT_MSR] &= ~1UL;
172			ucp->uc_mcontext.gp_regs[PT_NIP] += 8;
173		}
174	}
175
176	trap_event++;
177}
178
179void usr1_signal_handler(int signo, siginfo_t *si, void *not_used)
180{
181	/* Got a USR1 signal from ping(), so just tell pong() to exit */
182	exit_from_pong = 1;
183}
184
185void *ping(void *not_used)
186{
187	uint64_t i;
188
189	trap_event = 0;
190
191	/*
192	 * Wait an amount of context switches so load_fp and load_vec overflows
193	 * and MSR_[FP|VEC|V] is 0.
194	 */
195	for (i = 0; i < 1024*1024*512; i++)
196		;
197
198	asm goto(
199		/*
200		 * [NA] means "Native Endianness", i.e. it tells how a
201		 * instruction is executed on machine's native endianness (in
202		 * other words, native endianness matches kernel endianness).
203		 * [OP] means "Opposite Endianness", i.e. on a BE machine, it
204		 * tells how a instruction is executed as a LE instruction; con-
205		 * versely, on a LE machine, it tells how a instruction is
206		 * executed as a BE instruction. When [NA] is omitted, it means
207		 * that the native interpretation of a given instruction is not
208		 * relevant for the test. Likewise when [OP] is omitted.
209		 */
210
211		" tbegin.        ;" /* (0) tbegin. [NA]                    */
212		" tdi  0, 0, 0x48;" /* (1) nop     [NA]; b (3) [OP]        */
213		" trap           ;" /* (2) trap    [NA]                    */
214		".long 0x1D05007C;" /* (3) tbegin. [OP]                    */
215		".long 0x0800E07F;" /* (4) trap    [OP]; nop   [NA]        */
216		" b %l[failure]  ;" /* (5) b [NA]; MSR.LE flipped (bug)    */
217		" b %l[success]  ;" /* (6) b [NA]; MSR.LE did not flip (ok)*/
218
219		: : : : failure, success);
220
221failure:
222	success = false;
223	goto exit_from_ping;
224
225success:
226	success = true;
227
228exit_from_ping:
229	/* Tell pong() to exit before leaving */
230	pthread_kill(t1_pong, SIGUSR1);
231	return NULL;
232}
233
234void *pong(void *not_used)
235{
236	while (!exit_from_pong)
237		/*
238		 * Induce context switches on ping() thread
239		 * until ping() finishes its job and signs
240		 * to exit from this loop.
241		 */
242		sched_yield();
243
244	return NULL;
245}
246
247int tm_trap_test(void)
248{
249	uint16_t k = 1;
250	int cpu, rc;
251
252	pthread_attr_t attr;
253	cpu_set_t cpuset;
254
255	struct sigaction trap_sa;
256
257	SKIP_IF(!have_htm());
258	SKIP_IF(htm_is_synthetic());
259
260	trap_sa.sa_flags = SA_SIGINFO;
261	trap_sa.sa_sigaction = trap_signal_handler;
262	sigaction(SIGTRAP, &trap_sa, NULL);
263
264	struct sigaction usr1_sa;
265
266	usr1_sa.sa_flags = SA_SIGINFO;
267	usr1_sa.sa_sigaction = usr1_signal_handler;
268	sigaction(SIGUSR1, &usr1_sa, NULL);
269
270	cpu = pick_online_cpu();
271	FAIL_IF(cpu < 0);
272
273	// Set only one CPU in the mask. Both threads will be bound to that CPU.
274	CPU_ZERO(&cpuset);
275	CPU_SET(cpu, &cpuset);
276
277	/* Init pthread attribute */
278	rc = pthread_attr_init(&attr);
279	if (rc)
280		pr_error(rc, "pthread_attr_init()");
281
282	/*
283	 * Bind thread ping() and pong() both to CPU 0 so they ping-pong and
284	 * speed up context switches on ping() thread, speeding up the load_fp
285	 * and load_vec overflow.
286	 */
287	rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
288	if (rc)
289		pr_error(rc, "pthread_attr_setaffinity()");
290
291	/* Figure out the machine endianness */
292	le = (int) *(uint8_t *)&k;
293
294	printf("%s machine detected. Checking if endianness flips %s",
295		le ? "Little-Endian" : "Big-Endian",
296		"inadvertently on trap in TM... ");
297
298	rc = fflush(0);
299	if (rc)
300		pr_error(rc, "fflush()");
301
302	/* Launch ping() */
303	rc = pthread_create(&t0_ping, &attr, ping, NULL);
304	if (rc)
305		pr_error(rc, "pthread_create()");
306
307	exit_from_pong = 0;
308
309	/* Launch pong() */
310	rc = pthread_create(&t1_pong, &attr, pong, NULL);
311	if (rc)
312		pr_error(rc, "pthread_create()");
313
314	rc = pthread_join(t0_ping, NULL);
315	if (rc)
316		pr_error(rc, "pthread_join()");
317
318	rc = pthread_join(t1_pong, NULL);
319	if (rc)
320		pr_error(rc, "pthread_join()");
321
322	if (success) {
323		printf("no.\n"); /* no, endianness did not flip inadvertently */
324		return EXIT_SUCCESS;
325	}
326
327	printf("yes!\n"); /* yes, endianness did flip inadvertently */
328	return EXIT_FAILURE;
329}
330
331int main(int argc, char **argv)
332{
333	return test_harness(tm_trap_test, "tm_trap_test");
334}
335