2023-10-16 22:49:19

by Alex Williamson

[permalink] [raw]
Subject: [PATCH v2 0/2] vfio/mtty: Add migration support

We've seen a request for CI and development to have access to migratable
vfio devices without any specific hardware requirements. One way to do
that is to enable migration support on the mtty mdev sample driver, as
done here.

This device is particularly easy to migrate because it doesn't actually
do DMA, or in fact much of anything. Therefore we can claim P2P and
dirty logging as well. PRE_COPY support is also included in a similar
fashion to hisi_acc. This provides early compatibility testing, which
is probably over-done, but perhaps illustrates good practice with
matching data stream magic, versioning, and feature flags. These might
later be used for backwards compatibility, particularly since I'm not
positive that copying the struct serial_port between source and
destination is sufficient.

Along the way, testing migration where the source and target are
incompatible revealed an eventfd leak, where peeling back the onion
of mtty handling of SET_IRQS proved to require a substantial overhaul.

v2:
Incorporate comments from Cédric
- Fixing eventfd leak turned into SET_IRQS overhaul
- Factored out mtty_data_size()
- Factored mtty_save_state() and paired with mtty_load_state()

Alex Williamson (2):
vfio/mtty: Overhaul mtty interrupt handling
vfio/mtty: Enable migration support

samples/vfio-mdev/mtty.c | 829 +++++++++++++++++++++++++++++++++++----
1 file changed, 756 insertions(+), 73 deletions(-)

--
2.40.1


2023-10-16 22:49:20

by Alex Williamson

[permalink] [raw]
Subject: [PATCH v2 1/2] vfio/mtty: Overhaul mtty interrupt handling

The mtty driver does not currently conform to the vfio SET_IRQS uAPI.
For example, it claims to support mask and unmask of INTx, but actually
does nothing. It claims to support AUTOMASK for INTx, but doesn't. It
fails to teardown eventfds under the full semantics specified by the
SET_IRQS ioctl. It also fails to teardown eventfds when the device is
closed, leading to memory leaks. It claims to support the request IRQ,
but doesn't.

Fix all these.

A side effect of this is that QEMU will now report a warning:

vfio <uuid>: Failed to set up UNMASK eventfd signaling for interrupt \
INTX-0: VFIO_DEVICE_SET_IRQS failure: Inappropriate ioctl for device

The fact is that the unmask eventfd was never supported but quietly
failed. mtty never honored the AUTOMASK behavior, therefore there
was nothing to unmask. QEMU is verbose about the failure, but
properly falls back to userspace unmasking.

Fixes: 9d1a546c53b4 ("docs: Sample driver to demonstrate how to use Mediated device framework.")
Signed-off-by: Alex Williamson <[email protected]>
---
samples/vfio-mdev/mtty.c | 239 +++++++++++++++++++++++++++------------
1 file changed, 166 insertions(+), 73 deletions(-)

diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c
index 5af00387c519..245db52bedf2 100644
--- a/samples/vfio-mdev/mtty.c
+++ b/samples/vfio-mdev/mtty.c
@@ -127,7 +127,6 @@ struct serial_port {
/* State of each mdev device */
struct mdev_state {
struct vfio_device vdev;
- int irq_fd;
struct eventfd_ctx *intx_evtfd;
struct eventfd_ctx *msi_evtfd;
int irq_index;
@@ -141,6 +140,7 @@ struct mdev_state {
struct mutex rxtx_lock;
struct vfio_device_info dev_info;
int nr_ports;
+ u8 intx_mask:1;
};

static struct mtty_type {
@@ -166,10 +166,6 @@ static const struct file_operations vd_fops = {

static const struct vfio_device_ops mtty_dev_ops;

-/* function prototypes */
-
-static int mtty_trigger_interrupt(struct mdev_state *mdev_state);
-
/* Helper functions */

static void dump_buffer(u8 *buf, uint32_t count)
@@ -186,6 +182,36 @@ static void dump_buffer(u8 *buf, uint32_t count)
#endif
}

+static bool is_intx(struct mdev_state *mdev_state)
+{
+ return mdev_state->irq_index == VFIO_PCI_INTX_IRQ_INDEX;
+}
+
+static bool is_msi(struct mdev_state *mdev_state)
+{
+ return mdev_state->irq_index == VFIO_PCI_MSI_IRQ_INDEX;
+}
+
+static bool is_noirq(struct mdev_state *mdev_state)
+{
+ return !is_intx(mdev_state) && !is_msi(mdev_state);
+}
+
+static void mtty_trigger_interrupt(struct mdev_state *mdev_state)
+{
+ lockdep_assert_held(&mdev_state->ops_lock);
+
+ if (is_msi(mdev_state)) {
+ if (mdev_state->msi_evtfd)
+ eventfd_signal(mdev_state->msi_evtfd, 1);
+ } else if (is_intx(mdev_state)) {
+ if (mdev_state->intx_evtfd && !mdev_state->intx_mask) {
+ eventfd_signal(mdev_state->intx_evtfd, 1);
+ mdev_state->intx_mask = true;
+ }
+ }
+}
+
static void mtty_create_config_space(struct mdev_state *mdev_state)
{
/* PCI dev ID */
@@ -921,6 +947,25 @@ static ssize_t mtty_write(struct vfio_device *vdev, const char __user *buf,
return -EFAULT;
}

+static void mtty_disable_intx(struct mdev_state *mdev_state)
+{
+ if (mdev_state->intx_evtfd) {
+ eventfd_ctx_put(mdev_state->intx_evtfd);
+ mdev_state->intx_evtfd = NULL;
+ mdev_state->intx_mask = false;
+ mdev_state->irq_index = -1;
+ }
+}
+
+static void mtty_disable_msi(struct mdev_state *mdev_state)
+{
+ if (mdev_state->msi_evtfd) {
+ eventfd_ctx_put(mdev_state->msi_evtfd);
+ mdev_state->msi_evtfd = NULL;
+ mdev_state->irq_index = -1;
+ }
+}
+
static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
unsigned int index, unsigned int start,
unsigned int count, void *data)
@@ -932,59 +977,113 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
case VFIO_PCI_INTX_IRQ_INDEX:
switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
case VFIO_IRQ_SET_ACTION_MASK:
+ if (!is_intx(mdev_state) || start != 0 || count != 1) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (flags & VFIO_IRQ_SET_DATA_NONE) {
+ mdev_state->intx_mask = true;
+ } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
+ uint8_t mask = *(uint8_t *)data;
+
+ if (mask)
+ mdev_state->intx_mask = true;
+ } else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
+ ret = -ENOTTY; /* No support for mask fd */
+ }
+ break;
case VFIO_IRQ_SET_ACTION_UNMASK:
+ if (!is_intx(mdev_state) || start != 0 || count != 1) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (flags & VFIO_IRQ_SET_DATA_NONE) {
+ mdev_state->intx_mask = false;
+ } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
+ uint8_t mask = *(uint8_t *)data;
+
+ if (mask)
+ mdev_state->intx_mask = false;
+ } else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
+ ret = -ENOTTY; /* No support for unmask fd */
+ }
break;
case VFIO_IRQ_SET_ACTION_TRIGGER:
- {
- if (flags & VFIO_IRQ_SET_DATA_NONE) {
- pr_info("%s: disable INTx\n", __func__);
- if (mdev_state->intx_evtfd)
- eventfd_ctx_put(mdev_state->intx_evtfd);
+ if (is_intx(mdev_state) && !count &&
+ (flags & VFIO_IRQ_SET_DATA_NONE)) {
+ mtty_disable_intx(mdev_state);
+ break;
+ }
+
+ if (!(is_intx(mdev_state) || is_noirq(mdev_state)) ||
+ start != 0 || count != 1) {
+ ret = -EINVAL;
break;
}

if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
int fd = *(int *)data;
+ struct eventfd_ctx *evt;
+
+ mtty_disable_intx(mdev_state);
+
+ if (fd < 0)
+ break;

- if (fd > 0) {
- struct eventfd_ctx *evt;
-
- evt = eventfd_ctx_fdget(fd);
- if (IS_ERR(evt)) {
- ret = PTR_ERR(evt);
- break;
- }
- mdev_state->intx_evtfd = evt;
- mdev_state->irq_fd = fd;
- mdev_state->irq_index = index;
+ evt = eventfd_ctx_fdget(fd);
+ if (IS_ERR(evt)) {
+ ret = PTR_ERR(evt);
break;
}
+ mdev_state->intx_evtfd = evt;
+ mdev_state->irq_index = index;
+ break;
+ }
+
+ if (!is_intx(mdev_state)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (flags & VFIO_IRQ_SET_DATA_NONE) {
+ mtty_trigger_interrupt(mdev_state);
+ } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
+ uint8_t trigger = *(uint8_t *)data;
+
+ if (trigger)
+ mtty_trigger_interrupt(mdev_state);
}
break;
}
- }
break;
case VFIO_PCI_MSI_IRQ_INDEX:
switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
case VFIO_IRQ_SET_ACTION_MASK:
case VFIO_IRQ_SET_ACTION_UNMASK:
+ ret = -ENOTTY;
break;
case VFIO_IRQ_SET_ACTION_TRIGGER:
- if (flags & VFIO_IRQ_SET_DATA_NONE) {
- if (mdev_state->msi_evtfd)
- eventfd_ctx_put(mdev_state->msi_evtfd);
- pr_info("%s: disable MSI\n", __func__);
- mdev_state->irq_index = VFIO_PCI_INTX_IRQ_INDEX;
+ if (is_msi(mdev_state) && !count &&
+ (flags & VFIO_IRQ_SET_DATA_NONE)) {
+ mtty_disable_msi(mdev_state);
break;
}
+
+ if (!(is_msi(mdev_state) || is_noirq(mdev_state)) ||
+ start != 0 || count != 1) {
+ ret = -EINVAL;
+ break;
+ }
+
if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
int fd = *(int *)data;
struct eventfd_ctx *evt;

- if (fd <= 0)
- break;
+ mtty_disable_msi(mdev_state);

- if (mdev_state->msi_evtfd)
+ if (fd < 0)
break;

evt = eventfd_ctx_fdget(fd);
@@ -993,20 +1092,37 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
break;
}
mdev_state->msi_evtfd = evt;
- mdev_state->irq_fd = fd;
mdev_state->irq_index = index;
+ break;
+ }
+
+ if (!is_msi(mdev_state)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (flags & VFIO_IRQ_SET_DATA_NONE) {
+ mtty_trigger_interrupt(mdev_state);
+ } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
+ uint8_t trigger = *(uint8_t *)data;
+
+ if (trigger)
+ mtty_trigger_interrupt(mdev_state);
}
break;
- }
- break;
+ }
+ break;
case VFIO_PCI_MSIX_IRQ_INDEX:
- pr_info("%s: MSIX_IRQ\n", __func__);
+ dev_dbg(mdev_state->vdev.dev, "%s: MSIX_IRQ\n", __func__);
+ ret = -ENOTTY;
break;
case VFIO_PCI_ERR_IRQ_INDEX:
- pr_info("%s: ERR_IRQ\n", __func__);
+ dev_dbg(mdev_state->vdev.dev, "%s: ERR_IRQ\n", __func__);
+ ret = -ENOTTY;
break;
case VFIO_PCI_REQ_IRQ_INDEX:
- pr_info("%s: REQ_IRQ\n", __func__);
+ dev_dbg(mdev_state->vdev.dev, "%s: REQ_IRQ\n", __func__);
+ ret = -ENOTTY;
break;
}

