mbox series

[kvm-unit-tests,v5,0/2] Add specification exception tests

Message ID 20220720142526.29634-1-scgl@linux.ibm.com (mailing list archive)
Headers show
Series Add specification exception tests | expand

Message

Janis Schoetterl-Glausch July 20, 2022, 2:25 p.m. UTC
Test that specification exceptions cause the correct interruption code
during both normal and transactional execution.

TCG fails the tests setting an invalid PSW bit.
I had a look at how best to fix it, but where best to check for early
PSW exceptions was not immediately clear to me. Ideas welcome.

v4 -> v5
	add lpsw with invalid bit 12 test
		TCG fails as with lpswe but must also invert bit 12
	update copyright statement
	add comments
	cleanups and style fixes

v3 -> v4
	remove iterations argument in order to simplify the code
		for manual performance testing adding a for loop is easy
	move report out of fixup_invalid_psw
	simplify/improve readability of triggers
	use positive error values

v2 -> v3
	remove non-ascii symbol
	clean up load_psw
	fix nits

v1 -> v2
	Add license and test description
	Split test patch into normal test and transactional execution test
	Add comments to
		invalid PSW fixup function
		with_transaction
	Rename some variables/functions
	Pass mask as single parameter to asm
	Get rid of report_info_if macro
	Introduce report_pass/fail and use them

Janis Schoetterl-Glausch (2):
  s390x: Add specification exception test
  s390x: Test specification exceptions during transaction

 s390x/Makefile           |   1 +
 lib/s390x/asm/arch_def.h |   6 +
 s390x/spec_ex.c          | 369 +++++++++++++++++++++++++++++++++++++++
 s390x/unittests.cfg      |   3 +
 4 files changed, 379 insertions(+)
 create mode 100644 s390x/spec_ex.c

