| /* |
| * Copyright (c) 2020 Nutanix Inc. All rights reserved. |
| * |
| * Authors: Thanos Makatos <thanos@nutanix.com> |
| * Swapnil Ingle <swapnil.ingle@nutanix.com> |
| * Felipe Franciosi <felipe@nutanix.com> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * * Neither the name of Nutanix nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| * |
| */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <sys/eventfd.h> |
| |
| #include "irq.h" |
| |
| #define LM2VFIO_IRQT(type) (type - 1) |
| |
| static const char * |
| vfio_irq_idx_to_str(int index) |
| { |
| switch (index) { |
| case VFIO_PCI_INTX_IRQ_INDEX: return "INTx"; |
| case VFIO_PCI_MSI_IRQ_INDEX: return "MSI"; |
| case VFIO_PCI_MSIX_IRQ_INDEX: return "MSI-X"; |
| case VFIO_PCI_ERR_IRQ_INDEX: return "ERR"; |
| case VFIO_PCI_REQ_IRQ_INDEX: return "REQ"; |
| default: |
| abort(); |
| } |
| } |
| |
| int |
| handle_device_get_irq_info(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) |
| { |
| struct vfio_irq_info *in_info; |
| struct vfio_irq_info *out_info; |
| |
| assert(vfu_ctx != NULL); |
| assert(msg != NULL); |
| |
| in_info = msg->in.iov.iov_base; |
| |
| if (msg->in.iov.iov_len < sizeof(*in_info) || in_info->argsz < sizeof(*out_info)) { |
| return ERROR_INT(EINVAL); |
| } |
| |
| if (in_info->index >= VFU_DEV_NUM_IRQS) { |
| vfu_log(vfu_ctx, LOG_DEBUG, "bad irq_info index %d\n", in_info->index); |
| return ERROR_INT(EINVAL); |
| } |
| |
| msg->out.iov.iov_len = sizeof (*out_info); |
| msg->out.iov.iov_base = calloc(1, sizeof(*out_info)); |
| |
| if (msg->out.iov.iov_base == NULL) { |
| return -1; |
| } |
| |
| out_info = msg->out.iov.iov_base; |
| out_info->argsz = sizeof(*out_info); |
| out_info->flags = VFIO_IRQ_INFO_EVENTFD; |
| out_info->index = in_info->index; |
| out_info->count = vfu_ctx->irq_count[in_info->index]; |
| |
| return 0; |
| } |
| |
| static void |
| irqs_disable(vfu_ctx_t *vfu_ctx, uint32_t index, uint32_t start, uint32_t count) |
| { |
| size_t i; |
| int *efds; |
| |
| assert(vfu_ctx != NULL); |
| assert(index < VFU_DEV_NUM_IRQS); |
| assert(start + count <= vfu_ctx->irq_count[index]); |
| |
| if (count == 0) { |
| count = vfu_ctx->irq_count[index]; |
| } |
| |
| vfu_log(vfu_ctx, LOG_DEBUG, "disabling IRQ type %s range [%u, %u)", |
| vfio_irq_idx_to_str(index), start, start + count); |
| |
| switch (index) { |
| case VFIO_PCI_INTX_IRQ_INDEX: |
| case VFIO_PCI_MSI_IRQ_INDEX: |
| case VFIO_PCI_MSIX_IRQ_INDEX: |
| efds = vfu_ctx->irqs->efds; |
| break; |
| case VFIO_PCI_ERR_IRQ_INDEX: |
| efds = &vfu_ctx->irqs->err_efd; |
| break; |
| case VFIO_PCI_REQ_IRQ_INDEX: |
| efds = &vfu_ctx->irqs->req_efd; |
| break; |
| } |
| |
| for (i = start; i < count; i++) { |
| close_safely(&efds[i]); |
| } |
| } |
| |
| void |
| irqs_reset(vfu_ctx_t *vfu_ctx) |
| { |
| int *efds = vfu_ctx->irqs->efds; |
| size_t i; |
| |
| irqs_disable(vfu_ctx, VFIO_PCI_REQ_IRQ_INDEX, 0, 0); |
| irqs_disable(vfu_ctx, VFIO_PCI_ERR_IRQ_INDEX, 0, 0); |
| |
| for (i = 0; i < vfu_ctx->irqs->max_ivs; i++) { |
| close_safely(&efds[i]); |
| } |
| } |
| |
| static void |
| irqs_set_state(vfu_ctx_t *vfu_ctx, struct vfio_irq_set *irq_set) |
| { |
| vfu_dev_irq_state_cb_t *cb = NULL; |
| uint32_t irq_action; |
| bool mask = false; |
| |
| assert(irq_set->index < VFU_DEV_NUM_IRQS); |
| cb = vfu_ctx->irq_state_cbs[irq_set->index]; |
| if (cb == NULL) { |
| return; |
| } |
| |
| assert((irq_set->start + irq_set->count) <= |
| vfu_ctx->irq_count[irq_set->index]); |
| |
| irq_action = irq_set->flags & VFIO_IRQ_SET_ACTION_TYPE_MASK; |
| |
| assert((irq_action & VFIO_IRQ_SET_ACTION_MASK) || |
| (irq_action & VFIO_IRQ_SET_ACTION_UNMASK)); |
| |
| mask = (irq_action & VFIO_IRQ_SET_ACTION_MASK) ? true : false; |
| |
| cb(vfu_ctx, irq_set->start, irq_set->count, mask); |
| } |
| |
| static int * |
| irqs_get_efd(vfu_ctx_t *vfu_ctx, int index, int fd_idx) |
| { |
| switch (index) { |
| case VFIO_PCI_ERR_IRQ_INDEX: |
| return &vfu_ctx->irqs->err_efd; |
| case VFIO_PCI_REQ_IRQ_INDEX: |
| return &vfu_ctx->irqs->req_efd; |
| default: |
| return &vfu_ctx->irqs->efds[fd_idx]; |
| } |
| } |
| |
| static int |
| irqs_set_data_none(vfu_ctx_t *vfu_ctx, struct vfio_irq_set *irq_set) |
| { |
| int *efd; |
| uint32_t i; |
| long ret; |
| eventfd_t val; |
| |
| for (i = irq_set->start; i < (irq_set->start + irq_set->count); i++) { |
| efd = irqs_get_efd(vfu_ctx, irq_set->index, i); |
| if (*efd >= 0) { |
| val = 1; |
| ret = eventfd_write(*efd, val); |
| if (ret == -1) { |
| vfu_log(vfu_ctx, LOG_DEBUG, |
| "IRQ: failed to set data to none: %m"); |
| return -1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| irqs_set_data_bool(vfu_ctx_t *vfu_ctx, struct vfio_irq_set *irq_set, void *data) |
| { |
| uint8_t *d8; |
| int *efd; |
| uint32_t i; |
| long ret; |
| eventfd_t val; |
| |
| assert(data != NULL); |
| |
| for (i = irq_set->start, d8 = data; i < (irq_set->start + irq_set->count); |
| i++, d8++) { |
| efd = irqs_get_efd(vfu_ctx, irq_set->index, i); |
| if (*efd >= 0 && *d8 == 1) { |
| val = 1; |
| ret = eventfd_write(*efd, val); |
| if (ret == -1) { |
| vfu_log(vfu_ctx, LOG_DEBUG, |
| "IRQ: failed to set data to bool: %m"); |
| return -1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| irqs_set_data_eventfd(vfu_ctx_t *vfu_ctx, struct vfio_irq_set *irq_set, |
| int *data) |
| { |
| int *efd; |
| uint32_t i; |
| size_t j; |
| |
| assert(data != NULL); |
| for (i = irq_set->start, j = 0; i < (irq_set->start + irq_set->count); |
| i++, j++) { |
| efd = irqs_get_efd(vfu_ctx, irq_set->index, i); |
| close_safely(efd); |
| assert(data[j] >= 0); |
| /* |
| * We've already checked in handle_device_set_irqs that |
| * nr_fds == irq_set->count. |
| */ |
| *efd = consume_fd(data, irq_set->count, j); |
| switch (irq_set->index) { |
| case VFIO_PCI_ERR_IRQ_INDEX: |
| vfu_log(vfu_ctx, LOG_DEBUG, "err fd=%d", *efd); |
| break; |
| case VFIO_PCI_REQ_IRQ_INDEX: |
| vfu_log(vfu_ctx, LOG_DEBUG, "req fd=%d", *efd); |
| break; |
| default: |
| vfu_log(vfu_ctx, LOG_DEBUG, "event fd[%d]=%d", i, *efd); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| device_set_irqs_validate(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) |
| { |
| struct vfio_irq_set *irq_set = msg->in.iov.iov_base; |
| uint32_t a_type, d_type; |
| int line; |
| |
| assert(vfu_ctx != NULL); |
| assert(irq_set != NULL); |
| |
| if (msg->in.iov.iov_len < sizeof(*irq_set) || irq_set->argsz < sizeof(*irq_set)) { |
| vfu_log(vfu_ctx, LOG_ERR, "bad size %zu", msg->in.iov.iov_len); |
| return ERROR_INT(EINVAL); |
| } |
| |
| // Separate action and data types from flags. |
| a_type = (irq_set->flags & VFIO_IRQ_SET_ACTION_TYPE_MASK); |
| d_type = (irq_set->flags & VFIO_IRQ_SET_DATA_TYPE_MASK); |
| |
| // bools provided must match count |
| if (d_type == VFIO_IRQ_SET_DATA_BOOL && |
| (msg->in.iov.iov_len - sizeof(*irq_set) != sizeof(uint8_t) * irq_set->count)) { |
| line = __LINE__; |
| goto invalid; |
| } |
| |
| // Ensure index is within bounds. |
| if (irq_set->index >= VFU_DEV_NUM_IRQS) { |
| line = __LINE__; |
| goto invalid; |
| } |
| |
| // Only one of MASK/UNMASK/TRIGGER is valid. |
| if ((a_type != VFIO_IRQ_SET_ACTION_MASK) && |
| (a_type != VFIO_IRQ_SET_ACTION_UNMASK) && |
| (a_type != VFIO_IRQ_SET_ACTION_TRIGGER)) { |
| line = __LINE__; |
| goto invalid; |
| } |
| // Only one of NONE/BOOL/EVENTFD is valid. |
| if ((d_type != VFIO_IRQ_SET_DATA_NONE) && |
| (d_type != VFIO_IRQ_SET_DATA_BOOL) && |
| (d_type != VFIO_IRQ_SET_DATA_EVENTFD)) { |
| line = __LINE__; |
| goto invalid; |
| } |
| |
| // Ensure irq_set's start is within bounds. |
| if (irq_set->start >= vfu_ctx->irq_count[irq_set->index]) { |
| line = __LINE__; |
| goto invalid; |
| } |
| |
| // Ensure irq_set's start+count is within bounds. |
| if (satadd_u32(irq_set->start, irq_set->count) > |
| vfu_ctx->irq_count[irq_set->index]) { |
| line = __LINE__; |
| goto invalid; |
| } |
| |
| // Only TRIGGER is valid for ERR/REQ. |
| if (((irq_set->index == VFIO_PCI_ERR_IRQ_INDEX) || |
| (irq_set->index == VFIO_PCI_REQ_IRQ_INDEX)) && |
| (a_type != VFIO_IRQ_SET_ACTION_TRIGGER)) { |
| line = __LINE__; |
| goto invalid; |
| } |
| // count must be 0 or 1 for ERR/REQ |
| if (((irq_set->index == VFIO_PCI_ERR_IRQ_INDEX) || |
| (irq_set->index == VFIO_PCI_REQ_IRQ_INDEX)) && |
| (irq_set->count > 1)) { |
| line = __LINE__; |
| goto invalid; |
| } |
| // if count == 0, start must be 0 too |
| if ((irq_set->count == 0) && (irq_set->start != 0)) { |
| line = __LINE__; |
| goto invalid; |
| } |
| // count == 0 is only valid with ACTION_TRIGGER and DATA_NONE. |
| if ((irq_set->count == 0) && ((a_type != VFIO_IRQ_SET_ACTION_TRIGGER) || |
| (d_type != VFIO_IRQ_SET_DATA_NONE))) { |
| line = __LINE__; |
| goto invalid; |
| } |
| // If fd's are provided, ensure it's only for VFIO_IRQ_SET_DATA_EVENTFD |
| if (msg->in.nr_fds != 0 && d_type != VFIO_IRQ_SET_DATA_EVENTFD) { |
| line = __LINE__; |
| goto invalid; |
| } |
| // If fd's are provided, ensure they match ->count |
| if (msg->in.nr_fds != 0 && msg->in.nr_fds != irq_set->count) { |
| line = __LINE__; |
| goto invalid; |
| } |
| |
| return 0; |
| |
| invalid: |
| vfu_log(vfu_ctx, LOG_DEBUG, "invalid SET_IRQS (%d): action=%u data_type=%u " |
| "index=%u start=%u count=%u nr_fds=%zu", line, a_type, d_type, |
| irq_set->index, irq_set->start, irq_set->count, msg->in.nr_fds); |
| return ERROR_INT(EINVAL); |
| } |
| |
| int |
| handle_device_set_irqs(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) |
| { |
| struct vfio_irq_set *irq_set = msg->in.iov.iov_base; |
| uint32_t data_type; |
| int ret; |
| |
| assert(vfu_ctx != NULL); |
| assert(msg != NULL); |
| |
| ret = device_set_irqs_validate(vfu_ctx, msg); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| switch (irq_set->flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) { |
| case VFIO_IRQ_SET_ACTION_MASK: |
| case VFIO_IRQ_SET_ACTION_UNMASK: |
| irqs_set_state(vfu_ctx, irq_set); |
| return 0; |
| case VFIO_IRQ_SET_ACTION_TRIGGER: |
| break; |
| } |
| |
| data_type = irq_set->flags & VFIO_IRQ_SET_DATA_TYPE_MASK; |
| |
| if ((data_type == VFIO_IRQ_SET_DATA_NONE && irq_set->count == 0) || |
| (data_type == VFIO_IRQ_SET_DATA_EVENTFD && msg->in.nr_fds == 0)) { |
| irqs_disable(vfu_ctx, irq_set->index, irq_set->start, irq_set->count); |
| return 0; |
| } |
| |
| vfu_log(vfu_ctx, LOG_DEBUG, "setting IRQ %s flags=%#x range [%u, %u)", |
| vfio_irq_idx_to_str(irq_set->index), irq_set->flags, |
| irq_set->start, irq_set->start + irq_set->count); |
| |
| switch (data_type) { |
| case VFIO_IRQ_SET_DATA_NONE: |
| return irqs_set_data_none(vfu_ctx, irq_set); |
| case VFIO_IRQ_SET_DATA_EVENTFD: |
| return irqs_set_data_eventfd(vfu_ctx, irq_set, msg->in.fds); |
| case VFIO_IRQ_SET_DATA_BOOL: |
| return irqs_set_data_bool(vfu_ctx, irq_set, irq_set + 1); |
| break; |
| default: |
| // we already checked this |
| abort(); |
| } |
| } |
| |
| static bool |
| validate_irq_subindex(vfu_ctx_t *vfu_ctx, uint32_t subindex) |
| { |
| if ((subindex >= vfu_ctx->irqs->max_ivs)) { |
| vfu_log(vfu_ctx, LOG_ERR, "bad IRQ %d, max=%d", subindex, |
| vfu_ctx->irqs->max_ivs); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| EXPORT int |
| vfu_irq_trigger(vfu_ctx_t *vfu_ctx, uint32_t subindex) |
| { |
| eventfd_t val = 1; |
| |
| assert(vfu_ctx != NULL); |
| |
| if (!validate_irq_subindex(vfu_ctx, subindex)) { |
| return ERROR_INT(EINVAL); |
| } |
| |
| if (vfu_ctx->irqs->efds[subindex] == -1) { |
| vfu_log(vfu_ctx, LOG_ERR, "no fd for interrupt %d", subindex); |
| return ERROR_INT(ENOENT); |
| } |
| |
| return eventfd_write(vfu_ctx->irqs->efds[subindex], val); |
| } |
| |
| /* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */ |