iommufd/driver: Add iommufd_hw_queue_depend/undepend() helpers

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_hw_queue_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 HW QUEUE objects only.

Link: https://patch.msgid.link/r/2735c32e759c82f2e6c87cb32134eaf09b7589b5.1752126748.git.nicolinc@nvidia.com
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Pranjal Shrivastava <praan@google.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
This commit is contained in:
Nicolin Chen 2025-07-09 22:59:07 -07:00 committed by Jason Gunthorpe
parent 2238ddc2b0
commit 0b37d892d0
2 changed files with 72 additions and 0 deletions

View File

@ -3,6 +3,34 @@
*/
#include "iommufd_private.h"
/* 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)

View File

@ -251,6 +251,10 @@ static inline int iommufd_vfio_compat_set_no_iommu(struct iommufd_ctx *ictx)
#endif /* CONFIG_IOMMUFD */
#if IS_ENABLED(CONFIG_IOMMUFD_DRIVER_CORE)
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,
@ -259,6 +263,18 @@ int iommufd_viommu_report_event(struct iommufd_viommu *viommu,
enum iommu_veventq_type type, void *event_data,
size_t data_len);
#else /* !CONFIG_IOMMUFD_DRIVER_CORE */
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)
{
@ -298,4 +314,32 @@ static inline int iommufd_viommu_report_event(struct iommufd_viommu *viommu,
BUILD_BUG_ON_ZERO(!__same_type(struct iommufd_hw_queue, \
((drv_struct *)NULL)->member)))
/*
* Helpers for IOMMU driver to build/destroy a dependency between two sibling
* structures created by one of the allocators above
*/
#define iommufd_hw_queue_depend(dependent, depended, member) \
({ \
int ret = -EINVAL; \
\
static_assert(__same_type(struct iommufd_hw_queue, \
dependent->member)); \
static_assert(__same_type(typeof(*dependent), *depended)); \
if (!WARN_ON_ONCE(dependent->member.viommu != \
depended->member.viommu)) \
ret = _iommufd_object_depend(&dependent->member.obj, \
&depended->member.obj); \
ret; \
})
#define iommufd_hw_queue_undepend(dependent, depended, member) \
({ \
static_assert(__same_type(struct iommufd_hw_queue, \
dependent->member)); \
static_assert(__same_type(typeof(*dependent), *depended)); \
WARN_ON_ONCE(dependent->member.viommu != \
depended->member.viommu); \
_iommufd_object_undepend(&dependent->member.obj, \
&depended->member.obj); \
})
#endif