162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ACRN HSM irqfd: use eventfd objects to inject virtual interrupts
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020 Intel Corporation. All rights reserved.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Authors:
862306a36Sopenharmony_ci *	Shuo Liu <shuo.a.liu@intel.com>
962306a36Sopenharmony_ci *	Yakui Zhao <yakui.zhao@intel.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/eventfd.h>
1362306a36Sopenharmony_ci#include <linux/file.h>
1462306a36Sopenharmony_ci#include <linux/poll.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "acrn_drv.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic LIST_HEAD(acrn_irqfd_clients);
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/**
2262306a36Sopenharmony_ci * struct hsm_irqfd - Properties of HSM irqfd
2362306a36Sopenharmony_ci * @vm:		Associated VM pointer
2462306a36Sopenharmony_ci * @wait:	Entry of wait-queue
2562306a36Sopenharmony_ci * @shutdown:	Async shutdown work
2662306a36Sopenharmony_ci * @eventfd:	Associated eventfd
2762306a36Sopenharmony_ci * @list:	Entry within &acrn_vm.irqfds of irqfds of a VM
2862306a36Sopenharmony_ci * @pt:		Structure for select/poll on the associated eventfd
2962306a36Sopenharmony_ci * @msi:	MSI data
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_cistruct hsm_irqfd {
3262306a36Sopenharmony_ci	struct acrn_vm		*vm;
3362306a36Sopenharmony_ci	wait_queue_entry_t	wait;
3462306a36Sopenharmony_ci	struct work_struct	shutdown;
3562306a36Sopenharmony_ci	struct eventfd_ctx	*eventfd;
3662306a36Sopenharmony_ci	struct list_head	list;
3762306a36Sopenharmony_ci	poll_table		pt;
3862306a36Sopenharmony_ci	struct acrn_msi_entry	msi;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic void acrn_irqfd_inject(struct hsm_irqfd *irqfd)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct acrn_vm *vm = irqfd->vm;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	acrn_msi_inject(vm, irqfd->msi.msi_addr,
4662306a36Sopenharmony_ci			irqfd->msi.msi_data);
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic void hsm_irqfd_shutdown(struct hsm_irqfd *irqfd)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	u64 cnt;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	lockdep_assert_held(&irqfd->vm->irqfds_lock);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/* remove from wait queue */
5662306a36Sopenharmony_ci	list_del_init(&irqfd->list);
5762306a36Sopenharmony_ci	eventfd_ctx_remove_wait_queue(irqfd->eventfd, &irqfd->wait, &cnt);
5862306a36Sopenharmony_ci	eventfd_ctx_put(irqfd->eventfd);
5962306a36Sopenharmony_ci	kfree(irqfd);
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic void hsm_irqfd_shutdown_work(struct work_struct *work)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct hsm_irqfd *irqfd;
6562306a36Sopenharmony_ci	struct acrn_vm *vm;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	irqfd = container_of(work, struct hsm_irqfd, shutdown);
6862306a36Sopenharmony_ci	vm = irqfd->vm;
6962306a36Sopenharmony_ci	mutex_lock(&vm->irqfds_lock);
7062306a36Sopenharmony_ci	if (!list_empty(&irqfd->list))
7162306a36Sopenharmony_ci		hsm_irqfd_shutdown(irqfd);
7262306a36Sopenharmony_ci	mutex_unlock(&vm->irqfds_lock);
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci/* Called with wqh->lock held and interrupts disabled */
7662306a36Sopenharmony_cistatic int hsm_irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode,
7762306a36Sopenharmony_ci			    int sync, void *key)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	unsigned long poll_bits = (unsigned long)key;
8062306a36Sopenharmony_ci	struct hsm_irqfd *irqfd;
8162306a36Sopenharmony_ci	struct acrn_vm *vm;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	irqfd = container_of(wait, struct hsm_irqfd, wait);
8462306a36Sopenharmony_ci	vm = irqfd->vm;
8562306a36Sopenharmony_ci	if (poll_bits & POLLIN)
8662306a36Sopenharmony_ci		/* An event has been signaled, inject an interrupt */
8762306a36Sopenharmony_ci		acrn_irqfd_inject(irqfd);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (poll_bits & POLLHUP)
9062306a36Sopenharmony_ci		/* Do shutdown work in thread to hold wqh->lock */
9162306a36Sopenharmony_ci		queue_work(vm->irqfd_wq, &irqfd->shutdown);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	return 0;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic void hsm_irqfd_poll_func(struct file *file, wait_queue_head_t *wqh,
9762306a36Sopenharmony_ci				poll_table *pt)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct hsm_irqfd *irqfd;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	irqfd = container_of(pt, struct hsm_irqfd, pt);
10262306a36Sopenharmony_ci	add_wait_queue(wqh, &irqfd->wait);
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/*
10662306a36Sopenharmony_ci * Assign an eventfd to a VM and create a HSM irqfd associated with the
10762306a36Sopenharmony_ci * eventfd. The properties of the HSM irqfd are built from a &struct
10862306a36Sopenharmony_ci * acrn_irqfd.
10962306a36Sopenharmony_ci */
11062306a36Sopenharmony_cistatic int acrn_irqfd_assign(struct acrn_vm *vm, struct acrn_irqfd *args)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct eventfd_ctx *eventfd = NULL;
11362306a36Sopenharmony_ci	struct hsm_irqfd *irqfd, *tmp;
11462306a36Sopenharmony_ci	__poll_t events;
11562306a36Sopenharmony_ci	struct fd f;
11662306a36Sopenharmony_ci	int ret = 0;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);
11962306a36Sopenharmony_ci	if (!irqfd)
12062306a36Sopenharmony_ci		return -ENOMEM;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	irqfd->vm = vm;
12362306a36Sopenharmony_ci	memcpy(&irqfd->msi, &args->msi, sizeof(args->msi));
12462306a36Sopenharmony_ci	INIT_LIST_HEAD(&irqfd->list);
12562306a36Sopenharmony_ci	INIT_WORK(&irqfd->shutdown, hsm_irqfd_shutdown_work);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	f = fdget(args->fd);
12862306a36Sopenharmony_ci	if (!f.file) {
12962306a36Sopenharmony_ci		ret = -EBADF;
13062306a36Sopenharmony_ci		goto out;
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	eventfd = eventfd_ctx_fileget(f.file);
13462306a36Sopenharmony_ci	if (IS_ERR(eventfd)) {
13562306a36Sopenharmony_ci		ret = PTR_ERR(eventfd);
13662306a36Sopenharmony_ci		goto fail;
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	irqfd->eventfd = eventfd;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/*
14262306a36Sopenharmony_ci	 * Install custom wake-up handling to be notified whenever underlying
14362306a36Sopenharmony_ci	 * eventfd is signaled.
14462306a36Sopenharmony_ci	 */
14562306a36Sopenharmony_ci	init_waitqueue_func_entry(&irqfd->wait, hsm_irqfd_wakeup);
14662306a36Sopenharmony_ci	init_poll_funcptr(&irqfd->pt, hsm_irqfd_poll_func);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	mutex_lock(&vm->irqfds_lock);
14962306a36Sopenharmony_ci	list_for_each_entry(tmp, &vm->irqfds, list) {
15062306a36Sopenharmony_ci		if (irqfd->eventfd != tmp->eventfd)
15162306a36Sopenharmony_ci			continue;
15262306a36Sopenharmony_ci		ret = -EBUSY;
15362306a36Sopenharmony_ci		mutex_unlock(&vm->irqfds_lock);
15462306a36Sopenharmony_ci		goto fail;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci	list_add_tail(&irqfd->list, &vm->irqfds);
15762306a36Sopenharmony_ci	mutex_unlock(&vm->irqfds_lock);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* Check the pending event in this stage */
16062306a36Sopenharmony_ci	events = vfs_poll(f.file, &irqfd->pt);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if (events & EPOLLIN)
16362306a36Sopenharmony_ci		acrn_irqfd_inject(irqfd);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	fdput(f);
16662306a36Sopenharmony_ci	return 0;
16762306a36Sopenharmony_cifail:
16862306a36Sopenharmony_ci	if (eventfd && !IS_ERR(eventfd))
16962306a36Sopenharmony_ci		eventfd_ctx_put(eventfd);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	fdput(f);
17262306a36Sopenharmony_ciout:
17362306a36Sopenharmony_ci	kfree(irqfd);
17462306a36Sopenharmony_ci	return ret;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic int acrn_irqfd_deassign(struct acrn_vm *vm,
17862306a36Sopenharmony_ci			       struct acrn_irqfd *args)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	struct hsm_irqfd *irqfd, *tmp;
18162306a36Sopenharmony_ci	struct eventfd_ctx *eventfd;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	eventfd = eventfd_ctx_fdget(args->fd);
18462306a36Sopenharmony_ci	if (IS_ERR(eventfd))
18562306a36Sopenharmony_ci		return PTR_ERR(eventfd);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	mutex_lock(&vm->irqfds_lock);
18862306a36Sopenharmony_ci	list_for_each_entry_safe(irqfd, tmp, &vm->irqfds, list) {
18962306a36Sopenharmony_ci		if (irqfd->eventfd == eventfd) {
19062306a36Sopenharmony_ci			hsm_irqfd_shutdown(irqfd);
19162306a36Sopenharmony_ci			break;
19262306a36Sopenharmony_ci		}
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci	mutex_unlock(&vm->irqfds_lock);
19562306a36Sopenharmony_ci	eventfd_ctx_put(eventfd);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return 0;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ciint acrn_irqfd_config(struct acrn_vm *vm, struct acrn_irqfd *args)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	int ret;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (args->flags & ACRN_IRQFD_FLAG_DEASSIGN)
20562306a36Sopenharmony_ci		ret = acrn_irqfd_deassign(vm, args);
20662306a36Sopenharmony_ci	else
20762306a36Sopenharmony_ci		ret = acrn_irqfd_assign(vm, args);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return ret;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ciint acrn_irqfd_init(struct acrn_vm *vm)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	INIT_LIST_HEAD(&vm->irqfds);
21562306a36Sopenharmony_ci	mutex_init(&vm->irqfds_lock);
21662306a36Sopenharmony_ci	vm->irqfd_wq = alloc_workqueue("acrn_irqfd-%u", 0, 0, vm->vmid);
21762306a36Sopenharmony_ci	if (!vm->irqfd_wq)
21862306a36Sopenharmony_ci		return -ENOMEM;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	dev_dbg(acrn_dev.this_device, "VM %u irqfd init.\n", vm->vmid);
22162306a36Sopenharmony_ci	return 0;
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_civoid acrn_irqfd_deinit(struct acrn_vm *vm)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	struct hsm_irqfd *irqfd, *next;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	dev_dbg(acrn_dev.this_device, "VM %u irqfd deinit.\n", vm->vmid);
22962306a36Sopenharmony_ci	destroy_workqueue(vm->irqfd_wq);
23062306a36Sopenharmony_ci	mutex_lock(&vm->irqfds_lock);
23162306a36Sopenharmony_ci	list_for_each_entry_safe(irqfd, next, &vm->irqfds, list)
23262306a36Sopenharmony_ci		hsm_irqfd_shutdown(irqfd);
23362306a36Sopenharmony_ci	mutex_unlock(&vm->irqfds_lock);
23462306a36Sopenharmony_ci}
235