@@ -1014,33 +1130,6 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
return ret;
}

-static int mtty_trigger_interrupt(struct mdev_state *mdev_state)
-{
- int ret = -1;
-
- if ((mdev_state->irq_index == VFIO_PCI_MSI_IRQ_INDEX) &&
- (!mdev_state->msi_evtfd))
- return -EINVAL;
- else if ((mdev_state->irq_index == VFIO_PCI_INTX_IRQ_INDEX) &&
- (!mdev_state->intx_evtfd)) {
- pr_info("%s: Intr eventfd not found\n", __func__);
- return -EINVAL;
- }
-
- if (mdev_state->irq_index == VFIO_PCI_MSI_IRQ_INDEX)
- ret = eventfd_signal(mdev_state->msi_evtfd, 1);
- else
- ret = eventfd_signal(mdev_state->intx_evtfd, 1);
-
-#if defined(DEBUG_INTR)
- pr_info("Intx triggered\n");
-#endif
- if (ret != 1)
- pr_err("%s: eventfd signal failed (%d)\n", __func__, ret);
-
- return ret;
-}
-
static int mtty_get_region_info(struct mdev_state *mdev_state,
struct vfio_region_info *region_info,
u16 *cap_type_id, void **cap_type)
@@ -1084,22 +1173,16 @@ static int mtty_get_region_info(struct mdev_state *mdev_state,

static int mtty_get_irq_info(struct vfio_irq_info *irq_info)
{
- switch (irq_info->index) {
- case VFIO_PCI_INTX_IRQ_INDEX:
- case VFIO_PCI_MSI_IRQ_INDEX:
- case VFIO_PCI_REQ_IRQ_INDEX:
- break;
-
- default:
+ if (irq_info->index != VFIO_PCI_INTX_IRQ_INDEX &&
+ irq_info->index != VFIO_PCI_MSI_IRQ_INDEX)
return -EINVAL;
- }

irq_info->flags = VFIO_IRQ_INFO_EVENTFD;
irq_info->count = 1;

if (irq_info->index == VFIO_PCI_INTX_IRQ_INDEX)
- irq_info->flags |= (VFIO_IRQ_INFO_MASKABLE |
- VFIO_IRQ_INFO_AUTOMASKED);
+ irq_info->flags |= VFIO_IRQ_INFO_MASKABLE |
+ VFIO_IRQ_INFO_AUTOMASKED;
else
irq_info->flags |= VFIO_IRQ_INFO_NORESIZE;

@@ -1262,6 +1345,15 @@ static unsigned int mtty_get_available(struct mdev_type *mtype)
return atomic_read(&mdev_avail_ports) / type->nr_ports;
}

+static void mtty_close(struct vfio_device *vdev)
+{
+ struct mdev_state *mdev_state =
+ container_of(vdev, struct mdev_state, vdev);
+
+ mtty_disable_intx(mdev_state);
+ mtty_disable_msi(mdev_state);
+}
+
static const struct vfio_device_ops mtty_dev_ops = {
.name = "vfio-mtty",
.init = mtty_init_dev,
@@ -1273,6 +1365,7 @@ static const struct vfio_device_ops mtty_dev_ops = {
.unbind_iommufd = vfio_iommufd_emulated_unbind,
.attach_ioas = vfio_iommufd_emulated_attach_ioas,
.detach_ioas = vfio_iommufd_emulated_detach_ioas,
+ .close_device = mtty_close,
};

static struct mdev_driver mtty_driver = {
--
2.40.1

2023-10-16 22:49:32

by Alex Williamson

[permalink] [raw]
Subject: [PATCH v2 2/2] vfio/mtty: Enable migration support

The mtty driver exposes a PCI serial device to userspace and therefore
makes an easy target for a sample device supporting migration. The device
does not make use of DMA, therefore we can easily claim support for the
migration P2P states, as well as dirty logging. This implementation also
makes use of PRE_COPY support in order to provide migration stream
compatibility testing, which should generally be considered good practice.

Signed-off-by: Alex Williamson <[email protected]>
---
samples/vfio-mdev/mtty.c | 590 +++++++++++++++++++++++++++++++++++++++
1 file changed, 590 insertions(+)

diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c
index 245db52bedf2..69ba0281f9e0 100644
--- a/samples/vfio-mdev/mtty.c
+++ b/samples/vfio-mdev/mtty.c
@@ -29,6 +29,8 @@
#include <linux/serial.h>
#include <uapi/linux/serial_reg.h>
#include <linux/eventfd.h>
+#include <linux/anon_inodes.h>
+
/*
* #defines
*/
@@ -124,6 +126,29 @@ struct serial_port {
u8 intr_trigger_level; /* interrupt trigger level */
};

+struct mtty_data {
+ u64 magic;
+#define MTTY_MAGIC 0x7e9d09898c3e2c4e /* Nothing clever, just random */
+ u32 major_ver;
+#define MTTY_MAJOR_VER 1
+ u32 minor_ver;
+#define MTTY_MINOR_VER 0
+ u32 nr_ports;
+ u32 flags;
+ struct serial_port ports[2];
+};
+
+struct mdev_state;
+
+struct mtty_migration_file {
+ struct file *filp;
+ struct mutex lock;
+ struct mdev_state *mdev_state;
+ struct mtty_data data;
+ ssize_t filled_size;
+ u8 disabled:1;
+};
+
/* State of each mdev device */
struct mdev_state {
struct vfio_device vdev;
@@ -140,6 +165,12 @@ struct mdev_state {
struct mutex rxtx_lock;
struct vfio_device_info dev_info;
int nr_ports;
+ enum vfio_device_mig_state state;
+ struct mutex state_mutex;
+ struct mutex reset_mutex;
+ struct mtty_migration_file *saving_migf;
+ struct mtty_migration_file *resuming_migf;
+ u8 deferred_reset:1;
u8 intx_mask:1;
};

@@ -743,6 +774,543 @@ static ssize_t mdev_access(struct mdev_state *mdev_state, u8 *buf, size_t count,
return ret;
}

+static size_t mtty_data_size(struct mdev_state *mdev_state)
+{
+ return offsetof(struct mtty_data, ports) +
+ (mdev_state->nr_ports * sizeof(struct serial_port));
+}
+
+static void mtty_disable_file(struct mtty_migration_file *migf)
+{
+ mutex_lock(&migf->lock);
+ migf->disabled = true;
+ migf->filled_size = 0;
+ migf->filp->f_pos = 0;
+ mutex_unlock(&migf->lock);
+}
+
+static void mtty_disable_files(struct mdev_state *mdev_state)
+{
+ if (mdev_state->saving_migf) {
+ mtty_disable_file(mdev_state->saving_migf);
+ fput(mdev_state->saving_migf->filp);
+ mdev_state->saving_migf = NULL;
+ }
+
+ if (mdev_state->resuming_migf) {
+ mtty_disable_file(mdev_state->resuming_migf);
+ fput(mdev_state->resuming_migf->filp);
+ mdev_state->resuming_migf = NULL;
+ }
+}
+
+static void mtty_state_mutex_unlock(struct mdev_state *mdev_state)
+{
+again:
+ mutex_lock(&mdev_state->reset_mutex);
+ if (mdev_state->deferred_reset) {
+ mdev_state->deferred_reset = false;
+ mutex_unlock(&mdev_state->reset_mutex);
+ mdev_state->state = VFIO_DEVICE_STATE_RUNNING;
+ mtty_disable_files(mdev_state);
+ goto again;
+ }
+ mutex_unlock(&mdev_state->state_mutex);
+ mutex_unlock(&mdev_state->reset_mutex);
+}
+
+static int mtty_release_migf(struct inode *inode, struct file *filp)
+{
+ struct mtty_migration_file *migf = filp->private_data;
+
+ mtty_disable_file(migf);
+ mutex_destroy(&migf->lock);
+ kfree(migf);
+
+ return 0;
+}
+
+static long mtty_precopy_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mtty_migration_file *migf = filp->private_data;
+ struct mdev_state *mdev_state = migf->mdev_state;
+ loff_t *pos = &filp->f_pos;
+ struct vfio_precopy_info info = {};
+ unsigned long minsz;
+ int ret;
+
+ if (cmd != VFIO_MIG_GET_PRECOPY_INFO)
+ return -ENOTTY;
+
+ minsz = offsetofend(struct vfio_precopy_info, dirty_bytes);
+
+ if (copy_from_user(&info, (void __user *)arg, minsz))
+ return -EFAULT;
+ if (info.argsz < minsz)
+ return -EINVAL;
+
+ mutex_lock(&mdev_state->state_mutex);
+ if (mdev_state->state != VFIO_DEVICE_STATE_PRE_COPY &&
+ mdev_state->state != VFIO_DEVICE_STATE_PRE_COPY_P2P) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ mutex_lock(&migf->lock);
+
+ if (migf->disabled) {
+ mutex_unlock(&migf->lock);
+ ret = -ENODEV;
+ goto unlock;
+ }
+
+ if (*pos > migf->filled_size) {
+ mutex_unlock(&migf->lock);
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ info.dirty_bytes = 0;
+ info.initial_bytes = migf->filled_size - *pos;
+ mutex_unlock(&migf->lock);
+
+ ret = copy_to_user((void __user *)arg, &info, minsz) ? -EFAULT : 0;
+unlock:
+ mtty_state_mutex_unlock(mdev_state);
+ return ret;
+}
+
+static ssize_t mtty_save_read(struct file *filp, char __user *buf,
+ size_t len, loff_t *pos)
+{
+ struct mtty_migration_file *migf = filp->private_data;
+ ssize_t ret = 0;
+
+ if (pos)
+ return -ESPIPE;
+
+ pos = &filp->f_pos;
+
+ mutex_lock(&migf->lock);
+
+ dev_dbg(migf->mdev_state->vdev.dev, "%s ask %zu\n", __func__, len);
+
+ if (migf->disabled) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (*pos > migf->filled_size) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ len = min_t(size_t, migf->filled_size - *pos, len);
+ if (len) {
+ if (copy_to_user(buf, (void *)&migf->data + *pos, len)) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+ *pos += len;
+ ret = len;
+ }
+out_unlock:
+ dev_dbg(migf->mdev_state->vdev.dev, "%s read %zu\n", __func__, ret);
+ mutex_unlock(&migf->lock);
+ return ret;
+}
+
+static const struct file_operations mtty_save_fops = {
+ .owner = THIS_MODULE,
+ .read = mtty_save_read,
+ .unlocked_ioctl = mtty_precopy_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .release = mtty_release_migf,
+ .llseek = no_llseek,
+};
+
+static void mtty_save_state(struct mdev_state *mdev_state)
+{
+ struct mtty_migration_file *migf = mdev_state->saving_migf;
+ int i;
+
+ mutex_lock(&migf->lock);
+ for (i = 0; i < mdev_state->nr_ports; i++) {
+ memcpy(&migf->data.ports[i],
+ &mdev_state->s[i], sizeof(struct serial_port));
+ migf->filled_size += sizeof(struct serial_port);
+ }
+ dev_dbg(mdev_state->vdev.dev,
+ "%s filled to %zu\n", __func__, migf->filled_size);
+ mutex_unlock(&migf->lock);
+}
+
+static int mtty_load_state(struct mdev_state *mdev_state)
+{
+ struct mtty_migration_file *migf = mdev_state->resuming_migf;
+ int i;
+
+ mutex_lock(&migf->lock);
+ /* magic and version already tested by resume write fn */
+ if (migf->filled_size < mtty_data_size(mdev_state)) {
+ dev_dbg(mdev_state->vdev.dev, "%s expected %zu, got %zu\n",
+ __func__, mtty_data_size(mdev_state),
+ migf->filled_size);
+ mutex_unlock(&migf->lock);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < mdev_state->nr_ports; i++)
+ memcpy(&mdev_state->s[i],
+ &migf->data.ports[i], sizeof(struct serial_port));
+
+ mutex_unlock(&migf->lock);
+ return 0;
+}
+
+static struct mtty_migration_file *
+mtty_save_device_data(struct mdev_state *mdev_state,
+ enum vfio_device_mig_state state)
+{
+ struct mtty_migration_file *migf = mdev_state->saving_migf;
+ struct mtty_migration_file *ret = NULL;
+
+ if (migf) {
+ if (state == VFIO_DEVICE_STATE_STOP_COPY)
+ goto fill_data;
+ return ret;
+ }
+
+ migf = kzalloc(sizeof(*migf), GFP_KERNEL_ACCOUNT);
+ if (!migf)
+ return ERR_PTR(-ENOMEM);
+
+ migf->filp = anon_inode_getfile("mtty_mig", &mtty_save_fops,
+ migf, O_RDONLY);
+ if (IS_ERR(migf->filp)) {
+ int rc = PTR_ERR(migf->filp);
+
+ kfree(migf);
+ return ERR_PTR(rc);
+ }
+
+ stream_open(migf->filp->f_inode, migf->filp);
+ mutex_init(&migf->lock);
+ migf->mdev_state = mdev_state;
+
+ migf->data.magic = MTTY_MAGIC;
+ migf->data.major_ver = MTTY_MAJOR_VER;
+ migf->data.minor_ver = MTTY_MINOR_VER;
+ migf->data.nr_ports = mdev_state->nr_ports;
+
+ migf->filled_size = offsetof(struct mtty_data, ports);
+
+ dev_dbg(mdev_state->vdev.dev, "%s filled header to %zu\n",
+ __func__, migf->filled_size);
+
+ ret = mdev_state->saving_migf = migf;
+
+fill_data:
+ if (state == VFIO_DEVICE_STATE_STOP_COPY)
+ mtty_save_state(mdev_state);
+
+ return ret;
+}
+
+static ssize_t mtty_resume_write(struct file *filp, const char __user *buf,
+ size_t len, loff_t *pos)
+{
+ struct mtty_migration_file *migf = filp->private_data;
+ struct mdev_state *mdev_state = migf->mdev_state;
+ loff_t requested_length;
+ ssize_t ret = 0;
+
+ if (pos)
+ return -ESPIPE;
+
+ pos = &filp->f_pos;
+
+ if (*pos < 0 ||
+ check_add_overflow((loff_t)len, *pos, &requested_length))
+ return -EINVAL;
+
+ if (requested_length > mtty_data_size(mdev_state))
+ return -ENOMEM;
+
+ mutex_lock(&migf->lock);
+
+ if (migf->disabled) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (copy_from_user((void *)&migf->data + *pos, buf, len)) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ *pos += len;
+ ret = len;
+
+ dev_dbg(migf->mdev_state->vdev.dev, "%s received %zu, total %zu\n",
+ __func__, len, migf->filled_size + len);
+
+ if (migf->filled_size < offsetof(struct mtty_data, ports) &&
+ migf->filled_size + len >= offsetof(struct mtty_data, ports)) {
+ if (migf->data.magic != MTTY_MAGIC || migf->data.flags ||
+ migf->data.major_ver != MTTY_MAJOR_VER ||
+ migf->data.minor_ver != MTTY_MINOR_VER ||
+ migf->data.nr_ports != mdev_state->nr_ports) {
+ dev_dbg(migf->mdev_state->vdev.dev,
+ "%s failed validation\n", __func__);
+ ret = -EFAULT;
+ } else {
+ dev_dbg(migf->mdev_state->vdev.dev,
+ "%s header validated\n", __func__);
+ }
+ }
+
+ migf->filled_size += len;
+
+out_unlock:
+ mutex_unlock(&migf->lock);
+ return ret;
+}
+
+static const struct file_operations mtty_resume_fops = {
+ .owner = THIS_MODULE,
+ .write = mtty_resume_write,
+ .release = mtty_release_migf,
+ .llseek = no_llseek,
+};
+
+static struct mtty_migration_file *
+mtty_resume_device_data(struct mdev_state *mdev_state)
+{
+ struct mtty_migration_file *migf;
+ int ret;
+
+ migf = kzalloc(sizeof(*migf), GFP_KERNEL_ACCOUNT);
+ if (!migf)
+ return ERR_PTR(-ENOMEM);
+
+ migf->filp = anon_inode_getfile("mtty_mig", &mtty_resume_fops,
+ migf, O_WRONLY);
+ if (IS_ERR(migf->filp)) {
+ ret = PTR_ERR(migf->filp);
+ kfree(migf);
+ return ERR_PTR(ret);
+ }
+
+ stream_open(migf->filp->f_inode, migf->filp);
+ mutex_init(&migf->lock);
+ migf->mdev_state = mdev_state;
+
+ mdev_state->resuming_migf = migf;
+
+ return migf;
+}
+
+static struct file *mtty_step_state(struct mdev_state *mdev_state,
+ enum vfio_device_mig_state new)
+{
+ enum vfio_device_mig_state cur = mdev_state->state;
+
+ dev_dbg(mdev_state->vdev.dev, "%s: %d -> %d\n", __func__, cur, new);
+
+ /*
+ * The following state transitions are no-op considering
+ * mtty does not do DMA nor require any explicit start/stop.
+ *
+ * RUNNING -> RUNNING_P2P
+ * RUNNING_P2P -> RUNNING
+ * RUNNING_P2P -> STOP
+ * PRE_COPY -> PRE_COPY_P2P
+ * PRE_COPY_P2P -> PRE_COPY
+ * STOP -> RUNNING_P2P
+ */
+ if ((cur == VFIO_DEVICE_STATE_RUNNING &&
+ new == VFIO_DEVICE_STATE_RUNNING_P2P) ||
+ (cur == VFIO_DEVICE_STATE_RUNNING_P2P &&
+ (new == VFIO_DEVICE_STATE_RUNNING ||
+ new == VFIO_DEVICE_STATE_STOP)) ||
+ (cur == VFIO_DEVICE_STATE_PRE_COPY &&
+ new == VFIO_DEVICE_STATE_PRE_COPY_P2P) ||
+ (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P &&
+ new == VFIO_DEVICE_STATE_PRE_COPY) ||
+ (cur == VFIO_DEVICE_STATE_STOP &&
+ new == VFIO_DEVICE_STATE_RUNNING_P2P))
+ return NULL;
+
+ /*
+ * The following state transitions simply close migration files,
+ * with the exception of RESUMING -> STOP, which needs to load
+ * the state first.
+ *
+ * RESUMING -> STOP
+ * PRE_COPY -> RUNNING
+ * PRE_COPY_P2P -> RUNNING_P2P
+ * STOP_COPY -> STOP
+ */
+ if (cur == VFIO_DEVICE_STATE_RESUMING &&
+ new == VFIO_DEVICE_STATE_STOP) {
+ int ret;
+
+ ret = mtty_load_state(mdev_state);
+ if (ret)
+ return ERR_PTR(ret);
+ mtty_disable_files(mdev_state);
+ return NULL;
+ }
+
+ if ((cur == VFIO_DEVICE_STATE_PRE_COPY &&
+ new == VFIO_DEVICE_STATE_RUNNING) ||
+ (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P &&
+ new == VFIO_DEVICE_STATE_RUNNING_P2P) ||
+ (cur == VFIO_DEVICE_STATE_STOP_COPY &&
+ new == VFIO_DEVICE_STATE_STOP)) {
+ mtty_disable_files(mdev_state);
+ return NULL;
+ }
+
+ /*
+ * The following state transitions return migration files.
+ *
+ * RUNNING -> PRE_COPY
+ * RUNNING_P2P -> PRE_COPY_P2P
+ * STOP -> STOP_COPY
+ * STOP -> RESUMING
+ * PRE_COPY_P2P -> STOP_COPY
+ */
+ if ((cur == VFIO_DEVICE_STATE_RUNNING &&
+ new == VFIO_DEVICE_STATE_PRE_COPY) ||
+ (cur == VFIO_DEVICE_STATE_RUNNING_P2P &&
+ new == VFIO_DEVICE_STATE_PRE_COPY_P2P) ||
+ (cur == VFIO_DEVICE_STATE_STOP &&
+ new == VFIO_DEVICE_STATE_STOP_COPY) ||
+ (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P &&
+ new == VFIO_DEVICE_STATE_STOP_COPY)) {
+ struct mtty_migration_file *migf;
+
+ migf = mtty_save_device_data(mdev_state, new);
+ if (IS_ERR(migf))
+ return ERR_CAST(migf);
+
+ if (migf) {
+ get_file(migf->filp);
+
+ return migf->filp;
+ }
+ return NULL;
+ }
+
+ if (cur == VFIO_DEVICE_STATE_STOP &&
+ new == VFIO_DEVICE_STATE_RESUMING) {
+ struct mtty_migration_file *migf;
+
+ migf = mtty_resume_device_data(mdev_state);
+ if (IS_ERR(migf))
+ return ERR_CAST(migf);
+
+ get_file(migf->filp);
+
+ return migf->filp;
+ }
+
+ /* vfio_mig_get_next_state() does not use arcs other than the above */
+ WARN_ON(true);
+ return ERR_PTR(-EINVAL);
+}
+
+static struct file *mtty_set_state(struct vfio_device *vdev,
+ enum vfio_device_mig_state new_state)
+{
+ struct mdev_state *mdev_state =
+ container_of(vdev, struct mdev_state, vdev);
+ struct file *ret = NULL;
+
+ dev_dbg(vdev->dev, "%s -> %d\n", __func__, new_state);
+
+ mutex_lock(&mdev_state->state_mutex);
+ while (mdev_state->state != new_state) {
+ enum vfio_device_mig_state next_state;
+ int rc = vfio_mig_get_next_state(vdev, mdev_state->state,
+ new_state, &next_state);
+ if (rc) {
+ ret = ERR_PTR(rc);
+ break;
+ }
+
+ ret = mtty_step_state(mdev_state, next_state);
+ if (IS_ERR(ret))
+ break;
+
+ mdev_state->state = next_state;
+
+ if (WARN_ON(ret && new_state != next_state)) {
+ fput(ret);
+ ret = ERR_PTR(-EINVAL);
+ break;
+ }
+ }
+ mtty_state_mutex_unlock(mdev_state);
+ return ret;
+}
+
+static int mtty_get_state(struct vfio_device *vdev,
+ enum vfio_device_mig_state *current_state)
+{
+ struct mdev_state *mdev_state =
+ container_of(vdev, struct mdev_state, vdev);
+
+ mutex_lock(&mdev_state->state_mutex);
+ *current_state = mdev_state->state;
+ mtty_state_mutex_unlock(mdev_state);
+ return 0;
+}
+
+static int mtty_get_data_size(struct vfio_device *vdev,
+ unsigned long *stop_copy_length)
+{
+ struct mdev_state *mdev_state =
+ container_of(vdev, struct mdev_state, vdev);
+
+ *stop_copy_length = mtty_data_size(mdev_state);
+ return 0;
+}
+
+static const struct vfio_migration_ops mtty_migration_ops = {
+ .migration_set_state = mtty_set_state,
+ .migration_get_state = mtty_get_state,
+ .migration_get_data_size = mtty_get_data_size,
+};
+
+static int mtty_log_start(struct vfio_device *vdev,
+ struct rb_root_cached *ranges,
+ u32 nnodes, u64 *page_size)
+{
+ return 0;
+}
+
+static int mtty_log_stop(struct vfio_device *vdev)
+{
+ return 0;
+}
+
+static int mtty_log_read_and_clear(struct vfio_device *vdev,
+ unsigned long iova, unsigned long length,
+ struct iova_bitmap *dirty)
+{
+ return 0;
+}
+
+static const struct vfio_log_ops mtty_log_ops = {
+ .log_start = mtty_log_start,
+ .log_stop = mtty_log_stop,
+ .log_read_and_clear = mtty_log_read_and_clear,
+};
+
static int mtty_init_dev(struct vfio_device *vdev)
{
struct mdev_state *mdev_state =
@@ -775,6 +1343,16 @@ static int mtty_init_dev(struct vfio_device *vdev)
mutex_init(&mdev_state->ops_lock);
mdev_state->mdev = mdev;
mtty_create_config_space(mdev_state);
+
+ mutex_init(&mdev_state->state_mutex);
+ mutex_init(&mdev_state->reset_mutex);
+ vdev->migration_flags = VFIO_MIGRATION_STOP_COPY |
+ VFIO_MIGRATION_P2P |
+ VFIO_MIGRATION_PRE_COPY;
+ vdev->mig_ops = &mtty_migration_ops;
+ vdev->log_ops = &mtty_log_ops;
+ mdev_state->state = VFIO_DEVICE_STATE_RUNNING;
+
return 0;

err_nr_ports:
@@ -808,6 +1386,8 @@ static void mtty_release_dev(struct vfio_device *vdev)
struct mdev_state *mdev_state =
container_of(vdev, struct mdev_state, vdev);

+ mutex_destroy(&mdev_state->reset_mutex);
+ mutex_destroy(&mdev_state->state_mutex);
atomic_add(mdev_state->nr_ports, &mdev_avail_ports);
kfree(mdev_state->vconfig);
}
@@ -824,6 +1404,15 @@ static int mtty_reset(struct mdev_state *mdev_state)
{
pr_info("%s: called\n", __func__);

+ mutex_lock(&mdev_state->reset_mutex);
+ mdev_state->deferred_reset = true;
+ if (!mutex_trylock(&mdev_state->state_mutex)) {
+ mutex_unlock(&mdev_state->reset_mutex);
+ return 0;
+ }
+ mutex_unlock(&mdev_state->reset_mutex);
+ mtty_state_mutex_unlock(mdev_state);
+
return 0;
}

@@ -1350,6 +1939,7 @@ static void mtty_close(struct vfio_device *vdev)
struct mdev_state *mdev_state =
container_of(vdev, struct mdev_state, vdev);

+ mtty_disable_files(mdev_state);
mtty_disable_intx(mdev_state);
mtty_disable_msi(mdev_state);
}
--
2.40.1

2023-10-17 14:19:05

by Cédric Le Goater

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] vfio/mtty: Overhaul mtty interrupt handling

On 10/17/23 00:47, Alex Williamson wrote:
> The mtty driver does not currently conform to the vfio SET_IRQS uAPI.
> For example, it claims to support mask and unmask of INTx, but actually
> does nothing. It claims to support AUTOMASK for INTx, but doesn't. It
> fails to teardown eventfds under the full semantics specified by the
> SET_IRQS ioctl. It also fails to teardown eventfds when the device is
> closed, leading to memory leaks. It claims to support the request IRQ,
> but doesn't.
>
> Fix all these.
>
> A side effect of this is that QEMU will now report a warning:
>
> vfio <uuid>: Failed to set up UNMASK eventfd signaling for interrupt \
> INTX-0: VFIO_DEVICE_SET_IRQS failure: Inappropriate ioctl for device
>
> The fact is that the unmask eventfd was never supported but quietly
> failed. mtty never honored the AUTOMASK behavior, therefore there
> was nothing to unmask. QEMU is verbose about the failure, but
> properly falls back to userspace unmasking.

We can add a -ENOTTY test in QEMU for the failures.

Reviewed-by: Cédric Le Goater <[email protected]>

Thanks,

C.

> Fixes: 9d1a546c53b4 ("docs: Sample driver to demonstrate how to use Mediated device framework.")
> Signed-off-by: Alex Williamson <[email protected]>
> ---
> samples/vfio-mdev/mtty.c | 239 +++++++++++++++++++++++++++------------
> 1 file changed, 166 insertions(+), 73 deletions(-)
>
> diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c
> index 5af00387c519..245db52bedf2 100644
> --- a/samples/vfio-mdev/mtty.c
> +++ b/samples/vfio-mdev/mtty.c
> @@ -127,7 +127,6 @@ struct serial_port {
> /* State of each mdev device */
> struct mdev_state {
> struct vfio_device vdev;
> - int irq_fd;
> struct eventfd_ctx *intx_evtfd;
> struct eventfd_ctx *msi_evtfd;
> int irq_index;
> @@ -141,6 +140,7 @@ struct mdev_state {
> struct mutex rxtx_lock;
> struct vfio_device_info dev_info;
> int nr_ports;
> + u8 intx_mask:1;
> };
>
> static struct mtty_type {
> @@ -166,10 +166,6 @@ static const struct file_operations vd_fops = {
>
> static const struct vfio_device_ops mtty_dev_ops;
>
> -/* function prototypes */
> -
> -static int mtty_trigger_interrupt(struct mdev_state *mdev_state);
> -
> /* Helper functions */
>
> static void dump_buffer(u8 *buf, uint32_t count)
> @@ -186,6 +182,36 @@ static void dump_buffer(u8 *buf, uint32_t count)
> #endif
> }
>
> +static bool is_intx(struct mdev_state *mdev_state)
> +{
> + return mdev_state->irq_index == VFIO_PCI_INTX_IRQ_INDEX;
> +}
> +
> +static bool is_msi(struct mdev_state *mdev_state)
> +{
> + return mdev_state->irq_index == VFIO_PCI_MSI_IRQ_INDEX;
> +}
> +
> +static bool is_noirq(struct mdev_state *mdev_state)
> +{
> + return !is_intx(mdev_state) && !is_msi(mdev_state);
> +}
> +
> +static void mtty_trigger_interrupt(struct mdev_state *mdev_state)
> +{
> + lockdep_assert_held(&mdev_state->ops_lock);
> +
> + if (is_msi(mdev_state)) {
> + if (mdev_state->msi_evtfd)
> + eventfd_signal(mdev_state->msi_evtfd, 1);
> + } else if (is_intx(mdev_state)) {
> + if (mdev_state->intx_evtfd && !mdev_state->intx_mask) {
> + eventfd_signal(mdev_state->intx_evtfd, 1);
> + mdev_state->intx_mask = true;
> + }
> + }
> +}
> +
> static void mtty_create_config_space(struct mdev_state *mdev_state)
> {
> /* PCI dev ID */
> @@ -921,6 +947,25 @@ static ssize_t mtty_write(struct vfio_device *vdev, const char __user *buf,
> return -EFAULT;
> }
>
> +static void mtty_disable_intx(struct mdev_state *mdev_state)
> +{
> + if (mdev_state->intx_evtfd) {
> + eventfd_ctx_put(mdev_state->intx_evtfd);
> + mdev_state->intx_evtfd = NULL;
> + mdev_state->intx_mask = false;
> + mdev_state->irq_index = -1;
> + }
> +}
> +
> +static void mtty_disable_msi(struct mdev_state *mdev_state)
> +{
> + if (mdev_state->msi_evtfd) {
> + eventfd_ctx_put(mdev_state->msi_evtfd);
> + mdev_state->msi_evtfd = NULL;
> + mdev_state->irq_index = -1;
> + }
> +}
> +
> static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
> unsigned int index, unsigned int start,
> unsigned int count, void *data)
> @@ -932,59 +977,113 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
> case VFIO_PCI_INTX_IRQ_INDEX:
> switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
> case VFIO_IRQ_SET_ACTION_MASK:
> + if (!is_intx(mdev_state) || start != 0 || count != 1) {
> + ret = -EINVAL;
> + break;
> + }
> +
> + if (flags & VFIO_IRQ_SET_DATA_NONE) {
> + mdev_state->intx_mask = true;
> + } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
> + uint8_t mask = *(uint8_t *)data;
> +
> + if (mask)
> + mdev_state->intx_mask = true;
> + } else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
> + ret = -ENOTTY; /* No support for mask fd */
> + }
> + break;
> case VFIO_IRQ_SET_ACTION_UNMASK:
> + if (!is_intx(mdev_state) || start != 0 || count != 1) {
> + ret = -EINVAL;
> + break;
> + }
> +
> + if (flags & VFIO_IRQ_SET_DATA_NONE) {
> + mdev_state->intx_mask = false;
> + } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
> + uint8_t mask = *(uint8_t *)data;
> +
> + if (mask)
> + mdev_state->intx_mask = false;
> + } else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
> + ret = -ENOTTY; /* No support for unmask fd */
> + }
> break;
> case VFIO_IRQ_SET_ACTION_TRIGGER:
> - {
> - if (flags & VFIO_IRQ_SET_DATA_NONE) {
> - pr_info("%s: disable INTx\n", __func__);
> - if (mdev_state->intx_evtfd)
> - eventfd_ctx_put(mdev_state->intx_evtfd);
> + if (is_intx(mdev_state) && !count &&
> + (flags & VFIO_IRQ_SET_DATA_NONE)) {
> + mtty_disable_intx(mdev_state);
> + break;
> + }
> +
> + if (!(is_intx(mdev_state) || is_noirq(mdev_state)) ||
> + start != 0 || count != 1) {
> + ret = -EINVAL;
> break;
> }
>
> if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
> int fd = *(int *)data;
> + struct eventfd_ctx *evt;
> +
> + mtty_disable_intx(mdev_state);
> +
> + if (fd < 0)
> + break;
>
> - if (fd > 0) {
> - struct eventfd_ctx *evt;
> -
> - evt = eventfd_ctx_fdget(fd);
> - if (IS_ERR(evt)) {
> - ret = PTR_ERR(evt);
> - break;
> - }
> - mdev_state->intx_evtfd = evt;
> - mdev_state->irq_fd = fd;
> - mdev_state->irq_index = index;
> + evt = eventfd_ctx_fdget(fd);
> + if (IS_ERR(evt)) {
> + ret = PTR_ERR(evt);
> break;
> }
> + mdev_state->intx_evtfd = evt;
> + mdev_state->irq_index = index;
> + break;
> + }
> +
> + if (!is_intx(mdev_state)) {
> + ret = -EINVAL;
> + break;
> + }
> +
> + if (flags & VFIO_IRQ_SET_DATA_NONE) {
> + mtty_trigger_interrupt(mdev_state);
> + } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
> + uint8_t trigger = *(uint8_t *)data;
> +
> + if (trigger)
> + mtty_trigger_interrupt(mdev_state);
> }
> break;
> }
> - }
> break;
> case VFIO_PCI_MSI_IRQ_INDEX:
> switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
> case VFIO_IRQ_SET_ACTION_MASK:
> case VFIO_IRQ_SET_ACTION_UNMASK:
> + ret = -ENOTTY;
> break;
> case VFIO_IRQ_SET_ACTION_TRIGGER:
> - if (flags & VFIO_IRQ_SET_DATA_NONE) {
> - if (mdev_state->msi_evtfd)
> - eventfd_ctx_put(mdev_state->msi_evtfd);
> - pr_info("%s: disable MSI\n", __func__);
> - mdev_state->irq_index = VFIO_PCI_INTX_IRQ_INDEX;
> + if (is_msi(mdev_state) && !count &&
> + (flags & VFIO_IRQ_SET_DATA_NONE)) {
> + mtty_disable_msi(mdev_state);
> break;
> }
> +
> + if (!(is_msi(mdev_state) || is_noirq(mdev_state)) ||
> + start != 0 || count != 1) {
> + ret = -EINVAL;
> + break;
> + }
> +
> if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
> int fd = *(int *)data;
> struct eventfd_ctx *evt;
>
> - if (fd <= 0)
> - break;
> + mtty_disable_msi(mdev_state);
>
> - if (mdev_state->msi_evtfd)
> + if (fd < 0)
> break;
>
> evt = eventfd_ctx_fdget(fd);
> @@ -993,20 +1092,37 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
> break;
> }
> mdev_state->msi_evtfd = evt;
> - mdev_state->irq_fd = fd;
> mdev_state->irq_index = index;
> + break;
> + }
> +
> + if (!is_msi(mdev_state)) {
> + ret = -EINVAL;
> + break;
> + }
> +
> + if (flags & VFIO_IRQ_SET_DATA_NONE) {
> + mtty_trigger_interrupt(mdev_state);
> + } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
> + uint8_t trigger = *(uint8_t *)data;
> +
> + if (trigger)
> + mtty_trigger_interrupt(mdev_state);
> }
> break;
> - }
> - break;
> + }
> + break;
> case VFIO_PCI_MSIX_IRQ_INDEX:
> - pr_info("%s: MSIX_IRQ\n", __func__);
> + dev_dbg(mdev_state->vdev.dev, "%s: MSIX_IRQ\n", __func__);
> + ret = -ENOTTY;
> break;
> case VFIO_PCI_ERR_IRQ_INDEX:
> - pr_info("%s: ERR_IRQ\n", __func__);
> + dev_dbg(mdev_state->vdev.dev, "%s: ERR_IRQ\n", __func__);
> + ret = -ENOTTY;
> break;
> case VFIO_PCI_REQ_IRQ_INDEX:
> - pr_info("%s: REQ_IRQ\n", __func__);
> + dev_dbg(mdev_state->vdev.dev, "%s: REQ_IRQ\n", __func__);
> + ret = -ENOTTY;
> break;
> }
>
> @@ -1014,33 +1130,6 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags,
> return ret;
> }
>
> -static int mtty_trigger_interrupt(struct mdev_state *mdev_state)
> -{
> - int ret = -1;
> -
> - if ((mdev_state->irq_index == VFIO_PCI_MSI_IRQ_INDEX) &&
> - (!mdev_state->msi_evtfd))
> - return -EINVAL;
> - else if ((mdev_state->irq_index == VFIO_PCI_INTX_IRQ_INDEX) &&
> - (!mdev_state->intx_evtfd)) {
> - pr_info("%s: Intr eventfd not found\n", __func__);
> - return -EINVAL;
> - }
> -
> - if (mdev_state->irq_index == VFIO_PCI_MSI_IRQ_INDEX)
> - ret = eventfd_signal(mdev_state->msi_evtfd, 1);
> - else
> - ret = eventfd_signal(mdev_state->intx_evtfd, 1);
> -
> -#if defined(DEBUG_INTR)
> - pr_info("Intx triggered\n");
> -#endif
> - if (ret != 1)
> - pr_err("%s: eventfd signal failed (%d)\n", __func__, ret);
> -
> - return ret;
> -}
> -
> static int mtty_get_region_info(struct mdev_state *mdev_state,
> struct vfio_region_info *region_info,
> u16 *cap_type_id, void **cap_type)
> @@ -1084,22 +1173,16 @@ static int mtty_get_region_info(struct mdev_state *mdev_state,
>
> static int mtty_get_irq_info(struct vfio_irq_info *irq_info)
> {
> - switch (irq_info->index) {
> - case VFIO_PCI_INTX_IRQ_INDEX:
> - case VFIO_PCI_MSI_IRQ_INDEX:
> - case VFIO_PCI_REQ_IRQ_INDEX:
> - break;
> -
> - default:
> + if (irq_info->index != VFIO_PCI_INTX_IRQ_INDEX &&
> + irq_info->index != VFIO_PCI_MSI_IRQ_INDEX)
> return -EINVAL;
> - }
>
> irq_info->flags = VFIO_IRQ_INFO_EVENTFD;
> irq_info->count = 1;
>
> if (irq_info->index == VFIO_PCI_INTX_IRQ_INDEX)
> - irq_info->flags |= (VFIO_IRQ_INFO_MASKABLE |
> - VFIO_IRQ_INFO_AUTOMASKED);
> + irq_info->flags |= VFIO_IRQ_INFO_MASKABLE |
> + VFIO_IRQ_INFO_AUTOMASKED;
> else
> irq_info->flags |= VFIO_IRQ_INFO_NORESIZE;
>
> @@ -1262,6 +1345,15 @@ static unsigned int mtty_get_available(struct mdev_type *mtype)
> return atomic_read(&mdev_avail_ports) / type->nr_ports;
> }
>
> +static void mtty_close(struct vfio_device *vdev)
> +{
> + struct mdev_state *mdev_state =
> + container_of(vdev, struct mdev_state, vdev);
> +
> + mtty_disable_intx(mdev_state);
> + mtty_disable_msi(mdev_state);
> +}
> +
> static const struct vfio_device_ops mtty_dev_ops = {
> .name = "vfio-mtty",
> .init = mtty_init_dev,
> @@ -1273,6 +1365,7 @@ static const struct vfio_device_ops mtty_dev_ops = {
> .unbind_iommufd = vfio_iommufd_emulated_unbind,
> .attach_ioas = vfio_iommufd_emulated_attach_ioas,
> .detach_ioas = vfio_iommufd_emulated_detach_ioas,
> + .close_device = mtty_close,
> };
>
> static struct mdev_driver mtty_driver = {

2023-10-17 14:46:08

by Cédric Le Goater

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] vfio/mtty: Enable migration support

On 10/17/23 00:47, Alex Williamson wrote:
> The mtty driver exposes a PCI serial device to userspace and therefore
> makes an easy target for a sample device supporting migration. The device
> does not make use of DMA, therefore we can easily claim support for the
> migration P2P states, as well as dirty logging. This implementation also
> makes use of PRE_COPY support in order to provide migration stream
> compatibility testing, which should generally be considered good practice.
>
> Signed-off-by: Alex Williamson <[email protected]>


Reviewed-by: Cédric Le Goater <[email protected]>

Thanks,

C.


> ---
> samples/vfio-mdev/mtty.c | 590 +++++++++++++++++++++++++++++++++++++++
> 1 file changed, 590 insertions(+)
>
> diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c
> index 245db52bedf2..69ba0281f9e0 100644
> --- a/samples/vfio-mdev/mtty.c
> +++ b/samples/vfio-mdev/mtty.c
> @@ -29,6 +29,8 @@
> #include <linux/serial.h>
> #include <uapi/linux/serial_reg.h>
> #include <linux/eventfd.h>
> +#include <linux/anon_inodes.h>
> +
> /*
> * #defines
> */
> @@ -124,6 +126,29 @@ struct serial_port {
> u8 intr_trigger_level; /* interrupt trigger level */
> };
>
> +struct mtty_data {
> + u64 magic;
> +#define MTTY_MAGIC 0x7e9d09898c3e2c4e /* Nothing clever, just random */
> + u32 major_ver;
> +#define MTTY_MAJOR_VER 1
> + u32 minor_ver;
> +#define MTTY_MINOR_VER 0
> + u32 nr_ports;
> + u32 flags;
> + struct serial_port ports[2];
> +};
> +
> +struct mdev_state;
> +
> +struct mtty_migration_file {
> + struct file *filp;
> + struct mutex lock;
> + struct mdev_state *mdev_state;
> + struct mtty_data data;
> + ssize_t filled_size;
> + u8 disabled:1;
> +};
> +
> /* State of each mdev device */
> struct mdev_state {
> struct vfio_device vdev;
> @@ -140,6 +165,12 @@ struct mdev_state {
> struct mutex rxtx_lock;
> struct vfio_device_info dev_info;
> int nr_ports;
> + enum vfio_device_mig_state state;
> + struct mutex state_mutex;
> + struct mutex reset_mutex;
> + struct mtty_migration_file *saving_migf;
> + struct mtty_migration_file *resuming_migf;
> + u8 deferred_reset:1;
> u8 intx_mask:1;
> };
>
> @@ -743,6 +774,543 @@ static ssize_t mdev_access(struct mdev_state *mdev_state, u8 *buf, size_t count,
> return ret;
> }
>
> +static size_t mtty_data_size(struct mdev_state *mdev_state)
> +{
> + return offsetof(struct mtty_data, ports) +
> + (mdev_state->nr_ports * sizeof(struct serial_port));
> +}
> +
> +static void mtty_disable_file(struct mtty_migration_file *migf)
> +{
> + mutex_lock(&migf->lock);
> + migf->disabled = true;
> + migf->filled_size = 0;
> + migf->filp->f_pos = 0;
> + mutex_unlock(&migf->lock);
> +}
> +
> +static void mtty_disable_files(struct mdev_state *mdev_state)
> +{
> + if (mdev_state->saving_migf) {
> + mtty_disable_file(mdev_state->saving_migf);
> + fput(mdev_state->saving_migf->filp);
> + mdev_state->saving_migf = NULL;
> + }
> +
> + if (mdev_state->resuming_migf) {
> + mtty_disable_file(mdev_state->resuming_migf);
> + fput(mdev_state->resuming_migf->filp);
> + mdev_state->resuming_migf = NULL;
> + }
> +}
> +
> +static void mtty_state_mutex_unlock(struct mdev_state *mdev_state)
> +{
> +again:
> + mutex_lock(&mdev_state->reset_mutex);
> + if (mdev_state->deferred_reset) {
> + mdev_state->deferred_reset = false;
> + mutex_unlock(&mdev_state->reset_mutex);
> + mdev_state->state = VFIO_DEVICE_STATE_RUNNING;
> + mtty_disable_files(mdev_state);
> + goto again;
> + }
> + mutex_unlock(&mdev_state->state_mutex);
> + mutex_unlock(&mdev_state->reset_mutex);
> +}
> +
> +static int mtty_release_migf(struct inode *inode, struct file *filp)
> +{
> + struct mtty_migration_file *migf = filp->private_data;
> +
> + mtty_disable_file(migf);
> + mutex_destroy(&migf->lock);
> + kfree(migf);
> +
> + return 0;
> +}
> +
> +static long mtty_precopy_ioctl(struct file *filp, unsigned int cmd,
> + unsigned long arg)
> +{
> + struct mtty_migration_file *migf = filp->private_data;
> + struct mdev_state *mdev_state = migf->mdev_state;
> + loff_t *pos = &filp->f_pos;
> + struct vfio_precopy_info info = {};
> + unsigned long minsz;
> + int ret;
> +
> + if (cmd != VFIO_MIG_GET_PRECOPY_INFO)
> + return -ENOTTY;
> +
> + minsz = offsetofend(struct vfio_precopy_info, dirty_bytes);
> +
> + if (copy_from_user(&info, (void __user *)arg, minsz))
> + return -EFAULT;
> + if (info.argsz < minsz)
> + return -EINVAL;
> +
> + mutex_lock(&mdev_state->state_mutex);
> + if (mdev_state->state != VFIO_DEVICE_STATE_PRE_COPY &&
> + mdev_state->state != VFIO_DEVICE_STATE_PRE_COPY_P2P) {
> + ret = -EINVAL;
> + goto unlock;
> + }
> +
> + mutex_lock(&migf->lock);
> +
> + if (migf->disabled) {
> + mutex_unlock(&migf->lock);
> + ret = -ENODEV;
> + goto unlock;
> + }
> +
> + if (*pos > migf->filled_size) {
> + mutex_unlock(&migf->lock);
> + ret = -EINVAL;
> + goto unlock;
> + }
> +
> + info.dirty_bytes = 0;
> + info.initial_bytes = migf->filled_size - *pos;
> + mutex_unlock(&migf->lock);
> +
> + ret = copy_to_user((void __user *)arg, &info, minsz) ? -EFAULT : 0;
> +unlock:
> + mtty_state_mutex_unlock(mdev_state);
> + return ret;
> +}
> +
> +static ssize_t mtty_save_read(struct file *filp, char __user *buf,
> + size_t len, loff_t *pos)
> +{
> + struct mtty_migration_file *migf = filp->private_data;
> + ssize_t ret = 0;
> +
> + if (pos)
> + return -ESPIPE;
> +
> + pos = &filp->f_pos;
> +
> + mutex_lock(&migf->lock);
> +
> + dev_dbg(migf->mdev_state->vdev.dev, "%s ask %zu\n", __func__, len);
> +
> + if (migf->disabled) {
> + ret = -ENODEV;
> + goto out_unlock;
> + }
> +
> + if (*pos > migf->filled_size) {
> + ret = -EINVAL;
> + goto out_unlock;
> + }
> +
> + len = min_t(size_t, migf->filled_size - *pos, len);
> + if (len) {
> + if (copy_to_user(buf, (void *)&migf->data + *pos, len)) {
> + ret = -EFAULT;
> + goto out_unlock;
> + }
> + *pos += len;
> + ret = len;
> + }
> +out_unlock:
> + dev_dbg(migf->mdev_state->vdev.dev, "%s read %zu\n", __func__, ret);
> + mutex_unlock(&migf->lock);
> + return ret;
> +}
> +
> +static const struct file_operations mtty_save_fops = {
> + .owner = THIS_MODULE,
> + .read = mtty_save_read,
> + .unlocked_ioctl = mtty_precopy_ioctl,
> + .compat_ioctl = compat_ptr_ioctl,
> + .release = mtty_release_migf,
> + .llseek = no_llseek,
> +};
> +
> +static void mtty_save_state(struct mdev_state *mdev_state)
> +{
> + struct mtty_migration_file *migf = mdev_state->saving_migf;
> + int i;
> +
> + mutex_lock(&migf->lock);
> + for (i = 0; i < mdev_state->nr_ports; i++) {
> + memcpy(&migf->data.ports[i],
> + &mdev_state->s[i], sizeof(struct serial_port));
> + migf->filled_size += sizeof(struct serial_port);
> + }
> + dev_dbg(mdev_state->vdev.dev,
> + "%s filled to %zu\n", __func__, migf->filled_size);
> + mutex_unlock(&migf->lock);
> +}
> +
> +static int mtty_load_state(struct mdev_state *mdev_state)
> +{
> + struct mtty_migration_file *migf = mdev_state->resuming_migf;
> + int i;
> +
> + mutex_lock(&migf->lock);
> + /* magic and version already tested by resume write fn */
> + if (migf->filled_size < mtty_data_size(mdev_state)) {
> + dev_dbg(mdev_state->vdev.dev, "%s expected %zu, got %zu\n",
> + __func__, mtty_data_size(mdev_state),
> + migf->filled_size);
> + mutex_unlock(&migf->lock);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < mdev_state->nr_ports; i++)
> + memcpy(&mdev_state->s[i],
> + &migf->data.ports[i], sizeof(struct serial_port));
> +
> + mutex_unlock(&migf->lock);
> + return 0;
> +}
> +
> +static struct mtty_migration_file *
> +mtty_save_device_data(struct mdev_state *mdev_state,
> + enum vfio_device_mig_state state)
> +{
> + struct mtty_migration_file *migf = mdev_state->saving_migf;
> + struct mtty_migration_file *ret = NULL;
> +
> + if (migf) {
> + if (state == VFIO_DEVICE_STATE_STOP_COPY)
> + goto fill_data;
> + return ret;
> + }
> +
> + migf = kzalloc(sizeof(*migf), GFP_KERNEL_ACCOUNT);
> + if (!migf)
> + return ERR_PTR(-ENOMEM);
> +
> + migf->filp = anon_inode_getfile("mtty_mig", &mtty_save_fops,
> + migf, O_RDONLY);
> + if (IS_ERR(migf->filp)) {
> + int rc = PTR_ERR(migf->filp);
> +
> + kfree(migf);
> + return ERR_PTR(rc);
> + }
> +
> + stream_open(migf->filp->f_inode, migf->filp);
> + mutex_init(&migf->lock);
> + migf->mdev_state = mdev_state;
> +
> + migf->data.magic = MTTY_MAGIC;
> + migf->data.major_ver = MTTY_MAJOR_VER;
> + migf->data.minor_ver = MTTY_MINOR_VER;
> + migf->data.nr_ports = mdev_state->nr_ports;
> +
> + migf->filled_size = offsetof(struct mtty_data, ports);
> +
> + dev_dbg(mdev_state->vdev.dev, "%s filled header to %zu\n",
> + __func__, migf->filled_size);
> +
> + ret = mdev_state->saving_migf = migf;
> +
> +fill_data:
> + if (state == VFIO_DEVICE_STATE_STOP_COPY)
> + mtty_save_state(mdev_state);
> +
> + return ret;
> +}
> +
> +static ssize_t mtty_resume_write(struct file *filp, const char __user *buf,
> + size_t len, loff_t *pos)
> +{
> + struct mtty_migration_file *migf = filp->private_data;
> + struct mdev_state *mdev_state = migf->mdev_state;
> + loff_t requested_length;
> + ssize_t ret = 0;
> +
> + if (pos)
> + return -ESPIPE;
> +
> + pos = &filp->f_pos;
> +
> + if (*pos < 0 ||
> + check_add_overflow((loff_t)len, *pos, &requested_length))
> + return -EINVAL;
> +
> + if (requested_length > mtty_data_size(mdev_state))
> + return -ENOMEM;
> +
> + mutex_lock(&migf->lock);
> +
> + if (migf->disabled) {
> + ret = -ENODEV;
> + goto out_unlock;
> + }
> +
> + if (copy_from_user((void *)&migf->data + *pos, buf, len)) {
> + ret = -EFAULT;
> + goto out_unlock;
> + }
> +
> + *pos += len;
> + ret = len;
> +
> + dev_dbg(migf->mdev_state->vdev.dev, "%s received %zu, total %zu\n",
> + __func__, len, migf->filled_size + len);
> +
> + if (migf->filled_size < offsetof(struct mtty_data, ports) &&
> + migf->filled_size + len >= offsetof(struct mtty_data, ports)) {
> + if (migf->data.magic != MTTY_MAGIC || migf->data.flags ||
> + migf->data.major_ver != MTTY_MAJOR_VER ||
> + migf->data.minor_ver != MTTY_MINOR_VER ||
> + migf->data.nr_ports != mdev_state->nr_ports) {
> + dev_dbg(migf->mdev_state->vdev.dev,
> + "%s failed validation\n", __func__);
> + ret = -EFAULT;
> + } else {
> + dev_dbg(migf->mdev_state->vdev.dev,
> + "%s header validated\n", __func__);
> + }
> + }
> +
> + migf->filled_size += len;
> +
> +out_unlock:
> + mutex_unlock(&migf->lock);
> + return ret;
> +}
> +
> +static const struct file_operations mtty_resume_fops = {
> + .owner = THIS_MODULE,
> + .write = mtty_resume_write,
> + .release = mtty_release_migf,
> + .llseek = no_llseek,
> +};
> +
> +static struct mtty_migration_file *
> +mtty_resume_device_data(struct mdev_state *mdev_state)
> +{
> + struct mtty_migration_file *migf;
> + int ret;
> +
> + migf = kzalloc(sizeof(*migf), GFP_KERNEL_ACCOUNT);
> + if (!migf)
> + return ERR_PTR(-ENOMEM);
> +
> + migf->filp = anon_inode_getfile("mtty_mig", &mtty_resume_fops,
> + migf, O_WRONLY);
> + if (IS_ERR(migf->filp)) {
> + ret = PTR_ERR(migf->filp);
> + kfree(migf);
> + return ERR_PTR(ret);
> + }
> +
> + stream_open(migf->filp->f_inode, migf->filp);
> + mutex_init(&migf->lock);
> + migf->mdev_state = mdev_state;
> +
> + mdev_state->resuming_migf = migf;
> +
> + return migf;
> +}
> +
> +static struct file *mtty_step_state(struct mdev_state *mdev_state,
> + enum vfio_device_mig_state new)
> +{
> + enum vfio_device_mig_state cur = mdev_state->state;
> +
> + dev_dbg(mdev_state->vdev.dev, "%s: %d -> %d\n", __func__, cur, new);
> +
> + /*
> + * The following state transitions are no-op considering
> + * mtty does not do DMA nor require any explicit start/stop.
> + *
> + * RUNNING -> RUNNING_P2P
> + * RUNNING_P2P -> RUNNING
> + * RUNNING_P2P -> STOP
> + * PRE_COPY -> PRE_COPY_P2P
> + * PRE_COPY_P2P -> PRE_COPY
> + * STOP -> RUNNING_P2P
> + */
> + if ((cur == VFIO_DEVICE_STATE_RUNNING &&
> + new == VFIO_DEVICE_STATE_RUNNING_P2P) ||
> + (cur == VFIO_DEVICE_STATE_RUNNING_P2P &&
> + (new == VFIO_DEVICE_STATE_RUNNING ||
> + new == VFIO_DEVICE_STATE_STOP)) ||
> + (cur == VFIO_DEVICE_STATE_PRE_COPY &&
> + new == VFIO_DEVICE_STATE_PRE_COPY_P2P) ||
> + (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P &&
> + new == VFIO_DEVICE_STATE_PRE_COPY) ||
> + (cur == VFIO_DEVICE_STATE_STOP &&
> + new == VFIO_DEVICE_STATE_RUNNING_P2P))
> + return NULL;
> +
> + /*
> + * The following state transitions simply close migration files,
> + * with the exception of RESUMING -> STOP, which needs to load
> + * the state first.
> + *
> + * RESUMING -> STOP
> + * PRE_COPY -> RUNNING
> + * PRE_COPY_P2P -> RUNNING_P2P
> + * STOP_COPY -> STOP
> + */
> + if (cur == VFIO_DEVICE_STATE_RESUMING &&
> + new == VFIO_DEVICE_STATE_STOP) {
> + int ret;
> +
> + ret = mtty_load_state(mdev_state);
> + if (ret)
> + return ERR_PTR(ret);
> + mtty_disable_files(mdev_state);
> + return NULL;
> + }
> +
> + if ((cur == VFIO_DEVICE_STATE_PRE_COPY &&
> + new == VFIO_DEVICE_STATE_RUNNING) ||
> + (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P &&
> + new == VFIO_DEVICE_STATE_RUNNING_P2P) ||
> + (cur == VFIO_DEVICE_STATE_STOP_COPY &&
> + new == VFIO_DEVICE_STATE_STOP)) {
> + mtty_disable_files(mdev_state);
> + return NULL;
> + }
> +
> + /*
> + * The following state transitions return migration files.
> + *
> + * RUNNING -> PRE_COPY
> + * RUNNING_P2P -> PRE_COPY_P2P
> + * STOP -> STOP_COPY
> + * STOP -> RESUMING
> + * PRE_COPY_P2P -> STOP_COPY
> + */
> + if ((cur == VFIO_DEVICE_STATE_RUNNING &&
> + new == VFIO_DEVICE_STATE_PRE_COPY) ||
> + (cur == VFIO_DEVICE_STATE_RUNNING_P2P &&
> + new == VFIO_DEVICE_STATE_PRE_COPY_P2P) ||
> + (cur == VFIO_DEVICE_STATE_STOP &&
> + new == VFIO_DEVICE_STATE_STOP_COPY) ||
> + (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P &&
> + new == VFIO_DEVICE_STATE_STOP_COPY)) {
> + struct mtty_migration_file *migf;
> +
> + migf = mtty_save_device_data(mdev_state, new);
> + if (IS_ERR(migf))
> + return ERR_CAST(migf);
> +
> + if (migf) {
> + get_file(migf->filp);
> +
> + return migf->filp;
> + }
> + return NULL;
> + }
> +
> + if (cur == VFIO_DEVICE_STATE_STOP &&
> + new == VFIO_DEVICE_STATE_RESUMING) {
> + struct mtty_migration_file *migf;
> +
> + migf = mtty_resume_device_data(mdev_state);
> + if (IS_ERR(migf))
> + return ERR_CAST(migf);
> +
> + get_file(migf->filp);
> +
> + return migf->filp;
> + }
> +
> + /* vfio_mig_get_next_state() does not use arcs other than the above */
> + WARN_ON(true);
> + return ERR_PTR(-EINVAL);
> +}
> +
> +static struct file *mtty_set_state(struct vfio_device *vdev,
> + enum vfio_device_mig_state new_state)
> +{
> + struct mdev_state *mdev_state =
> + container_of(vdev, struct mdev_state, vdev);
> + struct file *ret = NULL;
> +
> + dev_dbg(vdev->dev, "%s -> %d\n", __func__, new_state);
> +
> + mutex_lock(&mdev_state->state_mutex);
> + while (mdev_state->state != new_state) {
> + enum vfio_device_mig_state next_state;
> + int rc = vfio_mig_get_next_state(vdev, mdev_state->state,
> + new_state, &next_state);
> + if (rc) {
> + ret = ERR_PTR(rc);
> + break;
> + }
> +
> + ret = mtty_step_state(mdev_state, next_state);
> + if (IS_ERR(ret))
> + break;
> +
> + mdev_state->state = next_state;
> +
> + if (WARN_ON(ret && new_state != next_state)) {
> + fput(ret);
> + ret = ERR_PTR(-EINVAL);
> + break;
> + }
> + }
> + mtty_state_mutex_unlock(mdev_state);
> + return ret;
> +}
> +
> +static int mtty_get_state(struct vfio_device *vdev,
> + enum vfio_device_mig_state *current_state)
> +{
> + struct mdev_state *mdev_state =
> + container_of(vdev, struct mdev_state, vdev);
> +
> + mutex_lock(&mdev_state->state_mutex);
> + *current_state = mdev_state->state;
> + mtty_state_mutex_unlock(mdev_state);
> + return 0;
> +}
> +
> +static int mtty_get_data_size(struct vfio_device *vdev,
> + unsigned long *stop_copy_length)
> +{
> + struct mdev_state *mdev_state =
> + container_of(vdev, struct mdev_state, vdev);
> +
> + *stop_copy_length = mtty_data_size(mdev_state);
> + return 0;
> +}
> +
> +static const struct vfio_migration_ops mtty_migration_ops = {
> + .migration_set_state = mtty_set_state,
> + .migration_get_state = mtty_get_state,
> + .migration_get_data_size = mtty_get_data_size,
> +};
> +
> +static int mtty_log_start(struct vfio_device *vdev,
> + struct rb_root_cached *ranges,
> + u32 nnodes, u64 *page_size)
> +{
> + return 0;
> +}
> +
> +static int mtty_log_stop(struct vfio_device *vdev)
> +{
> + return 0;
> +}
> +
> +static int mtty_log_read_and_clear(struct vfio_device *vdev,
> + unsigned long iova, unsigned long length,
> + struct iova_bitmap *dirty)
> +{
> + return 0;
> +}
> +
> +static const struct vfio_log_ops mtty_log_ops = {
> + .log_start = mtty_log_start,
> + .log_stop = mtty_log_stop,
> + .log_read_and_clear = mtty_log_read_and_clear,
> +};
> +
> static int mtty_init_dev(struct vfio_device *vdev)
> {
> struct mdev_state *mdev_state =
> @@ -775,6 +1343,16 @@ static int mtty_init_dev(struct vfio_device *vdev)
> mutex_init(&mdev_state->ops_lock);
> mdev_state->mdev = mdev;
> mtty_create_config_space(mdev_state);
> +
> + mutex_init(&mdev_state->state_mutex);
> + mutex_init(&mdev_state->reset_mutex);
> + vdev->migration_flags = VFIO_MIGRATION_STOP_COPY |
> + VFIO_MIGRATION_P2P |
> + VFIO_MIGRATION_PRE_COPY;
> + vdev->mig_ops = &mtty_migration_ops;
> + vdev->log_ops = &mtty_log_ops;
> + mdev_state->state = VFIO_DEVICE_STATE_RUNNING;
> +
> return 0;
>
> err_nr_ports:
> @@ -808,6 +1386,8 @@ static void mtty_release_dev(struct vfio_device *vdev)
> struct mdev_state *mdev_state =
> container_of(vdev, struct mdev_state, vdev);
>
> + mutex_destroy(&mdev_state->reset_mutex);
> + mutex_destroy(&mdev_state->state_mutex);
> atomic_add(mdev_state->nr_ports, &mdev_avail_ports);
> kfree(mdev_state->vconfig);
> }
> @@ -824,6 +1404,15 @@ static int mtty_reset(struct mdev_state *mdev_state)
> {
> pr_info("%s: called\n", __func__);
>
> + mutex_lock(&mdev_state->reset_mutex);
> + mdev_state->deferred_reset = true;
> + if (!mutex_trylock(&mdev_state->state_mutex)) {
> + mutex_unlock(&mdev_state->reset_mutex);
> + return 0;
> + }
> + mutex_unlock(&mdev_state->reset_mutex);
> + mtty_state_mutex_unlock(mdev_state);
> +
> return 0;
> }
>
> @@ -1350,6 +1939,7 @@ static void mtty_close(struct vfio_device *vdev)
> struct mdev_state *mdev_state =
> container_of(vdev, struct mdev_state, vdev);
>
> + mtty_disable_files(mdev_state);
> mtty_disable_intx(mdev_state);
> mtty_disable_msi(mdev_state);
> }