iommu/arm-smmu-v3: Add arm_smmu_attach_release()

The IOPF teardown is done in arm_smmu_remove_master_domain() when releasing
the master_domain on detach, under the global arm_smmu_asid_lock mutex.

A later change will add an IOPF workqueue flush to that teardown, which can
block on a user-faulting page-fault handler. Holding the arm_smmu_asid_lock
across it would stall every unrelated attachment in the system.

Split the teardown out of arm_smmu_remove_master_domain(), to a new helper
arm_smmu_attach_release() that runs after arm_smmu_asid_lock is released.

No functional change: the old master_domain belongs to no other device, so
freeing it outside the lock stays safe, still under iommu_group->mutex.

Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Will Deacon <will@kernel.org>
This commit is contained in:
Nicolin Chen 2026-09-15 09:38:14 -07:00 committed by Will Deacon
parent ab0dbf411a
commit 580bbfcdcd
3 changed files with 36 additions and 11 deletions

View File

@ -194,6 +194,7 @@ static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,
arm_smmu_install_ste_for_dev(master, &ste);
arm_smmu_attach_commit(&state);
mutex_unlock(&arm_smmu_asid_lock);
arm_smmu_attach_release(&state);
return 0;
}

View File

@ -3457,9 +3457,9 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
return master->build_invs;
}
static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
struct iommu_domain *domain,
ioasid_t ssid)
static struct arm_smmu_master_domain *
arm_smmu_remove_master_domain(struct arm_smmu_master *master,
struct iommu_domain *domain, ioasid_t ssid)
{
struct arm_smmu_domain *smmu_domain = to_smmu_domain_devices(domain);
struct arm_smmu_master_domain *master_domain;
@ -3467,7 +3467,7 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
unsigned long flags;
if (!smmu_domain)
return;
return NULL;
if (domain->type == IOMMU_DOMAIN_NESTED)
nested_ats_flush = to_smmu_nested_domain(domain)->enable_ats;
@ -3482,8 +3482,24 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
}
spin_unlock_irqrestore(&smmu_domain->devices_lock, flags);
/* arm_smmu_attach_release() will free it */
return master_domain;
}
/* Release the old master_domain detached by arm_smmu_remove_master_domain() */
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;
iommu_group_mutex_assert(master->dev);
if (!master_domain)
return;
arm_smmu_disable_iopf(master, master_domain);
kfree(master_domain);
state->old_master_domain = NULL;
}
/*
@ -3781,7 +3797,8 @@ void arm_smmu_attach_commit(struct arm_smmu_attach_state *state)
arm_smmu_atc_inv_master(master, IOMMU_NO_PASID);
}
arm_smmu_remove_master_domain(master, state->old_domain, state->ssid);
state->old_master_domain = arm_smmu_remove_master_domain(
master, state->old_domain, state->ssid);
arm_smmu_install_old_domain_invs(state);
master->ats_enabled = state->ats_enabled;
}
@ -3856,6 +3873,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
arm_smmu_attach_commit(&state);
mutex_unlock(&arm_smmu_asid_lock);
arm_smmu_attach_release(&state);
return 0;
}
@ -3938,8 +3956,10 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
mutex_lock(&arm_smmu_asid_lock);
ret = arm_smmu_attach_prepare(&state, &smmu_domain->domain);
if (ret)
goto out_unlock;
if (ret) {
mutex_unlock(&arm_smmu_asid_lock);
return ret;
}
/*
* We don't want to obtain to the asid_lock too early, so fix up the
@ -3953,10 +3973,9 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
arm_smmu_update_ste(master, sid_domain, state.ats_enabled);
arm_smmu_attach_commit(&state);
out_unlock:
mutex_unlock(&arm_smmu_asid_lock);
return ret;
arm_smmu_attach_release(&state);
return 0;
}
static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
@ -3976,9 +3995,11 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
arm_smmu_clear_cd(master, pasid);
if (master->ats_enabled)
arm_smmu_atc_inv_master(master, pasid);
arm_smmu_remove_master_domain(master, &smmu_domain->domain, pasid);
state.old_master_domain = arm_smmu_remove_master_domain(
master, &smmu_domain->domain, pasid);
arm_smmu_install_old_domain_invs(&state);
mutex_unlock(&arm_smmu_asid_lock);
arm_smmu_attach_release(&state);
/*
* When the last user of the CD table goes away downgrade the STE back
@ -4041,6 +4062,7 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
arm_smmu_install_ste_for_dev(master, ste);
arm_smmu_attach_commit(&state);
mutex_unlock(&arm_smmu_asid_lock);
arm_smmu_attach_release(&state);
/*
* This has to be done after removing the master from the

View File

@ -1261,12 +1261,14 @@ struct arm_smmu_attach_state {
struct arm_smmu_vmaster *vmaster;
struct arm_smmu_inv_state old_domain_invst;
struct arm_smmu_inv_state new_domain_invst;
struct arm_smmu_master_domain *old_master_domain;
bool ats_enabled;
};
int arm_smmu_attach_prepare(struct arm_smmu_attach_state *state,
struct iommu_domain *new_domain);
void arm_smmu_attach_commit(struct arm_smmu_attach_state *state);
void arm_smmu_attach_release(struct arm_smmu_attach_state *state);
void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
const struct arm_smmu_ste *target);