/* * Copyright (c) 2020 Nutanix Inc. All rights reserved. * * Authors: Thanos Makatos * Swapnil Ingle * Felipe Franciosi * * 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 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 #include #include #include #include #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: */