| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES | |
| */ | |
| #include <linux/vfio.h> | |
| #include <linux/iommufd.h> | |
| #include "vfio.h" | |
| MODULE_IMPORT_NS("IOMMUFD"); | |
| MODULE_IMPORT_NS("IOMMUFD_VFIO"); | |
| bool vfio_iommufd_device_has_compat_ioas(struct vfio_device *vdev, | |
| struct iommufd_ctx *ictx) | |
| { | |
| u32 ioas_id; | |
| return !iommufd_vfio_compat_ioas_get_id(ictx, &ioas_id); | |
| } | |
| int vfio_df_iommufd_bind(struct vfio_device_file *df) | |
| { | |
| struct vfio_device *vdev = df->device; | |
| struct iommufd_ctx *ictx = df->iommufd; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| /* Returns 0 to permit device opening under noiommu mode */ | |
| if (vfio_device_is_noiommu(vdev)) | |
| return 0; | |
| return vdev->ops->bind_iommufd(vdev, ictx, &df->devid); | |
| } | |
| int vfio_iommufd_compat_attach_ioas(struct vfio_device *vdev, | |
| struct iommufd_ctx *ictx) | |
| { | |
| u32 ioas_id; | |
| int ret; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| /* compat noiommu does not need to do ioas attach */ | |
| if (vfio_device_is_noiommu(vdev)) | |
| return 0; | |
| ret = iommufd_vfio_compat_ioas_get_id(ictx, &ioas_id); | |
| if (ret) | |
| return ret; | |
| /* The legacy path has no way to return the selected pt_id */ | |
| return vdev->ops->attach_ioas(vdev, &ioas_id); | |
| } | |
| void vfio_df_iommufd_unbind(struct vfio_device_file *df) | |
| { | |
| struct vfio_device *vdev = df->device; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (vfio_device_is_noiommu(vdev)) | |
| return; | |
| if (vdev->ops->unbind_iommufd) | |
| vdev->ops->unbind_iommufd(vdev); | |
| } | |
| struct iommufd_ctx *vfio_iommufd_device_ictx(struct vfio_device *vdev) | |
| { | |
| if (vdev->iommufd_device) | |
| return iommufd_device_to_ictx(vdev->iommufd_device); | |
| return NULL; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_device_ictx); | |
| static int vfio_iommufd_device_id(struct vfio_device *vdev) | |
| { | |
| if (vdev->iommufd_device) | |
| return iommufd_device_to_id(vdev->iommufd_device); | |
| return -EINVAL; | |
| } | |
| /* | |
| * Return devid for a device. | |
| * valid ID for the device that is owned by the ictx | |
| * -ENOENT = device is owned but there is no ID | |
| * -ENODEV or other error = device is not owned | |
| */ | |
| int vfio_iommufd_get_dev_id(struct vfio_device *vdev, struct iommufd_ctx *ictx) | |
| { | |
| struct iommu_group *group; | |
| int devid; | |
| if (vfio_iommufd_device_ictx(vdev) == ictx) | |
| return vfio_iommufd_device_id(vdev); | |
| group = iommu_group_get(vdev->dev); | |
| if (!group) | |
| return -ENODEV; | |
| if (iommufd_ctx_has_group(ictx, group)) | |
| devid = -ENOENT; | |
| else | |
| devid = -ENODEV; | |
| iommu_group_put(group); | |
| return devid; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_get_dev_id); | |
| /* | |
| * The physical standard ops mean that the iommufd_device is bound to the | |
| * physical device vdev->dev that was provided to vfio_init_group_dev(). Drivers | |
| * using this ops set should call vfio_register_group_dev() | |
| */ | |
| int vfio_iommufd_physical_bind(struct vfio_device *vdev, | |
| struct iommufd_ctx *ictx, u32 *out_device_id) | |
| { | |
| struct iommufd_device *idev; | |
| idev = iommufd_device_bind(ictx, vdev->dev, out_device_id); | |
| if (IS_ERR(idev)) | |
| return PTR_ERR(idev); | |
| vdev->iommufd_device = idev; | |
| ida_init(&vdev->pasids); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_physical_bind); | |
| void vfio_iommufd_physical_unbind(struct vfio_device *vdev) | |
| { | |
| int pasid; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| while ((pasid = ida_find_first(&vdev->pasids)) >= 0) { | |
| iommufd_device_detach(vdev->iommufd_device, pasid); | |
| ida_free(&vdev->pasids, pasid); | |
| } | |
| if (vdev->iommufd_attached) { | |
| iommufd_device_detach(vdev->iommufd_device, IOMMU_NO_PASID); | |
| vdev->iommufd_attached = false; | |
| } | |
| iommufd_device_unbind(vdev->iommufd_device); | |
| vdev->iommufd_device = NULL; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_physical_unbind); | |
| int vfio_iommufd_physical_attach_ioas(struct vfio_device *vdev, u32 *pt_id) | |
| { | |
| int rc; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (WARN_ON(!vdev->iommufd_device)) | |
| return -EINVAL; | |
| if (vdev->iommufd_attached) | |
| rc = iommufd_device_replace(vdev->iommufd_device, | |
| IOMMU_NO_PASID, pt_id); | |
| else | |
| rc = iommufd_device_attach(vdev->iommufd_device, | |
| IOMMU_NO_PASID, pt_id); | |
| if (rc) | |
| return rc; | |
| vdev->iommufd_attached = true; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_physical_attach_ioas); | |
| void vfio_iommufd_physical_detach_ioas(struct vfio_device *vdev) | |
| { | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (WARN_ON(!vdev->iommufd_device) || !vdev->iommufd_attached) | |
| return; | |
| iommufd_device_detach(vdev->iommufd_device, IOMMU_NO_PASID); | |
| vdev->iommufd_attached = false; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_physical_detach_ioas); | |
| int vfio_iommufd_physical_pasid_attach_ioas(struct vfio_device *vdev, | |
| u32 pasid, u32 *pt_id) | |
| { | |
| int rc; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (WARN_ON(!vdev->iommufd_device)) | |
| return -EINVAL; | |
| if (ida_exists(&vdev->pasids, pasid)) | |
| return iommufd_device_replace(vdev->iommufd_device, | |
| pasid, pt_id); | |
| rc = ida_alloc_range(&vdev->pasids, pasid, pasid, GFP_KERNEL); | |
| if (rc < 0) | |
| return rc; | |
| rc = iommufd_device_attach(vdev->iommufd_device, pasid, pt_id); | |
| if (rc) | |
| ida_free(&vdev->pasids, pasid); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_physical_pasid_attach_ioas); | |
| void vfio_iommufd_physical_pasid_detach_ioas(struct vfio_device *vdev, | |
| u32 pasid) | |
| { | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (WARN_ON(!vdev->iommufd_device)) | |
| return; | |
| if (!ida_exists(&vdev->pasids, pasid)) | |
| return; | |
| iommufd_device_detach(vdev->iommufd_device, pasid); | |
| ida_free(&vdev->pasids, pasid); | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_physical_pasid_detach_ioas); | |
| /* | |
| * The emulated standard ops mean that vfio_device is going to use the | |
| * "mdev path" and will call vfio_pin_pages()/vfio_dma_rw(). Drivers using this | |
| * ops set should call vfio_register_emulated_iommu_dev(). Drivers that do | |
| * not call vfio_pin_pages()/vfio_dma_rw() have no need to provide dma_unmap. | |
| */ | |
| static void vfio_emulated_unmap(void *data, unsigned long iova, | |
| unsigned long length) | |
| { | |
| struct vfio_device *vdev = data; | |
| if (vdev->ops->dma_unmap) | |
| vdev->ops->dma_unmap(vdev, iova, length); | |
| } | |
| static const struct iommufd_access_ops vfio_user_ops = { | |
| .needs_pin_pages = 1, | |
| .unmap = vfio_emulated_unmap, | |
| }; | |
| int vfio_iommufd_emulated_bind(struct vfio_device *vdev, | |
| struct iommufd_ctx *ictx, u32 *out_device_id) | |
| { | |
| struct iommufd_access *user; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| user = iommufd_access_create(ictx, &vfio_user_ops, vdev, out_device_id); | |
| if (IS_ERR(user)) | |
| return PTR_ERR(user); | |
| vdev->iommufd_access = user; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_emulated_bind); | |
| void vfio_iommufd_emulated_unbind(struct vfio_device *vdev) | |
| { | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (vdev->iommufd_access) { | |
| iommufd_access_destroy(vdev->iommufd_access); | |
| vdev->iommufd_attached = false; | |
| vdev->iommufd_access = NULL; | |
| } | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_emulated_unbind); | |
| int vfio_iommufd_emulated_attach_ioas(struct vfio_device *vdev, u32 *pt_id) | |
| { | |
| int rc; | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (vdev->iommufd_attached) | |
| rc = iommufd_access_replace(vdev->iommufd_access, *pt_id); | |
| else | |
| rc = iommufd_access_attach(vdev->iommufd_access, *pt_id); | |
| if (rc) | |
| return rc; | |
| vdev->iommufd_attached = true; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_emulated_attach_ioas); | |
| void vfio_iommufd_emulated_detach_ioas(struct vfio_device *vdev) | |
| { | |
| lockdep_assert_held(&vdev->dev_set->lock); | |
| if (WARN_ON(!vdev->iommufd_access) || | |
| !vdev->iommufd_attached) | |
| return; | |
| iommufd_access_detach(vdev->iommufd_access); | |
| vdev->iommufd_attached = false; | |
| } | |
| EXPORT_SYMBOL_GPL(vfio_iommufd_emulated_detach_ioas); |