x86/kvm/hyper-v: remove stale entries from vec_bitmap/auto_eoi_bitmap on vector change
[linux-2.6-microblaze.git] / arch / x86 / kvm / hyperv.c
index dc97f25..93a2274 100644 (file)
@@ -29,6 +29,7 @@
 #include <linux/kvm_host.h>
 #include <linux/highmem.h>
 #include <linux/sched/cputime.h>
+#include <linux/eventfd.h>
 
 #include <asm/apicdef.h>
 #include <trace/events/kvm.h>
@@ -74,13 +75,30 @@ static bool synic_has_vector_auto_eoi(struct kvm_vcpu_hv_synic *synic,
        return false;
 }
 
+static void synic_update_vector(struct kvm_vcpu_hv_synic *synic,
+                               int vector)
+{
+       if (vector < HV_SYNIC_FIRST_VALID_VECTOR)
+               return;
+
+       if (synic_has_vector_connected(synic, vector))
+               __set_bit(vector, synic->vec_bitmap);
+       else
+               __clear_bit(vector, synic->vec_bitmap);
+
+       if (synic_has_vector_auto_eoi(synic, vector))
+               __set_bit(vector, synic->auto_eoi_bitmap);
+       else
+               __clear_bit(vector, synic->auto_eoi_bitmap);
+}
+
 static int synic_set_sint(struct kvm_vcpu_hv_synic *synic, int sint,
                          u64 data, bool host)
 {
-       int vector;
+       int vector, old_vector;
 
        vector = data & HV_SYNIC_SINT_VECTOR_MASK;
-       if (vector < 16 && !host)
+       if (vector < HV_SYNIC_FIRST_VALID_VECTOR && !host)
                return 1;
        /*
         * Guest may configure multiple SINTs to use the same vector, so
@@ -88,18 +106,13 @@ static int synic_set_sint(struct kvm_vcpu_hv_synic *synic, int sint,
         * bitmap of vectors with auto-eoi behavior.  The bitmaps are
         * updated here, and atomically queried on fast paths.
         */
+       old_vector = synic_read_sint(synic, sint) & HV_SYNIC_SINT_VECTOR_MASK;
 
        atomic64_set(&synic->sint[sint], data);
 
-       if (synic_has_vector_connected(synic, vector))
-               __set_bit(vector, synic->vec_bitmap);
-       else
-               __clear_bit(vector, synic->vec_bitmap);
+       synic_update_vector(synic, old_vector);
 
-       if (synic_has_vector_auto_eoi(synic, vector))
-               __set_bit(vector, synic->auto_eoi_bitmap);
-       else
-               __clear_bit(vector, synic->auto_eoi_bitmap);
+       synic_update_vector(synic, vector);
 
        /* Load SynIC vectors into EOI exit bitmap */
        kvm_make_request(KVM_REQ_SCAN_IOAPIC, synic_to_vcpu(synic));
@@ -736,6 +749,9 @@ static bool kvm_hv_msr_partition_wide(u32 msr)
        case HV_X64_MSR_CRASH_CTL:
        case HV_X64_MSR_CRASH_P0 ... HV_X64_MSR_CRASH_P4:
        case HV_X64_MSR_RESET:
+       case HV_X64_MSR_REENLIGHTENMENT_CONTROL:
+       case HV_X64_MSR_TSC_EMULATION_CONTROL:
+       case HV_X64_MSR_TSC_EMULATION_STATUS:
                r = true;
                break;
        }
@@ -981,6 +997,15 @@ static int kvm_hv_set_msr_pw(struct kvm_vcpu *vcpu, u32 msr, u64 data,
                        kvm_make_request(KVM_REQ_HV_RESET, vcpu);
                }
                break;
+       case HV_X64_MSR_REENLIGHTENMENT_CONTROL:
+               hv->hv_reenlightenment_control = data;
+               break;
+       case HV_X64_MSR_TSC_EMULATION_CONTROL:
+               hv->hv_tsc_emulation_control = data;
+               break;
+       case HV_X64_MSR_TSC_EMULATION_STATUS:
+               hv->hv_tsc_emulation_status = data;
+               break;
        default:
                vcpu_unimpl(vcpu, "Hyper-V uhandled wrmsr: 0x%x data 0x%llx\n",
                            msr, data);
@@ -1105,6 +1130,15 @@ static int kvm_hv_get_msr_pw(struct kvm_vcpu *vcpu, u32 msr, u64 *pdata)
        case HV_X64_MSR_RESET:
                data = 0;
                break;
+       case HV_X64_MSR_REENLIGHTENMENT_CONTROL:
+               data = hv->hv_reenlightenment_control;
+               break;
+       case HV_X64_MSR_TSC_EMULATION_CONTROL:
+               data = hv->hv_tsc_emulation_control;
+               break;
+       case HV_X64_MSR_TSC_EMULATION_STATUS:
+               data = hv->hv_tsc_emulation_status;
+               break;
        default:
                vcpu_unimpl(vcpu, "Hyper-V unhandled rdmsr: 0x%x\n", msr);
                return 1;
@@ -1226,6 +1260,43 @@ static int kvm_hv_hypercall_complete_userspace(struct kvm_vcpu *vcpu)
        return 1;
 }
 
