[PATCH v2 22/26] iommufd: Add hw_queue_init and split queue alloc paths

From: Suravee Suthikulpanit

Date: Thu May 28 2026 - 01:23:56 EST


Add iommufd_viommu_ops.hw_queue_init for vIOMMU backends whose
hardware uses a guest physical queue base from hw_queue->base_addr
instead of a host physical address.

Previously, HW queue alloc always went through
iommufd_hw_queue_alloc_phys(), an iommufd_access, and
hw_queue_init_phys(base_pa). AMD vIOMMU instead takes GPA from
userspace in hw_queue->base_addr and programs hardware without host PA
resolution. Splitting helpers and dispatching from the ioctl keeps
one uAPI while making the contract explicit. Each vIOMMU driver should
implement only one of hw_queue_init_phys or hw_queue_init.

Refactor iommufd_hw_queue_alloc_ioctl() so shared validation and
viommu lookup stay in the ioctl, while setup is delegated to
_iommufd_hw_queue_init_phys() or _iommufd_hw_queue_init().

Signed-off-by: Suravee Suthikulpanit <suravee.suthikulpanit@xxxxxxx>
---
drivers/iommu/iommufd/viommu.c | 125 +++++++++++++++++++++++----------
include/linux/iommufd.h | 5 +-
2 files changed, 91 insertions(+), 39 deletions(-)

diff --git a/drivers/iommu/iommufd/viommu.c b/drivers/iommu/iommufd/viommu.c
index 4081deda9b33..bc58d240fafd 100644
--- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c
@@ -353,62 +353,35 @@ iommufd_hw_queue_alloc_phys(struct iommu_hw_queue_alloc *cmd,
return ERR_PTR(rc);
}

-int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd)
+static int _iommufd_hw_queue_init_phys(struct iommufd_ucmd *ucmd,
+ struct iommufd_viommu *viommu)
{
struct iommu_hw_queue_alloc *cmd = ucmd->cmd;
struct iommufd_hw_queue *hw_queue;
- struct iommufd_viommu *viommu;
struct iommufd_access *access;
size_t hw_queue_size;
phys_addr_t base_pa;
- u64 last;
int rc;

- if (cmd->flags || cmd->type == IOMMU_HW_QUEUE_TYPE_DEFAULT)
- return -EOPNOTSUPP;
- if (!cmd->length)
- return -EINVAL;
- if (check_add_overflow(cmd->nesting_parent_iova, cmd->length - 1,
- &last))
- return -EOVERFLOW;
-
- viommu = iommufd_get_viommu(ucmd, cmd->viommu_id);
- if (IS_ERR(viommu))
- return PTR_ERR(viommu);
-
- if (!viommu->ops || !viommu->ops->get_hw_queue_size ||
- !viommu->ops->hw_queue_init_phys) {
- rc = -EOPNOTSUPP;
- goto out_put_viommu;
- }
-
hw_queue_size = viommu->ops->get_hw_queue_size(viommu, cmd->type);
- if (!hw_queue_size) {
- rc = -EOPNOTSUPP;
- goto out_put_viommu;
- }
+ if (!hw_queue_size)
+ return -EOPNOTSUPP;

/*
* It is a driver bug for providing a hw_queue_size smaller than the
* core HW queue structure size
*/
- if (WARN_ON_ONCE(hw_queue_size < sizeof(*hw_queue))) {
- rc = -EOPNOTSUPP;
- goto out_put_viommu;
- }
+ if (WARN_ON_ONCE(hw_queue_size < sizeof(*hw_queue)))
+ return -EOPNOTSUPP;

hw_queue = (struct iommufd_hw_queue *)_iommufd_object_alloc_ucmd(
ucmd, hw_queue_size, IOMMUFD_OBJ_HW_QUEUE);
- if (IS_ERR(hw_queue)) {
- rc = PTR_ERR(hw_queue);
- goto out_put_viommu;
- }
+ if (IS_ERR(hw_queue))
+ return PTR_ERR(hw_queue);

access = iommufd_hw_queue_alloc_phys(cmd, viommu, &base_pa);
- if (IS_ERR(access)) {
- rc = PTR_ERR(access);
- goto out_put_viommu;
- }
+ if (IS_ERR(access))
+ return PTR_ERR(access);

hw_queue->viommu = viommu;
refcount_inc(&viommu->obj.users);
@@ -419,9 +392,85 @@ int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd)

