linux/tools/testing/selftests/powerpc/tm/tm-trap.c
<<
>>
Prefs
   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