mbox series

[v3,0/9] KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg

Message ID 20221117221758.66326-1-scgl@linux.ibm.com (mailing list archive)
Headers show
Series KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg | expand

Message

Janis Schoetterl-Glausch Nov. 17, 2022, 10:17 p.m. UTC
User space can use the MEM_OP ioctl to make storage key checked reads
and writes to the guest, however, it has no way of performing atomic,
key checked, accesses to the guest.
Extend the MEM_OP ioctl in order to allow for this, by adding a cmpxchg
mode. For now, support this mode for absolute accesses only.

This mode can be use, for example, to set the device-state-change
indicator and the adapter-local-summary indicator atomically.

Also contains some fixes/changes for the memop selftest independent of
the cmpxchg changes.

v2 -> v3
 * rebase onto the wip/cmpxchg_user_key branch in the s390 kernel repo
 * use __uint128_t instead of unsigned __int128
 * put moving of testlist into main into separate patch
 * pick up R-b's (thanks Nico)

v1 -> v2
 * get rid of xrk instruction for cmpxchg byte and short implementation
 * pass old parameter via pointer instead of in mem_op struct
 * indicate failure of cmpxchg due to wrong old value by special return
   code
 * picked up R-b's (thanks Thomas)

Janis Schoetterl-Glausch (9):
  KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg
  Documentation: KVM: s390: Describe KVM_S390_MEMOP_F_CMPXCHG
  KVM: s390: selftest: memop: Pass mop_desc via pointer
  KVM: s390: selftest: memop: Replace macros by functions
  KVM: s390: selftest: memop: Move testlist into main
  KVM: s390: selftest: memop: Add cmpxchg tests
  KVM: s390: selftest: memop: Add bad address test
  KVM: s390: selftest: memop: Fix typo
  KVM: s390: selftest: memop: Fix wrong address being used in test

 Documentation/virt/kvm/api.rst            |  21 +-
 include/uapi/linux/kvm.h                  |   5 +
 arch/s390/kvm/gaccess.h                   |   3 +
 arch/s390/kvm/gaccess.c                   | 101 ++++
 arch/s390/kvm/kvm-s390.c                  |  35 +-
 tools/testing/selftests/kvm/s390x/memop.c | 670 +++++++++++++++++-----
 6 files changed, 683 insertions(+), 152 deletions(-)

