root/drivers/virt/acrn/ioeventfd.c
// 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_obj(*p);
        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);
}