Skip to content

Commit

Permalink
KVM: s390: Extend MEM_OP ioctl by storage key checked cmpxchg
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Janis Schoetterl-Glausch <scgl@linux.ibm.com>
  • Loading branch information
Janis Schoetterl-Glausch authored and intel-lab-lkp committed Oct 12, 2022
1 parent a5a0936 commit 427c3a0
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 2 deletions.
57 changes: 57 additions & 0 deletions arch/s390/kvm/gaccess.c
Expand Up @@ -1161,6 +1161,63 @@ 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_p: Pointer to old value. If the location at @gpa contains this value, the
* exchange will succeed. After calling cmpxchg_guest_abs_with_key() *@old
* 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.
*
* Atomically exchange the value at @gpa by @new, if it contains *@old.
* Honors storage keys.
*
* Return: * 0: successful exchange
* * 1: exchange unsuccessful
* * a program interruption code indicating the reason cmpxchg could
* not be attempted
* * -EINVAL: address misaligned or len not power of two
*/
int cmpxchg_guest_abs_with_key(struct kvm *kvm, gpa_t gpa, int len,
unsigned __int128 *old_p, unsigned __int128 new,
u8 access_key)
{
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);
ret = cmpxchg_user_key_size(len, (void __user *)hva, old_p, new, access_key);
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
Expand Down
4 changes: 4 additions & 0 deletions arch/s390/kvm/gaccess.h
Expand Up @@ -206,6 +206,10 @@ 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,
unsigned __int128 *old,
unsigned __int128 new, u8 access_key);

/**
* write_guest_with_key - copy data from kernel space to guest space
* @vcpu: virtual cpu
Expand Down
35 changes: 33 additions & 2 deletions arch/s390/kvm/kvm-s390.c
Expand Up @@ -576,7 +576,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:
Expand All @@ -590,6 +589,14 @@ 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.
* The first extension doesn't use a flag, but pretend it does,
* this way that can be changed in the future.
*/
r = 0x3;
break;
case KVM_CAP_NR_VCPUS:
case KVM_CAP_MAX_VCPUS:
case KVM_CAP_MAX_VCPU_ID:
Expand Down Expand Up @@ -2714,12 +2721,19 @@ static bool access_key_invalid(u8 access_key)
static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
{
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)];
} old = { .quad = 0}, new = { .quad = 0 };
unsigned int off_in_quad = sizeof(unsigned __int128) - mop->size;
u64 supported_flags;
void *tmpbuf = NULL;
int r, srcu_idx;

supported_flags = KVM_S390_MEMOP_F_SKEY_PROTECTION
| KVM_S390_MEMOP_F_CHECK_ONLY;
| KVM_S390_MEMOP_F_CHECK_ONLY
| KVM_S390_MEMOP_F_CMPXCHG;
if (mop->flags & ~supported_flags || !mop->size)
return -EINVAL;
if (mop->size > MEM_OP_MAX_SIZE)
Expand All @@ -2741,6 +2755,15 @@ static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
} else {
mop->key = 0;
}
if (mop->flags & KVM_S390_MEMOP_F_CMPXCHG) {
if (mop->size > sizeof(new))
return -EINVAL;
/* off_in_quad has been validated */
if (copy_from_user(&new.raw[off_in_quad], uaddr, mop->size))
return -EFAULT;
if (copy_from_user(&old.raw[off_in_quad], old_p, mop->size))
return -EFAULT;
}
if (!(mop->flags & KVM_S390_MEMOP_F_CHECK_ONLY)) {
tmpbuf = vmalloc(mop->size);
if (!tmpbuf)
Expand Down Expand Up @@ -2771,6 +2794,14 @@ static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
case KVM_S390_MEMOP_ABSOLUTE_WRITE: {
if (mop->flags & KVM_S390_MEMOP_F_CHECK_ONLY) {
r = check_gpa_range(kvm, mop->gaddr, mop->size, GACC_STORE, mop->key);
} else if (mop->flags & KVM_S390_MEMOP_F_CMPXCHG) {
r = cmpxchg_guest_abs_with_key(kvm, mop->gaddr, mop->size,
&old.quad, new.quad, mop->key);
if (r == 1) {
r = KVM_S390_MEMOP_R_NO_XCHG;
if (copy_to_user(old_p, &old.raw[off_in_quad], mop->size))
r = -EFAULT;
}
} else {
if (copy_from_user(tmpbuf, uaddr, mop->size)) {
r = -EFAULT;
Expand Down
5 changes: 5 additions & 0 deletions include/uapi/linux/kvm.h
Expand Up @@ -588,6 +588,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_p; /* ignored if flag unset */
};
__u32 sida_offset; /* offset into the sida */
__u8 reserved[32]; /* ignored */
Expand All @@ -604,6 +606,9 @@ struct kvm_s390_mem_op {
#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)
#define KVM_S390_MEMOP_F_CMPXCHG (1ULL << 3)
/* Non program exception return codes (pgm codes are 16 bit) */
#define KVM_S390_MEMOP_R_NO_XCHG ((1 << 16) + 0)

/* for KVM_INTERRUPT */
struct kvm_interrupt {
Expand Down

0 comments on commit 427c3a0

Please sign in to comment.