* [PATCH 0/2] vfio/mtty: Add migration support @ 2023-10-13 19:56 Alex Williamson 2023-10-13 19:56 ` [PATCH 1/2] vfio/mtty: Fix eventfd leak Alex Williamson 2023-10-13 19:56 ` [PATCH 2/2] vfio/mtty: Enable migration support Alex Williamson 0 siblings, 2 replies; 6+ messages in thread From: Alex Williamson @ 2023-10-13 19:56 UTC (permalink / raw) To: alex.williamson; +Cc: kvm, linux-kernel, clg 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, I fixed an eventfd leak. Thanks, Alex Alex Williamson (2): vfio/mtty: Fix eventfd leak vfio/mtty: Enable migration support samples/vfio-mdev/mtty.c | 610 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 605 insertions(+), 5 deletions(-) -- 2.40.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] vfio/mtty: Fix eventfd leak 2023-10-13 19:56 [PATCH 0/2] vfio/mtty: Add migration support Alex Williamson @ 2023-10-13 19:56 ` Alex Williamson 2023-10-16 7:52 ` Cédric Le Goater 2023-10-13 19:56 ` [PATCH 2/2] vfio/mtty: Enable migration support Alex Williamson 1 sibling, 1 reply; 6+ messages in thread From: Alex Williamson @ 2023-10-13 19:56 UTC (permalink / raw) To: alex.williamson; +Cc: kvm, linux-kernel, clg Found via kmemleak, eventfd context is leaked if not explicitly torn down by userspace. Clear pointers to track released contexts. Also remove unused irq_fd field in mtty structure, set but never used. Fixes: 9d1a546c53b4 ("docs: Sample driver to demonstrate how to use Mediated device framework.") Signed-off-by: Alex Williamson <alex.williamson@redhat.com> --- samples/vfio-mdev/mtty.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c index 5af00387c519..0a2760818e46 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; @@ -938,8 +937,10 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, { if (flags & VFIO_IRQ_SET_DATA_NONE) { pr_info("%s: disable INTx\n", __func__); - if (mdev_state->intx_evtfd) + if (mdev_state->intx_evtfd) { eventfd_ctx_put(mdev_state->intx_evtfd); + mdev_state->intx_evtfd = NULL; + } break; } @@ -955,7 +956,6 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, break; } mdev_state->intx_evtfd = evt; - mdev_state->irq_fd = fd; mdev_state->irq_index = index; break; } @@ -971,8 +971,10 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, break; case VFIO_IRQ_SET_ACTION_TRIGGER: if (flags & VFIO_IRQ_SET_DATA_NONE) { - if (mdev_state->msi_evtfd) + if (mdev_state->msi_evtfd) { eventfd_ctx_put(mdev_state->msi_evtfd); + mdev_state->msi_evtfd = NULL; + } pr_info("%s: disable MSI\n", __func__); mdev_state->irq_index = VFIO_PCI_INTX_IRQ_INDEX; break; @@ -993,7 +995,6 @@ 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; @@ -1262,6 +1263,22 @@ 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); + + if (mdev_state->intx_evtfd) { + eventfd_ctx_put(mdev_state->intx_evtfd); + mdev_state->intx_evtfd = NULL; + } + if (mdev_state->msi_evtfd) { + eventfd_ctx_put(mdev_state->msi_evtfd); + mdev_state->msi_evtfd = NULL; + } + mdev_state->irq_index = -1; +} + static const struct vfio_device_ops mtty_dev_ops = { .name = "vfio-mtty", .init = mtty_init_dev, @@ -1273,6 +1290,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 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] vfio/mtty: Fix eventfd leak 2023-10-13 19:56 ` [PATCH 1/2] vfio/mtty: Fix eventfd leak Alex Williamson @ 2023-10-16 7:52 ` Cédric Le Goater 2023-10-16 20:16 ` Alex Williamson 0 siblings, 1 reply; 6+ messages in thread From: Cédric Le Goater @ 2023-10-16 7:52 UTC (permalink / raw) To: Alex Williamson; +Cc: kvm, linux-kernel On 10/13/23 21:56, Alex Williamson wrote: > Found via kmemleak, eventfd context is leaked if not explicitly torn > down by userspace. Clear pointers to track released contexts. Also > remove unused irq_fd field in mtty structure, set but never used. This could be 2 different patches, one cleanup and one fix. > Fixes: 9d1a546c53b4 ("docs: Sample driver to demonstrate how to use Mediated device framework.") > Signed-off-by: Alex Williamson <alex.williamson@redhat.com> > --- > samples/vfio-mdev/mtty.c | 28 +++++++++++++++++++++++----- > 1 file changed, 23 insertions(+), 5 deletions(-) > > diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c > index 5af00387c519..0a2760818e46 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; > @@ -938,8 +937,10 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, > { > if (flags & VFIO_IRQ_SET_DATA_NONE) { > pr_info("%s: disable INTx\n", __func__); > - if (mdev_state->intx_evtfd) > + if (mdev_state->intx_evtfd) { > eventfd_ctx_put(mdev_state->intx_evtfd); > + mdev_state->intx_evtfd = NULL; > + } > break; > } > > @@ -955,7 +956,6 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, > break; > } Shouln't mdev_state->intx_evtfd value be tested before calling eventfd_ctx() ? Thanks, C. > mdev_state->intx_evtfd = evt; > - mdev_state->irq_fd = fd; > mdev_state->irq_index = index; > break; > } > @@ -971,8 +971,10 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, > break; > case VFIO_IRQ_SET_ACTION_TRIGGER: > if (flags & VFIO_IRQ_SET_DATA_NONE) { > - if (mdev_state->msi_evtfd) > + if (mdev_state->msi_evtfd) { > eventfd_ctx_put(mdev_state->msi_evtfd); > + mdev_state->msi_evtfd = NULL; > + } > pr_info("%s: disable MSI\n", __func__); > mdev_state->irq_index = VFIO_PCI_INTX_IRQ_INDEX; > break; > @@ -993,7 +995,6 @@ 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; > @@ -1262,6 +1263,22 @@ 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); > + > + if (mdev_state->intx_evtfd) { > + eventfd_ctx_put(mdev_state->intx_evtfd); > + mdev_state->intx_evtfd = NULL; > + } > + if (mdev_state->msi_evtfd) { > + eventfd_ctx_put(mdev_state->msi_evtfd); > + mdev_state->msi_evtfd = NULL; > + } > + mdev_state->irq_index = -1; > +} > + > static const struct vfio_device_ops mtty_dev_ops = { > .name = "vfio-mtty", > .init = mtty_init_dev, > @@ -1273,6 +1290,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 = { ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] vfio/mtty: Fix eventfd leak 2023-10-16 7:52 ` Cédric Le Goater @ 2023-10-16 20:16 ` Alex Williamson 0 siblings, 0 replies; 6+ messages in thread From: Alex Williamson @ 2023-10-16 20:16 UTC (permalink / raw) To: Cédric Le Goater; +Cc: kvm, linux-kernel On Mon, 16 Oct 2023 09:52:41 +0200 Cédric Le Goater <clg@redhat.com> wrote: > On 10/13/23 21:56, Alex Williamson wrote: > > Found via kmemleak, eventfd context is leaked if not explicitly torn > > down by userspace. Clear pointers to track released contexts. Also > > remove unused irq_fd field in mtty structure, set but never used. > > This could be 2 different patches, one cleanup and one fix. Of course. > > Fixes: 9d1a546c53b4 ("docs: Sample driver to demonstrate how to use Mediated device framework.") > > Signed-off-by: Alex Williamson <alex.williamson@redhat.com> > > --- > > samples/vfio-mdev/mtty.c | 28 +++++++++++++++++++++++----- > > 1 file changed, 23 insertions(+), 5 deletions(-) > > > > diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c > > index 5af00387c519..0a2760818e46 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; > > @@ -938,8 +937,10 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, > > { > > if (flags & VFIO_IRQ_SET_DATA_NONE) { > > pr_info("%s: disable INTx\n", __func__); > > - if (mdev_state->intx_evtfd) > > + if (mdev_state->intx_evtfd) { > > eventfd_ctx_put(mdev_state->intx_evtfd); > > + mdev_state->intx_evtfd = NULL; > > + } > > break; > > } > > > > @@ -955,7 +956,6 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, > > break; > > } > > Shouln't mdev_state->intx_evtfd value be tested before calling > eventfd_ctx() ? The state of mtty interrupt handling is really quite atrocious, it's a pretty significant overhaul to really make it comply with the SET_IRQS ioctl. I'll see what I can do, but it's so broken that I hope you won't insist on splitting out each fix. Thanks, Alex > > mdev_state->intx_evtfd = evt; > > - mdev_state->irq_fd = fd; > > mdev_state->irq_index = index; > > break; > > } > > @@ -971,8 +971,10 @@ static int mtty_set_irqs(struct mdev_state *mdev_state, uint32_t flags, > > break; > > case VFIO_IRQ_SET_ACTION_TRIGGER: > > if (flags & VFIO_IRQ_SET_DATA_NONE) { > > - if (mdev_state->msi_evtfd) > > + if (mdev_state->msi_evtfd) { > > eventfd_ctx_put(mdev_state->msi_evtfd); > > + mdev_state->msi_evtfd = NULL; > > + } > > pr_info("%s: disable MSI\n", __func__); > > mdev_state->irq_index = VFIO_PCI_INTX_IRQ_INDEX; > > break; > > @@ -993,7 +995,6 @@ 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; > > @@ -1262,6 +1263,22 @@ 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); > > + > > + if (mdev_state->intx_evtfd) { > > + eventfd_ctx_put(mdev_state->intx_evtfd); > > + mdev_state->intx_evtfd = NULL; > > + } > > + if (mdev_state->msi_evtfd) { > > + eventfd_ctx_put(mdev_state->msi_evtfd); > > + mdev_state->msi_evtfd = NULL; > > + } > > + mdev_state->irq_index = -1; > > +} > > + > > static const struct vfio_device_ops mtty_dev_ops = { > > .name = "vfio-mtty", > > .init = mtty_init_dev, > > @@ -1273,6 +1290,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 = { > ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] vfio/mtty: Enable migration support 2023-10-13 19:56 [PATCH 0/2] vfio/mtty: Add migration support Alex Williamson 2023-10-13 19:56 ` [PATCH 1/2] vfio/mtty: Fix eventfd leak Alex Williamson @ 2023-10-13 19:56 ` Alex Williamson 2023-10-16 14:29 ` Cédric Le Goater 1 sibling, 1 reply; 6+ messages in thread From: Alex Williamson @ 2023-10-13 19:56 UTC (permalink / raw) To: alex.williamson; +Cc: kvm, linux-kernel, clg 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 <alex.williamson@redhat.com> --- Much of this should look familiar to vfio-pci variant driver developers ;) samples/vfio-mdev/mtty.c | 582 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 582 insertions(+) diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c index 0a2760818e46..572f51342fbd 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; + struct mutex state_mutex; + enum vfio_device_mig_state state; + struct mutex reset_mutex; + struct mtty_migration_file *saving_migf; + struct mtty_migration_file *resuming_migf; + u8 deferred_reset:1; }; static struct mtty_type { @@ -716,6 +747,534 @@ static ssize_t mdev_access(struct mdev_state *mdev_state, u8 *buf, size_t count, return ret; } +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 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) { + 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); + } + + 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; + 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 > sizeof(struct mtty_data)) + 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; + migf->filled_size += len; + + dev_dbg(migf->mdev_state->vdev.dev, "%s received %zu, total %zu\n", + __func__, len, migf->filled_size); + + if (migf->filled_size >= offsetof(struct mtty_data, ports)) { + struct mdev_state *mdev_state = migf->mdev_state; + + 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__); + } + } + +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 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 < offsetof(struct mtty_data, ports) + + (mdev_state->nr_ports * sizeof(struct serial_port))) { + dev_dbg(mdev_state->vdev.dev, "%s expected %zu, got %zu\n", + __func__, offsetof(struct mtty_data, ports) + + (mdev_state->nr_ports * sizeof(struct serial_port)), + 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 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 = offsetof(struct mtty_data, ports) + + (mdev_state->nr_ports * sizeof(struct serial_port)); + 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 = @@ -748,6 +1307,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: @@ -781,6 +1350,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); } @@ -797,6 +1368,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; } @@ -1268,6 +1848,8 @@ 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); + if (mdev_state->intx_evtfd) { eventfd_ctx_put(mdev_state->intx_evtfd); mdev_state->intx_evtfd = NULL; -- 2.40.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] vfio/mtty: Enable migration support 2023-10-13 19:56 ` [PATCH 2/2] vfio/mtty: Enable migration support Alex Williamson @ 2023-10-16 14:29 ` Cédric Le Goater 0 siblings, 0 replies; 6+ messages in thread From: Cédric Le Goater @ 2023-10-16 14:29 UTC (permalink / raw) To: Alex Williamson; +Cc: kvm, linux-kernel On 10/13/23 21:56, 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 <alex.williamson@redhat.com> > --- > > Much of this should look familiar to vfio-pci variant driver developers > ;) LGTM, though, I would isolate the part doing the get/set of the device state in their own routine to avoid extra noise in mtty_save_device_data() and mtty_resume_write(). The size of the state could also be dynamic and computed like done in .migration_get_data_size() handler. This is only a sample, so it might be worth the extra effort. That said, this driver could be a summary of the best practice to follow when implementing support for VFIO migration. Thanks, C. > > samples/vfio-mdev/mtty.c | 582 +++++++++++++++++++++++++++++++++++++++ > 1 file changed, 582 insertions(+) > > diff --git a/samples/vfio-mdev/mtty.c b/samples/vfio-mdev/mtty.c > index 0a2760818e46..572f51342fbd 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; > + struct mutex state_mutex; > + enum vfio_device_mig_state state; > + struct mutex reset_mutex; > + struct mtty_migration_file *saving_migf; > + struct mtty_migration_file *resuming_migf; > + u8 deferred_reset:1; > }; > > static struct mtty_type { > @@ -716,6 +747,534 @@ static ssize_t mdev_access(struct mdev_state *mdev_state, u8 *buf, size_t count, > return ret; > } > > +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 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) { > + 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); > + } > + > + 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; > + 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 > sizeof(struct mtty_data)) > + 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; > + migf->filled_size += len; > + > + dev_dbg(migf->mdev_state->vdev.dev, "%s received %zu, total %zu\n", > + __func__, len, migf->filled_size); > + > + if (migf->filled_size >= offsetof(struct mtty_data, ports)) { > + struct mdev_state *mdev_state = migf->mdev_state; > + > + 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__); > + } > + } > + > +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 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 < offsetof(struct mtty_data, ports) + > + (mdev_state->nr_ports * sizeof(struct serial_port))) { > + dev_dbg(mdev_state->vdev.dev, "%s expected %zu, got %zu\n", > + __func__, offsetof(struct mtty_data, ports) + > + (mdev_state->nr_ports * sizeof(struct serial_port)), > + 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 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 = offsetof(struct mtty_data, ports) + > + (mdev_state->nr_ports * sizeof(struct serial_port)); > + 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 = > @@ -748,6 +1307,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: > @@ -781,6 +1350,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); > } > @@ -797,6 +1368,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; > } > > @@ -1268,6 +1848,8 @@ 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); > + > if (mdev_state->intx_evtfd) { > eventfd_ctx_put(mdev_state->intx_evtfd); > mdev_state->intx_evtfd = NULL; ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2023-10-16 20:17 UTC | newest] Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2023-10-13 19:56 [PATCH 0/2] vfio/mtty: Add migration support Alex Williamson 2023-10-13 19:56 ` [PATCH 1/2] vfio/mtty: Fix eventfd leak Alex Williamson 2023-10-16 7:52 ` Cédric Le Goater 2023-10-16 20:16 ` Alex Williamson 2023-10-13 19:56 ` [PATCH 2/2] vfio/mtty: Enable migration support Alex Williamson 2023-10-16 14:29 ` Cédric Le Goater
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®