Re: [PATCH v2 10/22] iommufd/viommmu: Add IOMMUFD_CMD_VCMDQ_ALLOC ioctl

Vasant Hegde
2025-04-28T12:58:15Z

Hi Nicolin,

[+Suravee]

On 4/26/2025 11:28 AM, Nicolin Chen wrote:
> Introduce a new IOMMUFD_CMD_VCMDQ_ALLOC ioctl for user space to allocate
> a vCMDQ for a vIOMMU object. Simply increase the refcount of the vIOMMU.
>
> Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
> ---
> drivers/iommu/iommufd/iommufd_private.h | 2 +
> include/uapi/linux/iommufd.h | 41 +++++++++++
> drivers/iommu/iommufd/main.c | 6 ++
> drivers/iommu/iommufd/viommu.c | 94 +++++++++++++++++++++++++
> 4 files changed, 143 insertions(+)
>
> diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
> index 79160b039bc7..b974c207ae8a 100644
> --- a/drivers/iommu/iommufd/iommufd_private.h
> +++ b/drivers/iommu/iommufd/iommufd_private.h
> @@ -611,6 +611,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd);
> void iommufd_viommu_destroy(struct iommufd_object *obj);
> int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd);
> void iommufd_vdevice_destroy(struct iommufd_object *obj);
> +int iommufd_vcmdq_alloc_ioctl(struct iommufd_ucmd *ucmd);
> +void iommufd_vcmdq_destroy(struct iommufd_object *obj);
>
> #ifdef CONFIG_IOMMUFD_TEST
> int iommufd_test(struct iommufd_ucmd *ucmd);
> diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
> index cc90299a08d9..06a763fda47f 100644
> --- a/include/uapi/linux/iommufd.h
> +++ b/include/uapi/linux/iommufd.h
> @@ -56,6 +56,7 @@ enum {
> IOMMUFD_CMD_VDEVICE_ALLOC = 0x91,
> IOMMUFD_CMD_IOAS_CHANGE_PROCESS = 0x92,
> IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
> + IOMMUFD_CMD_VCMDQ_ALLOC = 0x94,
> };
>
> /**
> @@ -1147,4 +1148,44 @@ struct iommu_veventq_alloc {
> __u32 __reserved;
> };
> #define IOMMU_VEVENTQ_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_VEVENTQ_ALLOC)
> +
> +/**
> + * enum iommu_vcmdq_type - Virtual Command Queue Type
> + * @IOMMU_VCMDQ_TYPE_DEFAULT: Reserved for future use
> + */
> +enum iommu_vcmdq_type {
> + IOMMU_VCMDQ_TYPE_DEFAULT = 0,
> +};
> +
> +/**
> + * struct iommu_vcmdq_alloc - ioctl(IOMMU_VCMDQ_ALLOC)
> + * @size: sizeof(struct iommu_vcmdq_alloc)
> + * @flags: Must be 0
> + * @viommu_id: Virtual IOMMU ID to associate the virtual command queue with
> + * @type: One of enum iommu_vcmdq_type

> + * @index: The logical index to the virtual command queue per virtual IOMMU, for
> + * a multi-queue model
> + * @out_vcmdq_id: The ID of the new virtual command queue
> + * @addr: Base address of the queue memory in the guest physical address space

Sorry. I didn't get this part.

So here `addr` is command queue base address like
- NVIDIA's virtual command queue
- AMD vIOMMU's command buffer

.. and it will allocate vcmdq for each buffer type. Is that the correct
understanding?

In case of AMD vIOMMU, buffer base address is programmed in different register
(ex: MMIO Offset 0008h Command Buffer Base Address Register) and buffer
enable/disable is done via different register (ex: MMIO Offset 0018h IOMMU
Control Register). And we need to communicate both to hypervisor. Not sure this
API can accommodate this as addr seems to be mandatory.

[1]
https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/specifications/48882_IOMMU.pdf

> + * @length: Length of the queue memory in the guest physical address space
> + *
> + * Allocate a virtual command queue object for a vIOMMU-specific HW-accelerated
> + * feature that can access a guest queue memory described by @addr and @length.
> + * It's suggested for VMM to back the queue memory using a single huge page with
> + * a proper alignment for its contiguity in the host physical address space. The
> + * call will fail, if the queue memory is not contiguous in the physical address
> + * space. Upon success, its underlying physical pages will be pinned to prevent
> + * VMM from unmapping them in the IOAS, until the virtual CMDQ gets destroyed.
> + */
> +struct iommu_vcmdq_alloc {
> + __u32 size;
> + __u32 flags;
> + __u32 viommu_id;
> + __u32 type;
> + __u32 index;
> + __u32 out_vcmdq_id;
> + __aligned_u64 addr;
> + __aligned_u64 length;
> +};
> +#define IOMMU_VCMDQ_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_VCMDQ_ALLOC)
> #endif
> diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
> index 2b9ee9b4a424..ac51d5cfaa61 100644
> --- a/drivers/iommu/iommufd/main.c
> +++ b/drivers/iommu/iommufd/main.c
> @@ -303,6 +303,7 @@ union ucmd_buffer {
> struct iommu_ioas_map map;
> struct iommu_ioas_unmap unmap;
> struct iommu_option option;
> + struct iommu_vcmdq_alloc vcmdq;
> struct iommu_vdevice_alloc vdev;
> struct iommu_veventq_alloc veventq;
> struct iommu_vfio_ioas vfio_ioas;
> @@ -358,6 +359,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
> IOCTL_OP(IOMMU_IOAS_UNMAP, iommufd_ioas_unmap, struct iommu_ioas_unmap,
> length),
> IOCTL_OP(IOMMU_OPTION, iommufd_option, struct iommu_option, val64),
> + IOCTL_OP(IOMMU_VCMDQ_ALLOC, iommufd_vcmdq_alloc_ioctl,
> + struct iommu_vcmdq_alloc, length),
> IOCTL_OP(IOMMU_VDEVICE_ALLOC, iommufd_vdevice_alloc_ioctl,
> struct iommu_vdevice_alloc, virt_id),
> IOCTL_OP(IOMMU_VEVENTQ_ALLOC, iommufd_veventq_alloc,
> @@ -501,6 +504,9 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
> [IOMMUFD_OBJ_IOAS] = {
> .destroy = iommufd_ioas_destroy,
> },
> + [IOMMUFD_OBJ_VCMDQ] = {
> + .destroy = iommufd_vcmdq_destroy,
> + },
> [IOMMUFD_OBJ_VDEVICE] = {
> .destroy = iommufd_vdevice_destroy,
> },
> diff --git a/drivers/iommu/iommufd/viommu.c b/drivers/iommu/iommufd/viommu.c
> index a65153458a26..02a111710ffe 100644
> --- a/drivers/iommu/iommufd/viommu.c
> +++ b/drivers/iommu/iommufd/viommu.c
> @@ -170,3 +170,97 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
> iommufd_put_object(ucmd->ictx, &viommu->obj);
> return rc;
> }
> +
> +void iommufd_vcmdq_destroy(struct iommufd_object *obj)
> +{

I didn't understood destroy flow in general. Can you please help me to understand:

VMM is expected to track all buffers and call this interface? OR iommufd will
take care of it? What happens if VM crashes ?

> + struct iommufd_vcmdq *vcmdq =
> + container_of(obj, struct iommufd_vcmdq, obj);
> + struct iommufd_viommu *viommu = vcmdq->viommu;
> +
> + if (viommu->ops->vcmdq_destroy)
> + viommu->ops->vcmdq_destroy(vcmdq);
> + iopt_unpin_pages(&viommu->hwpt->ioas->iopt, vcmdq->addr, vcmdq->length);
> + refcount_dec(&viommu->obj.users);
> +}
> +
> +int iommufd_vcmdq_alloc_ioctl(struct iommufd_ucmd *ucmd)
> +{
> + struct iommu_vcmdq_alloc *cmd = ucmd->cmd;
> + struct iommufd_viommu *viommu;
> + struct iommufd_vcmdq *vcmdq;
> + struct page **pages;
> + int max_npages, i;
> + dma_addr_t end;
> + int rc;
> +
> + if (cmd->flags || cmd->type == IOMMU_VCMDQ_TYPE_DEFAULT)
> + return -EOPNOTSUPP;
> + if (!cmd->addr || !cmd->length)
> + return -EINVAL;
> + if (check_add_overflow(cmd->addr, cmd->length - 1, &end))
> + return -EOVERFLOW;
> +
> + max_npages = DIV_ROUND_UP(cmd->length, PAGE_SIZE);
> + pages = kcalloc(max_npages, sizeof(*pages), GFP_KERNEL);
> + if (!pages)
> + return -ENOMEM;
> +
> + viommu = iommufd_get_viommu(ucmd, cmd->viommu_id);
> + if (IS_ERR(viommu)) {
> + rc = PTR_ERR(viommu);
> + goto out_free;
> + }
> +
> + if (!viommu->ops || !viommu->ops->vcmdq_alloc) {
> + rc = -EOPNOTSUPP;
> + goto out_put_viommu;
> + }
> +
> + /* Quick test on the base address */
> + if (!iommu_iova_to_phys(viommu->hwpt->common.domain, cmd->addr)) {
> + rc = -ENXIO;
> + goto out_put_viommu;
> + }
> +
> + /* The underlying physical pages must be pinned in the IOAS */
> + rc = iopt_pin_pages(&viommu->hwpt->ioas->iopt, cmd->addr, cmd->length,
> + pages, 0);

Why do we need this? is it not pinned already as part of vfio binding?

-Vasant

> + if (rc)
> + goto out_put_viommu;
> +
> + /* Validate if the underlying physical pages are contiguous */
> + for (i = 1; i < max_npages && pages[i]; i++) {
> + if (page_to_pfn(pages[i]) == page_to_pfn(pages[i - 1]) + 1)
> + continue;
> + rc = -EFAULT;
> + goto out_unpin;
> + }
> +
> + vcmdq = viommu->ops->vcmdq_alloc(viommu, cmd->type, cmd->index,
> + cmd->addr, cmd->length);
> + if (IS_ERR(vcmdq)) {
> + rc = PTR_ERR(vcmdq);
> + goto out_unpin;
> + }
> +
> + vcmdq->viommu = viommu;
> + refcount_inc(&viommu->obj.users);
> + vcmdq->addr = cmd->addr;
> + vcmdq->ictx = ucmd->ictx;
> + vcmdq->length = cmd->length;
> + cmd->out_vcmdq_id = vcmdq->obj.id;
> + rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
> + if (rc)
> + iommufd_object_abort_and_destroy(ucmd->ictx, &vcmdq->obj);
> + else
> + iommufd_object_finalize(ucmd->ictx, &vcmdq->obj);
> + goto out_put_viommu;
> +
> +out_unpin:
> + iopt_unpin_pages(&viommu->hwpt->ioas->iopt, cmd->addr, cmd->length);
> +out_put_viommu:
> + iommufd_put_object(ucmd->ictx, &viommu->obj);
> +out_free:
> + kfree(pages);
> + return rc;
> +}