Merge tag 's390-5.18-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
[linux-2.6-microblaze.git] / arch / s390 / kvm / kvm-s390.c
index fd65ab4..ab73d99 100644 (file)
@@ -563,6 +563,7 @@ 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:
@@ -2193,6 +2194,9 @@ static int kvm_s390_cpus_from_pv(struct kvm *kvm, u16 *rcp, u16 *rrcp)
                }
                mutex_unlock(&vcpu->mutex);
        }
+       /* Ensure that we re-enable gisa if the non-PV guest used it but the PV guest did not. */
+       if (use_gisa)
+               kvm_s390_gisa_enable(kvm);
        return ret;
 }
 
@@ -2204,6 +2208,10 @@ static int kvm_s390_cpus_to_pv(struct kvm *kvm, u16 *rc, u16 *rrc)
 
        struct kvm_vcpu *vcpu;
 
+       /* Disable the GISA if the ultravisor does not support AIV. */
+       if (!test_bit_inv(BIT_UV_FEAT_AIV, &uv_info.uv_feature_indications))
+               kvm_s390_gisa_disable(kvm);
+
        kvm_for_each_vcpu(i, vcpu, kvm) {
                mutex_lock(&vcpu->mutex);
                r = kvm_s390_pv_create_cpu(vcpu, rc, rrc);
@@ -2358,6 +2366,83 @@ static int kvm_s390_handle_pv(struct kvm *kvm, struct kvm_pv_cmd *cmd)
        return r;
 }
 
+static bool access_key_invalid(u8 access_key)
+{
+       return access_key > 0xf;
+}
+
+static int kvm_s390_vm_mem_op(struct kvm *kvm, struct kvm_s390_mem_op *mop)
+{
+       void __user *uaddr = (void __user *)mop->buf;
+       u64 supported_flags;
+       void *tmpbuf = NULL;
+       int r, srcu_idx;
+
+       supported_flags = KVM_S390_MEMOP_F_SKEY_PROTECTION
+                         | KVM_S390_MEMOP_F_CHECK_ONLY;
+       if (mop->flags & ~supported_flags || !mop->size)
+               return -EINVAL;
+       if (mop->size > MEM_OP_MAX_SIZE)
+               return -E2BIG;
+       if (kvm_s390_pv_is_protected(kvm))
+               return -EINVAL;
+       if (mop->flags & KVM_S390_MEMOP_F_SKEY_PROTECTION) {
+               if (access_key_invalid(mop->key))
+                       return -EINVAL;
+       } else {
+               mop->key = 0;
+       }
+       if (!(mop->flags & KVM_S390_MEMOP_F_CHECK_ONLY)) {
+               tmpbuf = vmalloc(mop->size);
+               if (!tmpbuf)
+                       return -ENOMEM;
+       }
+
+       srcu_idx = srcu_read_lock(&kvm->srcu);
+
+       if (kvm_is_error_gpa(kvm, mop->gaddr)) {
+               r = PGM_ADDRESSING;
+               goto out_unlock;
+       }
+
+       switch (mop->op) {
+       case KVM_S390_MEMOP_ABSOLUTE_READ: {
+               if (mop->flags & KVM_S390_MEMOP_F_CHECK_ONLY) {
+                       r = check_gpa_range(kvm, mop->gaddr, mop->size, GACC_FETCH, mop->key);
+               } else {
+                       r = access_guest_abs_with_key(kvm, mop->gaddr, tmpbuf,
+                                                     mop->size, GACC_FETCH, mop->key);
+                       if (r == 0) {
+                               if (copy_to_user(uaddr, tmpbuf, mop->size))
+                                       r = -EFAULT;
+                       }
+               }
+               break;
+       }
+       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 (copy_from_user(tmpbuf, uaddr, mop->size)) {
+                               r = -EFAULT;
+                               break;
+                       }
+                       r = access_guest_abs_with_key(kvm, mop->gaddr, tmpbuf,
+                                                     mop->size, GACC_STORE, mop->key);
+               }
+               break;
+       }
+       default:
+               r = -EINVAL;
+       }
+
+out_unlock:
+       srcu_read_unlock(&kvm->srcu, srcu_idx);
+
+       vfree(tmpbuf);
+       return r;
+}
+
 long kvm_arch_vm_ioctl(struct file *filp,
                       unsigned int ioctl, unsigned long arg)
 {
@@ -2482,6 +2567,15 @@ long kvm_arch_vm_ioctl(struct file *filp,
                }
                break;
        }
+       case KVM_S390_MEM_OP: {
+               struct kvm_s390_mem_op mem_op;
+
+               if (copy_from_user(&mem_op, argp, sizeof(mem_op)) == 0)
+                       r = kvm_s390_vm_mem_op(kvm, &mem_op);
+               else
+                       r = -EFAULT;
+               break;
+       }
        default:
                r = -ENOTTY;
        }
@@ -3262,9 +3356,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
 
        vcpu->arch.sie_block->icpua = vcpu->vcpu_id;
        spin_lock_init(&vcpu->arch.local_int.lock);