Range-diff against v2:
 1:  c6731b0063ab !  1:  94820a73e9b0 KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg
    @@ arch/s390/kvm/gaccess.h: int access_guest_with_key(struct kvm_vcpu *vcpu, unsign
      		      void *data, unsigned long len, enum gacc_mode mode);
      
     +int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
    -+			       unsigned __int128 *old,
    -+			       unsigned __int128 new, u8 access_key);
    ++			       __uint128_t *old, __uint128_t new, u8 access_key);
     +
      /**
       * write_guest_with_key - copy data from kernel space to guest space
    @@ arch/s390/kvm/gaccess.c: int access_guest_real(struct kvm_vcpu *vcpu, unsigned l
     + *         * a program interruption code indicating the reason cmpxchg could
     + *           not be attempted
     + *         * -EINVAL: address misaligned or len not power of two
    ++ *         * -EAGAIN: transient failure (len 1 or 2)
     + */
     +int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
    -+			       unsigned __int128 *old_p, unsigned __int128 new,
    ++			       __uint128_t *old_p, __uint128_t new,
     +			       u8 access_key)
     +{
     +	gfn_t gfn = gpa >> PAGE_SHIFT;
    @@ arch/s390/kvm/gaccess.c: int access_guest_real(struct kvm_vcpu *vcpu, unsigned l
     +		return -EOPNOTSUPP;
     +
     +	hva += offset_in_page(gpa);
    -+	ret = cmpxchg_user_key_size(len, (void __user *)hva, old_p, new, access_key);
    ++	switch (len) {
    ++	case 1: {
    ++		u8 old;
    ++
    ++		ret = cmpxchg_user_key((u8 *)hva, &old, *old_p, new, access_key);
    ++		ret = ret < 0 ? ret : old != *old_p;
    ++		*old_p = old;
    ++		break;
    ++	}
    ++	case 2: {
    ++		u16 old;
    ++
    ++		ret = cmpxchg_user_key((u16 *)hva, &old, *old_p, new, access_key);
    ++		ret = ret < 0 ? ret : old != *old_p;
    ++		*old_p = old;
    ++		break;
    ++	}
    ++	case 4: {
    ++		u32 old;
    ++
    ++		ret = cmpxchg_user_key((u32 *)hva, &old, *old_p, new, access_key);
    ++		ret = ret < 0 ? ret : old != *old_p;
    ++		*old_p = old;
    ++		break;
    ++	}
    ++	case 8: {
    ++		u64 old;
    ++
    ++		ret = cmpxchg_user_key((u64 *)hva, &old, *old_p, new, access_key);
    ++		ret = ret < 0 ? ret : old != *old_p;
    ++		*old_p = old;
    ++		break;
    ++	}
    ++	case 16: {
    ++		__uint128_t old;
    ++
    ++		ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_p, new, access_key);
    ++		ret = ret < 0 ? ret : old != *old_p;
    ++		*old_p = old;
    ++		break;
    ++	}
    ++	default:
    ++		 return -EINVAL;
    ++	}
     +	mark_page_dirty_in_slot(kvm, slot, gfn);
     +	/*
     +	 * Assume that the fault is caused by protection, either key protection
    @@ arch/s390/kvm/kvm-s390.c: static bool access_key_invalid(u8 access_key)
      	void __user *uaddr = (void __user *)mop->buf;
     +	void __user *old_p = (void __user *)mop->old_p;
     +	union {
    -+		unsigned __int128 quad;
    -+		char raw[sizeof(unsigned __int128)];
    ++		__uint128_t quad;
    ++		char raw[sizeof(__uint128_t)];
     +	} old = { .quad = 0}, new = { .quad = 0 };
    -+	unsigned int off_in_quad = sizeof(unsigned __int128) - mop->size;
    ++	unsigned int off_in_quad = sizeof(__uint128_t) - mop->size;
      	u64 supported_flags;
      	void *tmpbuf = NULL;
      	int r, srcu_idx;
 2:  6cb32b244899 =  2:  28637a03f63e Documentation: KVM: s390: Describe KVM_S390_MEMOP_F_CMPXCHG
 3:  5f1217ad9d31 =  3:  31f45e4377c5 KVM: s390: selftest: memop: Pass mop_desc via pointer
 4:  86a15b53846a =  4:  dba0a8ba3ba2 KVM: s390: selftest: memop: Replace macros by functions
 -:  ------------ >  5:  ba8cdd064c02 KVM: s390: selftest: memop: Move testlist into main
 5:  49e67d7559de !  6:  3cf7f710e5db KVM: s390: selftest: memop: Add cmpxchg tests
    @@ tools/testing/selftests/kvm/s390x/memop.c: struct mop_desc {
      	uint8_t ar;
      	uint8_t key;
      };
    - 
    -+typedef unsigned __int128 uint128;
    -+
    - const uint8_t NO_KEY = 0xff;
    - 
    - static struct kvm_s390_mem_op ksmo_from_desc(const struct mop_desc *desc)
     @@ tools/testing/selftests/kvm/s390x/memop.c: static struct kvm_s390_mem_op ksmo_from_desc(const struct mop_desc *desc)
      		ksmo.flags |= KVM_S390_MEMOP_F_SKEY_PROTECTION;
      		ksmo.key = desc->key;
    @@ tools/testing/selftests/kvm/s390x/memop.c: enum stage {
      	vcpu_run(__vcpu);						\
      	get_ucall(__vcpu, &uc);						\
     +	if (uc.cmd == UCALL_ABORT) {					\
    -+		TEST_FAIL("line %lu: %s, hints: %lu, %lu",		\
    -+			  uc.args[1], (const char *)uc.args[0],		\
    -+			  uc.args[2], uc.args[3]);			\
    ++		REPORT_GUEST_ASSERT_2(uc, "hints: %lu, %lu");		\
     +	}								\
      	ASSERT_EQ(uc.cmd, UCALL_SYNC);					\
      	ASSERT_EQ(uc.args[1], __stage);					\
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	kvm_vm_free(t.kvm_vm);
     +}
     +
    -+static uint128 cut_to_size(int size, uint128 val)
    ++static __uint128_t cut_to_size(int size, __uint128_t val)
     +{
     +	switch (size) {
     +	case 1:
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	return 0;
     +}
     +
    -+static bool popcount_eq(uint128 a, uint128 b)
    ++static bool popcount_eq(__uint128_t a, __uint128_t b)
     +{
     +	unsigned int count_a, count_b;
     +
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	return count_a == count_b;
     +}
     +
    -+static uint128 rotate(int size, uint128 val, int amount)
    ++static __uint128_t rotate(int size, __uint128_t val, int amount)
     +{
     +	unsigned int bits = size * 8;
     +
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	}
     +}
     +
    -+static uint128 permutate_bits(bool guest, int i, int size, uint128 old)
    ++static __uint128_t permutate_bits(bool guest, int i, int size, __uint128_t old)
     +{
     +	unsigned int rand;
     +	bool swap;
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	swap = rand % 2 == 0;
     +	if (swap) {
     +		int i, j;
    -+		uint128 new;
    ++		__uint128_t new;
     +		uint8_t byte0, byte1;
     +
     +		rand = rand * 3 + 1;
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	}
     +}
     +
    -+static bool _cmpxchg(int size, void *target, uint128 *old_p, uint128 new)
    ++static bool _cmpxchg(int size, void *target, __uint128_t *old_p, __uint128_t new)
     +{
     +	bool ret;
     +
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +			return ret;
     +		}
     +	case 16: {
    -+			uint128 old = *old_p;
    ++			__uint128_t old = *old_p;
     +
     +			asm volatile ("cdsg %[old],%[new],%[address]"
     +			    : [old] "+d" (old),
    -+			      [address] "+Q" (*(uint128 *)(target))
    ++			      [address] "+Q" (*(__uint128_t *)(target))
     +			    : [new] "d" (new)
     +			    : "cc"
     +			);
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +static void guest_cmpxchg_key(void)
     +{
     +	int size, offset;
    -+	uint128 old, new;
    ++	__uint128_t old, new;
     +
     +	set_storage_key_range(mem1, max_block, 0x10);
     +	set_storage_key_range(mem2, max_block, 0x10);
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	return NULL;
     +}
     +
    -+static char *quad_to_char(uint128 *quad, int size)
    ++static char *quad_to_char(__uint128_t *quad, int size)
     +{
     +	return ((char *)quad) + (sizeof(*quad) - size);
     +}
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +{
     +	struct test_default t = test_default_init(guest_cmpxchg_key);
     +	int size, offset;
    -+	uint128 old, new;
    ++	__uint128_t old, new;
     +	bool success;
     +	pthread_t thread;
     +
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_copy_key(void)
     +	pthread_join(thread, NULL);
     +
     +	MOP(t.vcpu, LOGICAL, READ, mem2, max_block, GADDR_V(mem2));
    -+	TEST_ASSERT(popcount_eq(*(uint128 *)mem1, *(uint128 *)mem2),
    ++	TEST_ASSERT(popcount_eq(*(__uint128_t *)mem1, *(__uint128_t *)mem2),
     +		    "Must retain number of set bits");
     +
     +	kvm_vm_free(t.kvm_vm);
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_errors_key(void)
     +	HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
     +
     +	for (i = 1; i <= 16; i *= 2) {
    -+		uint128 old = 0;
    ++		__uint128_t old = 0;
     +
     +		CHECK_N_DO(ERR_PROT_MOP, t.vm, ABSOLUTE, WRITE, mem2, i, GADDR_V(mem2),
     +			   CMPXCHG_OLD(&old), KEY(2));
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_errors(void)
      	kvm_vm_free(t.kvm_vm);
      }
      
    --struct testdef {
    --	const char *name;
    --	void (*test)(void);
    --	int extension;
    --} testlist[] = {
    --	{
    --		.name = "simple copy",
    --		.test = test_copy,
    --	},
    --	{
    --		.name = "generic error checks",
    --		.test = test_errors,
    --	},
    --	{
    --		.name = "copy with storage keys",
    --		.test = test_copy_key,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "copy with key storage protection override",
    --		.test = test_copy_key_storage_prot_override,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "copy with key fetch protection",
    --		.test = test_copy_key_fetch_prot,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "copy with key fetch protection override",
    --		.test = test_copy_key_fetch_prot_override,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "error checks with key",
    --		.test = test_errors_key,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "termination",
    --		.test = test_termination,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "error checks with key storage protection override",
    --		.test = test_errors_key_storage_prot_override,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "error checks without key fetch prot override",
    --		.test = test_errors_key_fetch_prot_override_not_enabled,
    --		.extension = 1,
    --	},
    --	{
    --		.name = "error checks with key fetch prot override",
    --		.test = test_errors_key_fetch_prot_override_enabled,
    --		.extension = 1,
    --	},
    --};
     +static void test_errors_cmpxchg(void)
     +{
     +	struct test_default t = test_default_init(guest_idle);
    -+	uint128 old;
    ++	__uint128_t old;
     +	int rv, i, power = 1;
     +
     +	HOST_SYNC(t.vcpu, STAGE_INITED);
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_errors(void)
      
      int main(int argc, char *argv[])
      {
    - 	int extension_cap, idx;
    - 
    -+	setbuf(stdout, NULL);	/* Tell stdout not to buffer its content */
    - 	TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_MEM_OP));
    -+	extension_cap = kvm_check_cap(KVM_CAP_S390_MEM_OP_EXTENSION);
    - 
    --	setbuf(stdout, NULL);	/* Tell stdout not to buffer its content */
    -+	struct testdef {
    -+		const char *name;
    -+		void (*test)(void);
    -+		bool requirements_met;
    -+	} testlist[] = {
    -+		{
    -+			.name = "simple copy",
    -+			.test = test_copy,
    -+			.requirements_met = true,
    -+		},
    -+		{
    -+			.name = "generic error checks",
    -+			.test = test_errors,
    -+			.requirements_met = true,
    -+		},
    -+		{
    -+			.name = "copy with storage keys",
    -+			.test = test_copy_key,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    +@@ tools/testing/selftests/kvm/s390x/memop.c: int main(int argc, char *argv[])
    + 			.test = test_copy_key,
    + 			.requirements_met = extension_cap > 0,
    + 		},
     +		{
     +			.name = "cmpxchg with storage keys",
     +			.test = test_cmpxchg_key,
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_errors(void)
     +			.test = test_cmpxchg_key_concurrent,
     +			.requirements_met = extension_cap & 0x2,
     +		},
    -+		{
    -+			.name = "copy with key storage protection override",
    -+			.test = test_copy_key_storage_prot_override,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    -+		{
    -+			.name = "copy with key fetch protection",
    -+			.test = test_copy_key_fetch_prot,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    -+		{
    -+			.name = "copy with key fetch protection override",
    -+			.test = test_copy_key_fetch_prot_override,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    -+		{
    -+			.name = "error checks with key",
    -+			.test = test_errors_key,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    + 		{
    + 			.name = "copy with key storage protection override",
    + 			.test = test_copy_key_storage_prot_override,
    +@@ tools/testing/selftests/kvm/s390x/memop.c: int main(int argc, char *argv[])
    + 			.test = test_errors_key,
    + 			.requirements_met = extension_cap > 0,
    + 		},
     +		{
     +			.name = "error checks for cmpxchg with key",
     +			.test = test_errors_cmpxchg_key,
    @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_errors(void)
     +			.test = test_errors_cmpxchg,
     +			.requirements_met = extension_cap & 0x2,
     +		},
    -+		{
    -+			.name = "termination",
    -+			.test = test_termination,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    -+		{
    -+			.name = "error checks with key storage protection override",
    -+			.test = test_errors_key_storage_prot_override,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    -+		{
    -+			.name = "error checks without key fetch prot override",
    -+			.test = test_errors_key_fetch_prot_override_not_enabled,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    -+		{
    -+			.name = "error checks with key fetch prot override",
    -+			.test = test_errors_key_fetch_prot_override_enabled,
    -+			.requirements_met = extension_cap > 0,
    -+		},
    -+	};
    - 
    - 	ksft_print_header();
    --
    - 	ksft_set_plan(ARRAY_SIZE(testlist));
    - 
    --	extension_cap = kvm_check_cap(KVM_CAP_S390_MEM_OP_EXTENSION);
    - 	for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
    --		if (extension_cap >= testlist[idx].extension) {
    -+		if (testlist[idx].requirements_met) {
    - 			testlist[idx].test();
    - 			ksft_test_result_pass("%s\n", testlist[idx].name);
    - 		} else {
    --			ksft_test_result_skip("%s - extension level %d not supported\n",
    --					      testlist[idx].name,
    --					      testlist[idx].extension);
    -+			ksft_test_result_skip("%s - requirements not met (kernel has extension cap %#x\n)",
    -+					      testlist[idx].name, extension_cap);
    - 		}
    - 	}
    - 
    + 		{
    + 			.name = "termination",
    + 			.test = test_termination,
 6:  faad9cf03ea6 !  7:  d81d5697ba4b KVM: s390: selftest: memop: Add bad address test
    @@ Commit message
         Add test that tries to access, instead of CHECK_ONLY.
     
         Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
    +    Reviewed-by: Nico Boehr <nrb@linux.ibm.com>
     
      ## tools/testing/selftests/kvm/s390x/memop.c ##
     @@ tools/testing/selftests/kvm/s390x/memop.c: static void _test_errors_common(struct test_info info, enum mop_target target, i
 7:  8070036aa89a !  8:  22c9cd9589ba KVM: s390: selftest: memop: Fix typo
    @@ Commit message
     
         Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
         Reviewed-by: Thomas Huth <thuth@redhat.com>
    +    Reviewed-by: Nico Boehr <nrb@linux.ibm.com>
     
      ## tools/testing/selftests/kvm/s390x/memop.c ##
     @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_errors_key_fetch_prot_override_enabled(void)
 8:  18c423e4e3ad !  9:  4647be0790c8 KVM: s390: selftest: memop: Fix wrong address being used in test
    @@ Commit message
         protection exception the test codes needs to address mem1.
     
         Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
    +    Reviewed-by: Nico Boehr <nrb@linux.ibm.com>
     
      ## tools/testing/selftests/kvm/s390x/memop.c ##
     @@ tools/testing/selftests/kvm/s390x/memop.c: static void test_errors_key(void)

base-commit: b5b2a05e6de7b88bcfca9d4bbc8ac74e7db80c52