diff options
Diffstat (limited to 'drivers/iommu')
-rw-r--r-- | drivers/iommu/arm-smmu.c | 159 |
1 files changed, 93 insertions, 66 deletions
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index dfe13780ba54..69b6cab65421 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -170,12 +170,20 @@ #define S2CR_CBNDX_MASK 0xff #define S2CR_TYPE_SHIFT 16 #define S2CR_TYPE_MASK 0x3 -#define S2CR_TYPE_TRANS (0 << S2CR_TYPE_SHIFT) -#define S2CR_TYPE_BYPASS (1 << S2CR_TYPE_SHIFT) -#define S2CR_TYPE_FAULT (2 << S2CR_TYPE_SHIFT) +enum arm_smmu_s2cr_type { + S2CR_TYPE_TRANS, + S2CR_TYPE_BYPASS, + S2CR_TYPE_FAULT, +}; #define S2CR_PRIVCFG_SHIFT 24 -#define S2CR_PRIVCFG_UNPRIV (2 << S2CR_PRIVCFG_SHIFT) +#define S2CR_PRIVCFG_MASK 0x3 +enum arm_smmu_s2cr_privcfg { + S2CR_PRIVCFG_DEFAULT, + S2CR_PRIVCFG_DIPAN, + S2CR_PRIVCFG_UNPRIV, + S2CR_PRIVCFG_PRIV, +}; /* Context bank attribute registers */ #define ARM_SMMU_GR1_CBAR(n) (0x0 + ((n) << 2)) @@ -292,6 +300,16 @@ enum arm_smmu_implementation { CAVIUM_SMMUV2, }; +struct arm_smmu_s2cr { + enum arm_smmu_s2cr_type type; + enum arm_smmu_s2cr_privcfg privcfg; + u8 cbndx; +}; + +#define s2cr_init_val (struct arm_smmu_s2cr){ \ + .type = disable_bypass ? S2CR_TYPE_FAULT : S2CR_TYPE_BYPASS, \ +} + struct arm_smmu_smr { u16 mask; u16 id; @@ -346,6 +364,7 @@ struct arm_smmu_device { u16 streamid_mask; u16 smr_mask_mask; struct arm_smmu_smr *smrs; + struct arm_smmu_s2cr *s2crs; unsigned long va_size; unsigned long ipa_size; @@ -1108,6 +1127,23 @@ static void arm_smmu_write_smr(struct arm_smmu_device *smmu, int idx) writel_relaxed(reg, ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_SMR(idx)); } +static void arm_smmu_write_s2cr(struct arm_smmu_device *smmu, int idx) +{ + struct arm_smmu_s2cr *s2cr = smmu->s2crs + idx; + u32 reg = (s2cr->type & S2CR_TYPE_MASK) << S2CR_TYPE_SHIFT | + (s2cr->cbndx & S2CR_CBNDX_MASK) << S2CR_CBNDX_SHIFT | + (s2cr->privcfg & S2CR_PRIVCFG_MASK) << S2CR_PRIVCFG_SHIFT; + + writel_relaxed(reg, ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_S2CR(idx)); +} + +static void arm_smmu_write_sme(struct arm_smmu_device *smmu, int idx) +{ + arm_smmu_write_s2cr(smmu, idx); + if (smmu->smrs) + arm_smmu_write_smr(smmu, idx); +} + static int arm_smmu_master_alloc_smes(struct arm_smmu_device *smmu, struct arm_smmu_master_cfg *cfg) { @@ -1158,6 +1194,23 @@ static void arm_smmu_master_free_smes(struct arm_smmu_device *smmu, { int i; + /* + * We *must* clear the S2CR first, because freeing the SMR means + * that it can be re-allocated immediately. + */ + for (i = 0; i < cfg->num_streamids; ++i) { + int idx = cfg->smendx[i]; + + /* An IOMMU group is torn down by the first device to be removed */ + if (idx == INVALID_SMENDX) + return; + + smmu->s2crs[idx] = s2cr_init_val; + arm_smmu_write_s2cr(smmu, idx); + } + /* Sync S2CR updates before touching anything else */ + __iowmb(); + /* Invalidate the SMRs before freeing back to the allocator */ for (i = 0; i < cfg->num_streamids; ++i) { if (smmu->smrs) @@ -1170,9 +1223,16 @@ static void arm_smmu_master_free_smes(struct arm_smmu_device *smmu, static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain, struct arm_smmu_master_cfg *cfg) { - int i, ret; + int i, ret = 0; struct arm_smmu_device *smmu = smmu_domain->smmu; - void __iomem *gr0_base = ARM_SMMU_GR0(smmu); + struct arm_smmu_s2cr *s2cr = smmu->s2crs; + enum arm_smmu_s2cr_type type = S2CR_TYPE_TRANS; + u8 cbndx = smmu_domain->cfg.cbndx; + + if (cfg->smendx[0] == INVALID_SMENDX) + ret = arm_smmu_master_alloc_smes(smmu, cfg); + if (ret) + return ret; /* * FIXME: This won't be needed once we have IOMMU-backed DMA ops @@ -1181,58 +1241,21 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain, * and a PCI device (i.e. a PCI host controller) */ if (smmu_domain->domain.type == IOMMU_DOMAIN_DMA) - return 0; - - /* Devices in an IOMMU group may already be configured */ - ret = arm_smmu_master_alloc_smes(smmu, cfg); - if (ret) - return ret == -EEXIST ? 0 : ret; + type = S2CR_TYPE_BYPASS; for (i = 0; i < cfg->num_streamids; ++i) { - u32 idx, s2cr; - - idx = cfg->smendx[i]; - s2cr = S2CR_TYPE_TRANS | S2CR_PRIVCFG_UNPRIV | - (smmu_domain->cfg.cbndx << S2CR_CBNDX_SHIFT); - writel_relaxed(s2cr, gr0_base + ARM_SMMU_GR0_S2CR(idx)); - } - - return 0; -} - -static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain, - struct arm_smmu_master_cfg *cfg) -{ - int i; - struct arm_smmu_device *smmu = smmu_domain->smmu; - void __iomem *gr0_base = ARM_SMMU_GR0(smmu); - - /* - * We *must* clear the S2CR first, because freeing the SMR means - * that it can be re-allocated immediately. - */ - for (i = 0; i < cfg->num_streamids; ++i) { int idx = cfg->smendx[i]; - u32 reg = disable_bypass ? S2CR_TYPE_FAULT : S2CR_TYPE_BYPASS; - /* An IOMMU group is torn down by the first device to be removed */ - if (idx == INVALID_SMENDX) - return; + /* Devices in an IOMMU group may already be configured */ + if (type == s2cr[idx].type && cbndx == s2cr[idx].cbndx) + break; - writel_relaxed(reg, gr0_base + ARM_SMMU_GR0_S2CR(idx)); + s2cr[idx].type = type; + s2cr[idx].privcfg = S2CR_PRIVCFG_UNPRIV; + s2cr[idx].cbndx = cbndx; + arm_smmu_write_s2cr(smmu, idx); } - - arm_smmu_master_free_smes(smmu, cfg); -} - -static void arm_smmu_detach_dev(struct device *dev, - struct arm_smmu_master_cfg *cfg) -{ - struct iommu_domain *domain = dev->archdata.iommu; - struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain); - - dev->archdata.iommu = NULL; - arm_smmu_domain_remove_master(smmu_domain, cfg); + return 0; } static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) @@ -1269,14 +1292,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) if (!cfg) return -ENODEV; - /* Detach the dev from its current domain */ - if (dev->archdata.iommu) - arm_smmu_detach_dev(dev, cfg); - - ret = arm_smmu_domain_add_master(smmu_domain, cfg); - if (!ret) - dev->archdata.iommu = domain; - return ret; + return arm_smmu_domain_add_master(smmu_domain, cfg); } static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova, @@ -1477,6 +1493,12 @@ static int arm_smmu_add_device(struct device *dev) static void arm_smmu_remove_device(struct device *dev) { + struct arm_smmu_device *smmu = find_smmu_for_device(dev); + struct arm_smmu_master_cfg *cfg = find_smmu_master_cfg(dev); + + if (smmu && cfg) + arm_smmu_master_free_smes(smmu, cfg); + iommu_group_remove_device(dev); } @@ -1582,12 +1604,8 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) * Reset stream mapping groups: Initial values mark all SMRn as * invalid and all S2CRn as bypass unless overridden. */ - reg = disable_bypass ? S2CR_TYPE_FAULT : S2CR_TYPE_BYPASS; - for (i = 0; i < smmu->num_mapping_groups; ++i) { - if (smmu->smrs) - arm_smmu_write_smr(smmu, i); - writel_relaxed(reg, gr0_base + ARM_SMMU_GR0_S2CR(i)); - } + for (i = 0; i < smmu->num_mapping_groups; ++i) + arm_smmu_write_sme(smmu, i); /* * Before clearing ARM_MMU500_ACTLR_CPRE, need to @@ -1676,6 +1694,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) void __iomem *gr0_base = ARM_SMMU_GR0(smmu); u32 id; bool cttw_dt, cttw_reg; + int i; dev_notice(smmu->dev, "probing hardware configuration...\n"); dev_notice(smmu->dev, "SMMUv%d with:\n", @@ -1773,6 +1792,14 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) "\tstream matching with %lu register groups, mask 0x%x", size, smmu->smr_mask_mask); } + /* s2cr->type == 0 means translation, so initialise explicitly */ + smmu->s2crs = devm_kmalloc_array(smmu->dev, size, sizeof(*smmu->s2crs), + GFP_KERNEL); + if (!smmu->s2crs) + return -ENOMEM; + for (i = 0; i < size; i++) + smmu->s2crs[i] = s2cr_init_val; + smmu->num_mapping_groups = size; if (smmu->version < ARM_SMMU_V2 || !(id & ID0_PTFS_NO_AARCH32)) { |