mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
parent
ab0dbf411a
commit
580bbfcdcd
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user