To support VFIO based device assignment, we need to be able to get an EOI out of the KVM irqchip. This introduces a mechanism to do that by registering an eventfd to be signaled when the IRQ is ACKed. Signed-off-by: Alex Williamson <alex.williamson@xxxxxxxxxx> --- include/linux/kvm.h | 13 ++++++ include/linux/kvm_host.h | 6 +++ virt/kvm/eventfd.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++ virt/kvm/kvm_main.c | 8 ++++ 4 files changed, 122 insertions(+), 0 deletions(-) diff --git a/include/linux/kvm.h b/include/linux/kvm.h index ea2dc1a..92d5b27 100644 --- a/include/linux/kvm.h +++ b/include/linux/kvm.h @@ -541,6 +541,7 @@ struct kvm_ppc_pvinfo { #define KVM_CAP_PPC_GET_PVINFO 57 #define KVM_CAP_PPC_IRQ_LEVEL 58 #define KVM_CAP_ASYNC_PF 59 +#define KVM_CAP_EOI_EVENTFD 60 #ifdef KVM_CAP_IRQ_ROUTING @@ -620,6 +621,16 @@ struct kvm_clock_data { __u32 pad[9]; }; +#define KVM_EOI_EVENTFD_FLAG_DEASSIGN (1 << 0) +#define KVM_EOI_EVENTFD_FLAG_DEASSERT (1 << 1) + +struct kvm_eoi { + __u32 fd; + __u32 gsi; + __u32 flags; + __u8 pad[20]; +}; + /* * ioctls for VM fds */ @@ -677,6 +688,8 @@ struct kvm_clock_data { #define KVM_SET_PIT2 _IOW(KVMIO, 0xa0, struct kvm_pit_state2) /* Available with KVM_CAP_PPC_GET_PVINFO */ #define KVM_PPC_GET_PVINFO _IOW(KVMIO, 0xa1, struct kvm_ppc_pvinfo) +/* Available with KVM_CAP_EOI_EVENTFD */ +#define KVM_EOI_EVENTFD _IOW(KVMIO, 0xa2, struct kvm_eoi) /* * ioctls for vcpu fds diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index ee4314e..5d50a7e 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -227,6 +227,7 @@ struct kvm { struct list_head items; } irqfds; struct list_head ioeventfds; + struct list_head eoi_eventfds; #endif struct kvm_vm_stat stat; struct kvm_arch arch; @@ -643,6 +644,7 @@ void kvm_eventfd_init(struct kvm *kvm); int kvm_irqfd(struct kvm *kvm, int fd, int gsi, int flags); void kvm_irqfd_release(struct kvm *kvm); int kvm_ioeventfd(struct kvm *kvm, struct kvm_ioeventfd *args); +int kvm_eoi_eventfd(struct kvm *kvm, struct kvm_eoi *eoi); #else @@ -658,6 +660,10 @@ static inline int kvm_ioeventfd(struct kvm *kvm, struct kvm_ioeventfd *args) return -ENOSYS; } +static inline int kvm_eoi_eventfd(struct kvm *kvm, struct kvm_eoi *eoi) +{ + return -ENOSYS; +} #endif /* CONFIG_HAVE_KVM_EVENTFD */ #ifdef CONFIG_KVM_APIC_ARCHITECTURE diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c index c1f1e3c..3dbfb21 100644 --- a/virt/kvm/eventfd.c +++ b/virt/kvm/eventfd.c @@ -253,6 +253,7 @@ kvm_eventfd_init(struct kvm *kvm) spin_lock_init(&kvm->irqfds.lock); INIT_LIST_HEAD(&kvm->irqfds.items); INIT_LIST_HEAD(&kvm->ioeventfds); + INIT_LIST_HEAD(&kvm->eoi_eventfds); } /* @@ -586,3 +587,97 @@ kvm_ioeventfd(struct kvm *kvm, struct kvm_ioeventfd *args) return kvm_assign_ioeventfd(kvm, args); } + +/* + * -------------------------------------------------------------------- + * eoi_eventfd: Translate KVM APIC/IOAPIC EOI into eventfd signal. + * + * userspace can register GSIs with an eventfd for receiving notification + * when an EOI occurs. + * -------------------------------------------------------------------- + */ + +struct _eoi_eventfd { + struct list_head list; + struct kvm *kvm; + struct eventfd_ctx *eventfd; + bool deassert; + struct kvm_irq_ack_notifier notifier; +}; + +static void kvm_eoi_eventfd_acked(struct kvm_irq_ack_notifier *notifier) +{ + struct _eoi_eventfd *p; + + p = container_of(notifier, struct _eoi_eventfd, notifier); + + if (p->deassert) + kvm_set_irq(p->kvm, KVM_USERSPACE_IRQ_SOURCE_ID, + notifier->gsi, 0); + + eventfd_signal(p->eventfd, 1); +} + +static int kvm_assign_eoi_eventfd(struct kvm *kvm, struct kvm_eoi *eoi) +{ + struct eventfd_ctx *eventfd; + struct _eoi_eventfd *p; + + eventfd = eventfd_ctx_fdget(eoi->fd); + if (IS_ERR(eventfd)) + return PTR_ERR(eventfd); + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + eventfd_ctx_put(eventfd); + return -ENOMEM; + } + + INIT_LIST_HEAD(&p->list); + p->kvm = kvm; + p->eventfd = eventfd; + p->deassert = !!(eoi->flags & KVM_EOI_EVENTFD_FLAG_DEASSERT); + + p->notifier.gsi = eoi->gsi; + p->notifier.irq_acked = kvm_eoi_eventfd_acked; + + list_add_tail(&p->list, &kvm->eoi_eventfds); + kvm_register_irq_ack_notifier(kvm, &p->notifier); + + return 0; +} + +static int kvm_deassign_eoi_eventfd(struct kvm *kvm, struct kvm_eoi *eoi) +{ + struct eventfd_ctx *eventfd; + struct _eoi_eventfd *p, *tmp; + int ret = -ENOENT; + + eventfd = eventfd_ctx_fdget(eoi->fd); + if (IS_ERR(eventfd)) + return PTR_ERR(eventfd); + + list_for_each_entry_safe(p, tmp, &kvm->eoi_eventfds, list) { + if (p->eventfd != eventfd || p->notifier.gsi != eoi->gsi) + continue; + + kvm_unregister_irq_ack_notifier(kvm, &p->notifier); + eventfd_ctx_put(p->eventfd); + list_del(&p->list); + kfree(p); + ret = 0; + break; + } + + eventfd_ctx_put(eventfd); + + return ret; +} + +int kvm_eoi_eventfd(struct kvm *kvm, struct kvm_eoi *eoi) +{ + if (eoi->flags & KVM_EOI_EVENTFD_FLAG_DEASSIGN) + return kvm_deassign_eoi_eventfd(kvm, eoi); + + return kvm_assign_eoi_eventfd(kvm, eoi); +} diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 88d869e..7ca6f13 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1807,6 +1807,14 @@ static long kvm_vm_ioctl(struct file *filp, mutex_unlock(&kvm->lock); break; #endif + case KVM_EOI_EVENTFD: { + struct kvm_eoi eoi; + r = -EFAULT; + if (copy_from_user(&eoi, argp, sizeof eoi)) + goto out; + r = kvm_eoi_eventfd(kvm, &eoi); + break; + } default: r = kvm_arch_vm_ioctl(filp, ioctl, arg); if (r == -ENOTTY) -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html