| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * ACRN HSM eventfd - use eventfd objects to signal expected I/O requests | 
 |  * | 
 |  * Copyright (C) 2020 Intel Corporation. All rights reserved. | 
 |  * | 
 |  * Authors: | 
 |  *	Shuo Liu <shuo.a.liu@intel.com> | 
 |  *	Yakui Zhao <yakui.zhao@intel.com> | 
 |  */ | 
 |  | 
 | #include <linux/eventfd.h> | 
 | #include <linux/slab.h> | 
 |  | 
 | #include "acrn_drv.h" | 
 |  | 
 | /** | 
 |  * struct hsm_ioeventfd - Properties of HSM ioeventfd | 
 |  * @list:	Entry within &acrn_vm.ioeventfds of ioeventfds of a VM | 
 |  * @eventfd:	Eventfd of the HSM ioeventfd | 
 |  * @addr:	Address of I/O range | 
 |  * @data:	Data for matching | 
 |  * @length:	Length of I/O range | 
 |  * @type:	Type of I/O range (ACRN_IOREQ_TYPE_MMIO/ACRN_IOREQ_TYPE_PORTIO) | 
 |  * @wildcard:	Data matching or not | 
 |  */ | 
 | struct hsm_ioeventfd { | 
 | 	struct list_head	list; | 
 | 	struct eventfd_ctx	*eventfd; | 
 | 	u64			addr; | 
 | 	u64			data; | 
 | 	int			length; | 
 | 	int			type; | 
 | 	bool			wildcard; | 
 | }; | 
 |  | 
 | static inline int ioreq_type_from_flags(int flags) | 
 | { | 
 | 	return flags & ACRN_IOEVENTFD_FLAG_PIO ? | 
 | 		       ACRN_IOREQ_TYPE_PORTIO : ACRN_IOREQ_TYPE_MMIO; | 
 | } | 
 |  | 
 | static void acrn_ioeventfd_shutdown(struct acrn_vm *vm, struct hsm_ioeventfd *p) | 
 | { | 
 | 	lockdep_assert_held(&vm->ioeventfds_lock); | 
 |  | 
 | 	eventfd_ctx_put(p->eventfd); | 
 | 	list_del(&p->list); | 
 | 	kfree(p); | 
 | } | 
 |  | 
 | static bool hsm_ioeventfd_is_conflict(struct acrn_vm *vm, | 
 | 				      struct hsm_ioeventfd *ioeventfd) | 
 | { | 
 | 	struct hsm_ioeventfd *p; | 
 |  | 
 | 	lockdep_assert_held(&vm->ioeventfds_lock); | 
 |  | 
 | 	/* Either one is wildcard, the data matching will be skipped. */ | 
 | 	list_for_each_entry(p, &vm->ioeventfds, list) | 
 | 		if (p->eventfd == ioeventfd->eventfd && | 
 | 		    p->addr == ioeventfd->addr && | 
 | 		    p->type == ioeventfd->type && | 
 | 		    (p->wildcard || ioeventfd->wildcard || | 
 | 			p->data == ioeventfd->data)) | 
 | 			return true; | 
 |  | 
 | 	return false; | 
 | } | 
 |  | 
 | /* | 
 |  * Assign an eventfd to a VM and create a HSM ioeventfd associated with the | 
 |  * eventfd. The properties of the HSM ioeventfd are built from a &struct | 
 |  * acrn_ioeventfd. | 
 |  */ | 
 | static int acrn_ioeventfd_assign(struct acrn_vm *vm, | 
 | 				 struct acrn_ioeventfd *args) | 
 | { | 
 | 	struct eventfd_ctx *eventfd; | 
 | 	struct hsm_ioeventfd *p; | 
 | 	int ret; | 
 |  | 
 | 	/* Check for range overflow */ | 
 | 	if (args->addr + args->len < args->addr) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* | 
 | 	 * Currently, acrn_ioeventfd is used to support vhost. 1,2,4,8 width | 
 | 	 * accesses can cover vhost's requirements. | 
 | 	 */ | 
 | 	if (!(args->len == 1 || args->len == 2 || | 
 | 	      args->len == 4 || args->len == 8)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	eventfd = eventfd_ctx_fdget(args->fd); | 
 | 	if (IS_ERR(eventfd)) | 
 | 		return PTR_ERR(eventfd); | 
 |  | 
 | 	p = kzalloc(sizeof(*p), GFP_KERNEL); | 
 | 	if (!p) { | 
 | 		ret = -ENOMEM; | 
 | 		goto fail; | 
 | 	} | 
 |  | 
 | 	INIT_LIST_HEAD(&p->list); | 
 | 	p->addr = args->addr; | 
 | 	p->length = args->len; | 
 | 	p->eventfd = eventfd; | 
 | 	p->type = ioreq_type_from_flags(args->flags); | 
 |  | 
 | 	/* | 
 | 	 * ACRN_IOEVENTFD_FLAG_DATAMATCH flag is set in virtio 1.0 support, the | 
 | 	 * writing of notification register of each virtqueue may trigger the | 
 | 	 * notification. There is no data matching requirement. | 
 | 	 */ | 
 | 	if (args->flags & ACRN_IOEVENTFD_FLAG_DATAMATCH) | 
 | 		p->data = args->data; | 
 | 	else | 
 | 		p->wildcard = true; | 
 |  | 
 | 	mutex_lock(&vm->ioeventfds_lock); | 
 |  | 
 | 	if (hsm_ioeventfd_is_conflict(vm, p)) { | 
 | 		ret = -EEXIST; | 
 | 		goto unlock_fail; | 
 | 	} | 
 |  | 
 | 	/* register the I/O range into ioreq client */ | 
 | 	ret = acrn_ioreq_range_add(vm->ioeventfd_client, p->type, | 
 | 				   p->addr, p->addr + p->length - 1); | 
 | 	if (ret < 0) | 
 | 		goto unlock_fail; | 
 |  | 
 | 	list_add_tail(&p->list, &vm->ioeventfds); | 
 | 	mutex_unlock(&vm->ioeventfds_lock); | 
 |  | 
 | 	return 0; | 
 |  | 
 | unlock_fail: | 
 | 	mutex_unlock(&vm->ioeventfds_lock); | 
 | 	kfree(p); | 
 | fail: | 
 | 	eventfd_ctx_put(eventfd); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int acrn_ioeventfd_deassign(struct acrn_vm *vm, | 
 | 				   struct acrn_ioeventfd *args) | 
 | { | 
 | 	struct hsm_ioeventfd *p; | 
 | 	struct eventfd_ctx *eventfd; | 
 |  | 
 | 	eventfd = eventfd_ctx_fdget(args->fd); | 
 | 	if (IS_ERR(eventfd)) | 
 | 		return PTR_ERR(eventfd); | 
 |  | 
 | 	mutex_lock(&vm->ioeventfds_lock); | 
 | 	list_for_each_entry(p, &vm->ioeventfds, list) { | 
 | 		if (p->eventfd != eventfd) | 
 | 			continue; | 
 |  | 
 | 		acrn_ioreq_range_del(vm->ioeventfd_client, p->type, | 
 | 				     p->addr, p->addr + p->length - 1); | 
 | 		acrn_ioeventfd_shutdown(vm, p); | 
 | 		break; | 
 | 	} | 
 | 	mutex_unlock(&vm->ioeventfds_lock); | 
 |  | 
 | 	eventfd_ctx_put(eventfd); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct hsm_ioeventfd *hsm_ioeventfd_match(struct acrn_vm *vm, u64 addr, | 
 | 						 u64 data, int len, int type) | 
 | { | 
 | 	struct hsm_ioeventfd *p = NULL; | 
 |  | 
 | 	lockdep_assert_held(&vm->ioeventfds_lock); | 
 |  | 
 | 	list_for_each_entry(p, &vm->ioeventfds, list) { | 
 | 		if (p->type == type && p->addr == addr && p->length >= len && | 
 | 		    (p->wildcard || p->data == data)) | 
 | 			return p; | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static int acrn_ioeventfd_handler(struct acrn_ioreq_client *client, | 
 | 				  struct acrn_io_request *req) | 
 | { | 
 | 	struct hsm_ioeventfd *p; | 
 | 	u64 addr, val; | 
 | 	int size; | 
 |  | 
 | 	if (req->type == ACRN_IOREQ_TYPE_MMIO) { | 
 | 		/* | 
 | 		 * I/O requests are dispatched by range check only, so a | 
 | 		 * acrn_ioreq_client need process both READ and WRITE accesses | 
 | 		 * of same range. READ accesses are safe to be ignored here | 
 | 		 * because virtio PCI devices write the notify registers for | 
 | 		 * notification. | 
 | 		 */ | 
 | 		if (req->reqs.mmio_request.direction == ACRN_IOREQ_DIR_READ) { | 
 | 			/* reading does nothing and return 0 */ | 
 | 			req->reqs.mmio_request.value = 0; | 
 | 			return 0; | 
 | 		} | 
 | 		addr = req->reqs.mmio_request.address; | 
 | 		size = req->reqs.mmio_request.size; | 
 | 		val = req->reqs.mmio_request.value; | 
 | 	} else { | 
 | 		if (req->reqs.pio_request.direction == ACRN_IOREQ_DIR_READ) { | 
 | 			/* reading does nothing and return 0 */ | 
 | 			req->reqs.pio_request.value = 0; | 
 | 			return 0; | 
 | 		} | 
 | 		addr = req->reqs.pio_request.address; | 
 | 		size = req->reqs.pio_request.size; | 
 | 		val = req->reqs.pio_request.value; | 
 | 	} | 
 |  | 
 | 	mutex_lock(&client->vm->ioeventfds_lock); | 
 | 	p = hsm_ioeventfd_match(client->vm, addr, val, size, req->type); | 
 | 	if (p) | 
 | 		eventfd_signal(p->eventfd); | 
 | 	mutex_unlock(&client->vm->ioeventfds_lock); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int acrn_ioeventfd_config(struct acrn_vm *vm, struct acrn_ioeventfd *args) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	if (args->flags & ACRN_IOEVENTFD_FLAG_DEASSIGN) | 
 | 		ret = acrn_ioeventfd_deassign(vm, args); | 
 | 	else | 
 | 		ret = acrn_ioeventfd_assign(vm, args); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | int acrn_ioeventfd_init(struct acrn_vm *vm) | 
 | { | 
 | 	char name[ACRN_NAME_LEN]; | 
 |  | 
 | 	mutex_init(&vm->ioeventfds_lock); | 
 | 	INIT_LIST_HEAD(&vm->ioeventfds); | 
 | 	snprintf(name, sizeof(name), "ioeventfd-%u", vm->vmid); | 
 | 	vm->ioeventfd_client = acrn_ioreq_client_create(vm, | 
 | 							acrn_ioeventfd_handler, | 
 | 							NULL, false, name); | 
 | 	if (!vm->ioeventfd_client) { | 
 | 		dev_err(acrn_dev.this_device, "Failed to create ioeventfd ioreq client!\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	dev_dbg(acrn_dev.this_device, "VM %u ioeventfd init.\n", vm->vmid); | 
 | 	return 0; | 
 | } | 
 |  | 
 | void acrn_ioeventfd_deinit(struct acrn_vm *vm) | 
 | { | 
 | 	struct hsm_ioeventfd *p, *next; | 
 |  | 
 | 	dev_dbg(acrn_dev.this_device, "VM %u ioeventfd deinit.\n", vm->vmid); | 
 | 	acrn_ioreq_client_destroy(vm->ioeventfd_client); | 
 | 	mutex_lock(&vm->ioeventfds_lock); | 
 | 	list_for_each_entry_safe(p, next, &vm->ioeventfds, list) | 
 | 		acrn_ioeventfd_shutdown(vm, p); | 
 | 	mutex_unlock(&vm->ioeventfds_lock); | 
 | } |