-       vcpu->arch.sie_block->gd = (u32)(u64)vcpu->kvm->arch.gisa_int.origin;
-       if (vcpu->arch.sie_block->gd && sclp.has_gisaf)
-               vcpu->arch.sie_block->gd |= GISA_FORMAT1;
+       vcpu->arch.sie_block->gd = kvm_s390_get_gisa_desc(vcpu->kvm);
        seqcount_init(&vcpu->arch.cputm_seqcount);
 
        vcpu->arch.pfault_token = KVM_S390_PFAULT_TOKEN_INVALID;
@@ -3393,7 +3485,7 @@ static void kvm_gmap_notifier(struct gmap *gmap, unsigned long start,
                if (prefix <= end && start <= prefix + 2*PAGE_SIZE - 1) {
                        VCPU_EVENT(vcpu, 2, "gmap notifier for %lx-%lx",
                                   start, end);
-                       kvm_s390_sync_request(KVM_REQ_MMU_RELOAD, vcpu);
+                       kvm_s390_sync_request(KVM_REQ_REFRESH_GUEST_PREFIX, vcpu);
                }
        }
 }
@@ -3795,19 +3887,19 @@ retry:
        if (!kvm_request_pending(vcpu))
                return 0;
        /*
-        * We use MMU_RELOAD just to re-arm the ipte notifier for the
+        * If the guest prefix changed, re-arm the ipte notifier for the
         * guest prefix page. gmap_mprotect_notify will wait on the ptl lock.
         * This ensures that the ipte instruction for this request has
         * already finished. We might race against a second unmapper that
         * wants to set the blocking bit. Lets just retry the request loop.
         */