+static u16 kvm_hvcall_signal_event(struct kvm_vcpu *vcpu, bool fast, u64 param)
+{
+       struct eventfd_ctx *eventfd;
+
+       if (unlikely(!fast)) {
+               int ret;
+               gpa_t gpa = param;
+
+               if ((gpa & (__alignof__(param) - 1)) ||
+                   offset_in_page(gpa) + sizeof(param) > PAGE_SIZE)
+                       return HV_STATUS_INVALID_ALIGNMENT;
+
+               ret = kvm_vcpu_read_guest(vcpu, gpa, &param, sizeof(param));
+               if (ret < 0)
+                       return HV_STATUS_INVALID_ALIGNMENT;
+       }
+
+       /*
+        * Per spec, bits 32-47 contain the extra "flag number".  However, we
+        * have no use for it, and in all known usecases it is zero, so just
+        * report lookup failure if it isn't.
+        */
+       if (param & 0xffff00000000ULL)
+               return HV_STATUS_INVALID_PORT_ID;
+       /* remaining bits are reserved-zero */
+       if (param & ~KVM_HYPERV_CONN_ID_MASK)
+               return HV_STATUS_INVALID_HYPERCALL_INPUT;
+
+       /* conn_to_evt is protected by vcpu->kvm->srcu */
+       eventfd = idr_find(&vcpu->kvm->arch.hyperv.conn_to_evt, param);
+       if (!eventfd)
+               return HV_STATUS_INVALID_PORT_ID;
+
+       eventfd_signal(eventfd, 1);
+       return HV_STATUS_SUCCESS;
+}
+
 int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
 {
        u64 param, ingpa, outgpa, ret;
@@ -1276,8 +1347,12 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
        case HVCALL_NOTIFY_LONG_SPIN_WAIT:
                kvm_vcpu_on_spin(vcpu, true);
                break;
-       case HVCALL_POST_MESSAGE:
        case HVCALL_SIGNAL_EVENT:
+               res = kvm_hvcall_signal_event(vcpu, fast, ingpa);
+               if (res != HV_STATUS_INVALID_PORT_ID)
+                       break;
+               /* maybe userspace knows this conn_id: fall through */
+       case HVCALL_POST_MESSAGE:
                /* don't bother userspace if it has no way to handle it */
                if (!vcpu_to_synic(vcpu)->active) {
                        res = HV_STATUS_INVALID_HYPERCALL_CODE;
@@ -1301,3 +1376,71 @@ set_result:
        kvm_hv_hypercall_set_result(vcpu, ret);
        return 1;
 }
+
+void kvm_hv_init_vm(struct kvm *kvm)
+{
+       mutex_init(&kvm->arch.hyperv.hv_lock);
+       idr_init(&kvm->arch.hyperv.conn_to_evt);
+}
+
+void kvm_hv_destroy_vm(struct kvm *kvm)
+{
+       struct eventfd_ctx *eventfd;
+       int i;
+
+       idr_for_each_entry(&kvm->arch.hyperv.conn_to_evt, eventfd, i)
+               eventfd_ctx_put(eventfd);
+       idr_destroy(&kvm->arch.hyperv.conn_to_evt);
+}
+
+static int kvm_hv_eventfd_assign(struct kvm *kvm, u32 conn_id, int fd)
+{
+       struct kvm_hv *hv = &kvm->arch.hyperv;
+       struct eventfd_ctx *eventfd;
+       int ret;
+
+       eventfd = eventfd_ctx_fdget(fd);
+       if (IS_ERR(eventfd))
+               return PTR_ERR(eventfd);
+
+       mutex_lock(&hv->hv_lock);
+       ret = idr_alloc(&hv->conn_to_evt, eventfd, conn_id, conn_id + 1,
+                       GFP_KERNEL);
+       mutex_unlock(&hv->hv_lock);
+
+       if (ret >= 0)
+               return 0;
+
+       if (ret == -ENOSPC)
+               ret = -EEXIST;
+       eventfd_ctx_put(eventfd);
+       return ret;
+}
+
+static int kvm_hv_eventfd_deassign(struct kvm *kvm, u32 conn_id)
+{
+       struct kvm_hv *hv = &kvm->arch.hyperv;
+       struct eventfd_ctx *eventfd;
+
+       mutex_lock(&hv->hv_lock);
+       eventfd = idr_remove(&hv->conn_to_evt, conn_id);
+       mutex_unlock(&hv->hv_lock);
+
+       if (!eventfd)
+               return -ENOENT;
+
+       synchronize_srcu(&kvm->srcu);
+       eventfd_ctx_put(eventfd);
+       return 0;
+}
+
+int kvm_vm_ioctl_hv_eventfd(struct kvm *kvm, struct kvm_hyperv_eventfd *args)
+{
+       if ((args->flags & ~KVM_HYPERV_EVENTFD_DEASSIGN) ||
+           (args->conn_id & ~KVM_HYPERV_CONN_ID_MASK))
+               return -EINVAL;
+
+       if (args->flags == KVM_HYPERV_EVENTFD_DEASSIGN)
+               return kvm_hv_eventfd_deassign(kvm, args->conn_id);
+       return kvm_hv_eventfd_assign(kvm, args->conn_id, args->fd);
+}