Expose guest_memfd provider operations through the VFIO mapping superblock and forward them to the device driver. Validate the driver callbacks before attachment and keep the source file pinned through provider release. Cc: Alex Williamson Cc: Sean Christopherson Cc: Paolo Bonzini Cc: David Hildenbrand Cc: kvm@vger.kernel.org Cc: patches@lists.linux.dev Signed-off-by: Aneesh Kumar K.V (Arm) --- drivers/vfio/vfio_main.c | 115 ++++++++++++++++++++++++++++++++++++++- include/linux/vfio.h | 4 ++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c index ed96acfa8635..2051e14ac616 100644 --- a/drivers/vfio/vfio_main.c +++ b/drivers/vfio/vfio_main.c @@ -14,8 +14,10 @@ #include #include #include +#include #include #include +#include #if IS_ENABLED(CONFIG_KVM) #include #endif @@ -248,9 +250,120 @@ struct vfio_device *_vfio_alloc_device(size_t size, struct device *dev, } EXPORT_SYMBOL_GPL(_vfio_alloc_device); +struct vfio_gmem_provider { + const struct guest_memfd_provider_operations *ops; + void *data; + struct file *file; +}; + +static void *vfio_gmem_provider_attach(struct file *file, + struct guest_memfd *gmem) +{ + const struct guest_memfd_provider_operations *ops; + struct vfio_gmem_provider *provider; + struct vfio_device_file *df; + void *data; + + /* Reject other file types before interpreting their private_data. */ + if (file->f_op != &vfio_device_fops) + return ERR_PTR(-EOPNOTSUPP); + + df = file->private_data; + ops = df->device->ops->gmem_provider_ops; + /* Every provider must support attachment and release. */ + if (!ops || !ops->attach || !ops->release) + return ERR_PTR(-EOPNOTSUPP); + + /* VFIO requires PFN lookup, binding and conversion. */ + if (!ops->get_pfn || !ops->bind || !ops->prepare_conversion || + !ops->finish_conversion) + return ERR_PTR(-EOPNOTSUPP); + + /* VFIO provides PFNs, not folios. */ + if (ops->alloc_folio || ops->invalidate_folio) + return ERR_PTR(-EOPNOTSUPP); + + provider = kzalloc_obj(*provider); + if (!provider) + return ERR_PTR(-ENOMEM); + + data = ops->attach(file, gmem); + if (IS_ERR(data)) { + kfree(provider); + return data; + } + + provider->ops = ops; + provider->data = data; + /* Pin the driver operations through release(), including its return. */ + provider->file = get_file(file); + return provider; +} + +static void vfio_gmem_provider_release(void *data) +{ + struct vfio_gmem_provider *provider = data; + + provider->ops->release(provider->data); + fput(provider->file); + kfree(provider); +} + +static int vfio_gmem_provider_bind(void *data, u64 offset, u64 size, + struct guest_memfd_device_binding *binding) +{ + struct vfio_gmem_provider *provider = data; + + return provider->ops->bind(provider->data, offset, size, binding); +} + +static int vfio_gmem_provider_get_pfn(void *data, u64 offset, unsigned long *pfn) +{ + struct vfio_gmem_provider *provider = data; + + return provider->ops->get_pfn(provider->data, offset, pfn); +} + +static int vfio_gmem_provider_prepare_conversion( + void *context, const struct guest_memfd_device_request *req) +{ + struct vfio_gmem_provider *provider = context; + + return provider->ops->prepare_conversion(provider->data, req); +} + +static void vfio_gmem_provider_finish_conversion( + void *context, const struct guest_memfd_device_request *req) +{ + struct vfio_gmem_provider *provider = context; + + provider->ops->finish_conversion(provider->data, req); +} + + + +static const struct guest_memfd_provider_operations vfio_gmem_provider_ops = { + .attach = vfio_gmem_provider_attach, + .release = vfio_gmem_provider_release, + .bind = vfio_gmem_provider_bind, + .get_pfn = vfio_gmem_provider_get_pfn, + .prepare_conversion = vfio_gmem_provider_prepare_conversion, + .finish_conversion = vfio_gmem_provider_finish_conversion, +}; + +static const struct super_operations vfio_fs_super_ops = { + .statfs = simple_statfs, + .gmem_provider_ops = &vfio_gmem_provider_ops, +}; + static int vfio_fs_init_fs_context(struct fs_context *fc) { - return init_pseudo(fc, VFIO_MAGIC) ? 0 : -ENOMEM; + struct pseudo_fs_context *ctx = init_pseudo(fc, VFIO_MAGIC); + + if (!ctx) + return -ENOMEM; + ctx->ops = &vfio_fs_super_ops; + return 0; } static struct file_system_type vfio_fs_type = { diff --git a/include/linux/vfio.h b/include/linux/vfio.h index 0cc91c6f96d2..f33573f99048 100644 --- a/include/linux/vfio.h +++ b/include/linux/vfio.h @@ -81,6 +81,8 @@ struct vfio_device { struct dentry *debug_root; }; +struct guest_memfd_provider_operations; + /** * struct vfio_device_ops - VFIO bus driver device callbacks * @@ -111,9 +113,11 @@ struct vfio_device { * if none was provided. * @dma_unmap: Called when userspace unmaps IOVA from the container * this device is attached to. + * @gmem_provider_ops: Optional driver guest_memfd resource operations * @device_feature: Optional, fill in the VFIO_DEVICE_FEATURE ioctl */ struct vfio_device_ops { + const struct guest_memfd_provider_operations *gmem_provider_ops; char *name; int (*init)(struct vfio_device *vdev); void (*release)(struct vfio_device *vdev); -- 2.43.0