-       if (kvm_check_request(KVM_REQ_MMU_RELOAD, vcpu)) {
+       if (kvm_check_request(KVM_REQ_REFRESH_GUEST_PREFIX, vcpu)) {
                int rc;
                rc = gmap_mprotect_notify(vcpu->arch.gmap,
                                          kvm_s390_get_prefix(vcpu),
                                          PAGE_SIZE * 2, PROT_WRITE);
                if (rc) {
-                       kvm_make_request(KVM_REQ_MMU_RELOAD, vcpu);
+                       kvm_make_request(KVM_REQ_REFRESH_GUEST_PREFIX, vcpu);
                        return rc;
                }
                goto retry;
@@ -3868,14 +3960,12 @@ retry:
        return 0;
 }
 
-void kvm_s390_set_tod_clock(struct kvm *kvm,
-                           const struct kvm_s390_vm_tod_clock *gtod)
+static void __kvm_s390_set_tod_clock(struct kvm *kvm, const struct kvm_s390_vm_tod_clock *gtod)
 {
        struct kvm_vcpu *vcpu;
        union tod_clock clk;
        unsigned long i;
 
-       mutex_lock(&kvm->lock);
        preempt_disable();
 
        store_tod_clock_ext(&clk);
@@ -3896,7 +3986,22 @@ void kvm_s390_set_tod_clock(struct kvm *kvm,
 
        kvm_s390_vcpu_unblock_all(kvm);
        preempt_enable();
+}
+
+void kvm_s390_set_tod_clock(struct kvm *kvm, const struct kvm_s390_vm_tod_clock *gtod)
+{
+       mutex_lock(&kvm->lock);
+       __kvm_s390_set_tod_clock(kvm, gtod);
+       mutex_unlock(&kvm->lock);
+}
+
+int kvm_s390_try_set_tod_clock(struct kvm *kvm, const struct kvm_s390_vm_tod_clock *gtod)
+{
+       if (!mutex_trylock(&kvm->lock))
+               return 0;
+       __kvm_s390_set_tod_clock(kvm, gtod);
        mutex_unlock(&kvm->lock);
+       return 1;
 }
 
 /**
@@ -4654,8 +4759,8 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
        return r;
 }
 
-static long kvm_s390_guest_sida_op(struct kvm_vcpu *vcpu,
-                                  struct kvm_s390_mem_op *mop)
+static long kvm_s390_vcpu_sida_op(struct kvm_vcpu *vcpu,
+                                 struct kvm_s390_mem_op *mop)
 {
        void __user *uaddr = (void __user *)mop->buf;
        int r = 0;
@@ -4666,6 +4771,8 @@ static long kvm_s390_guest_sida_op(struct kvm_vcpu *vcpu,
                return -EINVAL;
        if (mop->size + mop->sida_offset > sida_size(vcpu->arch.sie_block))
                return -E2BIG;
+       if (!kvm_s390_pv_cpu_is_protected(vcpu))
+               return -EINVAL;
 
        switch (mop->op) {
        case KVM_S390_MEMOP_SIDA_READ:
@@ -4682,24 +4789,29 @@ static long kvm_s390_guest_sida_op(struct kvm_vcpu *vcpu,
        }
        return r;
 }
-static long kvm_s390_guest_mem_op(struct kvm_vcpu *vcpu,
-                                 struct kvm_s390_mem_op *mop)
+
+static long kvm_s390_vcpu_mem_op(struct kvm_vcpu *vcpu,
+                                struct kvm_s390_mem_op *mop)
 {
        void __user *uaddr = (void __user *)mop->buf;
        void *tmpbuf = NULL;
        int r = 0;
        const u64 supported_flags = KVM_S390_MEMOP_F_INJECT_EXCEPTION
-                                   | KVM_S390_MEMOP_F_CHECK_ONLY;
+                                   | KVM_S390_MEMOP_F_CHECK_ONLY
+                                   | KVM_S390_MEMOP_F_SKEY_PROTECTION;
 
        if (mop->flags & ~supported_flags || mop->ar >= NUM_ACRS || !mop->size)
                return -EINVAL;
-
        if (mop->size > MEM_OP_MAX_SIZE)
                return -E2BIG;
-
        if (kvm_s390_pv_cpu_is_protected(vcpu))
                return -EINVAL;
-
+       if (mop->flags & KVM_S390_MEMOP_F_SKEY_PROTECTION) {
+               if (access_key_invalid(mop->key))
+                       return -EINVAL;
+       } else {
+               mop->key = 0;
+       }
        if (!(mop->flags & KVM_S390_MEMOP_F_CHECK_ONLY)) {
                tmpbuf = vmalloc(mop->size);
                if (!tmpbuf)
@@ -4709,11 +4821,12 @@ static long kvm_s390_guest_mem_op(struct kvm_vcpu *vcpu,
        switch (mop->op) {
        case KVM_S390_MEMOP_LOGICAL_READ:
                if (mop->flags & KVM_S390_MEMOP_F_CHECK_ONLY) {
-                       r = check_gva_range(vcpu, mop->gaddr, mop->ar,
-                                           mop->size, GACC_FETCH);
+                       r = check_gva_range(vcpu, mop->gaddr, mop->ar, mop->size,
+                                           GACC_FETCH, mop->key);
                        break;
                }
-               r = read_guest(vcpu, mop->gaddr, mop->ar, tmpbuf, mop->size);
+               r = read_guest_with_key(vcpu, mop->gaddr, mop->ar, tmpbuf,
+                                       mop->size, mop->key);
                if (r == 0) {
                        if (copy_to_user(uaddr, tmpbuf, mop->size))
                                r = -EFAULT;
@@ -4721,15 +4834,16 @@ static long kvm_s390_guest_mem_op(struct kvm_vcpu *vcpu,
                break;
        case KVM_S390_MEMOP_LOGICAL_WRITE:
                if (mop->flags & KVM_S390_MEMOP_F_CHECK_ONLY) {
-                       r = check_gva_range(vcpu, mop->gaddr, mop->ar,
-                                           mop->size, GACC_STORE);
+                       r = check_gva_range(vcpu, mop->gaddr, mop->ar, mop->size,
+                                           GACC_STORE, mop->key);
                        break;
                }
                if (copy_from_user(tmpbuf, uaddr, mop->size)) {
                        r = -EFAULT;
                        break;
                }
-               r = write_guest(vcpu, mop->gaddr, mop->ar, tmpbuf, mop->size);
+               r = write_guest_with_key(vcpu, mop->gaddr, mop->ar, tmpbuf,
+                                        mop->size, mop->key);
                break;
        }
 
@@ -4740,8 +4854,8 @@ static long kvm_s390_guest_mem_op(struct kvm_vcpu *vcpu,
        return r;
 }
 
-static long kvm_s390_guest_memsida_op(struct kvm_vcpu *vcpu,
-                                     struct kvm_s390_mem_op *mop)
+static long kvm_s390_vcpu_memsida_op(struct kvm_vcpu *vcpu,
+                                    struct kvm_s390_mem_op *mop)
 {
        int r, srcu_idx;
 
@@ -4750,12 +4864,12 @@ static long kvm_s390_guest_memsida_op(struct kvm_vcpu *vcpu,
        switch (mop->op) {
        case KVM_S390_MEMOP_LOGICAL_READ:
        case KVM_S390_MEMOP_LOGICAL_WRITE:
-               r = kvm_s390_guest_mem_op(vcpu, mop);
+               r = kvm_s390_vcpu_mem_op(vcpu, mop);
                break;
        case KVM_S390_MEMOP_SIDA_READ:
        case KVM_S390_MEMOP_SIDA_WRITE:
                /* we are locked against sida going away by the vcpu->mutex */
-               r = kvm_s390_guest_sida_op(vcpu, mop);
+               r = kvm_s390_vcpu_sida_op(vcpu, mop);
                break;
        default:
                r = -EINVAL;
@@ -4918,7 +5032,7 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
                struct kvm_s390_mem_op mem_op;
 
                if (copy_from_user(&mem_op, argp, sizeof(mem_op)) == 0)
-                       r = kvm_s390_guest_memsida_op(vcpu, &mem_op);
+                       r = kvm_s390_vcpu_memsida_op(vcpu, &mem_op);
                else
                        r = -EFAULT;
                break;