437 lines
13 KiB
C
437 lines
13 KiB
C
/*
|
|
* 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++) {
|
|
if (efds[i] >= 0) {
|
|
if (close(efds[i]) == -1) {
|
|
vfu_log(vfu_ctx, LOG_DEBUG, "failed to close IRQ fd %d: %m",
|
|
efds[i]);
|
|
}
|
|
|
|
efds[i] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
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++) {
|
|
if (efds[i] >= 0) {
|
|
if (close(efds[i]) == -1) {
|
|
vfu_log(vfu_ctx, LOG_DEBUG, "failed to close IRQ fd %d: %m",
|
|
efds[i]);
|
|
}
|
|
|
|
efds[i] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
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_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 = vfu_ctx->irqs->efds[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 = vfu_ctx->irqs->efds[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 = vfu_ctx->irqs->efds[i];
|
|
if (efd >= 0) {
|
|
if (close(efd) == -1) {
|
|
vfu_log(vfu_ctx, LOG_DEBUG, "failed to close IRQ fd %d: %m", efd);
|
|
}
|
|
|
|
vfu_ctx->irqs->efds[i] = -1;
|
|
}
|
|
assert(data[j] >= 0);
|
|
/*
|
|
* We've already checked in handle_device_set_irqs that
|
|
* nr_fds == irq_set->count.
|
|
*/
|
|
vfu_ctx->irqs->efds[i] = consume_fd(data, irq_set->count, j);
|
|
vfu_log(vfu_ctx, LOG_DEBUG, "event fd[%d]=%d", i, vfu_ctx->irqs->efds[i]);
|
|
}
|
|
|
|
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 and count are within bounds.
|
|
if ((irq_set->start >= vfu_ctx->irq_count[irq_set->index]) ||
|
|
(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;
|
|
}
|
|
// 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: */
|