clear-pkgs-linux-iot-lts2018/0559-vhm-add-irqfd-support-...

499 lines
14 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shuo Liu <shuo.a.liu@intel.com>
Date: Thu, 12 Jul 2018 15:40:52 +0800
Subject: [PATCH] vhm: add irqfd support for ACRN hypervisor service module
irqfd which is based on eventfd, provides a pipe for injecting guest
interrupt through a file description writing operation.
Each irqfd registered by userspace can map a interrupt of the guest
to eventfd, and a writing operation on one side of the eventfd will
trigger the interrupt injection on vhm side.
Tracked-On: projectacrn/acrn-hypervisor#1329
Signed-off-by: Shuo Liu <shuo.a.liu@intel.com>
Reviewed-by: Zhao Yakui <yakui.zhao@intel.com>
Reviewed-by: Yu Wang <yu1.wang@intel.com>
---
drivers/char/vhm/vhm_dev.c | 11 +
drivers/vhm/Makefile | 2 +-
drivers/vhm/vhm_irqfd.c | 381 +++++++++++++++++++++++++++++
include/linux/vhm/vhm_eventfd.h | 6 +
include/linux/vhm/vhm_ioctl_defs.h | 8 +
5 files changed, 407 insertions(+), 1 deletion(-)
create mode 100644 drivers/vhm/vhm_irqfd.c
diff --git a/drivers/char/vhm/vhm_dev.c b/drivers/char/vhm/vhm_dev.c
index 672b58a29a1f..4d76905e616d 100644
--- a/drivers/char/vhm/vhm_dev.c
+++ b/drivers/char/vhm/vhm_dev.c
@@ -238,6 +238,7 @@ static long vhm_dev_ioctl(struct file *filep,
}
acrn_ioeventfd_init(vm->vmid);
+ acrn_irqfd_init(vm->vmid);
pr_info("vhm: VM %d created\n", created_vm.vmid);
break;
@@ -280,6 +281,7 @@ static long vhm_dev_ioctl(struct file *filep,
case IC_DESTROY_VM: {
acrn_ioeventfd_deinit(vm->vmid);
+ acrn_irqfd_deinit(vm->vmid);
ret = hcall_destroy_vm(vm->vmid);
if (ret < 0) {
pr_err("failed to destroy VM %ld\n", vm->vmid);
@@ -660,6 +662,15 @@ static long vhm_dev_ioctl(struct file *filep,
break;
}
+ case IC_EVENT_IRQFD: {
+ struct acrn_irqfd args;
+
+ if (copy_from_user(&args, (void *)ioctl_param, sizeof(args)))
+ return -EFAULT;
+ ret = acrn_irqfd(vm->vmid, &args);
+ break;
+ }
+
default:
pr_warn("Unknown IOCTL 0x%x\n", ioctl_num);
ret = 0;
diff --git a/drivers/vhm/Makefile b/drivers/vhm/Makefile
index 193f7692c9e3..4abfbfcba4aa 100644
--- a/drivers/vhm/Makefile
+++ b/drivers/vhm/Makefile
@@ -1,2 +1,2 @@
subdir-ccflags-$(CONFIG_ACRN_VHM) := -Werror
-obj-y += vhm_mm.o vhm_hugetlb.o vhm_ioreq.o vhm_vm_mngt.o vhm_msi.o vhm_hypercall.o vhm_ioeventfd.o
+obj-y += vhm_mm.o vhm_hugetlb.o vhm_ioreq.o vhm_vm_mngt.o vhm_msi.o vhm_hypercall.o vhm_ioeventfd.o vhm_irqfd.o
diff --git a/drivers/vhm/vhm_irqfd.c b/drivers/vhm/vhm_irqfd.c
new file mode 100644
index 000000000000..b8c122d5ea1f
--- /dev/null
+++ b/drivers/vhm/vhm_irqfd.c
@@ -0,0 +1,381 @@
+/*
+ * irqfd for ACRN hypervisor
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright (c) 2018 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright (C) 2018 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ */
+#include <linux/syscalls.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/file.h>
+#include <linux/list.h>
+#include <linux/eventfd.h>
+#include <linux/kernel.h>
+#include <linux/async.h>
+#include <linux/slab.h>
+
+#include <linux/vhm/acrn_common.h>
+#include <linux/vhm/acrn_vhm_ioreq.h>
+#include <linux/vhm/vhm_vm_mngt.h>
+#include <linux/vhm/vhm_ioctl_defs.h>
+#include <linux/vhm/vhm_hypercall.h>
+
+static LIST_HEAD(vhm_irqfd_clients);
+static DEFINE_MUTEX(vhm_irqfds_mutex);
+static ASYNC_DOMAIN_EXCLUSIVE(irqfd_domain);
+
+/* use internally to record properties of each irqfd */
+struct acrn_vhm_irqfd {
+ /* vhm_irqfd_info which this irqfd belong to */
+ struct vhm_irqfd_info *info;
+ /* wait queue node */
+ wait_queue_entry_t wait;
+ /* async shutdown work */
+ struct work_struct shutdown;
+ /* eventfd of this irqfd */
+ struct eventfd_ctx *eventfd;
+ /* list to link all ioventfd together */
+ struct list_head list;
+ /* poll_table of this irqfd */
+ poll_table pt;
+ /* msi to send when this irqfd triggerd */
+ struct acrn_msi_entry msi;
+};
+
+/* instance to bind irqfds of each VM */
+struct vhm_irqfd_info {
+ struct list_head list;
+ int refcnt;
+ /* vmid of VM */
+ uint16_t vmid;
+ /* workqueue for async shutdown work */
+ struct workqueue_struct *wq;
+
+ /* irqfds in this instance */
+ struct list_head irqfds;
+ spinlock_t irqfds_lock;
+};
+
+static struct vhm_irqfd_info *get_irqfd_info_by_vm(uint16_t vmid)
+{
+ struct vhm_irqfd_info *info = NULL;
+
+ mutex_lock(&vhm_irqfds_mutex);
+ list_for_each_entry(info, &vhm_irqfd_clients, list) {
+ if (info->vmid == vmid) {
+ info->refcnt++;
+ mutex_unlock(&vhm_irqfds_mutex);
+ return info;
+ }
+ }
+ mutex_unlock(&vhm_irqfds_mutex);
+ return NULL;
+}
+
+static void put_irqfd_info(struct vhm_irqfd_info *info)
+{
+ mutex_lock(&vhm_irqfds_mutex);
+ info->refcnt--;
+ if (info->refcnt == 0) {
+ list_del(&info->list);
+ kfree(info);
+ }
+ mutex_unlock(&vhm_irqfds_mutex);
+}
+
+static void vhm_irqfd_inject(struct acrn_vhm_irqfd *irqfd)
+{
+ struct vhm_irqfd_info *info = irqfd->info;
+
+ vhm_inject_msi(info->vmid, irqfd->msi.msi_addr,
+ irqfd->msi.msi_data);
+}
+
+/*
+ * Try to find if the irqfd still in list info->irqfds
+ *
+ * assumes info->irqfds_lock is held
+ */
+static bool vhm_irqfd_is_active(struct vhm_irqfd_info *info,
+ struct acrn_vhm_irqfd *irqfd)
+{
+ struct acrn_vhm_irqfd *_irqfd;
+
+ list_for_each_entry(_irqfd, &info->irqfds, list)
+ if (_irqfd == irqfd)
+ return true;
+
+ return false;
+}
+
+/*
+ * Remove irqfd and free it.
+ *
+ * assumes info->irqfds_lock is held
+ */
+static void vhm_irqfd_shutdown(struct acrn_vhm_irqfd *irqfd)
+{
+ u64 cnt;
+
+ /* remove from wait queue */
+ list_del_init(&irqfd->list);
+ eventfd_ctx_remove_wait_queue(irqfd->eventfd, &irqfd->wait, &cnt);
+ eventfd_ctx_put(irqfd->eventfd);
+ kfree(irqfd);
+}
+
+static void vhm_irqfd_shutdown_work(struct work_struct *work)
+{
+ unsigned long flags;
+ struct acrn_vhm_irqfd *irqfd =
+ container_of(work, struct acrn_vhm_irqfd, shutdown);
+ struct vhm_irqfd_info *info = irqfd->info;
+
+ spin_lock_irqsave(&info->irqfds_lock, flags);
+ if (vhm_irqfd_is_active(info, irqfd))
+ vhm_irqfd_shutdown(irqfd);
+ spin_unlock_irqrestore(&info->irqfds_lock, flags);
+}
+
+/*
+ * Called with wqh->lock held and interrupts disabled
+ */
+static int vhm_irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode,
+ int sync, void *key)
+{
+ struct acrn_vhm_irqfd *irqfd =
+ container_of(wait, struct acrn_vhm_irqfd, wait);
+ unsigned long poll_bits = (unsigned long)key;
+ struct vhm_irqfd_info *info = irqfd->info;
+
+ if (poll_bits & POLLIN)
+ /* An event has been signaled, inject an interrupt */
+ vhm_irqfd_inject(irqfd);
+
+ if (poll_bits & POLLHUP)
+ /* async close eventfd as shutdown need hold wqh->lock */
+ queue_work(info->wq, &irqfd->shutdown);
+
+ return 0;
+}
+
+static void vhm_irqfd_poll_func(struct file *file,
+ wait_queue_head_t *wqh, poll_table *pt)
+{
+ struct acrn_vhm_irqfd *irqfd =
+ container_of(pt, struct acrn_vhm_irqfd, pt);
+ add_wait_queue(wqh, &irqfd->wait);
+}
+
+static int acrn_irqfd_assign(struct vhm_irqfd_info *info,
+ struct acrn_irqfd *args)
+{
+ struct acrn_vhm_irqfd *irqfd, *tmp;
+ struct fd f;
+ struct eventfd_ctx *eventfd = NULL;
+ int ret = 0;
+ unsigned int events;
+
+ irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);
+ if (!irqfd)
+ return -ENOMEM;
+
+ irqfd->info = info;
+ memcpy(&irqfd->msi, &args->msi, sizeof(args->msi));
+ INIT_LIST_HEAD(&irqfd->list);
+ INIT_WORK(&irqfd->shutdown, vhm_irqfd_shutdown_work);
+
+ f = fdget(args->fd);
+ if (!f.file) {
+ ret = -EBADF;
+ goto out;
+ }
+
+ eventfd = eventfd_ctx_fileget(f.file);
+ if (IS_ERR(eventfd)) {
+ ret = PTR_ERR(eventfd);
+ goto fail;
+ }
+
+ irqfd->eventfd = eventfd;
+
+ /*
+ * Install our own custom wake-up handling so we are notified via
+ * a callback whenever someone signals the underlying eventfd
+ */
+ init_waitqueue_func_entry(&irqfd->wait, vhm_irqfd_wakeup);
+ init_poll_funcptr(&irqfd->pt, vhm_irqfd_poll_func);
+
+ spin_lock_irq(&info->irqfds_lock);
+
+ list_for_each_entry(tmp, &info->irqfds, list) {
+ if (irqfd->eventfd != tmp->eventfd)
+ continue;
+ /* This fd is used for another irq already. */
+ ret = -EBUSY;
+ spin_unlock_irq(&info->irqfds_lock);
+ goto fail;
+ }
+ list_add_tail(&irqfd->list, &info->irqfds);
+
+ spin_unlock_irq(&info->irqfds_lock);
+
+ /* Check the pending event in this stage */
+ events = f.file->f_op->poll(f.file, &irqfd->pt);
+
+ if (events & POLLIN)
+ vhm_irqfd_inject(irqfd);
+
+ fdput(f);
+
+ return 0;
+fail:
+ if (eventfd && !IS_ERR(eventfd))
+ eventfd_ctx_put(eventfd);
+
+ fdput(f);
+out:
+ kfree(irqfd);
+ return ret;
+}
+
+static int acrn_irqfd_deassign(struct vhm_irqfd_info *info,
+ struct acrn_irqfd *args)
+{
+ struct acrn_vhm_irqfd *irqfd, *tmp;
+ struct eventfd_ctx *eventfd;
+
+ eventfd = eventfd_ctx_fdget(args->fd);
+ if (IS_ERR(eventfd))
+ return PTR_ERR(eventfd);
+
+ spin_lock_irq(&info->irqfds_lock);
+
+ list_for_each_entry_safe(irqfd, tmp, &info->irqfds, list) {
+ if (irqfd->eventfd == eventfd) {
+ vhm_irqfd_shutdown(irqfd);
+ break;
+ }
+ }
+
+ spin_unlock_irq(&info->irqfds_lock);
+ eventfd_ctx_put(eventfd);
+
+ return 0;
+}
+
+int acrn_irqfd(uint16_t vmid, struct acrn_irqfd *args)
+{
+ struct vhm_irqfd_info *info;
+ int ret;
+
+ info = get_irqfd_info_by_vm(vmid);
+ if (!info)
+ return -ENOENT;
+
+ if (args->flags & ACRN_IRQFD_FLAG_DEASSIGN)
+ ret = acrn_irqfd_deassign(info, args);
+ else
+ ret = acrn_irqfd_assign(info, args);
+
+ put_irqfd_info(info);
+ return ret;
+}
+
+int acrn_irqfd_init(uint16_t vmid)
+{
+ struct vhm_irqfd_info *info;
+
+ info = get_irqfd_info_by_vm(vmid);
+ if (info) {
+ put_irqfd_info(info);
+ return -EEXIST;
+ }
+
+ info = kzalloc(sizeof(struct vhm_irqfd_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->vmid = vmid;
+ info->refcnt = 1;
+ INIT_LIST_HEAD(&info->irqfds);
+ spin_lock_init(&info->irqfds_lock);
+
+ info->wq = alloc_workqueue("acrn_irqfd-%d", 0, 0, vmid);
+ if (!info->wq) {
+ kfree(info);
+ return -ENOMEM;
+ }
+
+ mutex_lock(&vhm_irqfds_mutex);
+ list_add(&info->list, &vhm_irqfd_clients);
+ mutex_unlock(&vhm_irqfds_mutex);
+
+ pr_info("ACRN vhm irqfd init done!\n");
+ return 0;
+}
+
+void acrn_irqfd_deinit(uint16_t vmid)
+{
+ struct acrn_vhm_irqfd *irqfd, *tmp;
+ struct vhm_irqfd_info *info;
+
+ info = get_irqfd_info_by_vm(vmid);
+ if (!info)
+ return;
+ put_irqfd_info(info);
+
+ destroy_workqueue(info->wq);
+
+ spin_lock_irq(&info->irqfds_lock);
+ list_for_each_entry_safe(irqfd, tmp, &info->irqfds, list)
+ vhm_irqfd_shutdown(irqfd);
+ spin_unlock_irq(&info->irqfds_lock);
+
+ /* put one more to release it */
+ put_irqfd_info(info);
+}
diff --git a/include/linux/vhm/vhm_eventfd.h b/include/linux/vhm/vhm_eventfd.h
index 3c73194cd1a1..7ee5843eca0e 100644
--- a/include/linux/vhm/vhm_eventfd.h
+++ b/include/linux/vhm/vhm_eventfd.h
@@ -7,4 +7,10 @@ int acrn_ioeventfd_init(int vmid);
int acrn_ioeventfd(int vmid, struct acrn_ioeventfd *args);
void acrn_ioeventfd_deinit(int vmid);
+/* irqfd APIs */
+struct acrn_irqfd;
+int acrn_irqfd_init(uint16_t vmid);
+int acrn_irqfd(uint16_t vmid, struct acrn_irqfd *args);
+void acrn_irqfd_deinit(uint16_t vmid);
+
#endif
diff --git a/include/linux/vhm/vhm_ioctl_defs.h b/include/linux/vhm/vhm_ioctl_defs.h
index 4ab6b02aced8..2007d58faa3f 100644
--- a/include/linux/vhm/vhm_ioctl_defs.h
+++ b/include/linux/vhm/vhm_ioctl_defs.h
@@ -113,6 +113,7 @@
/* VHM eventfd */
#define IC_ID_EVENT_BASE 0x70UL
#define IC_EVENT_IOEVENTFD _IC_ID(IC_ID, IC_ID_EVENT_BASE + 0x00)
+#define IC_EVENT_IRQFD _IC_ID(IC_ID, IC_ID_EVENT_BASE + 0x01)
/**
* struct vm_memseg - memory segment info for guest
@@ -232,4 +233,11 @@ struct acrn_ioeventfd {
uint64_t data;
};
+#define ACRN_IRQFD_FLAG_DEASSIGN 0x01
+struct acrn_irqfd {
+ int32_t fd;
+ uint32_t flags;
+ struct acrn_msi_entry msi;
+};
+
#endif /* VHM_IOCTL_DEFS_H */
--
https://clearlinux.org