diff mbox series

[v6,12/14] KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg

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

Commit Message

Janis Schoetterl-Glausch Jan. 25, 2023, 9:26 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
op. For now, support this op for absolute accesses only.

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

Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
---
 include/uapi/linux/kvm.h |   8 +++
 arch/s390/kvm/gaccess.h  |   3 ++
 arch/s390/kvm/gaccess.c  | 103 +++++++++++++++++++++++++++++++++++++++
 arch/s390/kvm/kvm-s390.c |  56 ++++++++++++++++++++-
 4 files changed, 169 insertions(+), 1 deletion(-)

Comments

Heiko Carstens Jan. 26, 2023, 8:19 a.m. UTC | #1
On Wed, Jan 25, 2023 at 10:26:06PM +0100, Janis Schoetterl-Glausch wrote:
> 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
> op. For now, support this op for absolute accesses only.
> 
> This op can be use, for example, to set the device-state-change
> indicator and the adapter-local-summary indicator atomically.
> 
> Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
> ---
>  include/uapi/linux/kvm.h |   8 +++
>  arch/s390/kvm/gaccess.h  |   3 ++
>  arch/s390/kvm/gaccess.c  | 103 +++++++++++++++++++++++++++++++++++++++
>  arch/s390/kvm/kvm-s390.c |  56 ++++++++++++++++++++-
>  4 files changed, 169 insertions(+), 1 deletion(-)
...
> +		ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);

FWIW, this and the three others need a __user annotation:

		ret = cmpxchg_user_key((u8 __user *)hva, &old, *old_addr, new, access_key);

Otherwise you end up with sparse warnings (compile with C=1).
Janosch Frank Jan. 26, 2023, 4:10 p.m. UTC | #2
On 1/25/23 22:26, Janis Schoetterl-Glausch wrote:
> 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
> op. For now, support this op for absolute accesses only.
> 
> This op can be use, for example, to set the device-state-change

s/use/used/

