235 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * ACRN HSM irqfd: use eventfd objects to inject virtual interrupts4 *5 * Copyright (C) 2020 Intel Corporation. All rights reserved.6 *7 * Authors:8 * Shuo Liu <shuo.a.liu@intel.com>9 * Yakui Zhao <yakui.zhao@intel.com>10 */11 12#include <linux/eventfd.h>13#include <linux/file.h>14#include <linux/poll.h>15#include <linux/slab.h>16 17#include "acrn_drv.h"18 19static LIST_HEAD(acrn_irqfd_clients);20 21/**22 * struct hsm_irqfd - Properties of HSM irqfd23 * @vm: Associated VM pointer24 * @wait: Entry of wait-queue25 * @shutdown: Async shutdown work26 * @eventfd: Associated eventfd27 * @list: Entry within &acrn_vm.irqfds of irqfds of a VM28 * @pt: Structure for select/poll on the associated eventfd29 * @msi: MSI data30 */31struct hsm_irqfd {32 struct acrn_vm *vm;33 wait_queue_entry_t wait;34 struct work_struct shutdown;35 struct eventfd_ctx *eventfd;36 struct list_head list;37 poll_table pt;38 struct acrn_msi_entry msi;39};40 41static void acrn_irqfd_inject(struct hsm_irqfd *irqfd)42{43 struct acrn_vm *vm = irqfd->vm;44 45 acrn_msi_inject(vm, irqfd->msi.msi_addr,46 irqfd->msi.msi_data);47}48 49static void hsm_irqfd_shutdown(struct hsm_irqfd *irqfd)50{51 u64 cnt;52 53 lockdep_assert_held(&irqfd->vm->irqfds_lock);54 55 /* remove from wait queue */56 list_del_init(&irqfd->list);57 eventfd_ctx_remove_wait_queue(irqfd->eventfd, &irqfd->wait, &cnt);58 eventfd_ctx_put(irqfd->eventfd);59 kfree(irqfd);60}61 62static void hsm_irqfd_shutdown_work(struct work_struct *work)63{64 struct hsm_irqfd *irqfd;65 struct acrn_vm *vm;66 67 irqfd = container_of(work, struct hsm_irqfd, shutdown);68 vm = irqfd->vm;69 mutex_lock(&vm->irqfds_lock);70 if (!list_empty(&irqfd->list))71 hsm_irqfd_shutdown(irqfd);72 mutex_unlock(&vm->irqfds_lock);73}74 75/* Called with wqh->lock held and interrupts disabled */76static int hsm_irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode,77 int sync, void *key)78{79 unsigned long poll_bits = (unsigned long)key;80 struct hsm_irqfd *irqfd;81 struct acrn_vm *vm;82 83 irqfd = container_of(wait, struct hsm_irqfd, wait);84 vm = irqfd->vm;85 if (poll_bits & POLLIN)86 /* An event has been signaled, inject an interrupt */87 acrn_irqfd_inject(irqfd);88 89 if (poll_bits & POLLHUP)90 /* Do shutdown work in thread to hold wqh->lock */91 queue_work(vm->irqfd_wq, &irqfd->shutdown);92 93 return 0;94}95 96static void hsm_irqfd_poll_func(struct file *file, wait_queue_head_t *wqh,97 poll_table *pt)98{99 struct hsm_irqfd *irqfd;100 101 irqfd = container_of(pt, struct hsm_irqfd, pt);102 add_wait_queue(wqh, &irqfd->wait);103}104 105/*106 * Assign an eventfd to a VM and create a HSM irqfd associated with the107 * eventfd. The properties of the HSM irqfd are built from a &struct108 * acrn_irqfd.109 */110static int acrn_irqfd_assign(struct acrn_vm *vm, struct acrn_irqfd *args)111{112 struct eventfd_ctx *eventfd = NULL;113 struct hsm_irqfd *irqfd, *tmp;114 __poll_t events;115 struct fd f;116 int ret = 0;117 118 irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);119 if (!irqfd)120 return -ENOMEM;121 122 irqfd->vm = vm;123 memcpy(&irqfd->msi, &args->msi, sizeof(args->msi));124 INIT_LIST_HEAD(&irqfd->list);125 INIT_WORK(&irqfd->shutdown, hsm_irqfd_shutdown_work);126 127 f = fdget(args->fd);128 if (!fd_file(f)) {129 ret = -EBADF;130 goto out;131 }132 133 eventfd = eventfd_ctx_fileget(fd_file(f));134 if (IS_ERR(eventfd)) {135 ret = PTR_ERR(eventfd);136 goto fail;137 }138 139 irqfd->eventfd = eventfd;140 141 /*142 * Install custom wake-up handling to be notified whenever underlying143 * eventfd is signaled.144 */145 init_waitqueue_func_entry(&irqfd->wait, hsm_irqfd_wakeup);146 init_poll_funcptr(&irqfd->pt, hsm_irqfd_poll_func);147 148 mutex_lock(&vm->irqfds_lock);149 list_for_each_entry(tmp, &vm->irqfds, list) {150 if (irqfd->eventfd != tmp->eventfd)151 continue;152 ret = -EBUSY;153 mutex_unlock(&vm->irqfds_lock);154 goto fail;155 }156 list_add_tail(&irqfd->list, &vm->irqfds);157 mutex_unlock(&vm->irqfds_lock);158 159 /* Check the pending event in this stage */160 events = vfs_poll(fd_file(f), &irqfd->pt);161 162 if (events & EPOLLIN)163 acrn_irqfd_inject(irqfd);164 165 fdput(f);166 return 0;167fail:168 if (eventfd && !IS_ERR(eventfd))169 eventfd_ctx_put(eventfd);170 171 fdput(f);172out:173 kfree(irqfd);174 return ret;175}176 177static int acrn_irqfd_deassign(struct acrn_vm *vm,178 struct acrn_irqfd *args)179{180 struct hsm_irqfd *irqfd, *tmp;181 struct eventfd_ctx *eventfd;182 183 eventfd = eventfd_ctx_fdget(args->fd);184 if (IS_ERR(eventfd))185 return PTR_ERR(eventfd);186 187 mutex_lock(&vm->irqfds_lock);188 list_for_each_entry_safe(irqfd, tmp, &vm->irqfds, list) {189 if (irqfd->eventfd == eventfd) {190 hsm_irqfd_shutdown(irqfd);191 break;192 }193 }194 mutex_unlock(&vm->irqfds_lock);195 eventfd_ctx_put(eventfd);196 197 return 0;198}199 200int acrn_irqfd_config(struct acrn_vm *vm, struct acrn_irqfd *args)201{202 int ret;203 204 if (args->flags & ACRN_IRQFD_FLAG_DEASSIGN)205 ret = acrn_irqfd_deassign(vm, args);206 else207 ret = acrn_irqfd_assign(vm, args);208 209 return ret;210}211 212int acrn_irqfd_init(struct acrn_vm *vm)213{214 INIT_LIST_HEAD(&vm->irqfds);215 mutex_init(&vm->irqfds_lock);216 vm->irqfd_wq = alloc_workqueue("acrn_irqfd-%u", 0, 0, vm->vmid);217 if (!vm->irqfd_wq)218 return -ENOMEM;219 220 dev_dbg(acrn_dev.this_device, "VM %u irqfd init.\n", vm->vmid);221 return 0;222}223 224void acrn_irqfd_deinit(struct acrn_vm *vm)225{226 struct hsm_irqfd *irqfd, *next;227 228 dev_dbg(acrn_dev.this_device, "VM %u irqfd deinit.\n", vm->vmid);229 destroy_workqueue(vm->irqfd_wq);230 mutex_lock(&vm->irqfds_lock);231 list_for_each_entry_safe(irqfd, next, &vm->irqfds, list)232 hsm_irqfd_shutdown(irqfd);233 mutex_unlock(&vm->irqfds_lock);234}235