| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- // 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);
- }
|