NVIDIA Virtual Command Queue is one of the iommufd users exposing vIOMMU features to user space VMs. Its hardware has a strict rule when mapping and unmapping multiple global CMDQVs to/from a VM-owned VINTF, requiring mappings in ascending order and unmappings in descending order.
The tegra241-cmdqv driver can apply the rule for a mapping in the LVCMDQ allocation handler, however it can't do the same for an unmapping since the destroy op returns void.
Add iommufd_vcmdq_depend/undepend() for-driver helpers, allowing LVCMDQ allocator to refcount_inc() a sibling LVCMDQ object and LVCMDQ destroyer to refcount_dec().
This is a bit of compromise, because a driver might end up with abusing the API that deadlocks the objects. So restrict the API to a dependency between two driver-allocated objects of the same type, as iommufd would unlikely build any core-level dependency in this case.
Signed-off-by: Nicolin Chen nicolinc@nvidia.com --- include/linux/iommufd.h | 47 ++++++++++++++++++++++++++++++++++ drivers/iommu/iommufd/driver.c | 28 ++++++++++++++++++++ 2 files changed, 75 insertions(+)
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h index e91381aaec5a..5dff154e8ce1 100644 --- a/include/linux/iommufd.h +++ b/include/linux/iommufd.h @@ -232,6 +232,10 @@ struct iommufd_object *_iommufd_object_alloc(struct iommufd_ctx *ictx, size_t size, enum iommufd_object_type type); void iommufd_object_abort(struct iommufd_ctx *ictx, struct iommufd_object *obj); +int iommufd_object_depend(struct iommufd_object *obj_dependent, + struct iommufd_object *obj_depended); +void iommufd_object_undepend(struct iommufd_object *obj_dependent, + struct iommufd_object *obj_depended); struct device *iommufd_viommu_find_dev(struct iommufd_viommu *viommu, unsigned long vdev_id); int iommufd_viommu_get_vdev_id(struct iommufd_viommu *viommu, @@ -252,6 +256,17 @@ static inline void iommufd_object_abort(struct iommufd_ctx *ictx, { }
+static inline int iommufd_object_depend(struct iommufd_object *obj_dependent, + struct iommufd_object *obj_depended) +{ + return -EOPNOTSUPP; +} + +static inline void iommufd_object_undepend(struct iommufd_object *obj_dependent, + struct iommufd_object *obj_depended) +{ +} + static inline struct device * iommufd_viommu_find_dev(struct iommufd_viommu *viommu, unsigned long vdev_id) { @@ -329,4 +344,36 @@ static inline int iommufd_viommu_report_event(struct iommufd_viommu *viommu, static_assert(offsetof(typeof(*drv_struct), member.obj) == 0); \ iommufd_object_abort(ictx, &drv_struct->member.obj); \ }) + +/* + * Helpers for IOMMU driver to build/destroy a dependency between two sibling + * structures created by one of the allocators above + */ +#define iommufd_vcmdq_depend(vcmdq_dependent, vcmdq_depended, member) \ + ({ \ + static_assert(__same_type(struct iommufd_object, \ + vcmdq_dependent->member.obj)); \ + static_assert(offsetof(typeof(*vcmdq_dependent), \ + member.obj) == 0); \ + static_assert(__same_type(struct iommufd_object, \ + vcmdq_depended->member.obj)); \ + static_assert(offsetof(typeof(*vcmdq_depended), \ + member.obj) == 0); \ + iommufd_object_depend(&vcmdq_dependent->member.obj, \ + &vcmdq_depended->member.obj); \ + }) + +#define iommufd_vcmdq_undepend(vcmdq_dependent, vcmdq_depended, member) \ + ({ \ + static_assert(__same_type(struct iommufd_object, \ + vcmdq_dependent->member.obj)); \ + static_assert(offsetof(typeof(*vcmdq_dependent), \ + member.obj) == 0); \ + static_assert(__same_type(struct iommufd_object, \ + vcmdq_depended->member.obj)); \ + static_assert(offsetof(typeof(*vcmdq_depended), \ + member.obj) == 0); \ + iommufd_object_undepend(&vcmdq_dependent->member.obj, \ + &vcmdq_depended->member.obj); \ + }) #endif diff --git a/drivers/iommu/iommufd/driver.c b/drivers/iommu/iommufd/driver.c index 7980a09761c2..fb7f8fe40f95 100644 --- a/drivers/iommu/iommufd/driver.c +++ b/drivers/iommu/iommufd/driver.c @@ -50,6 +50,34 @@ void iommufd_object_abort(struct iommufd_ctx *ictx, struct iommufd_object *obj) } EXPORT_SYMBOL_NS_GPL(iommufd_object_abort, "IOMMUFD");
+/* A per-structure helper is available in include/linux/iommufd.h */ +int iommufd_object_depend(struct iommufd_object *obj_dependent, + struct iommufd_object *obj_depended) +{ + /* Reject self dependency that dead locks */ + if (obj_dependent == obj_depended) + return -EINVAL; + /* Only support dependency between two objects of the same type */ + if (obj_dependent->type != obj_depended->type) + return -EINVAL; + + refcount_inc(&obj_depended->users); + return 0; +} +EXPORT_SYMBOL_NS_GPL(iommufd_object_depend, "IOMMUFD"); + +/* A per-structure helper is available in include/linux/iommufd.h */ +void iommufd_object_undepend(struct iommufd_object *obj_dependent, + struct iommufd_object *obj_depended) +{ + if (WARN_ON_ONCE(obj_dependent == obj_depended || + obj_dependent->type != obj_depended->type)) + return; + + refcount_dec(&obj_depended->users); +} +EXPORT_SYMBOL_NS_GPL(iommufd_object_undepend, "IOMMUFD"); + /* Caller should xa_lock(&viommu->vdevs) to protect the return value */ struct device *iommufd_viommu_find_dev(struct iommufd_viommu *viommu, unsigned long vdev_id)