diff mbox series

[v6,2/5] selftests: kvm: s390: Add uc_skey VM test case

Message ID 20241015083744.761838-3-schlameuss@linux.ibm.com (mailing list archive)
State New
Headers show
Series selftests: kvm: s390: Add ucontrol memory selftests | expand

Commit Message

Christoph Schlameuss Oct. 15, 2024, 8:37 a.m. UTC
Add a test case manipulating s390 storage keys from within the ucontrol
VM.

Storage key instruction (ISKE, SSKE and RRBE) intercepts and
Keyless-subset facility are disabled on first use, where the skeys are
setup by KVM in non ucontrol VMs.

Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
 .../selftests/kvm/include/s390x/processor.h   |   6 +
 .../selftests/kvm/s390x/ucontrol_test.c       | 130 +++++++++++++++++-
 2 files changed, 134 insertions(+), 2 deletions(-)

Comments

Janosch Frank Oct. 15, 2024, 1:22 p.m. UTC | #1
On 10/15/24 10:37 AM, Christoph Schlameuss wrote:
> Add a test case manipulating s390 storage keys from within the ucontrol
> VM.
> 
> Storage key instruction (ISKE, SSKE and RRBE) intercepts and
> Keyless-subset facility are disabled on first use, where the skeys are
> setup by KVM in non ucontrol VMs.
> 

[...]