> indicator and the adapter-local-summary indicator atomically.
> 
> Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
> ---
[...]
> +/**
> + * cmpxchg_guest_abs_with_key() - Perform cmpxchg on guest absolute address.
> + * @kvm: Virtual machine instance.
> + * @gpa: Absolute guest address of the location to be changed.
> + * @len: Operand length of the cmpxchg, required: 1 <= len <= 16. Providing a
> + *       non power of two will result in failure.
> + * @old_addr: Pointer to old value. If the location at @gpa contains this value,
> + *            the exchange will succeed. After calling cmpxchg_guest_abs_with_key()
> + *            *@old_addr contains the value at @gpa before the attempt to
> + *            exchange the value.
> + * @new: The value to place at @gpa.
> + * @access_key: The access key to use for the guest access.
> + * @success: output value indicating if an exchange occurred.
> + *
> + * Atomically exchange the value at @gpa by @new, if it contains *@old.
> + * Honors storage keys.
> + *
> + * Return: * 0: successful exchange
> + *         * a program interruption code indicating the reason cmpxchg could
> + *           not be attempted

Nit:
 >0: a program interruption code...


> + *         * -EINVAL: address misaligned or len not power of two
> + *         * -EAGAIN: transient failure (len 1 or 2)
> + *         * -EOPNOTSUPP: read-only memslot (should never occur)
> + */
> +int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
> +			       __uint128_t *old_addr, __uint128_t new,
> +			       u8 access_key, bool *success)
> +{
> +	gfn_t gfn = gpa >> PAGE_SHIFT;

  gpa_to_gfn()?

> +	struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
> +	bool writable;
> +	hva_t hva;
> +	int ret;
> +
> +	if (!IS_ALIGNED(gpa, len))
> +		return -EINVAL;
> +
> +	hva = gfn_to_hva_memslot_prot(slot, gfn, &writable);
> +	if (kvm_is_error_hva(hva))
> +		return PGM_ADDRESSING;
> +	/*
> +	 * Check if it's a read-only memslot, even though that cannot occur
> +	 * since those are unsupported.
> +	 * Don't try to actually handle that case.
> +	 */
> +	if (!writable)
> +		return -EOPNOTSUPP;
> +
> +	hva += offset_in_page(gpa);

Hmm if we don't use a macro to generate these then I'd add an explanation:

cmpxchg_user_key() is a macro that is dependent on the type of "old" so 
there's no deduplication possible without further macros.

> +	switch (len) {
> +	case 1: {
> +		u8 old;
> +
> +		ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
> +		*success = !ret && old == *old_addr;
> +		*old_addr = old;
> +		break;
> +	}
> +	case 2: {
> +		u16 old;
> +
> +		ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
> +		*success = !ret && old == *old_addr;
> +		*old_addr = old;
> +		break;
> +	}
> +	case 4: {
> +		u32 old;
> +
> +		ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
> +		*success = !ret && old == *old_addr;
> +		*old_addr = old;
> +		break;
> +	}
> +	case 8: {
> +		u64 old;
> +
> +		ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
> +		*success = !ret && old == *old_addr;
> +		*old_addr = old;
> +		break;
> +	}
> +	case 16: {
> +		__uint128_t old;
> +
> +		ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
> +		*success = !ret && old == *old_addr;
> +		*old_addr = old;
> +		break;
> +	}
> +	default:
> +		return -EINVAL;
> +	}
> +	mark_page_dirty_in_slot(kvm, slot, gfn);

Is that needed if we failed the store?

> +	/*
> +	 * Assume that the fault is caused by protection, either key protection
> +	 * or user page write protection.
> +	 */
> +	if (ret == -EFAULT)
> +		ret = PGM_PROTECTION;
> +	return ret;
> +}
> +
>   /**
>    * guest_translate_address_with_key - translate guest logical into guest absolute address
>    * @vcpu: virtual cpu
> diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
> index 4b8b41be7aed..86e9734d5782 100644
> --- a/arch/s390/kvm/kvm-s390.c
> +++ b/arch/s390/kvm/kvm-s390.c
> @@ -584,7 +584,6 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
>   	case KVM_CAP_S390_VCPU_RESETS:
>   	case KVM_CAP_SET_GUEST_DEBUG:
>   	case KVM_CAP_S390_DIAG318:
> -	case KVM_CAP_S390_MEM_OP_EXTENSION:
>   		r = 1;
>   		break;
>   	case KVM_CAP_SET_GUEST_DEBUG2:
> @@ -598,6 +597,15 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
>   	case KVM_CAP_S390_MEM_OP:
>   		r = MEM_OP_MAX_SIZE;
>   		break;
> +	case KVM_CAP_S390_MEM_OP_EXTENSION:
> +		/*
> +		 * Flag bits indicating which extensions are supported.
> +		 * If r > 0, the base extension must also be supported/indicated,
> +		 * in order to maintain backwards compatibility.
> +		 */
> +		r = KVM_S390_MEMOP_EXTENSION_CAP_BASE |
> +		    KVM_S390_MEMOP_EXTENSION_CAP_CMPXCHG;
> +		break;
>   	case KVM_CAP_NR_VCPUS:
>   	case KVM_CAP_MAX_VCPUS:
>   	case KVM_CAP_MAX_VCPU_ID:
> @@ -2840,6 +2848,50 @@ static int kvm_s390_vm_mem_op_abs(struct kvm *kvm, struct kvm_s390_mem_op *mop)
>   	return r;
>   }
>   
> +static int kvm_s390_vm_mem_op_cmpxchg(struct kvm *kvm, struct kvm_s390_mem_op *mop)
> +{
> +	void __user *uaddr = (void __user *)mop->buf;
> +	void __user *old_addr = (void __user *)mop->old_addr;
> +	union {
> +		__uint128_t quad;
> +		char raw[sizeof(__uint128_t)];
> +	} old = { .quad = 0}, new = { .quad = 0 };
> +	unsigned int off_in_quad = sizeof(new) - mop->size;
> +	int r, srcu_idx;
> +	bool success;
> +
> +	r = mem_op_validate_common(mop, KVM_S390_MEMOP_F_SKEY_PROTECTION);
> +	if (r)
> +		return r;
> +	/*
> +	 * This validates off_in_quad. Checking that size is a power
> +	 * of two is not necessary, as cmpxchg_guest_abs_with_key
> +	 * takes care of that
> +	 */
> +	if (mop->size > sizeof(new))
> +		return -EINVAL;
> +	if (copy_from_user(&new.raw[off_in_quad], uaddr, mop->size))
> +		return -EFAULT;
> +	if (copy_from_user(&old.raw[off_in_quad], old_addr, mop->size))
> +		return -EFAULT;
> +
> +	srcu_idx = srcu_read_lock(&kvm->srcu);
> +
> +	if (kvm_is_error_gpa(kvm, mop->gaddr)) {
> +		r = PGM_ADDRESSING;
> +		goto out_unlock;
> +	}
> +
> +	r = cmpxchg_guest_abs_with_key(kvm, mop->gaddr, mop->size, &old.quad,
> +				       new.quad, mop->key, &success);
> +	if (!success && copy_to_user(old_addr, &old.raw[off_in_quad], mop->size))
> +		r = -EFAULT;
> +
> +out_unlock:
> +	srcu_read_unlock(&kvm->srcu, srcu_idx);
> +	return r;
> +}
> +
>   static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
>   {
>   	/*
> @@ -2858,6 +2910,8 @@ static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
>   	case KVM_S390_MEMOP_ABSOLUTE_READ:
>   	case KVM_S390_MEMOP_ABSOLUTE_WRITE:
>   		return kvm_s390_vm_mem_op_abs(kvm, mop);
> +	case KVM_S390_MEMOP_ABSOLUTE_CMPXCHG:
> +		return kvm_s390_vm_mem_op_cmpxchg(kvm, mop);
>   	default:
>   		return -EINVAL;
>   	}
Janis Schoetterl-Glausch Jan. 27, 2023, 6:15 p.m. UTC | #3
On Thu, 2023-01-26 at 17:10 +0100, Janosch Frank wrote:
> On 1/25/23 22:26, Janis Schoetterl-Glausch wrote:
> > 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
> > op. For now, support this op for absolute accesses only.
> > 
> > This op can be use, for example, to set the device-state-change
> 
> s/use/used/
> 
> > indicator and the adapter-local-summary indicator atomically.
> > 
> > Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
> > ---
> [...]
> > +/**
> > + * cmpxchg_guest_abs_with_key() - Perform cmpxchg on guest absolute address.
> > + * @kvm: Virtual machine instance.
> > + * @gpa: Absolute guest address of the location to be changed.
> > + * @len: Operand length of the cmpxchg, required: 1 <= len <= 16. Providing a
> > + *       non power of two will result in failure.
> > + * @old_addr: Pointer to old value. If the location at @gpa contains this value,
> > + *            the exchange will succeed. After calling cmpxchg_guest_abs_with_key()
> > + *            *@old_addr contains the value at @gpa before the attempt to
> > + *            exchange the value.
> > + * @new: The value to place at @gpa.
> > + * @access_key: The access key to use for the guest access.
> > + * @success: output value indicating if an exchange occurred.
> > + *
> > + * Atomically exchange the value at @gpa by @new, if it contains *@old.
> > + * Honors storage keys.
> > + *
> > + * Return: * 0: successful exchange
> > + *         * a program interruption code indicating the reason cmpxchg could
> > + *           not be attempted
> 
> Nit:
>  >0: a program interruption code...
> 
> 
> > + *         * -EINVAL: address misaligned or len not power of two
> > + *         * -EAGAIN: transient failure (len 1 or 2)
> > + *         * -EOPNOTSUPP: read-only memslot (should never occur)
> > + */
> > +int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
> > +			       __uint128_t *old_addr, __uint128_t new,
> > +			       u8 access_key, bool *success)
> > +{
> > +	gfn_t gfn = gpa >> PAGE_SHIFT;
> 
>   gpa_to_gfn()?

Yes.
> 
> > +	struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
> > +	bool writable;
> > +	hva_t hva;
> > +	int ret;
> > +
> > +	if (!IS_ALIGNED(gpa, len))
> > +		return -EINVAL;
> > +
> > +	hva = gfn_to_hva_memslot_prot(slot, gfn, &writable);
> > +	if (kvm_is_error_hva(hva))
> > +		return PGM_ADDRESSING;
> > +	/*
> > +	 * Check if it's a read-only memslot, even though that cannot occur
> > +	 * since those are unsupported.
> > +	 * Don't try to actually handle that case.
> > +	 */
> > +	if (!writable)
> > +		return -EOPNOTSUPP;
> > +
> > +	hva += offset_in_page(gpa);
> 
> Hmm if we don't use a macro to generate these then I'd add an explanation:
> 
> cmpxchg_user_key() is a macro that is dependent on the type of "old" so 
> there's no deduplication possible without further macros.

Can do.
Btw. I could move the other two statements out of the switch by using a union of old values,
memcmp and memcpy, but I think that would be less readable.

> 
> > +	switch (len) {
> > +	case 1: {
> > +		u8 old;
> > +
> > +		ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
> > +		*success = !ret && old == *old_addr;
> > +		*old_addr = old;
> > +		break;
> > +	}
> > +	case 2: {
> > +		u16 old;
> > +
> > +		ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
> > +		*success = !ret && old == *old_addr;
> > +		*old_addr = old;
> > +		break;
> > +	}
> > +	case 4: {
> > +		u32 old;
> > +
> > +		ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
> > +		*success = !ret && old == *old_addr;
> > +		*old_addr = old;
> > +		break;
> > +	}
> > +	case 8: {
> > +		u64 old;
> > +
> > +		ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
> > +		*success = !ret && old == *old_addr;
> > +		*old_addr = old;
> > +		break;
> > +	}
> > +	case 16: {
> > +		__uint128_t old;
> > +
> > +		ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
> > +		*success = !ret && old == *old_addr;
> > +		*old_addr = old;
> > +		break;
> > +	}
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +	mark_page_dirty_in_slot(kvm, slot, gfn);
> 
> Is that needed if we failed the store?

Indeed it isn't.

[...]
kernel test robot Jan. 28, 2023, 9:29 a.m. UTC | #4
Hi Janis,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on kvm/queue]
[also build test ERROR on lwn/docs-next linus/master]
[cannot apply to kvms390/next kvm/linux-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Janis-Schoetterl-Glausch/KVM-s390-selftest-memop-Pass-mop_desc-via-pointer/20230128-132603
base:   https://git.kernel.org/pub/scm/virt/kvm/kvm.git queue
patch link:    https://lore.kernel.org/r/20230125212608.1860251-13-scgl%40linux.ibm.com
patch subject: [PATCH v6 12/14] KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg
config: s390-allyesconfig (https://download.01.org/0day-ci/archive/20230128/202301281752.6gSopuWh-lkp@intel.com/config)
compiler: s390-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/6e6b3d99b9978a70b148b989d46b039feda3a3c3
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Janis-Schoetterl-Glausch/KVM-s390-selftest-memop-Pass-mop_desc-via-pointer/20230128-132603
        git checkout 6e6b3d99b9978a70b148b989d46b039feda3a3c3
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=s390 olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   arch/s390/kvm/gaccess.c: In function 'cmpxchg_guest_abs_with_key':
>> arch/s390/kvm/gaccess.c:1217:23: error: implicit declaration of function 'cmpxchg_user_key'; did you mean 'copy_to_user_key'? [-Werror=implicit-function-declaration]
    1217 |                 ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
         |                       ^~~~~~~~~~~~~~~~
         |                       copy_to_user_key
   cc1: some warnings being treated as errors


vim +1217 arch/s390/kvm/gaccess.c

  1163	
  1164	/**
  1165	 * cmpxchg_guest_abs_with_key() - Perform cmpxchg on guest absolute address.
  1166	 * @kvm: Virtual machine instance.
  1167	 * @gpa: Absolute guest address of the location to be changed.
  1168	 * @len: Operand length of the cmpxchg, required: 1 <= len <= 16. Providing a
  1169	 *       non power of two will result in failure.
  1170	 * @old_addr: Pointer to old value. If the location at @gpa contains this value,
  1171	 *            the exchange will succeed. After calling cmpxchg_guest_abs_with_key()
  1172	 *            *@old_addr contains the value at @gpa before the attempt to
  1173	 *            exchange the value.
  1174	 * @new: The value to place at @gpa.
  1175	 * @access_key: The access key to use for the guest access.
  1176	 * @success: output value indicating if an exchange occurred.
  1177	 *
  1178	 * Atomically exchange the value at @gpa by @new, if it contains *@old.
  1179	 * Honors storage keys.
  1180	 *
  1181	 * Return: * 0: successful exchange
  1182	 *         * a program interruption code indicating the reason cmpxchg could
  1183	 *           not be attempted
  1184	 *         * -EINVAL: address misaligned or len not power of two
  1185	 *         * -EAGAIN: transient failure (len 1 or 2)
  1186	 *         * -EOPNOTSUPP: read-only memslot (should never occur)
  1187	 */
  1188	int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
  1189				       __uint128_t *old_addr, __uint128_t new,
  1190				       u8 access_key, bool *success)
  1191	{
  1192		gfn_t gfn = gpa >> PAGE_SHIFT;
  1193		struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
  1194		bool writable;
  1195		hva_t hva;
  1196		int ret;
  1197	
  1198		if (!IS_ALIGNED(gpa, len))
  1199			return -EINVAL;
  1200	
  1201		hva = gfn_to_hva_memslot_prot(slot, gfn, &writable);
  1202		if (kvm_is_error_hva(hva))
  1203			return PGM_ADDRESSING;
  1204		/*
  1205		 * Check if it's a read-only memslot, even though that cannot occur
  1206		 * since those are unsupported.
  1207		 * Don't try to actually handle that case.
  1208		 */
  1209		if (!writable)
  1210			return -EOPNOTSUPP;
  1211	
  1212		hva += offset_in_page(gpa);
  1213		switch (len) {
  1214		case 1: {
  1215			u8 old;
  1216	
> 1217			ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
  1218			*success = !ret && old == *old_addr;
  1219			*old_addr = old;
  1220			break;
  1221		}
  1222		case 2: {
  1223			u16 old;
  1224	
  1225			ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
  1226			*success = !ret && old == *old_addr;
  1227			*old_addr = old;
  1228			break;
  1229		}
  1230		case 4: {
  1231			u32 old;
  1232	
  1233			ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
  1234			*success = !ret && old == *old_addr;
  1235			*old_addr = old;
  1236			break;
  1237		}
  1238		case 8: {
  1239			u64 old;
  1240	
  1241			ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
  1242			*success = !ret && old == *old_addr;
  1243			*old_addr = old;
  1244			break;
  1245		}
  1246		case 16: {
  1247			__uint128_t old;
  1248	
  1249			ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
  1250			*success = !ret && old == *old_addr;
  1251			*old_addr = old;
  1252			break;
  1253		}
  1254		default:
  1255			return -EINVAL;
  1256		}
  1257		mark_page_dirty_in_slot(kvm, slot, gfn);
  1258		/*
  1259		 * Assume that the fault is caused by protection, either key protection
  1260		 * or user page write protection.
  1261		 */
  1262		if (ret == -EFAULT)
  1263			ret = PGM_PROTECTION;
  1264		return ret;
  1265	}
  1266
kernel test robot Jan. 28, 2023, 2:38 p.m. UTC | #5
Hi Janis,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on kvm/queue]
[also build test ERROR on lwn/docs-next linus/master v6.2-rc5 next-20230127]
[cannot apply to kvms390/next kvm/linux-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Janis-Schoetterl-Glausch/KVM-s390-selftest-memop-Pass-mop_desc-via-pointer/20230128-132603
base:   https://git.kernel.org/pub/scm/virt/kvm/kvm.git queue
patch link:    https://lore.kernel.org/r/20230125212608.1860251-13-scgl%40linux.ibm.com
patch subject: [PATCH v6 12/14] KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg
config: s390-randconfig-r022-20230123 (https://download.01.org/0day-ci/archive/20230128/202301282258.RvVJOYVA-lkp@intel.com/config)
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 4196ca3278f78c6e19246e54ab0ecb364e37d66a)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install s390 cross compiling tool for clang build
        # apt-get install binutils-s390x-linux-gnu
        # https://github.com/intel-lab-lkp/linux/commit/6e6b3d99b9978a70b148b989d46b039feda3a3c3
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Janis-Schoetterl-Glausch/KVM-s390-selftest-memop-Pass-mop_desc-via-pointer/20230128-132603
        git checkout 6e6b3d99b9978a70b148b989d46b039feda3a3c3
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash arch/s390/kvm/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   In file included from arch/s390/kvm/gaccess.c:16:
   In file included from arch/s390/kvm/kvm-s390.h:17:
   In file included from include/linux/kvm_host.h:19:
   In file included from include/linux/msi.h:27:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/s390/include/asm/io.h:75:
   include/asm-generic/io.h:547:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __raw_readb(PCI_IOBASE + addr);
                             ~~~~~~~~~~ ^
   include/asm-generic/io.h:560:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
                                                           ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/big_endian.h:37:59: note: expanded from macro '__le16_to_cpu'
   #define __le16_to_cpu(x) __swab16((__force __u16)(__le16)(x))
                                                             ^
   include/uapi/linux/swab.h:102:54: note: expanded from macro '__swab16'
   #define __swab16(x) (__u16)__builtin_bswap16((__u16)(x))
                                                        ^
   In file included from arch/s390/kvm/gaccess.c:16:
   In file included from arch/s390/kvm/kvm-s390.h:17:
   In file included from include/linux/kvm_host.h:19:
   In file included from include/linux/msi.h:27:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/s390/include/asm/io.h:75:
   include/asm-generic/io.h:573:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
                                                           ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/big_endian.h:35:59: note: expanded from macro '__le32_to_cpu'
   #define __le32_to_cpu(x) __swab32((__force __u32)(__le32)(x))
                                                             ^
   include/uapi/linux/swab.h:115:54: note: expanded from macro '__swab32'
   #define __swab32(x) (__u32)__builtin_bswap32((__u32)(x))
                                                        ^
   In file included from arch/s390/kvm/gaccess.c:16:
   In file included from arch/s390/kvm/kvm-s390.h:17:
   In file included from include/linux/kvm_host.h:19:
   In file included from include/linux/msi.h:27:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/s390/include/asm/io.h:75:
   include/asm-generic/io.h:584:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writeb(value, PCI_IOBASE + addr);
                               ~~~~~~~~~~ ^
   include/asm-generic/io.h:594:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
                                                         ~~~~~~~~~~ ^
   include/asm-generic/io.h:604:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
                                                         ~~~~~~~~~~ ^
   include/asm-generic/io.h:692:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           readsb(PCI_IOBASE + addr, buffer, count);
                  ~~~~~~~~~~ ^
   include/asm-generic/io.h:700:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           readsw(PCI_IOBASE + addr, buffer, count);
                  ~~~~~~~~~~ ^
   include/asm-generic/io.h:708:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           readsl(PCI_IOBASE + addr, buffer, count);
                  ~~~~~~~~~~ ^
   include/asm-generic/io.h:717:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           writesb(PCI_IOBASE + addr, buffer, count);
                   ~~~~~~~~~~ ^
   include/asm-generic/io.h:726:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           writesw(PCI_IOBASE + addr, buffer, count);
                   ~~~~~~~~~~ ^
   include/asm-generic/io.h:735:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           writesl(PCI_IOBASE + addr, buffer, count);
                   ~~~~~~~~~~ ^
>> arch/s390/kvm/gaccess.c:1217:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1217:9: note: did you mean 'copy_to_user_key'?
   arch/s390/include/asm/uaccess.h:51:1: note: 'copy_to_user_key' declared here
   copy_to_user_key(void __user *to, const void *from, unsigned long n, unsigned long key)
   ^
   arch/s390/kvm/gaccess.c:1225:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1233:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1241:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1249:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
                         ^
   12 warnings and 5 errors generated.


vim +/cmpxchg_user_key +1217 arch/s390/kvm/gaccess.c

  1163	
  1164	/**
  1165	 * cmpxchg_guest_abs_with_key() - Perform cmpxchg on guest absolute address.
  1166	 * @kvm: Virtual machine instance.
  1167	 * @gpa: Absolute guest address of the location to be changed.
  1168	 * @len: Operand length of the cmpxchg, required: 1 <= len <= 16. Providing a
  1169	 *       non power of two will result in failure.
  1170	 * @old_addr: Pointer to old value. If the location at @gpa contains this value,
  1171	 *            the exchange will succeed. After calling cmpxchg_guest_abs_with_key()
  1172	 *            *@old_addr contains the value at @gpa before the attempt to
  1173	 *            exchange the value.
  1174	 * @new: The value to place at @gpa.
  1175	 * @access_key: The access key to use for the guest access.
  1176	 * @success: output value indicating if an exchange occurred.
  1177	 *
  1178	 * Atomically exchange the value at @gpa by @new, if it contains *@old.
  1179	 * Honors storage keys.
  1180	 *
  1181	 * Return: * 0: successful exchange
  1182	 *         * a program interruption code indicating the reason cmpxchg could
  1183	 *           not be attempted
  1184	 *         * -EINVAL: address misaligned or len not power of two
  1185	 *         * -EAGAIN: transient failure (len 1 or 2)
  1186	 *         * -EOPNOTSUPP: read-only memslot (should never occur)
  1187	 */
  1188	int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
  1189				       __uint128_t *old_addr, __uint128_t new,
  1190				       u8 access_key, bool *success)
  1191	{
  1192		gfn_t gfn = gpa >> PAGE_SHIFT;
  1193		struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
  1194		bool writable;
  1195		hva_t hva;
  1196		int ret;
  1197	
  1198		if (!IS_ALIGNED(gpa, len))
  1199			return -EINVAL;
  1200	
  1201		hva = gfn_to_hva_memslot_prot(slot, gfn, &writable);
  1202		if (kvm_is_error_hva(hva))
  1203			return PGM_ADDRESSING;
  1204		/*
  1205		 * Check if it's a read-only memslot, even though that cannot occur
  1206		 * since those are unsupported.
  1207		 * Don't try to actually handle that case.
  1208		 */
  1209		if (!writable)
  1210			return -EOPNOTSUPP;
  1211	
  1212		hva += offset_in_page(gpa);
  1213		switch (len) {
  1214		case 1: {
  1215			u8 old;
  1216	
> 1217			ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
  1218			*success = !ret && old == *old_addr;
  1219			*old_addr = old;
  1220			break;
  1221		}
  1222		case 2: {
  1223			u16 old;
  1224	
  1225			ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
  1226			*success = !ret && old == *old_addr;
  1227			*old_addr = old;
  1228			break;
  1229		}
  1230		case 4: {
  1231			u32 old;
  1232	
  1233			ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
  1234			*success = !ret && old == *old_addr;
  1235			*old_addr = old;
  1236			break;
  1237		}
  1238		case 8: {
  1239			u64 old;
  1240	
  1241			ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
  1242			*success = !ret && old == *old_addr;
  1243			*old_addr = old;
  1244			break;
  1245		}
  1246		case 16: {
  1247			__uint128_t old;
  1248	
  1249			ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
  1250			*success = !ret && old == *old_addr;
  1251			*old_addr = old;
  1252			break;
  1253		}
  1254		default:
  1255			return -EINVAL;
  1256		}
  1257		mark_page_dirty_in_slot(kvm, slot, gfn);
  1258		/*
  1259		 * Assume that the fault is caused by protection, either key protection
  1260		 * or user page write protection.
  1261		 */
  1262		if (ret == -EFAULT)
  1263			ret = PGM_PROTECTION;
  1264		return ret;
  1265	}
  1266
kernel test robot Jan. 28, 2023, 2:38 p.m. UTC | #6
Hi Janis,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on kvm/queue]
[also build test ERROR on lwn/docs-next linus/master v6.2-rc5 next-20230127]
[cannot apply to kvms390/next kvm/linux-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Janis-Schoetterl-Glausch/KVM-s390-selftest-memop-Pass-mop_desc-via-pointer/20230128-132603
base:   https://git.kernel.org/pub/scm/virt/kvm/kvm.git queue
patch link:    https://lore.kernel.org/r/20230125212608.1860251-13-scgl%40linux.ibm.com
patch subject: [PATCH v6 12/14] KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg
config: s390-randconfig-r044-20230123 (https://download.01.org/0day-ci/archive/20230128/202301282223.YafIuB1E-lkp@intel.com/config)
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 4196ca3278f78c6e19246e54ab0ecb364e37d66a)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install s390 cross compiling tool for clang build
        # apt-get install binutils-s390x-linux-gnu
        # https://github.com/intel-lab-lkp/linux/commit/6e6b3d99b9978a70b148b989d46b039feda3a3c3
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Janis-Schoetterl-Glausch/KVM-s390-selftest-memop-Pass-mop_desc-via-pointer/20230128-132603
        git checkout 6e6b3d99b9978a70b148b989d46b039feda3a3c3
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash arch/s390/kvm/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   In file included from arch/s390/kvm/gaccess.c:16:
   In file included from arch/s390/kvm/kvm-s390.h:17:
   In file included from include/linux/kvm_host.h:19:
   In file included from include/linux/msi.h:27:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/s390/include/asm/io.h:75:
   include/asm-generic/io.h:547:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __raw_readb(PCI_IOBASE + addr);
                             ~~~~~~~~~~ ^
   include/asm-generic/io.h:560:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
                                                           ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/big_endian.h:37:59: note: expanded from macro '__le16_to_cpu'
   #define __le16_to_cpu(x) __swab16((__force __u16)(__le16)(x))
                                                             ^
   include/uapi/linux/swab.h:102:54: note: expanded from macro '__swab16'
   #define __swab16(x) (__u16)__builtin_bswap16((__u16)(x))
                                                        ^
   In file included from arch/s390/kvm/gaccess.c:16:
   In file included from arch/s390/kvm/kvm-s390.h:17:
   In file included from include/linux/kvm_host.h:19:
   In file included from include/linux/msi.h:27:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/s390/include/asm/io.h:75:
   include/asm-generic/io.h:573:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
                                                           ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/big_endian.h:35:59: note: expanded from macro '__le32_to_cpu'
   #define __le32_to_cpu(x) __swab32((__force __u32)(__le32)(x))
                                                             ^
   include/uapi/linux/swab.h:115:54: note: expanded from macro '__swab32'
   #define __swab32(x) (__u32)__builtin_bswap32((__u32)(x))
                                                        ^
   In file included from arch/s390/kvm/gaccess.c:16:
   In file included from arch/s390/kvm/kvm-s390.h:17:
   In file included from include/linux/kvm_host.h:19:
   In file included from include/linux/msi.h:27:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/s390/include/asm/io.h:75:
   include/asm-generic/io.h:584:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writeb(value, PCI_IOBASE + addr);
                               ~~~~~~~~~~ ^
   include/asm-generic/io.h:594:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
                                                         ~~~~~~~~~~ ^
   include/asm-generic/io.h:604:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
                                                         ~~~~~~~~~~ ^
   include/asm-generic/io.h:692:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           readsb(PCI_IOBASE + addr, buffer, count);
                  ~~~~~~~~~~ ^
   include/asm-generic/io.h:700:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           readsw(PCI_IOBASE + addr, buffer, count);
                  ~~~~~~~~~~ ^
   include/asm-generic/io.h:708:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           readsl(PCI_IOBASE + addr, buffer, count);
                  ~~~~~~~~~~ ^
   include/asm-generic/io.h:717:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           writesb(PCI_IOBASE + addr, buffer, count);
                   ~~~~~~~~~~ ^
   include/asm-generic/io.h:726:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           writesw(PCI_IOBASE + addr, buffer, count);
                   ~~~~~~~~~~ ^
   include/asm-generic/io.h:735:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           writesl(PCI_IOBASE + addr, buffer, count);
                   ~~~~~~~~~~ ^
>> arch/s390/kvm/gaccess.c:1217:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1217:9: note: did you mean 'copy_to_user_key'?
   arch/s390/include/asm/uaccess.h:51:1: note: 'copy_to_user_key' declared here
   copy_to_user_key(void __user *to, const void *from, unsigned long n, unsigned long key)
   ^
   arch/s390/kvm/gaccess.c:1225:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1233:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1241:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
                         ^
   arch/s390/kvm/gaccess.c:1249:9: error: call to undeclared function 'cmpxchg_user_key'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
                         ^
   12 warnings and 5 errors generated.


vim +/cmpxchg_user_key +1217 arch/s390/kvm/gaccess.c

  1163	
  1164	/**
  1165	 * cmpxchg_guest_abs_with_key() - Perform cmpxchg on guest absolute address.
  1166	 * @kvm: Virtual machine instance.
  1167	 * @gpa: Absolute guest address of the location to be changed.
  1168	 * @len: Operand length of the cmpxchg, required: 1 <= len <= 16. Providing a
  1169	 *       non power of two will result in failure.
  1170	 * @old_addr: Pointer to old value. If the location at @gpa contains this value,
  1171	 *            the exchange will succeed. After calling cmpxchg_guest_abs_with_key()
  1172	 *            *@old_addr contains the value at @gpa before the attempt to
  1173	 *            exchange the value.
  1174	 * @new: The value to place at @gpa.
  1175	 * @access_key: The access key to use for the guest access.
  1176	 * @success: output value indicating if an exchange occurred.
  1177	 *
  1178	 * Atomically exchange the value at @gpa by @new, if it contains *@old.
  1179	 * Honors storage keys.
  1180	 *
  1181	 * Return: * 0: successful exchange
  1182	 *         * a program interruption code indicating the reason cmpxchg could
  1183	 *           not be attempted
  1184	 *         * -EINVAL: address misaligned or len not power of two
  1185	 *         * -EAGAIN: transient failure (len 1 or 2)
  1186	 *         * -EOPNOTSUPP: read-only memslot (should never occur)
  1187	 */
  1188	int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
  1189				       __uint128_t *old_addr, __uint128_t new,
  1190				       u8 access_key, bool *success)
  1191	{
  1192		gfn_t gfn = gpa >> PAGE_SHIFT;
  1193		struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
  1194		bool writable;
  1195		hva_t hva;
  1196		int ret;
  1197	
  1198		if (!IS_ALIGNED(gpa, len))
  1199			return -EINVAL;
  1200	
  1201		hva = gfn_to_hva_memslot_prot(slot, gfn, &writable);
  1202		if (kvm_is_error_hva(hva))
  1203			return PGM_ADDRESSING;
  1204		/*
  1205		 * Check if it's a read-only memslot, even though that cannot occur
  1206		 * since those are unsupported.
  1207		 * Don't try to actually handle that case.
  1208		 */
  1209		if (!writable)
  1210			return -EOPNOTSUPP;
  1211	
  1212		hva += offset_in_page(gpa);
  1213		switch (len) {
  1214		case 1: {
  1215			u8 old;
  1216	
> 1217			ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
  1218			*success = !ret && old == *old_addr;
  1219			*old_addr = old;
  1220			break;
  1221		}
  1222		case 2: {
  1223			u16 old;
  1224	
  1225			ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
  1226			*success = !ret && old == *old_addr;
  1227			*old_addr = old;
  1228			break;
  1229		}
  1230		case 4: {
  1231			u32 old;
  1232	
  1233			ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
  1234			*success = !ret && old == *old_addr;
  1235			*old_addr = old;
  1236			break;
  1237		}
  1238		case 8: {
  1239			u64 old;
  1240	
  1241			ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
  1242			*success = !ret && old == *old_addr;
  1243			*old_addr = old;
  1244			break;
  1245		}
  1246		case 16: {
  1247			__uint128_t old;
  1248	
  1249			ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
  1250			*success = !ret && old == *old_addr;
  1251			*old_addr = old;
  1252			break;
  1253		}
  1254		default:
  1255			return -EINVAL;
  1256		}
  1257		mark_page_dirty_in_slot(kvm, slot, gfn);
  1258		/*
  1259		 * Assume that the fault is caused by protection, either key protection
  1260		 * or user page write protection.
  1261		 */
  1262		if (ret == -EFAULT)
  1263			ret = PGM_PROTECTION;
  1264		return ret;
  1265	}
  1266