Range-diff against v4:
1:  a242e84b ! 1:  fd9780d8 s390x: Add specification exception test
    @@ s390x/Makefile: tests += $(TEST_DIR)/uv-host.elf
      tests += $(TEST_DIR)/spec_ex-sie.elf
     +tests += $(TEST_DIR)/spec_ex.elf
      tests += $(TEST_DIR)/firq.elf
    + tests += $(TEST_DIR)/epsw.elf
    + tests += $(TEST_DIR)/adtl-status.elf
    +
    + ## lib/s390x/asm/arch_def.h ##
    +@@ lib/s390x/asm/arch_def.h: struct psw {
    + 	uint64_t	addr;
    + };
      
    - tests_binary = $(patsubst %.elf,%.bin,$(tests))
    ++struct short_psw {
    ++	uint32_t	mask;
    ++	uint32_t	addr;
    ++};
    ++
    + #define AS_PRIM				0
    + #define AS_ACCR				1
    + #define AS_SECN				2
     
      ## s390x/spec_ex.c (new) ##
     @@
     +// SPDX-License-Identifier: GPL-2.0-only
     +/*
    -+ * Copyright IBM Corp. 2021
    ++ * Copyright IBM Corp. 2021, 2022
     + *
     + * Specification exception test.
     + * Tests that specification exceptions occur when expected.
    @@ s390x/spec_ex.c (new)
     +#include <libcflat.h>
     +#include <asm/interrupt.h>
     +
    -+static struct lowcore *lc = (struct lowcore *) 0;
    -+
     +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,
    ++/*
    ++ * 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(void)
     +{
    -+	// signal occurrence of invalid psw fixup
    ++	/* signal occurrence of invalid psw fixup */
     +	invalid_psw_expected = false;
    -+	invalid_psw = lc->pgm_old_psw;
    -+	lc->pgm_old_psw = fixup_psw;
    ++	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.
    ++/*
    ++ * 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)
    @@ s390x/spec_ex.c (new)
     +	uint64_t scratch;
     +
     +	fixup_psw.mask = extract_psw_mask();
    -+	asm volatile ( "larl	%[scratch],nop%=\n"
    ++	asm volatile ( "larl	%[scratch],0f\n"
     +		"	stg	%[scratch],%[addr]\n"
     +		"	lpswe	%[psw]\n"
    -+		"nop%=:	nop\n"
    -+		: [scratch] "=&r"(scratch),
    ++		"0:	nop\n"
    ++		: [scratch] "=&d"(scratch),
    ++		  [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],%[addr]\n"
    ++		"	lpsw	%[psw]\n"
    ++		"0:	nop\n"
    ++		: [scratch] "=&d"(scratch),
     +		  [addr] "=&T"(fixup_psw.addr)
     +		: [psw] "Q"(psw)
     +		: "cc", "memory"
    @@ s390x/spec_ex.c (new)
     +
     +static int check_invalid_psw(void)
     +{
    -+	// toggled to signal occurrence of invalid psw fixup
    ++	/* toggled to signal occurrence of invalid psw fixup */
     +	if (!invalid_psw_expected) {
     +		if (expected_psw.mask == invalid_psw.mask &&
     +		    expected_psw.addr == invalid_psw.addr)
    @@ s390x/spec_ex.c (new)
     +	struct psw invalid = { .mask = 0x0008000000000000, .addr = 0x00000000deadbeee};
     +
     +	expect_invalid_psw(invalid);
    -+	load_psw(expected_psw);
    ++	load_psw(invalid);
     +	return check_invalid_psw();
     +}
     +
    ++static int short_psw_bit_12_is_0(void)
    ++{
    ++	struct short_psw short_invalid = { .mask = 0x00000000, .addr = 0xdeadbeee};
    ++
    ++	/*
    ++	 * lpsw may optionally check bit 12 before loading the new psw
    ++	 * -> cannot check the expected invalid psw like with lpswe
    ++	 */
    ++	load_short_psw(short_invalid);
    ++	return 0;
    ++}
    ++
     +static int bad_alignment(void)
     +{
     +	uint32_t words[5] __attribute__((aligned(16)));
    @@ s390x/spec_ex.c (new)
     +{
     +	uint64_t quad[2] __attribute__((aligned(16))) = {0};
     +
    -+	asm volatile (".insn	rxy,0xe3000000008f,%%r7,%[quad]" //lpq %%r7,%[quad]
    ++	asm volatile (".insn	rxy,0xe3000000008f,%%r7,%[quad]" /* lpq %%r7,%[quad] */
     +		      : : [quad] "T"(quad)
     +		      : "%r7", "%r8"
     +	);
    @@ s390x/spec_ex.c (new)
     +/* 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, &fixup_invalid_psw },
    ++	{ "short_psw_bit_12_is_0", &short_psw_bit_12_is_0, &fixup_invalid_psw },
     +	{ "bad_alignment", &bad_alignment, NULL },
     +	{ "not_even", &not_even, NULL },
     +	{ NULL, NULL, NULL },
    @@ s390x/spec_ex.c (new)
     +
     +static void test_spec_ex(const struct spec_ex_trigger *trigger)
     +{
    -+	uint16_t expected_pgm = PGM_INT_CODE_SPECIFICATION;
    -+	uint16_t pgm;
     +	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;
    -+	pgm = clear_pgm_int();
    -+	report(pgm == expected_pgm, "Program interrupt: expected(%d) == received(%d)",
    -+	       expected_pgm, pgm);
    ++	check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
     +}
     +
     +int main(int argc, char **argv)
    @@ s390x/unittests.cfg: file = mvpg-sie.elf
     +[spec_ex]
     +file = spec_ex.elf
     +
    - [firq-linear-cpu-ids]
    + [firq-linear-cpu-ids-kvm]
      file = firq.elf
      timeout = 20
2:  16ce8bb0 ! 2:  c14092a3 s390x: Test specification exceptions during transaction
    @@ Commit message
         Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
     
      ## lib/s390x/asm/arch_def.h ##
    -@@ lib/s390x/asm/arch_def.h: struct psw {
    +@@ lib/s390x/asm/arch_def.h: struct short_psw {
      #define PSW_MASK_BA			0x0000000080000000UL
      #define PSW_MASK_64			(PSW_MASK_BA | PSW_MASK_EA)
      
    -+#define CTL0_TRANSACT_EX_CTL		(63 -  8)
    - #define CTL0_LOW_ADDR_PROT		(63 - 35)
    - #define CTL0_EDAT			(63 - 40)
    - #define CTL0_IEP			(63 - 43)
    ++#define CTL0_TRANSACT_EX_CTL			(63 -  8)
    + #define CTL0_LOW_ADDR_PROT			(63 - 35)
    + #define CTL0_EDAT				(63 - 40)
    + #define CTL0_FETCH_PROTECTION_OVERRIDE		(63 - 38)
     
      ## s390x/spec_ex.c ##
     @@
    @@ s390x/spec_ex.c
      #include <asm/interrupt.h>
     +#include <asm/facility.h>
      
    - static struct lowcore *lc = (struct lowcore *) 0;
    - 
    + static bool invalid_psw_expected;
    + static struct psw expected_psw;
     @@ s390x/spec_ex.c: static int not_even(void)
      /*
       * Harness for specification exception testing.
    @@ s390x/spec_ex.c: static int not_even(void)
      /* 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, &fixup_invalid_psw },
    +-	{ "short_psw_bit_12_is_0", &short_psw_bit_12_is_0, &fixup_invalid_psw },
     -	{ "bad_alignment", &bad_alignment, NULL },
     -	{ "not_even", &not_even, NULL },
     -	{ NULL, NULL, NULL },
     +	{ "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 },
     +	{ "bad_alignment", &bad_alignment, true, NULL },
     +	{ "not_even", &not_even, true, NULL },
     +	{ NULL, NULL, false, NULL },
    @@ s390x/spec_ex.c: static int not_even(void)
      
      static void test_spec_ex(const struct spec_ex_trigger *trigger)
     @@ s390x/spec_ex.c: static void test_spec_ex(const struct spec_ex_trigger *trigger)
    - 	       expected_pgm, pgm);
    + 	check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
      }
      
     +#define TRANSACTION_COMPLETED 4
     +#define TRANSACTION_MAX_RETRIES 5
     +
    -+/* NULL must be passed to __builtin_tbegin via constant, forbid diagnose from
    ++/*
    ++ * NULL must be passed to __builtin_tbegin via constant, forbid diagnose from
     + * being NULL to keep things simple
     + */
     +static int __attribute__((nonnull))
    @@ s390x/spec_ex.c: static void test_spec_ex(const struct spec_ex_trigger *trigger)
     +	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
    ++		/*
    ++		 * return code is meaningless: transaction needs to complete
     +		 * in order to return and completion indicates a test failure
     +		 */
     +		trigger();
    @@ s390x/spec_ex.c: static void test_spec_ex(const struct spec_ex_trigger *trigger)
     +		trans_result = with_transaction(trigger->func, tdb);
     +		if (trans_result == _HTM_TBEGIN_TRANSIENT) {
     +			mb();
    -+			pgm = lc->pgm_int_code;
    -+			if (pgm == 0)
    -+				continue;
    -+			else if (pgm == expected_pgm)
    ++			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;
     +	}
    @@ s390x/spec_ex.c: static void test_spec_ex(const struct spec_ex_trigger *trigger)
     +		report_fail("Transaction completed without exception");
     +		break;
     +	case TRANSACTION_MAX_RETRIES:
    -+		report_info("Retried transaction %lu times without exception",
    ++		report_skip("Transaction retried %lu times with transient failures, giving up",
     +			    args->max_retries);
     +		break;
     +	default:
    -+		report_fail("Invalid return transaction result");
    ++		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;
    -+	long arg;
    -+	bool no_arg;
    -+	char *end;
    ++	unsigned int i, arg;
    ++	bool has_arg;
     +	const char *flag;
    -+	uint64_t *argp;
     +
     +	for (i = 1; i < argc; i++) {
    -+		no_arg = true;
    -+		if (i < argc - 1) {
    -+			no_arg = *argv[i + 1] == '\0';
    -+			arg = strtol(argv[i + 1], &end, 10);
    -+			no_arg |= *end != '\0';
    -+			no_arg |= arg < 0;
    -+		}
    ++		if (i + 1 < argc)
    ++			has_arg = parse_unsigned(argv[i + 1], &arg);
    ++		else
    ++			has_arg = false;
     +
     +		flag = "--max-retries";
    -+		argp = &args.max_retries;
     +		if (!strcmp(flag, argv[i])) {
    -+			if (no_arg)
    ++			if (!has_arg)
     +				report_abort("%s needs a positive parameter", flag);
    -+			*argp = arg;
    ++			args.max_retries = arg;
     +			++i;
     +			continue;
     +		}

base-commit: ca85dda2671e88d34acfbca6de48a9ab32b1810d