> -/* verify SIEIC exit
> +/*
> + * Disable skey intercepts and rewind last instruction
> + * (KVM would init the skeys here)
> + */
> +static void uc_skey_enable(FIXTURE_DATA(uc_kvm) *self)
> +{
> +	struct kvm_s390_sie_block *sie_block = self->sie_block;
> +	int ilen = insn_length(sie_block->ipa >> 8);
> +	struct kvm_run *run = self->run;
> +
> +	/* disable KSS */
> +	sie_block->cpuflags &= ~CPUSTAT_KSS;
> +	/* disable skey inst interception */
> +	sie_block->ictl &= ~(ICTL_ISKE | ICTL_SSKE | ICTL_RRBE);
> +
> +	/* rewind to reexecute intercepted instruction */
> +	run->psw_addr = run->psw_addr - ilen;

There's a very important detail between KSS and the SKEY ICTLs:
KSS is (mostly) nullifying i.e. the PSW points to the instruction that 
caused the KSS exit.
ICTL intercepts are suppressing which means the PSW points after the 
instruction and hence we need to rewind the PSW if we want to re-issue 
the instruction.

Re-winding on a KSS intercept makes the guest cpu execute the 
instruction before the intercept producing instruction twice.
Christoph Schlameuss Oct. 15, 2024, 1:33 p.m. UTC | #2
On Tue Oct 15, 2024 at 3:22 PM CEST, Janosch Frank wrote:
> On 10/15/24 10:37 AM, Christoph Schlameuss wrote:
> > Add a test case manipulating s390 storage keys from within the ucontrol
> > VM.
> > 
> > Storage key instruction (ISKE, SSKE and RRBE) intercepts and
> > Keyless-subset facility are disabled on first use, where the skeys are
> > setup by KVM in non ucontrol VMs.
> > 
>
> [...]
>
> > -/* verify SIEIC exit
> > +/*
> > + * Disable skey intercepts and rewind last instruction
> > + * (KVM would init the skeys here)
> > + */
> > +static void uc_skey_enable(FIXTURE_DATA(uc_kvm) *self)
> > +{
> > +	struct kvm_s390_sie_block *sie_block = self->sie_block;
> > +	int ilen = insn_length(sie_block->ipa >> 8);
> > +	struct kvm_run *run = self->run;
> > +
> > +	/* disable KSS */
> > +	sie_block->cpuflags &= ~CPUSTAT_KSS;
> > +	/* disable skey inst interception */
> > +	sie_block->ictl &= ~(ICTL_ISKE | ICTL_SSKE | ICTL_RRBE);
> > +
> > +	/* rewind to reexecute intercepted instruction */
> > +	run->psw_addr = run->psw_addr - ilen;
>
> There's a very important detail between KSS and the SKEY ICTLs:
> KSS is (mostly) nullifying i.e. the PSW points to the instruction that 
> caused the KSS exit.
> ICTL intercepts are suppressing which means the PSW points after the 
> instruction and hence we need to rewind the PSW if we want to re-issue 
> the instruction.
>
> Re-winding on a KSS intercept makes the guest cpu execute the 
> instruction before the intercept producing instruction twice.

Oh, yes. You are right, I did mess that up in my cleanup. I will fix that.
Here it does now only work since the KSS is not intercepted on the second
invocation. But I am with you, it should only be executed once.
diff mbox series

Patch

diff --git a/tools/testing/selftests/kvm/include/s390x/processor.h b/tools/testing/selftests/kvm/include/s390x/processor.h
index 481bd2fd6a32..33fef6fd9617 100644
--- a/tools/testing/selftests/kvm/include/s390x/processor.h
+++ b/tools/testing/selftests/kvm/include/s390x/processor.h
@@ -32,4 +32,10 @@  static inline void cpu_relax(void)
 	barrier();
 }
 
+/* Get the instruction length */
+static inline int insn_length(unsigned char code)
+{
+	return ((((int)code + 64) >> 7) + 1) << 1;
+}
+
 #endif
diff --git a/tools/testing/selftests/kvm/s390x/ucontrol_test.c b/tools/testing/selftests/kvm/s390x/ucontrol_test.c
index 3e649b12a0b9..9568a4e03e4b 100644
--- a/tools/testing/selftests/kvm/s390x/ucontrol_test.c
+++ b/tools/testing/selftests/kvm/s390x/ucontrol_test.c
@@ -79,6 +79,33 @@  asm("test_mem_asm:\n"
 	"	j	0b\n"
 );
 
+/* Test program manipulating storage keys */
+extern char test_skey_asm[];
+asm("test_skey_asm:\n"
+	"xgr	%r0, %r0\n"
+
+	"0:\n"
+	"	ahi	%r0,1\n"
+	"	st	%r1,0(%r5,%r6)\n"
+
+	"	iske	%r1,%r6\n"
+	"	ahi	%r0,1\n"
+	"	diag	0,0,0x44\n"
+
+	"	sske	%r1,%r6\n"
+	"	xgr	%r1,%r1\n"
+	"	iske	%r1,%r6\n"
+	"	ahi	%r0,1\n"
+	"	diag	0,0,0x44\n"
+
+	"	rrbe	%r1,%r6\n"
+	"	iske	%r1,%r6\n"
+	"	ahi	%r0,1\n"
+	"	diag	0,0,0x44\n"
+
+	"	j	0b\n"
+);
+
 FIXTURE(uc_kvm)
 {
 	struct kvm_s390_sie_block *sie_block;
@@ -298,8 +325,49 @@  static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) *self)
 	}
 }
 
-/* verify SIEIC exit
+/*
+ * Disable skey intercepts and rewind last instruction
+ * (KVM would init the skeys here)
+ */
+static void uc_skey_enable(FIXTURE_DATA(uc_kvm) *self)
+{
+	struct kvm_s390_sie_block *sie_block = self->sie_block;
+	int ilen = insn_length(sie_block->ipa >> 8);
+	struct kvm_run *run = self->run;
+
+	/* disable KSS */
+	sie_block->cpuflags &= ~CPUSTAT_KSS;
+	/* disable skey inst interception */
+	sie_block->ictl &= ~(ICTL_ISKE | ICTL_SSKE | ICTL_RRBE);
+
+	/* rewind to reexecute intercepted instruction */
+	run->psw_addr = run->psw_addr - ilen;
+	pr_info("rewind guest addr to 0x%.16llx\n", run->psw_addr);
+}
+
+/*
+ * Handle the instruction intercept
+ * Returns if interception is handled / execution can be continued
+ */
+static bool uc_handle_insn_ic(FIXTURE_DATA(uc_kvm) *self)
+{
+	struct kvm_run *run = self->run;
+
+	switch (run->s390_sieic.ipa) {
+	case 0xB229: /* ISKE */
+	case 0xB22b: /* SSKE */
+	case 0xB22a: /* RRBE */
+		uc_skey_enable(self);
+		return true;
+	default:
+		return false;
+	}
+}
+
+/*
+ * Handle the SIEIC exit
  * * fail on codes not expected in the test cases
+ * Returns if interception is handled / execution can be continued
  */
 static bool uc_handle_sieic(FIXTURE_DATA(uc_kvm) * self)
 {
@@ -315,7 +383,10 @@  static bool uc_handle_sieic(FIXTURE_DATA(uc_kvm) * self)
 	case ICPT_INST:
 		/* end execution in caller on intercepted instruction */
 		pr_info("sie instruction interception\n");
-		return false;
+		return uc_handle_insn_ic(self);
+	case ICPT_KSS:
+		uc_skey_enable(self);
+		return true;
 	case ICPT_OPEREXC:
 		/* operation exception */
 		TEST_FAIL("sie exception on %.4x%.8x", sie_block->ipa, sie_block->ipb);
@@ -472,4 +543,59 @@  TEST_F(uc_kvm, uc_gprs)
 	ASSERT_EQ(1, sync_regs->gprs[0]);
 }
 