diff mbox series

Patch

diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 55155e262646..d2f30463c133 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -583,6 +583,8 @@  struct kvm_s390_mem_op {
 		struct {
 			__u8 ar;	/* the access register number */
 			__u8 key;	/* access key, ignored if flag unset */
+			__u8 pad1[6];	/* ignored */
+			__u64 old_addr;	/* ignored if cmpxchg flag unset */
 		};
 		__u32 sida_offset; /* offset into the sida */
 		__u8 reserved[32]; /* ignored */
@@ -595,11 +597,17 @@  struct kvm_s390_mem_op {
 #define KVM_S390_MEMOP_SIDA_WRITE	3
 #define KVM_S390_MEMOP_ABSOLUTE_READ	4
 #define KVM_S390_MEMOP_ABSOLUTE_WRITE	5
+#define KVM_S390_MEMOP_ABSOLUTE_CMPXCHG	6
+
 /* flags for kvm_s390_mem_op->flags */
 #define KVM_S390_MEMOP_F_CHECK_ONLY		(1ULL << 0)
 #define KVM_S390_MEMOP_F_INJECT_EXCEPTION	(1ULL << 1)
 #define KVM_S390_MEMOP_F_SKEY_PROTECTION	(1ULL << 2)
 
+/* flags specifying extension support via KVM_CAP_S390_MEM_OP_EXTENSION */
+#define KVM_S390_MEMOP_EXTENSION_CAP_BASE	(1 << 0)
+#define KVM_S390_MEMOP_EXTENSION_CAP_CMPXCHG	(1 << 1)
+
 /* for KVM_INTERRUPT */
 struct kvm_interrupt {
 	/* in */
diff --git a/arch/s390/kvm/gaccess.h b/arch/s390/kvm/gaccess.h
index 9408d6cc8e2c..b320d12aa049 100644
--- a/arch/s390/kvm/gaccess.h
+++ b/arch/s390/kvm/gaccess.h
@@ -206,6 +206,9 @@  int access_guest_with_key(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar,
 int access_guest_real(struct kvm_vcpu *vcpu, unsigned long gra,
 		      void *data, unsigned long len, enum gacc_mode mode);
 
+int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len, __uint128_t *old,
+			       __uint128_t new, u8 access_key, bool *success);
+
 /**
  * write_guest_with_key - copy data from kernel space to guest space
  * @vcpu: virtual cpu
diff --git a/arch/s390/kvm/gaccess.c b/arch/s390/kvm/gaccess.c
index 0243b6e38d36..74cb728a0f24 100644
--- a/arch/s390/kvm/gaccess.c
+++ b/arch/s390/kvm/gaccess.c
@@ -1161,6 +1161,109 @@  int access_guest_real(struct kvm_vcpu *vcpu, unsigned long gra,
 	return rc;
 }
 
+/**
+ * cmpxchg_guest_abs_with_key() - Perform cmpxchg on guest absolute address.
+ * @kvm: Virtual machine instance.
+ * @gpa: Absolute guest address of the location to be changed.
+ * @len: Operand length of the cmpxchg, required: 1 <= len <= 16. Providing a
+ *       non power of two will result in failure.
+ * @old_addr: Pointer to old value. If the location at @gpa contains this value,
+ *            the exchange will succeed. After calling cmpxchg_guest_abs_with_key()
+ *            *@old_addr contains the value at @gpa before the attempt to
+ *            exchange the value.
+ * @new: The value to place at @gpa.
+ * @access_key: The access key to use for the guest access.
+ * @success: output value indicating if an exchange occurred.
+ *
+ * Atomically exchange the value at @gpa by @new, if it contains *@old.
+ * Honors storage keys.
+ *
+ * Return: * 0: successful exchange
+ *         * 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)
+ *         * -EOPNOTSUPP: read-only memslot (should never occur)
+ */
+int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
+			       __uint128_t *old_addr, __uint128_t new,
+			       u8 access_key, bool *success)
+{
+	gfn_t gfn = gpa >> PAGE_SHIFT;
+	struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
+	bool writable;
+	hva_t hva;
+	int ret;
+
+	if (!IS_ALIGNED(gpa, len))
+		return -EINVAL;
+
+	hva = gfn_to_hva_memslot_prot(slot, gfn, &writable);
+	if (kvm_is_error_hva(hva))
+		return PGM_ADDRESSING;
+	/*
+	 * Check if it's a read-only memslot, even though that cannot occur
+	 * since those are unsupported.
+	 * Don't try to actually handle that case.
+	 */
+	if (!writable)
+		return -EOPNOTSUPP;
+
+	hva += offset_in_page(gpa);
+	switch (len) {
+	case 1: {
+		u8 old;
+
+		ret = cmpxchg_user_key((u8 *)hva, &old, *old_addr, new, access_key);
+		*success = !ret && old == *old_addr;
+		*old_addr = old;
+		break;
+	}
+	case 2: {
+		u16 old;
+
+		ret = cmpxchg_user_key((u16 *)hva, &old, *old_addr, new, access_key);
+		*success = !ret && old == *old_addr;
+		*old_addr = old;
+		break;
+	}
+	case 4: {
+		u32 old;
+
+		ret = cmpxchg_user_key((u32 *)hva, &old, *old_addr, new, access_key);
+		*success = !ret && old == *old_addr;
+		*old_addr = old;
+		break;
+	}
+	case 8: {
+		u64 old;
+
+		ret = cmpxchg_user_key((u64 *)hva, &old, *old_addr, new, access_key);
+		*success = !ret && old == *old_addr;
+		*old_addr = old;
+		break;
+	}
+	case 16: {
+		__uint128_t old;
+
+		ret = cmpxchg_user_key((__uint128_t *)hva, &old, *old_addr, new, access_key);
+		*success = !ret && old == *old_addr;
+		*old_addr = old;
+		break;
+	}
+	default:
+		return -EINVAL;
+	}
+	mark_page_dirty_in_slot(kvm, slot, gfn);
+	/*
+	 * Assume that the fault is caused by protection, either key protection
+	 * or user page write protection.
+	 */
+	if (ret == -EFAULT)
+		ret = PGM_PROTECTION;
+	return ret;
+}
+
 /**
  * guest_translate_address_with_key - translate guest logical into guest absolute address
  * @vcpu: virtual cpu
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index 4b8b41be7aed..86e9734d5782 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -584,7 +584,6 @@  int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	case KVM_CAP_S390_VCPU_RESETS:
 	case KVM_CAP_SET_GUEST_DEBUG:
 	case KVM_CAP_S390_DIAG318:
-	case KVM_CAP_S390_MEM_OP_EXTENSION:
 		r = 1;
 		break;
 	case KVM_CAP_SET_GUEST_DEBUG2:
@@ -598,6 +597,15 @@  int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	case KVM_CAP_S390_MEM_OP:
 		r = MEM_OP_MAX_SIZE;
 		break;
+	case KVM_CAP_S390_MEM_OP_EXTENSION:
+		/*
+		 * Flag bits indicating which extensions are supported.
+		 * If r > 0, the base extension must also be supported/indicated,
+		 * in order to maintain backwards compatibility.
+		 */
+		r = KVM_S390_MEMOP_EXTENSION_CAP_BASE |
+		    KVM_S390_MEMOP_EXTENSION_CAP_CMPXCHG;
+		break;
 	case KVM_CAP_NR_VCPUS:
 	case KVM_CAP_MAX_VCPUS:
 	case KVM_CAP_MAX_VCPU_ID:
@@ -2840,6 +2848,50 @@  static int kvm_s390_vm_mem_op_abs(struct kvm *kvm, struct kvm_s390_mem_op *mop)
 	return r;
 }
 
+static int kvm_s390_vm_mem_op_cmpxchg(struct kvm *kvm, struct kvm_s390_mem_op *mop)
+{
+	void __user *uaddr = (void __user *)mop->buf;
+	void __user *old_addr = (void __user *)mop->old_addr;
+	union {
+		__uint128_t quad;
+		char raw[sizeof(__uint128_t)];
+	} old = { .quad = 0}, new = { .quad = 0 };
+	unsigned int off_in_quad = sizeof(new) - mop->size;
+	int r, srcu_idx;
+	bool success;
+
+	r = mem_op_validate_common(mop, KVM_S390_MEMOP_F_SKEY_PROTECTION);
+	if (r)
+		return r;
+	/*
+	 * This validates off_in_quad. Checking that size is a power
+	 * of two is not necessary, as cmpxchg_guest_abs_with_key
+	 * takes care of that
+	 */
+	if (mop->size > sizeof(new))
+		return -EINVAL;
+	if (copy_from_user(&new.raw[off_in_quad], uaddr, mop->size))
+		return -EFAULT;
+	if (copy_from_user(&old.raw[off_in_quad], old_addr, mop->size))
+		return -EFAULT;
+
+	srcu_idx = srcu_read_lock(&kvm->srcu);
+
+	if (kvm_is_error_gpa(kvm, mop->gaddr)) {
+		r = PGM_ADDRESSING;
+		goto out_unlock;
+	}
+
+	r = cmpxchg_guest_abs_with_key(kvm, mop->gaddr, mop->size, &old.quad,
+				       new.quad, mop->key, &success);
+	if (!success && copy_to_user(old_addr, &old.raw[off_in_quad], mop->size))
+		r = -EFAULT;
+
+out_unlock:
+	srcu_read_unlock(&kvm->srcu, srcu_idx);
+	return r;
+}
+
 static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
 {
 	/*
@@ -2858,6 +2910,8 @@  static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
 	case KVM_S390_MEMOP_ABSOLUTE_READ:
 	case KVM_S390_MEMOP_ABSOLUTE_WRITE:
 		return kvm_s390_vm_mem_op_abs(kvm, mop);
+	case KVM_S390_MEMOP_ABSOLUTE_CMPXCHG:
+		return kvm_s390_vm_mem_op_cmpxchg(kvm, mop);
 	default:
 		return -EINVAL;
 	}