rc = viommu->ops->hw_queue_init_phys(hw_queue, cmd->index, base_pa);
if (rc)
- goto out_put_viommu;
+ return rc;

cmd->out_hw_queue_id = hw_queue->obj.id;
+ return rc;
+}
+
+static int _iommufd_hw_queue_init(struct iommufd_ucmd *ucmd,
+ struct iommufd_viommu *viommu)
+{
+ struct iommu_hw_queue_alloc *cmd = ucmd->cmd;
+ struct iommufd_hw_queue *hw_queue;
+ size_t hw_queue_size;
+ int rc;
+
+ hw_queue_size = viommu->ops->get_hw_queue_size(viommu, cmd->type);
+ if (!hw_queue_size)
+ return -EOPNOTSUPP;
+
+ /*
+ * It is a driver bug for providing a hw_queue_size smaller than the
+ * core HW queue structure size
+ */
+ if (WARN_ON_ONCE(hw_queue_size < sizeof(*hw_queue)))
+ return -EOPNOTSUPP;
+
+ hw_queue = (struct iommufd_hw_queue *)_iommufd_object_alloc_ucmd(
+ ucmd, hw_queue_size, IOMMUFD_OBJ_HW_QUEUE);
+ if (IS_ERR(hw_queue))
+ return PTR_ERR(hw_queue);
+
+ hw_queue->viommu = viommu;
+ refcount_inc(&viommu->obj.users);
+ hw_queue->access = NULL;
+ hw_queue->type = cmd->type;
+ hw_queue->length = cmd->length;
+ hw_queue->base_addr = cmd->nesting_parent_iova;
+
+ rc = viommu->ops->hw_queue_init(hw_queue, cmd->index);
+ if (rc)
+ return rc;
+
+ cmd->out_hw_queue_id = hw_queue->obj.id;
+ return rc;
+}
+
+int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd)
+{
+ struct iommu_hw_queue_alloc *cmd = ucmd->cmd;
+ struct iommufd_viommu *viommu;
+ u64 last;
+ int rc;
+
+ if (cmd->flags || cmd->type == IOMMU_HW_QUEUE_TYPE_DEFAULT)
+ return -EOPNOTSUPP;
+ if (!cmd->length)
+ return -EINVAL;
+ if (check_add_overflow(cmd->nesting_parent_iova, cmd->length - 1,
+ &last))
+ return -EOVERFLOW;
+
+ viommu = iommufd_get_viommu(ucmd, cmd->viommu_id);
+ if (IS_ERR(viommu))
+ return PTR_ERR(viommu);
+
+ if (!viommu->ops || !viommu->ops->get_hw_queue_size) {
+ rc = -EOPNOTSUPP;
+ goto out_put_viommu;
+ }
+
+ if (viommu->ops->hw_queue_init_phys)
+ rc = _iommufd_hw_queue_init_phys(ucmd, viommu);
+ else if (viommu->ops->hw_queue_init)
+ rc = _iommufd_hw_queue_init(ucmd, viommu);
+ else
+ rc = -EOPNOTSUPP;
+
+ if (rc)
+ goto out_put_viommu;
+
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));

out_put_viommu:
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 6e7efe83bc5d..c0030677e13c 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -180,6 +180,9 @@ struct iommufd_hw_queue {
* the physical location of the guest queue
* If driver has a deinit function to revert what this op
* does, it should set it to the @hw_queue->destroy pointer
+ * @hw_queue_init: Similar to hw_queue_init_phys, but driver providing this op
+ * indicates that HW accesses the guest queue memory via
+ * @hw_queue->baseaddr.
*/
struct iommufd_viommu_ops {
void (*destroy)(struct iommufd_viommu *viommu);
@@ -192,9 +195,9 @@ struct iommufd_viommu_ops {
int (*vdevice_init)(struct iommufd_vdevice *vdev);
size_t (*get_hw_queue_size)(struct iommufd_viommu *viommu,
enum iommu_hw_queue_type queue_type);
- /* AMD's HW will add hw_queue_init simply using @hw_queue->base_addr */
int (*hw_queue_init_phys)(struct iommufd_hw_queue *hw_queue, u32 index,
phys_addr_t base_addr_pa);
+ int (*hw_queue_init)(struct iommufd_hw_queue *hw_queue, u32 index);
};

#if IS_ENABLED(CONFIG_IOMMUFD)
--
2.34.1