kvm: x86: hyperv: guest->host event signaling via eventfd
authorRoman Kagan <rkagan@virtuozzo.com>
Thu, 1 Feb 2018 13:48:32 +0000 (16:48 +0300)
committerRadim Krčmář <rkrcmar@redhat.com>
Tue, 6 Mar 2018 17:40:36 +0000 (18:40 +0100)
In Hyper-V, the fast guest->host notification mechanism is the
SIGNAL_EVENT hypercall, with a single parameter of the connection ID to
signal.

Currently this hypercall incurs a user exit and requires the userspace
to decode the parameters and trigger the notification of the potentially
different I/O context.

To avoid the costly user exit, process this hypercall and signal the
corresponding eventfd in KVM, similar to ioeventfd.  The association
between the connection id and the eventfd is established via the newly
introduced KVM_HYPERV_EVENTFD ioctl, and maintained in an
(srcu-protected) IDR.

Signed-off-by: Roman Kagan <rkagan@virtuozzo.com>
Reviewed-by: David Hildenbrand <david@redhat.com>
[asm/hyperv.h changes approved by KY Srinivasan. - Radim]
Signed-off-by: Radim Krčmář <rkrcmar@redhat.com>
Documentation/virtual/kvm/api.txt
arch/x86/include/asm/kvm_host.h
arch/x86/include/uapi/asm/hyperv.h
arch/x86/kvm/hyperv.c
arch/x86/kvm/hyperv.h
arch/x86/kvm/x86.c
include/uapi/linux/kvm.h

index d6b3ff51a14fd96600cc9f65f8ed11cd5c64b768..db992e036bdf7ca512c76b5b901550f4d3141dc2 100644 (file)
@@ -3516,6 +3516,38 @@ Returns: 0 on success; -1 on error
 This ioctl can be used to unregister the guest memory region registered
 with KVM_MEMORY_ENCRYPT_REG_REGION ioctl above.
 
+4.113 KVM_HYPERV_EVENTFD
+
+Capability: KVM_CAP_HYPERV_EVENTFD
+Architectures: x86
+Type: vm ioctl
+Parameters: struct kvm_hyperv_eventfd (in)
+
+This ioctl (un)registers an eventfd to receive notifications from the guest on
+the specified Hyper-V connection id through the SIGNAL_EVENT hypercall, without
+causing a user exit.  SIGNAL_EVENT hypercall with non-zero event flag number
+(bits 24-31) still triggers a KVM_EXIT_HYPERV_HCALL user exit.
+
+struct kvm_hyperv_eventfd {
+       __u32 conn_id;
+       __s32 fd;
+       __u32 flags;
+       __u32 padding[3];
+};
+
+The conn_id field should fit within 24 bits:
+
+#define KVM_HYPERV_CONN_ID_MASK                0x00ffffff
+
+The acceptable values for the flags field are:
+
+#define KVM_HYPERV_EVENTFD_DEASSIGN    (1 << 0)
+
+Returns: 0 on success,
+       -EINVAL if conn_id or flags is outside the allowed range
+       -ENOENT on deassign if the conn_id isn't registered
+       -EEXIST on assign if the conn_id is already registered
+
 
 5. The kvm_run structure
 ------------------------
index b605a5b6a30c38f1bdcf647be241516b5e1310b8..df6720fc57e6acc3cef50128a2563c673cc5d7c1 100644 (file)
@@ -754,6 +754,8 @@ struct kvm_hv {
        u64 hv_crash_ctl;
 
        HV_REFERENCE_TSC_PAGE tsc_ref;
+
+       struct idr conn_to_evt;
 };
 
 enum kvm_irqchip_mode {
index 099414345865d588e6c55b63078f8b168fc8e907..31d7a0a91f5004fae07f7513fc90c965161196fb 100644 (file)
@@ -303,7 +303,9 @@ enum HV_GENERIC_SET_FORMAT {
 #define HV_STATUS_INVALID_HYPERCALL_CODE       2
 #define HV_STATUS_INVALID_HYPERCALL_INPUT      3
 #define HV_STATUS_INVALID_ALIGNMENT            4
+#define HV_STATUS_INVALID_PARAMETER            5
 #define HV_STATUS_INSUFFICIENT_MEMORY          11
+#define HV_STATUS_INVALID_PORT_ID              17
 #define HV_STATUS_INVALID_CONNECTION_ID                18
 #define HV_STATUS_INSUFFICIENT_BUFFERS         19
 
index 015fb06c75226c2eedc0ffd9ce846833a5853d19..53bd1913b6fd0c33611e8d76133a502079813bab 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>
@@ -1226,6 +1227,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 +1314,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;
@@ -1305,8 +1347,67 @@ set_result:
 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);
 }
