blob: 183d07183fdcf31c4ae602e7782a06ae60936e10 [file] [log] [blame]
/*
* 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 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;
}
// 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: */