1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright 2017, Gustavo Romero, IBM Corp.
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).
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:
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).
35 #include <htmintrin.h>
45 #define pr_error(error_code, format, ...) \
46 error_at_line(1, error_code, __FILE__, __LINE__, format, ##__VA_ARGS__)
61 void trap_signal_handler(int signo
, siginfo_t
*si
, void *uc
)
64 uint64_t thread_endianness
;
66 /* Get thread endianness: extract bit LE from MSR */
67 thread_endianness
= MSR_LE
& ucp
->uc_mcontext
.gp_regs
[PT_MSR
];
70 * Little-Endian Machine
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
83 /* Second trap event */
84 else if (trap_event
== 1) {
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
114 if (thread_endianness
== LE
) {
115 /* Go to 'success', i.e. instruction (6) */
116 ucp
->uc_mcontext
.gp_regs
[PT_NIP
] += 16;
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).
123 ucp
->uc_mcontext
.gp_regs
[PT_MSR
] |= 1UL;
124 ucp
->uc_mcontext
.gp_regs
[PT_NIP
] += 4;
134 /* First trap event */
135 if (trap_event
== 0) {
137 * Force thread endianness to be LE. Instructions (1),
138 * (3), and (4) will be executed, generating a second
141 ucp
->uc_mcontext
.gp_regs
[PT_MSR
] |= 1UL;
143 /* Second trap event */
144 else if (trap_event
== 1) {
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.
152 /* A third trap event */
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
168 * Flip back to BE and go to instruction (6), i.e. go to
171 ucp
->uc_mcontext
.gp_regs
[PT_MSR
] &= ~1UL;
172 ucp
->uc_mcontext
.gp_regs
[PT_NIP
] += 8;
179 void usr1_signal_handler(int signo
, siginfo_t
*si
, void *not_used
)
181 /* Got a USR1 signal from ping(), so just tell pong() to exit */
185 void *ping(void *not_used
)
192 * Wait an amount of context switches so load_fp and load_vec overflows
193 * and MSR_[FP|VEC|V] is 0.
195 for (i
= 0; i
< 1024*1024*512; i
++)
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.
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)*/
219 : : : : failure
, success
);
229 /* Tell pong() to exit before leaving */
230 pthread_kill(t1_pong
, SIGUSR1
);
234 void *pong(void *not_used
)
236 while (!exit_from_pong
)
238 * Induce context switches on ping() thread
239 * until ping() finishes its job and signs
240 * to exit from this loop.
247 int tm_trap_test(void)
255 struct sigaction trap_sa
;
257 SKIP_IF(!have_htm());
258 SKIP_IF(htm_is_synthetic());
260 trap_sa
.sa_flags
= SA_SIGINFO
;
261 trap_sa
.sa_sigaction
= trap_signal_handler
;
262 sigaction(SIGTRAP
, &trap_sa
, NULL
);
264 struct sigaction usr1_sa
;
266 usr1_sa
.sa_flags
= SA_SIGINFO
;
267 usr1_sa
.sa_sigaction
= usr1_signal_handler
;
268 sigaction(SIGUSR1
, &usr1_sa
, NULL
);
270 cpu
= pick_online_cpu();
273 // Set only one CPU in the mask. Both threads will be bound to that CPU.
275 CPU_SET(cpu
, &cpuset
);
277 /* Init pthread attribute */
278 rc
= pthread_attr_init(&attr
);
280 pr_error(rc
, "pthread_attr_init()");
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.
287 rc
= pthread_attr_setaffinity_np(&attr
, sizeof(cpu_set_t
), &cpuset
);
289 pr_error(rc
, "pthread_attr_setaffinity()");
291 /* Figure out the machine endianness */
292 le
= (int) *(uint8_t *)&k
;
294 printf("%s machine detected. Checking if endianness flips %s",
295 le
? "Little-Endian" : "Big-Endian",
296 "inadvertently on trap in TM... ");
300 pr_error(rc
, "fflush()");
303 rc
= pthread_create(&t0_ping
, &attr
, ping
, NULL
);
305 pr_error(rc
, "pthread_create()");
310 rc
= pthread_create(&t1_pong
, &attr
, pong
, NULL
);
312 pr_error(rc
, "pthread_create()");
314 rc
= pthread_join(t0_ping
, NULL
);
316 pr_error(rc
, "pthread_join()");
318 rc
= pthread_join(t1_pong
, NULL
);
320 pr_error(rc
, "pthread_join()");
323 printf("no.\n"); /* no, endianness did not flip inadvertently */
327 printf("yes!\n"); /* yes, endianness did flip inadvertently */
331 int main(int argc
, char **argv
)
333 return test_harness(tm_trap_test
, "tm_trap_test");