index cc2468244ca28bc337ea704c22297d82a9a8282c..837465d69c6d894ae895680deda4675184811ac6 100644 (file)
@@ -90,5 +90,6 @@ void kvm_hv_setup_tsc_page(struct kvm *kvm,
 
 void kvm_hv_init_vm(struct kvm *kvm);
 void kvm_hv_destroy_vm(struct kvm *kvm);
+int kvm_vm_ioctl_hv_eventfd(struct kvm *kvm, struct kvm_hyperv_eventfd *args);
 
 #endif
index fee833c4a132c306d816561a241514e1a31c03dc..3b6b7ee9fa8f905057b6021f95800449b77cbc96 100644 (file)
@@ -2809,6 +2809,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
        case KVM_CAP_HYPERV_SYNIC:
        case KVM_CAP_HYPERV_SYNIC2:
        case KVM_CAP_HYPERV_VP_INDEX:
+       case KVM_CAP_HYPERV_EVENTFD:
        case KVM_CAP_PCI_SEGMENT:
        case KVM_CAP_DEBUGREGS:
        case KVM_CAP_X86_ROBUST_SINGLESTEP:
@@ -4482,6 +4483,15 @@ set_identity_unlock:
                        r = kvm_x86_ops->mem_enc_unreg_region(kvm, &region);
                break;
        }
+       case KVM_HYPERV_EVENTFD: {
+               struct kvm_hyperv_eventfd hvevfd;
+
+               r = -EFAULT;
+               if (copy_from_user(&hvevfd, argp, sizeof(hvevfd)))
+                       goto out;
+               r = kvm_vm_ioctl_hv_eventfd(kvm, &hvevfd);
+               break;
+       }
        default:
                r = -ENOTTY;
        }
index 7b26d4b0b0529649816ec1523d225eec7fa8ee26..2d2d926113bac06c10419fab643cb6b492436919 100644 (file)
@@ -936,6 +936,7 @@ struct kvm_ppc_resize_hpt {
 #define KVM_CAP_PPC_GET_CPU_CHAR 151
 #define KVM_CAP_S390_BPB 152
 #define KVM_CAP_GET_MSR_FEATURES 153
+#define KVM_CAP_HYPERV_EVENTFD 154
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
@@ -1375,6 +1376,10 @@ struct kvm_enc_region {
 #define KVM_MEMORY_ENCRYPT_REG_REGION    _IOR(KVMIO, 0xbb, struct kvm_enc_region)
 #define KVM_MEMORY_ENCRYPT_UNREG_REGION  _IOR(KVMIO, 0xbc, struct kvm_enc_region)
 
+/* Available with KVM_CAP_HYPERV_EVENTFD */
+#define KVM_HYPERV_EVENTFD        _IOW(KVMIO,  0xbd, struct kvm_hyperv_eventfd)
+
+
 /* Secure Encrypted Virtualization command */
 enum sev_cmd_id {
        /* Guest initialization commands */
@@ -1515,4 +1520,14 @@ struct kvm_assigned_msix_entry {
 #define KVM_ARM_DEV_EL1_PTIMER         (1 << 1)
 #define KVM_ARM_DEV_PMU                        (1 << 2)
 
+struct kvm_hyperv_eventfd {
+       __u32 conn_id;
+       __s32 fd;
+       __u32 flags;
+       __u32 padding[3];
+};
+
+#define KVM_HYPERV_CONN_ID_MASK                0x00ffffff
+#define KVM_HYPERV_EVENTFD_DEASSIGN    (1 << 0)
+
 #endif /* __LINUX_KVM_H */