iommu/arm-smmu: fix races on domain->smmu
Clients are allowed to attach/detach and map/unmap independently. I.e., they don't have to be attached at the time they map or unmap. Currently if they detach and unmap at the same time there's a race condition where the SMMU associated with the domain could go away while we try to use it. Fix this by locking the domain lock everywhere that domain->smmu is used. Change-Id: I2b74c0863c28e3bb87e8bd45dae363c8e67e008b Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
This commit is contained in:
parent
25252ab2ab
commit
19fe116e06
1 changed files with 63 additions and 24 deletions
|
@ -827,6 +827,7 @@ static void arm_smmu_tlb_sync_cb_atomic(struct arm_smmu_device *smmu,
|
||||||
dev_err(smmu->dev, "TLBSYNC timeout!\n");
|
dev_err(smmu->dev, "TLBSYNC timeout!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* smmu_domain->lock must be held across any calls to this function */
|
||||||
static void arm_smmu_tlb_inv_context(struct arm_smmu_domain *smmu_domain)
|
static void arm_smmu_tlb_inv_context(struct arm_smmu_domain *smmu_domain)
|
||||||
{
|
{
|
||||||
struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
|
struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
|
||||||
|
@ -858,13 +859,21 @@ static irqreturn_t arm_smmu_context_fault(int irq, void *dev)
|
||||||
struct iommu_domain *domain = dev;
|
struct iommu_domain *domain = dev;
|
||||||
struct arm_smmu_domain *smmu_domain = domain->priv;
|
struct arm_smmu_domain *smmu_domain = domain->priv;
|
||||||
struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
|
struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
|
||||||
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
struct arm_smmu_device *smmu;
|
||||||
void __iomem *cb_base;
|
void __iomem *cb_base;
|
||||||
bool ctx_hang_errata =
|
bool ctx_hang_errata;
|
||||||
smmu->options & ARM_SMMU_OPT_ERRATA_CTX_FAULT_HANG;
|
bool fatal_asf;
|
||||||
bool fatal_asf = smmu->options & ARM_SMMU_OPT_FATAL_ASF;
|
|
||||||
phys_addr_t phys_soft;
|
phys_addr_t phys_soft;
|
||||||
|
|
||||||
|
mutex_lock(&smmu_domain->lock);
|
||||||
|
smmu = smmu_domain->smmu;
|
||||||
|
if (!smmu) {
|
||||||
|
pr_err("took a fault on a detached domain (%p)\n", domain);
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
ctx_hang_errata = smmu->options & ARM_SMMU_OPT_ERRATA_CTX_FAULT_HANG;
|
||||||
|
fatal_asf = smmu->options & ARM_SMMU_OPT_FATAL_ASF;
|
||||||
|
|
||||||
arm_smmu_enable_clocks(smmu);
|
arm_smmu_enable_clocks(smmu);
|
||||||
|
|
||||||
cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx);
|
cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx);
|
||||||
|
@ -872,6 +881,7 @@ static irqreturn_t arm_smmu_context_fault(int irq, void *dev)
|
||||||
|
|
||||||
if (!(fsr & FSR_FAULT)) {
|
if (!(fsr & FSR_FAULT)) {
|
||||||
arm_smmu_disable_clocks(smmu);
|
arm_smmu_disable_clocks(smmu);
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -931,6 +941,7 @@ static irqreturn_t arm_smmu_context_fault(int irq, void *dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
arm_smmu_disable_clocks(smmu);
|
arm_smmu_disable_clocks(smmu);
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -964,6 +975,7 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev)
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* smmu_domain->lock must be held across any calls to this function */
|
||||||
static void arm_smmu_flush_pgtable(struct arm_smmu_domain *smmu_domain,
|
static void arm_smmu_flush_pgtable(struct arm_smmu_domain *smmu_domain,
|
||||||
void *addr, size_t size)
|
void *addr, size_t size)
|
||||||
{
|
{
|
||||||
|
@ -1167,9 +1179,8 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
|
||||||
struct arm_smmu_domain *smmu_domain = domain->priv;
|
struct arm_smmu_domain *smmu_domain = domain->priv;
|
||||||
struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
|
struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
|
||||||
|
|
||||||
mutex_lock(&smmu_domain->lock);
|
|
||||||
if (smmu_domain->smmu)
|
if (smmu_domain->smmu)
|
||||||
goto out_unlock;
|
goto out;
|
||||||
|
|
||||||
if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) {
|
if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) {
|
||||||
/*
|
/*
|
||||||
|
@ -1189,7 +1200,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
|
||||||
ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
|
ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
|
||||||
smmu->num_context_banks);
|
smmu->num_context_banks);
|
||||||
if (IS_ERR_VALUE(ret))
|
if (IS_ERR_VALUE(ret))
|
||||||
goto out_unlock;
|
goto out;
|
||||||
|
|
||||||
cfg->cbndx = ret;
|
cfg->cbndx = ret;
|
||||||
if (smmu->version == ARM_SMMU_V1) {
|
if (smmu->version == ARM_SMMU_V1) {
|
||||||
|
@ -1201,7 +1212,6 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
|
||||||
|
|
||||||
ACCESS_ONCE(smmu_domain->smmu) = smmu;
|
ACCESS_ONCE(smmu_domain->smmu) = smmu;
|
||||||
arm_smmu_init_context_bank(smmu_domain);
|
arm_smmu_init_context_bank(smmu_domain);
|
||||||
mutex_unlock(&smmu_domain->lock);
|
|
||||||
|
|
||||||
irq = smmu->irqs[smmu->num_global_irqs + cfg->irptndx];
|
irq = smmu->irqs[smmu->num_global_irqs + cfg->irptndx];
|
||||||
ret = request_threaded_irq(irq, NULL, arm_smmu_context_fault,
|
ret = request_threaded_irq(irq, NULL, arm_smmu_context_fault,
|
||||||
|
@ -1215,8 +1225,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_unlock:
|
out:
|
||||||
mutex_unlock(&smmu_domain->lock);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1228,9 +1237,6 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain)
|
||||||
void __iomem *cb_base;
|
void __iomem *cb_base;
|
||||||
int irq;
|
int irq;
|
||||||
|
|
||||||
if (!smmu)
|
|
||||||
return;
|
|
||||||
|
|
||||||
arm_smmu_enable_clocks(smmu_domain->smmu);
|
arm_smmu_enable_clocks(smmu_domain->smmu);
|
||||||
/* Disable the context bank and nuke the TLB before freeing it. */
|
/* Disable the context bank and nuke the TLB before freeing it. */
|
||||||
cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx);
|
cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx);
|
||||||
|
@ -1495,9 +1501,11 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
||||||
struct arm_smmu_device *smmu, *dom_smmu;
|
struct arm_smmu_device *smmu, *dom_smmu;
|
||||||
struct arm_smmu_master_cfg *cfg;
|
struct arm_smmu_master_cfg *cfg;
|
||||||
|
|
||||||
|
mutex_lock(&smmu_domain->lock);
|
||||||
smmu = find_smmu_for_device(dev);
|
smmu = find_smmu_for_device(dev);
|
||||||
if (!smmu) {
|
if (!smmu) {
|
||||||
dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n");
|
dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n");
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1560,10 +1568,12 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
||||||
if (!ret)
|
if (!ret)
|
||||||
dev->archdata.iommu = domain;
|
dev->archdata.iommu = domain;
|
||||||
arm_smmu_disable_clocks(smmu);
|
arm_smmu_disable_clocks(smmu);
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
err_disable_clocks:
|
err_disable_clocks:
|
||||||
arm_smmu_disable_clocks(smmu);
|
arm_smmu_disable_clocks(smmu);
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
mutex_lock(&smmu->attach_lock);
|
mutex_lock(&smmu->attach_lock);
|
||||||
if (!--smmu->attach_count &&
|
if (!--smmu->attach_count &&
|
||||||
(!(smmu->options & ARM_SMMU_OPT_REGISTER_SAVE)))
|
(!(smmu->options & ARM_SMMU_OPT_REGISTER_SAVE)))
|
||||||
|
@ -1587,11 +1597,21 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
|
||||||
{
|
{
|
||||||
struct arm_smmu_domain *smmu_domain = domain->priv;
|
struct arm_smmu_domain *smmu_domain = domain->priv;
|
||||||
struct arm_smmu_master_cfg *cfg;
|
struct arm_smmu_master_cfg *cfg;
|
||||||
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
struct arm_smmu_device *smmu;
|
||||||
|
|
||||||
|
mutex_lock(&smmu_domain->lock);
|
||||||
|
smmu = smmu_domain->smmu;
|
||||||
|
if (!smmu) {
|
||||||
|
dev_err(dev, "Domain already detached!\n");
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cfg = find_smmu_master_cfg(dev);
|
cfg = find_smmu_master_cfg(dev);
|
||||||
if (!cfg)
|
if (!cfg) {
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dev->archdata.iommu = NULL;
|
dev->archdata.iommu = NULL;
|
||||||
arm_smmu_domain_remove_master(smmu_domain, cfg);
|
arm_smmu_domain_remove_master(smmu_domain, cfg);
|
||||||
|
@ -1600,6 +1620,7 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
|
||||||
if (!--smmu->attach_count)
|
if (!--smmu->attach_count)
|
||||||
arm_smmu_power_off(smmu);
|
arm_smmu_power_off(smmu);
|
||||||
mutex_unlock(&smmu->attach_lock);
|
mutex_unlock(&smmu->attach_lock);
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool arm_smmu_pte_is_contiguous_range(unsigned long addr,
|
static bool arm_smmu_pte_is_contiguous_range(unsigned long addr,
|
||||||
|
@ -1825,7 +1846,6 @@ static int arm_smmu_handle_mapping(struct arm_smmu_domain *smmu_domain,
|
||||||
if (size & ~PAGE_MASK)
|
if (size & ~PAGE_MASK)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
mutex_lock(&smmu_domain->lock);
|
|
||||||
pgd += pgd_index(iova);
|
pgd += pgd_index(iova);
|
||||||
end = iova + size;
|
end = iova + size;
|
||||||
do {
|
do {
|
||||||
|
@ -1834,15 +1854,12 @@ static int arm_smmu_handle_mapping(struct arm_smmu_domain *smmu_domain,
|
||||||
ret = arm_smmu_alloc_init_pud(smmu_domain, pgd, iova, next,
|
ret = arm_smmu_alloc_init_pud(smmu_domain, pgd, iova, next,
|
||||||
paddr, prot, stage);
|
paddr, prot, stage);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_unlock;
|
return ret;
|
||||||
|
|
||||||
paddr += next - iova;
|
paddr += next - iova;
|
||||||
iova = next;
|
iova = next;
|
||||||
} while (pgd++, iova != end);
|
} while (pgd++, iova != end);
|
||||||
|
|
||||||
out_unlock:
|
|
||||||
mutex_unlock(&smmu_domain->lock);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1855,6 +1872,7 @@ static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
|
||||||
if (!smmu_domain)
|
if (!smmu_domain)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
|
mutex_lock(&smmu_domain->lock);
|
||||||
ret = arm_smmu_handle_mapping(smmu_domain, iova, paddr, size, prot);
|
ret = arm_smmu_handle_mapping(smmu_domain, iova, paddr, size, prot);
|
||||||
|
|
||||||
if (!ret && smmu_domain->smmu &&
|
if (!ret && smmu_domain->smmu &&
|
||||||
|
@ -1863,6 +1881,7 @@ static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
|
||||||
arm_smmu_tlb_inv_context(smmu_domain);
|
arm_smmu_tlb_inv_context(smmu_domain);
|
||||||
arm_smmu_disable_clocks(smmu_domain->smmu);
|
arm_smmu_disable_clocks(smmu_domain->smmu);
|
||||||
}
|
}
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1873,12 +1892,14 @@ static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
|
||||||
int ret;
|
int ret;
|
||||||
struct arm_smmu_domain *smmu_domain = domain->priv;
|
struct arm_smmu_domain *smmu_domain = domain->priv;
|
||||||
|
|
||||||
|
mutex_lock(&smmu_domain->lock);
|
||||||
ret = arm_smmu_handle_mapping(smmu_domain, iova, 0, size, 0);
|
ret = arm_smmu_handle_mapping(smmu_domain, iova, 0, size, 0);
|
||||||
if (smmu_domain->smmu) {
|
if (smmu_domain->smmu) {
|
||||||
arm_smmu_enable_clocks(smmu_domain->smmu);
|
arm_smmu_enable_clocks(smmu_domain->smmu);
|
||||||
arm_smmu_tlb_inv_context(smmu_domain);
|
arm_smmu_tlb_inv_context(smmu_domain);
|
||||||
arm_smmu_disable_clocks(smmu_domain->smmu);
|
arm_smmu_disable_clocks(smmu_domain->smmu);
|
||||||
}
|
}
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
return ret ? 0 : size;
|
return ret ? 0 : size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2038,10 +2059,23 @@ err_unlock:
|
||||||
static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
|
static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
|
||||||
dma_addr_t iova)
|
dma_addr_t iova)
|
||||||
{
|
{
|
||||||
|
phys_addr_t phys;
|
||||||
|
bool try_htw = true;
|
||||||
struct arm_smmu_domain *smmu_domain = domain->priv;
|
struct arm_smmu_domain *smmu_domain = domain->priv;
|
||||||
if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS)
|
|
||||||
return arm_smmu_iova_to_phys_hard(domain, iova);
|
mutex_lock(&smmu_domain->lock);
|
||||||
return arm_smmu_iova_to_phys_soft(domain, iova);
|
|
||||||
|
if (!smmu_domain->smmu) {
|
||||||
|
pr_err("Called iova_to_phys on a detached domain. Falling back to software table walk.\n");
|
||||||
|
try_htw = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try_htw && smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS)
|
||||||
|
phys = arm_smmu_iova_to_phys_hard(domain, iova);
|
||||||
|
else
|
||||||
|
phys = arm_smmu_iova_to_phys_soft(domain, iova);
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
|
return phys;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool arm_smmu_capable(enum iommu_cap cap)
|
static bool arm_smmu_capable(enum iommu_cap cap)
|
||||||
|
@ -2157,15 +2191,20 @@ static int arm_smmu_domain_set_attr(struct iommu_domain *domain,
|
||||||
switch (attr) {
|
switch (attr) {
|
||||||
case DOMAIN_ATTR_COHERENT_HTW_DISABLE:
|
case DOMAIN_ATTR_COHERENT_HTW_DISABLE:
|
||||||
{
|
{
|
||||||
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
struct arm_smmu_device *smmu;
|
||||||
int htw_disable = *((int *)data);
|
int htw_disable = *((int *)data);
|
||||||
|
|
||||||
|
mutex_lock(&smmu_domain->lock);
|
||||||
|
smmu = smmu_domain->smmu;
|
||||||
|
|
||||||
if (smmu && !(smmu->features & ARM_SMMU_FEAT_COHERENT_WALK)
|
if (smmu && !(smmu->features & ARM_SMMU_FEAT_COHERENT_WALK)
|
||||||
&& !htw_disable) {
|
&& !htw_disable) {
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
dev_err(smmu->dev,
|
dev_err(smmu->dev,
|
||||||
"Can't enable coherent htw on this domain: this SMMU doesn't support it\n");
|
"Can't enable coherent htw on this domain: this SMMU doesn't support it\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
mutex_unlock(&smmu_domain->lock);
|
||||||
|
|
||||||
if (htw_disable)
|
if (htw_disable)
|
||||||
smmu_domain->attributes |=
|
smmu_domain->attributes |=
|
||||||
|
|
Loading…
Add table
Reference in a new issue