| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright IBM Corp. 2021, 2022 | |
| * | |
| * Specification exception test. | |
| * Tests that specification exceptions occur when expected. | |
| * This includes specification exceptions occurring during transactional execution | |
| * as these result in another interruption code (the transactional-execution-aborted | |
| * bit is set). | |
| * | |
| * Can be extended by adding triggers to spec_ex_triggers, see comments below. | |
| */ | |
| #include <stdlib.h> | |
| #include <htmintrin.h> | |
| #include <libcflat.h> | |
| #include <bitops.h> | |
| #include <asm/barrier.h> | |
| #include <asm/interrupt.h> | |
| #include <asm/facility.h> | |
| /* toggled to signal occurrence of invalid psw fixup */ | |
| static bool invalid_psw_expected; | |
| static struct psw expected_psw; | |
| static struct psw invalid_psw; | |
| static struct psw fixup_psw; | |
| /* | |
| * The standard program exception handler cannot deal with invalid old PSWs, | |
| * especially not invalid instruction addresses, as in that case one cannot | |
| * find the instruction following the faulting one from the old PSW. | |
| * The PSW to return to is set by load_psw. | |
| */ | |
| static void fixup_invalid_psw(struct stack_frame_int *stack) | |
| { | |
| assert_msg(invalid_psw_expected, | |
| "Unexpected invalid PSW during program interrupt fixup: %#lx %#lx", | |
| lowcore.pgm_old_psw.mask, lowcore.pgm_old_psw.addr); | |
| /* signal occurrence of invalid psw fixup */ | |
| invalid_psw_expected = false; | |
| invalid_psw = lowcore.pgm_old_psw; | |
| lowcore.pgm_old_psw = fixup_psw; | |
| } | |
| /* | |
| * Load possibly invalid psw, but setup fixup_psw before, | |
| * so that fixup_invalid_psw() can bring us back onto the right track. | |
| * Also acts as compiler barrier, -> none required in expect/check_invalid_psw | |
| */ | |
| static void load_psw(struct psw psw) | |
| { | |
| uint64_t scratch; | |
| /* | |
| * The fixup psw is the current psw with the instruction address replaced | |
| * by the address of the nop following the instruction loading the new psw. | |
| */ | |
| fixup_psw.mask = extract_psw_mask(); | |
| asm volatile ( "larl %[scratch],0f\n" | |
| " stg %[scratch],%[fixup_addr]\n" | |
| " lpswe %[psw]\n" | |
| "0: nop\n" | |
| : [scratch] "=&d" (scratch), | |
| [fixup_addr] "=&T" (fixup_psw.addr) | |
| : [psw] "Q" (psw) | |
| : "cc", "memory" | |
| ); | |
| } | |
| static void load_short_psw(struct short_psw psw) | |
| { | |
| uint64_t scratch; | |
| fixup_psw.mask = extract_psw_mask(); | |
| asm volatile ( "larl %[scratch],0f\n" | |
| " stg %[scratch],%[fixup_addr]\n" | |
| " lpsw %[psw]\n" | |
| "0: nop\n" | |
| : [scratch] "=&d" (scratch), | |
| [fixup_addr] "=&T" (fixup_psw.addr) | |
| : [psw] "Q" (psw) | |
| : "cc", "memory" | |
| ); | |
| } | |
| static void expect_invalid_psw(struct psw psw) | |
| { | |
| expected_psw = psw; | |
| invalid_psw_expected = true; | |
| } | |
| static void clear_invalid_psw(void) | |
| { | |
| expected_psw = PSW(0, 0); | |
| invalid_psw_expected = false; | |
| } | |
| static int check_invalid_psw(void) | |
| { | |
| /* Since the fixup sets this to false we check for false here. */ | |
| if (!invalid_psw_expected) { | |
| /* | |
| * Early exception recognition: pgm_int_id == 0. | |
| * Late exception recognition: psw address has been | |
| * incremented by pgm_int_id (unpredictable value) | |
| */ | |
| if (expected_psw.mask == invalid_psw.mask && | |
| expected_psw.addr == invalid_psw.addr - lowcore.pgm_int_id) | |
| return 0; | |
| report_fail("Wrong invalid PSW"); | |
| } else { | |
| report_fail("Expected exception due to invalid PSW"); | |
| } | |
| return 1; | |
| } | |
| /* For normal PSWs bit 12 has to be 0 to be a valid PSW*/ | |
| static int psw_bit_12_is_1(void) | |
| { | |
| struct psw invalid = PSW(BIT(63 - 12), 0x00000000deadbeee); | |
| expect_invalid_psw(invalid); | |
| load_psw(invalid); | |
| return check_invalid_psw(); | |
| } | |
| extern char misaligned_code_pre[]; | |
| asm ( ".balign 2\n" | |
| "misaligned_code_pre:\n" | |
| " . = . + 1\n" | |
| " larl %r0,0\n" | |
| " br %r1\n" | |
| ); | |
| static int psw_odd_address(void) | |
| { | |
| struct psw odd = PSW_WITH_CUR_MASK(((uint64_t)&misaligned_code_pre) + 1); | |
| uint64_t executed_addr; | |
| expect_invalid_psw(odd); | |
| fixup_psw.mask = extract_psw_mask(); | |
| asm volatile ( "xgr %%r0,%%r0\n" | |
| " larl %%r1,0f\n" | |
| " stg %%r1,%[fixup_addr]\n" | |
| " lpswe %[odd_psw]\n" | |
| "0: lgr %[executed_addr],%%r0\n" | |
| : [fixup_addr] "=&T" (fixup_psw.addr), | |
| [executed_addr] "=d" (executed_addr) | |
| : [odd_psw] "Q" (odd) | |
| : "cc", "%r0", "%r1", "memory" /* Compiler barrier like in load_psw */ | |
| ); | |
| if (!executed_addr) { | |
| return check_invalid_psw(); | |
| } else { | |
| assert(executed_addr == odd.addr); | |
| clear_invalid_psw(); | |
| report_fail("did not execute unaligned instructions"); | |
| return 1; | |
| } | |
| } | |
| /* A short PSW needs to have bit 12 set to be valid. */ | |
| static int short_psw_bit_12_is_0(void) | |
| { | |
| struct psw invalid = PSW(BIT(63 - 12), 0x00000000deadbeee); | |
| struct short_psw short_invalid = { | |
| .mask = 0x0, | |
| .addr = 0xdeadbeee | |
| }; | |
| expect_invalid_psw(invalid); | |
| load_short_psw(short_invalid); | |
| /* | |
| * lpsw may optionally check bit 12 before loading the new psw | |
| * -> cannot check the expected invalid psw like with lpswe | |
| */ | |
| return 0; | |
| } | |
| static int odd_ex_target(void) | |
| { | |
| uint64_t pre_target_addr; | |
| int to = 0, from = 0x0dd; | |
| asm volatile ( ".pushsection .text.ex_odd\n" | |
| " .balign 2\n" | |
| "pre_odd_ex_target%=:\n" | |
| " . = . + 1\n" | |
| " lr %[to],%[from]\n" | |
| " .popsection\n" | |
| " larl %[pre_target_addr],pre_odd_ex_target%=\n" | |
| " ex 0,1(%[pre_target_addr])\n" | |
| : [pre_target_addr] "=&a" (pre_target_addr), | |
| [to] "+d" (to) | |
| : [from] "d" (from) | |
| ); | |
| assert((pre_target_addr + 1) & 1); | |
| report(to != from, "did not perform ex with odd target"); | |
| return 0; | |
| } | |
| static int bad_alignment_lqp(void) | |
| { | |
| uint32_t words[5] __attribute__((aligned(16))); | |
| uint32_t (*bad_aligned)[4] = (uint32_t (*)[4])&words[1]; | |
| /* LOAD PAIR FROM QUADWORD (LPQ) requires quadword alignment */ | |
| asm volatile ("lpq %%r6,%[bad]" | |
| : : [bad] "T" (*bad_aligned) | |
| : "%r6", "%r7" | |
| ); | |
| return 0; | |
| } | |
| static int bad_alignment_lrl(void) | |
| { | |
| uint64_t r; | |
| asm volatile ( ".pushsection .rodata\n" | |
| " .balign 4\n" | |
| " . = . + 2\n" | |
| "0: .fill 4\n" | |
| " .popsection\n" | |
| " lrl %0,0b\n" | |
| : "=d" (r) | |
| ); | |
| return 0; | |
| } | |
| static int not_even(void) | |
| { | |
| uint64_t quad[2] __attribute__((aligned(16))) = {0}; | |
| asm volatile (".insn rxy,0xe3000000008f,%%r7,%[quad]" /* lpq %%r7,%[quad] */ | |
| : : [quad] "T" (quad) | |
| : "%r7", "%r8" | |
| ); | |
| return 0; | |
| } | |
| /* | |
| * Harness for specification exception testing. | |
| * func only triggers exception, reporting is taken care of automatically. | |
| * If a trigger is transactable it will also be executed during a transaction. | |
| */ | |
| struct spec_ex_trigger { | |
| const char *name; | |
| int (*func)(void); | |
| bool transactable; | |
| void (*fixup)(struct stack_frame_int *stack); | |
| }; | |
| /* List of all tests to execute */ | |
| static const struct spec_ex_trigger spec_ex_triggers[] = { | |
| { "psw_bit_12_is_1", &psw_bit_12_is_1, false, &fixup_invalid_psw }, | |
| { "short_psw_bit_12_is_0", &short_psw_bit_12_is_0, false, &fixup_invalid_psw }, | |
| { "psw_odd_address", &psw_odd_address, false, &fixup_invalid_psw }, | |
| { "odd_ex_target", &odd_ex_target, true, NULL }, | |
| { "bad_alignment_lqp", &bad_alignment_lqp, true, NULL }, | |
| { "bad_alignment_lrl", &bad_alignment_lrl, true, NULL }, | |
| { "not_even", ¬_even, true, NULL }, | |
| { NULL, NULL, false, NULL }, | |
| }; | |
| static void test_spec_ex(const struct spec_ex_trigger *trigger) | |
| { | |
| int rc; | |
| expect_pgm_int(); | |
| register_pgm_cleanup_func(trigger->fixup); | |
| rc = trigger->func(); | |
| register_pgm_cleanup_func(NULL); | |
| /* test failed, nothing to be done, reporting responsibility of trigger */ | |
| if (rc) | |
| return; | |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); | |
| } | |
| #define TRANSACTION_COMPLETED 4 | |
| #define TRANSACTION_MAX_RETRIES 5 | |
| /* | |
| * NULL must not be passed to __builtin_tbegin via variable, only constant, | |
| * forbid diagnose from being NULL at all to keep things simple | |
| */ | |
| static int __attribute__((nonnull)) | |
| with_transaction(int (*trigger)(void), struct __htm_tdb *diagnose) | |
| { | |
| int cc; | |
| cc = __builtin_tbegin(diagnose); | |
| /* | |
| * Everything between tbegin and tend is part of the transaction, | |
| * which either completes in its entirety or does not have any effect. | |
| * If the transaction fails, execution is reset to this point with another | |
| * condition code indicating why the transaction failed. | |
| */ | |
| if (cc == _HTM_TBEGIN_STARTED) { | |
| /* | |
| * return code is meaningless: transaction needs to complete | |
| * in order to return and completion indicates a test failure | |
| */ | |
| trigger(); | |
| __builtin_tend(); | |
| return TRANSACTION_COMPLETED; | |
| } else { | |
| return cc; | |
| } | |
| } | |
| static int retry_transaction(const struct spec_ex_trigger *trigger, unsigned int max_retries, | |
| struct __htm_tdb *tdb, uint16_t expected_pgm) | |
| { | |
| int trans_result, i; | |
| uint16_t pgm; | |
| for (i = 0; i < max_retries; i++) { | |
| expect_pgm_int(); | |
| trans_result = with_transaction(trigger->func, tdb); | |
| if (trans_result == _HTM_TBEGIN_TRANSIENT) { | |
| mb(); | |
| pgm = lowcore.pgm_int_code; | |
| if (pgm == expected_pgm) | |
| return 0; | |
| else if (pgm == 0) | |
| /* | |
| * Transaction failed for unknown reason but not because | |
| * of an unexpected program exception. Give it another | |
| * go so that hopefully it reaches the triggering instruction. | |
| */ | |
| continue; | |
| } | |
| return trans_result; | |
| } | |
| return TRANSACTION_MAX_RETRIES; | |
| } | |
| struct args { | |
| uint64_t max_retries; | |
| bool diagnose; | |
| }; | |
| static void test_spec_ex_trans(struct args *args, const struct spec_ex_trigger *trigger) | |
| { | |
| const uint16_t expected_pgm = PGM_INT_CODE_SPECIFICATION | | |
| PGM_INT_CODE_TX_ABORTED_EVENT; | |
| union { | |
| struct __htm_tdb tdb; | |
| uint64_t dwords[sizeof(struct __htm_tdb) / sizeof(uint64_t)]; | |
| } diag; | |
| unsigned int i; | |
| int trans_result; | |
| if (!test_facility(73)) { | |
| report_skip("transactional-execution facility not installed"); | |
| return; | |
| } | |
| ctl_set_bit(0, CTL0_TRANSACT_EX_CTL); /* enable transactional-exec */ | |
| register_pgm_cleanup_func(trigger->fixup); | |
| trans_result = retry_transaction(trigger, args->max_retries, &diag.tdb, expected_pgm); | |
| register_pgm_cleanup_func(NULL); | |
| switch (trans_result) { | |
| case 0: | |
| report_pass("Program interrupt: expected(%d) == received(%d)", | |
| expected_pgm, expected_pgm); | |
| break; | |
| case _HTM_TBEGIN_INDETERMINATE: | |
| case _HTM_TBEGIN_PERSISTENT: | |
| report_info("transaction failed with cc %d", trans_result); | |
| report_info("transaction abort code: %llu", diag.tdb.abort_code); | |
| if (args->diagnose) | |
| for (i = 0; i < 32; i++) | |
| report_info("diag+%03d: %016lx", i * 8, diag.dwords[i]); | |
| break; | |
| case _HTM_TBEGIN_TRANSIENT: | |
| report_fail("Program interrupt: expected(%d) == received(%d)", | |
| expected_pgm, clear_pgm_int()); | |
| break; | |
| case TRANSACTION_COMPLETED: | |
| report_fail("Transaction completed without exception"); | |
| break; | |
| case TRANSACTION_MAX_RETRIES: | |
| report_skip("Transaction retried %lu times with transient failures, giving up", | |
| args->max_retries); | |
| break; | |
| default: | |
| report_fail("Invalid transaction result"); | |
| break; | |
| } | |
| ctl_clear_bit(0, CTL0_TRANSACT_EX_CTL); | |
| } | |
| static bool parse_unsigned(const char *arg, unsigned int *out) | |
| { | |
| char *end; | |
| long num; | |
| if (arg[0] == '\0') | |
| return false; | |
| num = strtol(arg, &end, 10); | |
| if (end[0] != '\0' || num < 0) | |
| return false; | |
| *out = num; | |
| return true; | |
| } | |
| static struct args parse_args(int argc, char **argv) | |
| { | |
| struct args args = { | |
| .max_retries = 20, | |
| .diagnose = false | |
| }; | |
| unsigned int i, arg; | |
| bool has_arg; | |
| const char *flag; | |
| for (i = 1; i < argc; i++) { | |
| if (i + 1 < argc) | |
| has_arg = parse_unsigned(argv[i + 1], &arg); | |
| else | |
| has_arg = false; | |
| flag = "--max-retries"; | |
| if (!strcmp(flag, argv[i])) { | |
| if (!has_arg) | |
| report_abort("%s needs a positive parameter", flag); | |
| args.max_retries = arg; | |
| ++i; | |
| continue; | |
| } | |
| if (!strcmp("--diagnose", argv[i])) { | |
| args.diagnose = true; | |
| continue; | |
| } | |
| if (!strcmp("--no-diagnose", argv[i])) { | |
| args.diagnose = false; | |
| continue; | |
| } | |
| report_abort("Unsupported parameter '%s'", | |
| argv[i]); | |
| } | |
| return args; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| unsigned int i; | |
| struct args args = parse_args(argc, argv); | |
| report_prefix_push("specification exception"); | |
| for (i = 0; spec_ex_triggers[i].name; i++) { | |
| report_prefix_push(spec_ex_triggers[i].name); | |
| test_spec_ex(&spec_ex_triggers[i]); | |
| report_prefix_pop(); | |
| } | |
| report_prefix_pop(); | |
| report_prefix_push("specification exception during transaction"); | |
| for (i = 0; spec_ex_triggers[i].name; i++) { | |
| if (spec_ex_triggers[i].transactable) { | |
| report_prefix_push(spec_ex_triggers[i].name); | |
| test_spec_ex_trans(&args, &spec_ex_triggers[i]); | |
| report_prefix_pop(); | |
| } | |
| } | |
| report_prefix_pop(); | |
| return report_summary(); | |
| } |