[PATCH v3 04/13] iommu/arm-smmu-v3: Flush in-flight fault work on domain detach
Jonathan Cameron
jonathan.cameron at oss.qualcomm.com
Thu Sep 3 12:18:33 PDT 2026
> arm_smmu_attach_release() polls the hardware event queue to drain all the
> in-flight stall events for an old domain that the IOMMU core might free at
> any moment. However, a drained event is dequeued, yet it is not necessarily
> handled, and the IOPF work for a handled one can still be running.
>
> So, first synchronize_irq() on the evtq and the combined IRQs following the
> drain, in order to guarantee that every dequeued event has reached the IOPF
> workqueue, since synchronize_irq() waits for an in-flight IRQ thread. Skip
> the waits on a timed-out drain though, since a stuck consumer would block
drop the "though" - doesn't read well or add anything.
> the unbounded wait_event() inside synchronize_irq() as well.
>
> Then, invoke iopf_queue_flush_dev() to finally drain the IOPF workqueue, as
> the fault work references the old domain via its attach handle, while the
> IOMMU core might free that old domain once the detach call returns.
>
> If arm_smmu_drain_queue() times out, fault work may still be in flight, and
> iopf_queue_remove_device() would free iopf groups that the queued work also
> references. Skip the iopf teardown and leak the master_domain, rather than
> risk a use-after-free.
>
> The skip also leaks the iopf refcount, keeping the device enrolled on the
> IOPF queue, which would strand its fault parameter on the queue list once
> the device teardown frees dev->iommu, crashing a later iopf_queue_free().
> Reclaim the enrollment in arm_smmu_release_device(), where all the attach
> handles are gone so a straggler report cannot queue a new fault group.
>
> Note that a residual race window remains between an iopf_queue_flush_dev()
> and iopf_queue_remove_device(): a fault arriving in between still resolves
> to the old attach handle, as the IOMMU core publishes a handle change only
> after the driver ops return. This window predates the drain narrowing it,
> and is only closable by an ordering fix in the IOMMU core. Furthermore, a
> timed-out drain shares exactly the same window, given that it must keep the
> device enrolled on the IOPF queue, where iopf_queue_remove_device() would
> free the iopf groups that any in-flight fault work still references.
>
> Fixes: cfea71aea921 ("iommu/arm-smmu-v3: Put iopf enablement in the domain attach path")
> Cc: stable at vger.kernel.org # v6.16
> Co-developed-by: Barak Biber <bbiber at nvidia.com>
> Signed-off-by: Barak Biber <bbiber at nvidia.com>
> Co-developed-by: Stefan Kaestle <skaestle at nvidia.com>
> Signed-off-by: Stefan Kaestle <skaestle at nvidia.com>
> Signed-off-by: Malak Marrid <mmarrid at nvidia.com>
> Assisted-by: Claude:claude-fable-5
> Signed-off-by: Nicolin Chen <nicolinc at nvidia.com>
>
> diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> index d255ff2519f9..1851ee7ec15b 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -3399,6 +3399,7 @@ void arm_smmu_attach_release(struct arm_smmu_attach_state *state)
> struct arm_smmu_master_domain *master_domain = state->old_master_domain;
> struct arm_smmu_master *master = state->master;
> struct arm_smmu_device *smmu = master->smmu;
> + int ret = 0;
>
> lockdep_assert_not_held(&arm_smmu_asid_lock);
> iommu_group_mutex_assert(master->dev);
> @@ -3412,8 +3413,40 @@ void arm_smmu_attach_release(struct arm_smmu_attach_state *state)
> * up once the IOMMU core swaps the handle, mistakenly resuming it
> * against the next domain.
> */
> - if (master->stall_enabled)
> - arm_smmu_drain_queue(smmu, &smmu->evtq.q, false);
> + if (master->stall_enabled) {
> + ret = arm_smmu_drain_queue(smmu, &smmu->evtq.q, false);
> + /*
> + * Ensure pending events have reached the IOPF queue, unless
> + * the drain timed out: a stuck consumer would also block an
> + * unbounded wait_event() inside the synchronize_irq().
> + */
> + if (!ret && smmu->evtq.q.irq)
> + synchronize_irq(smmu->evtq.q.irq);
> + /* Pending events might be in the combined_irq handler */
> + if (!ret && smmu->combined_irq)
> + synchronize_irq(smmu->combined_irq);
I'd group the two things that happen if we didn't time out.
if (!ret) { /* Didn't time out */
if (smmu->evtq.q.irq)
synchronize_irq(smmu->evt.q.irq);
if (smmu->combined_irq)
synchronize_irq(smmu->combined_irq);
}
> + }
Thanks,
Jonathan
--
Jonathan Cameron <jonathan.cameron at oss.qualcomm.com>
More information about the linux-arm-kernel
mailing list