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 user space could start random destroy calls breaking the rule, while the destroy op in the driver level can't reject a destroy call as it returns void. Add iommufd_vqueue_depend/undepend() for-driver helpers, allowing LVCMDQ allocator to refcount_inc() a sibling LVCMDQ object and LVCMDQ destroyer to refcount_dec(), so that iommufd core will help block a random destroy call that breaks the rule. 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. And encourage to use the macro version that currently supports the vQUEUE objects only. Reviewed-by: Lu Baolu <baolu...@linux.intel.com> Reviewed-by: Pranjal Shrivastava <pr...@google.com> Signed-off-by: Nicolin Chen <nicol...@nvidia.com> --- include/linux/iommufd.h | 48 ++++++++++++++++++++++++++++++++++ drivers/iommu/iommufd/driver.c | 28 ++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h index 676b54c5cf53..cbe9910dbdff 100644 --- a/include/linux/iommufd.h +++ b/include/linux/iommufd.h @@ -235,6 +235,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, @@ -255,6 +259,18 @@ 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) { @@ -339,4 +355,36 @@ static inline int iommufd_viommu_report_event(struct iommufd_viommu *viommu, iommufd_object_abort(drv_struct->member.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_vqueue_depend(vqueue_dependent, vqueue_depended, member) \ + ({ \ + static_assert(__same_type(struct iommufd_vqueue, \ + vqueue_dependent->member)); \ + static_assert(offsetof(typeof(*vqueue_dependent), \ + member.obj) == 0); \ + static_assert(__same_type(struct iommufd_vqueue, \ + vqueue_depended->member)); \ + static_assert(offsetof(typeof(*vqueue_depended), \ + member.obj) == 0); \ + _iommufd_object_depend(&vqueue_dependent->member.obj, \ + &vqueue_depended->member.obj); \ + }) + +#define iommufd_vqueue_undepend(vqueue_dependent, vqueue_depended, member) \ + ({ \ + static_assert(__same_type(struct iommufd_vqueue, \ + vqueue_dependent->member)); \ + static_assert(offsetof(typeof(*vqueue_dependent), \ + member.obj) == 0); \ + static_assert(__same_type(struct iommufd_vqueue, \ + vqueue_depended->member)); \ + static_assert(offsetof(typeof(*vqueue_depended), \ + member.obj) == 0); \ + _iommufd_object_undepend(&vqueue_dependent->member.obj, \ + &vqueue_depended->member.obj); \ + }) #endif diff --git a/drivers/iommu/iommufd/driver.c b/drivers/iommu/iommufd/driver.c index 7980a09761c2..0bcf0438d255 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"); +/* Driver should use a per-structure helper 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"); + +/* Driver should use a per-structure helper 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) -- 2.43.0