+TEST_F(uc_kvm, uc_skey)
+{
+	struct kvm_sync_regs *sync_regs = &self->run->s.regs;
+	u64 test_vaddr = VM_MEM_SIZE - (SZ_1M / 2);
+	struct kvm_run *run = self->run;
+	const u8 skeyvalue = 0x34;
+
+	/* copy test_skey_asm to code_hva / code_gpa */
+	TH_LOG("copy code %p to vm mapped memory %p / %p",
+	       &test_skey_asm, (void *)self->code_hva, (void *)self->code_gpa);
+	memcpy((void *)self->code_hva, &test_skey_asm, PAGE_SIZE);
+
+	/* set register content for test_skey_asm to access not mapped memory */
+	sync_regs->gprs[1] = skeyvalue;
+	sync_regs->gprs[5] = self->base_gpa;
+	sync_regs->gprs[6] = test_vaddr;
+	run->kvm_dirty_regs |= KVM_SYNC_GPRS;
+
+	/* DAT disabled + 64 bit mode */
+	run->psw_mask = 0x0000000180000000ULL;
+	run->psw_addr = self->code_gpa;
+
+	ASSERT_EQ(0, uc_run_once(self));
+	ASSERT_EQ(true, uc_handle_exit(self));
+	ASSERT_EQ(1, sync_regs->gprs[0]);
+
+	/* ISKE */
+	ASSERT_EQ(0, uc_run_once(self));
+	ASSERT_EQ(false, uc_handle_exit(self));
+	ASSERT_EQ(2, sync_regs->gprs[0]);
+	/* assert initial skey (ACC = 0, R & C = 1) */
+	ASSERT_EQ(0x06, sync_regs->gprs[1]);
+	uc_assert_diag44(self);
+
+	/* SSKE + ISKE */
+	sync_regs->gprs[1] = skeyvalue;
+	run->kvm_dirty_regs |= KVM_SYNC_GPRS;
+	ASSERT_EQ(0, uc_run_once(self));
+	ASSERT_EQ(false, uc_handle_exit(self));
+	ASSERT_EQ(3, sync_regs->gprs[0]);
+	ASSERT_EQ(skeyvalue, sync_regs->gprs[1]);
+	uc_assert_diag44(self);
+
+	/* RRBE + ISKE */
+	sync_regs->gprs[1] = skeyvalue;
+	run->kvm_dirty_regs |= KVM_SYNC_GPRS;
+	ASSERT_EQ(0, uc_run_once(self));
+	ASSERT_EQ(false, uc_handle_exit(self));
+	ASSERT_EQ(4, sync_regs->gprs[0]);
+	/* assert R reset but rest of skey unchanged */
+	ASSERT_EQ(skeyvalue & 0xfa, sync_regs->gprs[1]);
+	ASSERT_EQ(0, sync_regs->gprs[1] & 0x04);
+	uc_assert_diag44(self);
+}
+
 TEST_HARNESS_MAIN