e42edf9b9d
Transactional Memory was removed from the architecture in ISA v3.1. For threads running in P8/P9 compatibility mode on P10 a synthetic TM implementation is provided. In this implementation, tbegin. always sets cr0 eq meaning the abort handler is always called. This is not an issue as users of TM are expected to have a fallback non transactional way to make forward progress in the abort handler. The TEXASR indicates if a transaction failure is due to a synthetic implementation. Some of the TM self tests need a non-degenerate TM implementation for their testing to be meaningful so check for a synthetic implementation and skip the test if so. Signed-off-by: Jordan Niethe <jniethe5@gmail.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/20210729041317.366612-2-jniethe5@gmail.com
412 lines
11 KiB
C
412 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2017, Gustavo Romero, Breno Leitao, Cyril Bur, IBM Corp.
|
|
*
|
|
* Force FP, VEC and VSX unavailable exception during transaction in all
|
|
* possible scenarios regarding the MSR.FP and MSR.VEC state, e.g. when FP
|
|
* is enable and VEC is disable, when FP is disable and VEC is enable, and
|
|
* so on. Then we check if the restored state is correctly set for the
|
|
* FP and VEC registers to the previous state we set just before we entered
|
|
* in TM, i.e. we check if it corrupts somehow the recheckpointed FP and
|
|
* VEC/Altivec registers on abortion due to an unavailable exception in TM.
|
|
* N.B. In this test we do not test all the FP/Altivec/VSX registers for
|
|
* corruption, but only for registers vs0 and vs32, which are respectively
|
|
* representatives of FP and VEC/Altivec reg sets.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <error.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
|
|
#include "tm.h"
|
|
|
|
#define DEBUG 0
|
|
|
|
/* Unavailable exceptions to test in HTM */
|
|
#define FP_UNA_EXCEPTION 0
|
|
#define VEC_UNA_EXCEPTION 1
|
|
#define VSX_UNA_EXCEPTION 2
|
|
|
|
#define NUM_EXCEPTIONS 3
|
|
#define err_at_line(status, errnum, format, ...) \
|
|
error_at_line(status, errnum, __FILE__, __LINE__, format ##__VA_ARGS__)
|
|
|
|
#define pr_warn(code, format, ...) err_at_line(0, code, format, ##__VA_ARGS__)
|
|
#define pr_err(code, format, ...) err_at_line(1, code, format, ##__VA_ARGS__)
|
|
|
|
struct Flags {
|
|
int touch_fp;
|
|
int touch_vec;
|
|
int result;
|
|
int exception;
|
|
} flags;
|
|
|
|
bool expecting_failure(void)
|
|
{
|
|
if (flags.touch_fp && flags.exception == FP_UNA_EXCEPTION)
|
|
return false;
|
|
|
|
if (flags.touch_vec && flags.exception == VEC_UNA_EXCEPTION)
|
|
return false;
|
|
|
|
/*
|
|
* If both FP and VEC are touched it does not mean that touching VSX
|
|
* won't raise an exception. However since FP and VEC state are already
|
|
* correctly loaded, the transaction is not aborted (i.e.
|
|
* treclaimed/trecheckpointed) and MSR.VSX is just set as 1, so a TM
|
|
* failure is not expected also in this case.
|
|
*/
|
|
if ((flags.touch_fp && flags.touch_vec) &&
|
|
flags.exception == VSX_UNA_EXCEPTION)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Check if failure occurred whilst in transaction. */
|
|
bool is_failure(uint64_t condition_reg)
|
|
{
|
|
/*
|
|
* When failure handling occurs, CR0 is set to 0b1010 (0xa). Otherwise
|
|
* transaction completes without failure and hence reaches out 'tend.'
|
|
* that sets CR0 to 0b0100 (0x4).
|
|
*/
|
|
return ((condition_reg >> 28) & 0xa) == 0xa;
|
|
}
|
|
|
|
void *tm_una_ping(void *input)
|
|
{
|
|
|
|
/*
|
|
* Expected values for vs0 and vs32 after a TM failure. They must never
|
|
* change, otherwise they got corrupted.
|
|
*/
|
|
uint64_t high_vs0 = 0x5555555555555555;
|
|
uint64_t low_vs0 = 0xffffffffffffffff;
|
|
uint64_t high_vs32 = 0x5555555555555555;
|
|
uint64_t low_vs32 = 0xffffffffffffffff;
|
|
|
|
/* Counter for busy wait */
|
|
uint64_t counter = 0x1ff000000;
|
|
|
|
/*
|
|
* Variable to keep a copy of CR register content taken just after we
|
|
* leave the transactional state.
|
|
*/
|
|
uint64_t cr_ = 0;
|
|
|
|
/*
|
|
* Wait a bit so thread can get its name "ping". This is not important
|
|
* to reproduce the issue but it's nice to have for systemtap debugging.
|
|
*/
|
|
if (DEBUG)
|
|
sleep(1);
|
|
|
|
printf("If MSR.FP=%d MSR.VEC=%d: ", flags.touch_fp, flags.touch_vec);
|
|
|
|
if (flags.exception != FP_UNA_EXCEPTION &&
|
|
flags.exception != VEC_UNA_EXCEPTION &&
|
|
flags.exception != VSX_UNA_EXCEPTION) {
|
|
printf("No valid exception specified to test.\n");
|
|
return NULL;
|
|
}
|
|
|
|
asm (
|
|
/* Prepare to merge low and high. */
|
|
" mtvsrd 33, %[high_vs0] ;"
|
|
" mtvsrd 34, %[low_vs0] ;"
|
|
|
|
/*
|
|
* Adjust VS0 expected value after an TM failure,
|
|
* i.e. vs0 = 0x5555555555555555555FFFFFFFFFFFFFFFF
|
|
*/
|
|
" xxmrghd 0, 33, 34 ;"
|
|
|
|
/*
|
|
* Adjust VS32 expected value after an TM failure,
|
|
* i.e. vs32 = 0x5555555555555555555FFFFFFFFFFFFFFFF
|
|
*/
|
|
" xxmrghd 32, 33, 34 ;"
|
|
|
|
/*
|
|
* Wait an amount of context switches so load_fp and load_vec
|
|
* overflow and MSR.FP, MSR.VEC, and MSR.VSX become zero (off).
|
|
*/
|
|
" mtctr %[counter] ;"
|
|
|
|
/* Decrement CTR branch if CTR non zero. */
|
|
"1: bdnz 1b ;"
|
|
|
|
/*
|
|
* Check if we want to touch FP prior to the test in order
|
|
* to set MSR.FP = 1 before provoking an unavailable
|
|
* exception in TM.
|
|
*/
|
|
" cmpldi %[touch_fp], 0 ;"
|
|
" beq no_fp ;"
|
|
" fadd 10, 10, 10 ;"
|
|
"no_fp: ;"
|
|
|
|
/*
|
|
* Check if we want to touch VEC prior to the test in order
|
|
* to set MSR.VEC = 1 before provoking an unavailable
|
|
* exception in TM.
|
|
*/
|
|
" cmpldi %[touch_vec], 0 ;"
|
|
" beq no_vec ;"
|
|
" vaddcuw 10, 10, 10 ;"
|
|
"no_vec: ;"
|
|
|
|
/*
|
|
* Perhaps it would be a better idea to do the
|
|
* compares outside transactional context and simply
|
|
* duplicate code.
|
|
*/
|
|
" tbegin. ;"
|
|
" beq trans_fail ;"
|
|
|
|
/* Do we do FP Unavailable? */
|
|
" cmpldi %[exception], %[ex_fp] ;"
|
|
" bne 1f ;"
|
|
" fadd 10, 10, 10 ;"
|
|
" b done ;"
|
|
|
|
/* Do we do VEC Unavailable? */
|
|
"1: cmpldi %[exception], %[ex_vec] ;"
|
|
" bne 2f ;"
|
|
" vaddcuw 10, 10, 10 ;"
|
|
" b done ;"
|
|
|
|
/*
|
|
* Not FP or VEC, therefore VSX. Ensure this
|
|
* instruction always generates a VSX Unavailable.
|
|
* ISA 3.0 is tricky here.
|
|
* (xxmrghd will on ISA 2.07 and ISA 3.0)
|
|
*/
|
|
"2: xxmrghd 10, 10, 10 ;"
|
|
|
|
"done: tend. ;"
|
|
|
|
"trans_fail: ;"
|
|
|
|
/* Give values back to C. */
|
|
" mfvsrd %[high_vs0], 0 ;"
|
|
" xxsldwi 3, 0, 0, 2 ;"
|
|
" mfvsrd %[low_vs0], 3 ;"
|
|
" mfvsrd %[high_vs32], 32 ;"
|
|
" xxsldwi 3, 32, 32, 2 ;"
|
|
" mfvsrd %[low_vs32], 3 ;"
|
|
|
|
/* Give CR back to C so that it can check what happened. */
|
|
" mfcr %[cr_] ;"
|
|
|
|
: [high_vs0] "+r" (high_vs0),
|
|
[low_vs0] "+r" (low_vs0),
|
|
[high_vs32] "=r" (high_vs32),
|
|
[low_vs32] "=r" (low_vs32),
|
|
[cr_] "+r" (cr_)
|
|
: [touch_fp] "r" (flags.touch_fp),
|
|
[touch_vec] "r" (flags.touch_vec),
|
|
[exception] "r" (flags.exception),
|
|
[ex_fp] "i" (FP_UNA_EXCEPTION),
|
|
[ex_vec] "i" (VEC_UNA_EXCEPTION),
|
|
[ex_vsx] "i" (VSX_UNA_EXCEPTION),
|
|
[counter] "r" (counter)
|
|
|
|
: "cr0", "ctr", "v10", "vs0", "vs10", "vs3", "vs32", "vs33",
|
|
"vs34", "fr10"
|
|
|
|
);
|
|
|
|
/*
|
|
* Check if we were expecting a failure and it did not occur by checking
|
|
* CR0 state just after we leave the transaction. Either way we check if
|
|
* vs0 or vs32 got corrupted.
|
|
*/
|
|
if (expecting_failure() && !is_failure(cr_)) {
|
|
printf("\n\tExpecting the transaction to fail, %s",
|
|
"but it didn't\n\t");
|
|
flags.result++;
|
|
}
|
|
|
|
/* Check if we were not expecting a failure and a it occurred. */
|
|
if (!expecting_failure() && is_failure(cr_) &&
|
|
!failure_is_reschedule()) {
|
|
printf("\n\tUnexpected transaction failure 0x%02lx\n\t",
|
|
failure_code());
|
|
return (void *) -1;
|
|
}
|
|
|
|
/*
|
|
* Check if TM failed due to the cause we were expecting. 0xda is a
|
|
* TM_CAUSE_FAC_UNAV cause, otherwise it's an unexpected cause, unless
|
|
* it was caused by a reschedule.
|
|
*/
|
|
if (is_failure(cr_) && !failure_is_unavailable() &&
|
|
!failure_is_reschedule()) {
|
|
printf("\n\tUnexpected failure cause 0x%02lx\n\t",
|
|
failure_code());
|
|
return (void *) -1;
|
|
}
|
|
|
|
/* 0x4 is a success and 0xa is a fail. See comment in is_failure(). */
|
|
if (DEBUG)
|
|
printf("CR0: 0x%1lx ", cr_ >> 28);
|
|
|
|
/* Check FP (vs0) for the expected value. */
|
|
if (high_vs0 != 0x5555555555555555 || low_vs0 != 0xFFFFFFFFFFFFFFFF) {
|
|
printf("FP corrupted!");
|
|
printf(" high = %#16" PRIx64 " low = %#16" PRIx64 " ",
|
|
high_vs0, low_vs0);
|
|
flags.result++;
|
|
} else
|
|
printf("FP ok ");
|
|
|
|
/* Check VEC (vs32) for the expected value. */
|
|
if (high_vs32 != 0x5555555555555555 || low_vs32 != 0xFFFFFFFFFFFFFFFF) {
|
|
printf("VEC corrupted!");
|
|
printf(" high = %#16" PRIx64 " low = %#16" PRIx64,
|
|
high_vs32, low_vs32);
|
|
flags.result++;
|
|
} else
|
|
printf("VEC ok");
|
|
|
|
putchar('\n');
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Thread to force context switch */
|
|
void *tm_una_pong(void *not_used)
|
|
{
|
|
/* Wait thread get its name "pong". */
|
|
if (DEBUG)
|
|
sleep(1);
|
|
|
|
/* Classed as an interactive-like thread. */
|
|
while (1)
|
|
sched_yield();
|
|
}
|
|
|
|
/* Function that creates a thread and launches the "ping" task. */
|
|
void test_fp_vec(int fp, int vec, pthread_attr_t *attr)
|
|
{
|
|
int retries = 2;
|
|
void *ret_value;
|
|
pthread_t t0;
|
|
|
|
flags.touch_fp = fp;
|
|
flags.touch_vec = vec;
|
|
|
|
/*
|
|
* Without luck it's possible that the transaction is aborted not due to
|
|
* the unavailable exception caught in the middle as we expect but also,
|
|
* for instance, due to a context switch or due to a KVM reschedule (if
|
|
* it's running on a VM). Thus we try a few times before giving up,
|
|
* checking if the failure cause is the one we expect.
|
|
*/
|
|
do {
|
|
int rc;
|
|
|
|
/* Bind to CPU 0, as specified in 'attr'. */
|
|
rc = pthread_create(&t0, attr, tm_una_ping, (void *) &flags);
|
|
if (rc)
|
|
pr_err(rc, "pthread_create()");
|
|
rc = pthread_setname_np(t0, "tm_una_ping");
|
|
if (rc)
|
|
pr_warn(rc, "pthread_setname_np");
|
|
rc = pthread_join(t0, &ret_value);
|
|
if (rc)
|
|
pr_err(rc, "pthread_join");
|
|
|
|
retries--;
|
|
} while (ret_value != NULL && retries);
|
|
|
|
if (!retries) {
|
|
flags.result = 1;
|
|
if (DEBUG)
|
|
printf("All transactions failed unexpectedly\n");
|
|
|
|
}
|
|
}
|
|
|
|
int tm_unavailable_test(void)
|
|
{
|
|
int cpu, rc, exception; /* FP = 0, VEC = 1, VSX = 2 */
|
|
pthread_t t1;
|
|
pthread_attr_t attr;
|
|
cpu_set_t cpuset;
|
|
|
|
SKIP_IF(!have_htm());
|
|
SKIP_IF(htm_is_synthetic());
|
|
|
|
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_err(rc, "pthread_attr_init()");
|
|
|
|
/* Set CPU 0 mask into the pthread attribute. */
|
|
rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
|
|
if (rc)
|
|
pr_err(rc, "pthread_attr_setaffinity_np()");
|
|
|
|
rc = pthread_create(&t1, &attr /* Bind to CPU 0 */, tm_una_pong, NULL);
|
|
if (rc)
|
|
pr_err(rc, "pthread_create()");
|
|
|
|
/* Name it for systemtap convenience */
|
|
rc = pthread_setname_np(t1, "tm_una_pong");
|
|
if (rc)
|
|
pr_warn(rc, "pthread_create()");
|
|
|
|
flags.result = 0;
|
|
|
|
for (exception = 0; exception < NUM_EXCEPTIONS; exception++) {
|
|
printf("Checking if FP/VEC registers are sane after");
|
|
|
|
if (exception == FP_UNA_EXCEPTION)
|
|
printf(" a FP unavailable exception...\n");
|
|
|
|
else if (exception == VEC_UNA_EXCEPTION)
|
|
printf(" a VEC unavailable exception...\n");
|
|
|
|
else
|
|
printf(" a VSX unavailable exception...\n");
|
|
|
|
flags.exception = exception;
|
|
|
|
test_fp_vec(0, 0, &attr);
|
|
test_fp_vec(1, 0, &attr);
|
|
test_fp_vec(0, 1, &attr);
|
|
test_fp_vec(1, 1, &attr);
|
|
|
|
}
|
|
|
|
if (flags.result > 0) {
|
|
printf("result: failed!\n");
|
|
exit(1);
|
|
} else {
|
|
printf("result: success\n");
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
test_harness_set_timeout(220);
|
|
return test_harness(tm_unavailable_test, "tm_unavailable_test");
|
|
}
|