From 4cf740b0b6628bda1e5c9201ae0d4f56fc6c06a5 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Mon, 14 Jul 2014 19:47:39 +0100 Subject: [PATCH 1/8] iommu/arm-smmu: allow translation stage to be forced on the cmdline When debugging and testing code on an SMMU that supports nested translation, it can be useful to restrict the driver to a particular stage of translation. This patch adds a module parameter to the ARM SMMU driver to allow this by restricting the ability of the probe() code to detect support for only the specified stage. Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index a83cc2a2a2cab..958ae8194afec 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -326,6 +326,11 @@ #define FSYNR0_WNR (1 << 4) +static int force_stage; +module_param_named(force_stage, force_stage, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(force_stage, + "Force SMMU mappings to be installed at a particular stage of translation. A value of '1' or '2' forces the corresponding stage. All other values are ignored (i.e. no stage is forced). Note that selecting a specific stage will disable support for nested translation."); + struct arm_smmu_smr { u8 idx; u16 mask; @@ -1716,6 +1721,13 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return -ENODEV; } #endif + + /* Restrict available stages based on module parameter */ + if (force_stage == 1) + id &= ~(ID0_S2TS | ID0_NTS); + else if (force_stage == 2) + id &= ~(ID0_S1TS | ID0_NTS); + if (id & ID0_S1TS) { smmu->features |= ARM_SMMU_FEAT_TRANS_S1; dev_notice(smmu->dev, "\tstage 1 translation\n"); @@ -1732,8 +1744,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) } if (!(smmu->features & - (ARM_SMMU_FEAT_TRANS_S1 | ARM_SMMU_FEAT_TRANS_S2 | - ARM_SMMU_FEAT_TRANS_NESTED))) { + (ARM_SMMU_FEAT_TRANS_S1 | ARM_SMMU_FEAT_TRANS_S2))) { dev_err(smmu->dev, "\tno translation support!\n"); return -ENODEV; } From 8f68f8e28298abdf518648e794c71e534eb8841c Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Tue, 15 Jul 2014 11:27:08 +0100 Subject: [PATCH 2/8] iommu/arm-smmu: add support for multi-master iommu groups Whilst the driver currently creates one IOMMU group per device, this will soon change when we start supporting non-transparent PCI bridges which require all upstream masters to be assigned to the same address space. This patch reworks our IOMMU group code so that we can easily support multi-master groups. The master configuration (streamids and smrs) is stored as private iommudata on the group, whilst the low-level attach/detach code is updated to avoid double alloc/free when dealing with multiple masters sharing the same SMMU configuration. This unifies device handling, regardless of whether the device sits on the platform or pci bus. Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 65 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 958ae8194afec..38f8d670afb3e 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -431,17 +431,17 @@ static void parse_driver_options(struct arm_smmu_device *smmu) } while (arm_smmu_options[++i].opt); } -static struct device *dev_get_master_dev(struct device *dev) +static struct device_node *dev_get_dev_node(struct device *dev) { if (dev_is_pci(dev)) { struct pci_bus *bus = to_pci_dev(dev)->bus; while (!pci_is_root_bus(bus)) bus = bus->parent; - return bus->bridge->parent; + return bus->bridge->parent->of_node; } - return dev; + return dev->of_node; } static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu, @@ -466,15 +466,17 @@ static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu, } static struct arm_smmu_master_cfg * -find_smmu_master_cfg(struct arm_smmu_device *smmu, struct device *dev) +find_smmu_master_cfg(struct device *dev) { - struct arm_smmu_master *master; + struct arm_smmu_master_cfg *cfg = NULL; + struct iommu_group *group = iommu_group_get(dev); - if (dev_is_pci(dev)) - return dev->archdata.iommu; + if (group) { + cfg = iommu_group_get_iommudata(group); + iommu_group_put(group); + } - master = find_smmu_master(smmu, dev->of_node); - return master ? &master->cfg : NULL; + return cfg; } static int insert_smmu_master(struct arm_smmu_device *smmu, @@ -550,7 +552,7 @@ static struct arm_smmu_device *find_smmu_for_device(struct device *dev) { struct arm_smmu_device *smmu; struct arm_smmu_master *master = NULL; - struct device_node *dev_node = dev_get_master_dev(dev)->of_node; + struct device_node *dev_node = dev_get_dev_node(dev); spin_lock(&arm_smmu_devices_lock); list_for_each_entry(smmu, &arm_smmu_devices, list) { @@ -1156,9 +1158,10 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain, struct arm_smmu_device *smmu = smmu_domain->smmu; void __iomem *gr0_base = ARM_SMMU_GR0(smmu); + /* Devices in an IOMMU group may already be configured */ ret = arm_smmu_master_configure_smrs(smmu, cfg); if (ret) - return ret; + return ret == -EEXIST ? 0 : ret; for (i = 0; i < cfg->num_streamids; ++i) { u32 idx, s2cr; @@ -1179,6 +1182,10 @@ static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain, struct arm_smmu_device *smmu = smmu_domain->smmu; void __iomem *gr0_base = ARM_SMMU_GR0(smmu); + /* An IOMMU group is torn down by the first device to be removed */ + if ((smmu->features & ARM_SMMU_FEAT_STREAM_MATCH) && !cfg->smrs) + return; + /* * We *must* clear the S2CR first, because freeing the SMR means * that it can be re-allocated immediately. @@ -1200,7 +1207,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) struct arm_smmu_device *smmu, *dom_smmu; struct arm_smmu_master_cfg *cfg; - smmu = dev_get_master_dev(dev)->archdata.iommu; + smmu = find_smmu_for_device(dev); if (!smmu) { dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n"); return -ENXIO; @@ -1228,7 +1235,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) } /* Looks ok, so add the device to the domain */ - cfg = find_smmu_master_cfg(smmu_domain->smmu, dev); + cfg = find_smmu_master_cfg(dev); if (!cfg) return -ENODEV; @@ -1240,7 +1247,7 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) struct arm_smmu_domain *smmu_domain = domain->priv; struct arm_smmu_master_cfg *cfg; - cfg = find_smmu_master_cfg(smmu_domain->smmu, dev); + cfg = find_smmu_master_cfg(dev); if (cfg) arm_smmu_domain_remove_master(smmu_domain, cfg); } @@ -1554,17 +1561,19 @@ static int __arm_smmu_get_pci_sid(struct pci_dev *pdev, u16 alias, void *data) return 0; /* Continue walking */ } +static void __arm_smmu_release_pci_iommudata(void *data) +{ + kfree(data); +} + static int arm_smmu_add_device(struct device *dev) { struct arm_smmu_device *smmu; + struct arm_smmu_master_cfg *cfg; struct iommu_group *group; + void (*releasefn)(void *) = NULL; int ret; - if (dev->archdata.iommu) { - dev_warn(dev, "IOMMU driver already assigned to device\n"); - return -EINVAL; - } - smmu = find_smmu_for_device(dev); if (!smmu) return -ENODEV; @@ -1576,7 +1585,6 @@ static int arm_smmu_add_device(struct device *dev) } if (dev_is_pci(dev)) { - struct arm_smmu_master_cfg *cfg; struct pci_dev *pdev = to_pci_dev(dev); cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); @@ -1592,11 +1600,20 @@ static int arm_smmu_add_device(struct device *dev) */ pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid, &cfg->streamids[0]); - dev->archdata.iommu = cfg; + releasefn = __arm_smmu_release_pci_iommudata; } else { - dev->archdata.iommu = smmu; + struct arm_smmu_master *master; + + master = find_smmu_master(smmu, dev->of_node); + if (!master) { + ret = -ENODEV; + goto out_put_group; + } + + cfg = &master->cfg; } + iommu_group_set_iommudata(group, cfg, releasefn); ret = iommu_group_add_device(group, dev); out_put_group: @@ -1606,10 +1623,6 @@ static int arm_smmu_add_device(struct device *dev) static void arm_smmu_remove_device(struct device *dev) { - if (dev_is_pci(dev)) - kfree(dev->archdata.iommu); - - dev->archdata.iommu = NULL; iommu_group_remove_device(dev); } From 844e35bdfe834fccb5def1bc4cd614ca22409d0c Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Thu, 17 Jul 2014 11:23:51 +0100 Subject: [PATCH 3/8] iommu/arm-smmu: put iommu_domain pointer in dev->archdata.iommu In preparation for nested translation support, stick a pointer to the iommu_domain in dev->archdata.iommu. This makes it much easier to grab hold of the physical group configuration (e.g. cbndx) when dealing with vSMMU accesses from a guest. Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 38f8d670afb3e..e8d3111522037 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -1213,6 +1213,11 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) return -ENXIO; } + if (dev->archdata.iommu) { + dev_err(dev, "already attached to IOMMU domain\n"); + return -EEXIST; + } + /* * Sanity check the domain. We don't support domains across * different SMMUs. @@ -1239,7 +1244,10 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) if (!cfg) return -ENODEV; - return arm_smmu_domain_add_master(smmu_domain, cfg); + ret = arm_smmu_domain_add_master(smmu_domain, cfg); + if (!ret) + dev->archdata.iommu = domain; + return ret; } static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) @@ -1248,8 +1256,11 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) struct arm_smmu_master_cfg *cfg; cfg = find_smmu_master_cfg(dev); - if (cfg) - arm_smmu_domain_remove_master(smmu_domain, cfg); + if (!cfg) + return; + + dev->archdata.iommu = NULL; + arm_smmu_domain_remove_master(smmu_domain, cfg); } static bool arm_smmu_pte_is_contiguous_range(unsigned long addr, From c757e8528a304214d0a9be2e99011b94bf374d37 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 30 Jul 2014 11:33:25 +0100 Subject: [PATCH 4/8] iommu/arm-smmu: use page shift instead of page size to avoid division Arbitrary integer division is not available in all ARM CPUs, so the GCC may spit out calls to helper functions which are not implemented in the kernel. This patch avoids these problems in the SMMU driver by using page shift instead of page size, so that divisions by the page size (as required by the vSMMU code) can be expressed as a simple right shift. Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index e8d3111522037..482cc9e3138a5 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -59,7 +59,7 @@ /* SMMU global address space */ #define ARM_SMMU_GR0(smmu) ((smmu)->base) -#define ARM_SMMU_GR1(smmu) ((smmu)->base + (smmu)->pagesize) +#define ARM_SMMU_GR1(smmu) ((smmu)->base + (1 << (smmu)->pgshift)) /* * SMMU global address space with conditional offset to access secure @@ -224,7 +224,7 @@ /* Translation context bank */ #define ARM_SMMU_CB_BASE(smmu) ((smmu)->base + ((smmu)->size >> 1)) -#define ARM_SMMU_CB(smmu, n) ((n) * (smmu)->pagesize) +#define ARM_SMMU_CB(smmu, n) ((n) * (1 << (smmu)->pgshift)) #define ARM_SMMU_CB_SCTLR 0x0 #define ARM_SMMU_CB_RESUME 0x8 @@ -354,7 +354,7 @@ struct arm_smmu_device { void __iomem *base; unsigned long size; - unsigned long pagesize; + unsigned long pgshift; #define ARM_SMMU_FEAT_COHERENT_WALK (1 << 0) #define ARM_SMMU_FEAT_STREAM_MATCH (1 << 1) @@ -1814,12 +1814,12 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) /* ID1 */ id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID1); - smmu->pagesize = (id & ID1_PAGESIZE) ? SZ_64K : SZ_4K; + smmu->pgshift = (id & ID1_PAGESIZE) ? 16 : 12; /* Check for size mismatch of SMMU address space from mapped region */ size = 1 << (((id >> ID1_NUMPAGENDXB_SHIFT) & ID1_NUMPAGENDXB_MASK) + 1); - size *= (smmu->pagesize << 1); + size *= 2 << smmu->pgshift; if (smmu->size != size) dev_warn(smmu->dev, "SMMU address space size (0x%lx) differs from mapped region size (0x%lx)!\n", From 28d6007ba2fd344164e01ef300af7f621e9e6b0d Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Mon, 1 Sep 2014 16:24:48 +0100 Subject: [PATCH 5/8] iommu/arm-smmu: don't bother truncating the s1 output size to VA_BITS In order for nested translation to work correctly, we need to ensure that the maximum output address size from stage-1 is <= the maximum supported input address size to stage-2. The latter is currently defined by VA_BITS, since we make use of the CPU page table functions for allocating out tables and so the driver currently enforces this restriction by truncating the stage-1 output size during probe. In reality, this doesn't make a lot of sense; the guest OS is responsible for managing the stage-1 page tables, so we actually just need to ensure that the ID registers of the virtual SMMU interface only advertise the supported stage-2 input size. This patch fixes the problem by treating the stage-1 and stage-2 input address sizes separately. Reported-by: Tirumalesh Chalamarla Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 45 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 482cc9e3138a5..27fd3d7ba8860 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -24,7 +24,7 @@ * - v7/v8 long-descriptor format * - Non-secure access to the SMMU * - 4k and 64k pages, with contiguous pte hints. - * - Up to 42-bit addressing (dependent on VA_BITS) + * - Up to 48-bit addressing (dependent on VA_BITS) * - Context fault reporting */ @@ -375,8 +375,9 @@ struct arm_smmu_device { u32 num_mapping_groups; DECLARE_BITMAP(smr_map, ARM_SMMU_MAX_SMRS); - unsigned long input_size; + unsigned long s1_input_size; unsigned long s1_output_size; + unsigned long s2_input_size; unsigned long s2_output_size; u32 num_global_irqs; @@ -762,7 +763,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) gr1_base + ARM_SMMU_GR1_CBA2R(cfg->cbndx)); /* TTBCR2 */ - switch (smmu->input_size) { + switch (smmu->s1_input_size) { case 32: reg = (TTBCR2_ADDR_32 << TTBCR2_SEP_SHIFT); break; @@ -831,7 +832,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) reg = TTBCR_TG0_64K; if (!stage1) { - reg |= (64 - smmu->s1_output_size) << TTBCR_T0SZ_SHIFT; + reg |= (64 - smmu->s2_input_size) << TTBCR_T0SZ_SHIFT; switch (smmu->s2_output_size) { case 32: @@ -854,7 +855,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) break; } } else { - reg |= (64 - smmu->input_size) << TTBCR_T0SZ_SHIFT; + reg |= (64 - smmu->s1_input_size) << TTBCR_T0SZ_SHIFT; } } else { reg = 0; @@ -1454,9 +1455,11 @@ static int arm_smmu_handle_mapping(struct arm_smmu_domain *smmu_domain, if (cfg->cbar == CBAR_TYPE_S2_TRANS) { stage = 2; + input_mask = (1ULL << smmu->s2_input_size) - 1; output_mask = (1ULL << smmu->s2_output_size) - 1; } else { stage = 1; + input_mask = (1ULL << smmu->s1_input_size) - 1; output_mask = (1ULL << smmu->s1_output_size) - 1; } @@ -1466,7 +1469,6 @@ static int arm_smmu_handle_mapping(struct arm_smmu_domain *smmu_domain, if (size & ~PAGE_MASK) return -EINVAL; - input_mask = (1ULL << smmu->input_size) - 1; if ((phys_addr_t)iova & ~input_mask) return -ERANGE; @@ -1838,28 +1840,21 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) /* ID2 */ id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID2); size = arm_smmu_id_size_to_bits((id >> ID2_IAS_SHIFT) & ID2_IAS_MASK); + smmu->s1_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, size); - /* - * Stage-1 output limited by stage-2 input size due to pgd - * allocation (PTRS_PER_PGD). - */ - if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) { + /* Stage-2 input size limited due to pgd allocation (PTRS_PER_PGD) */ #ifdef CONFIG_64BIT - smmu->s1_output_size = min_t(unsigned long, VA_BITS, size); + smmu->s2_input_size = min_t(unsigned long, VA_BITS, size); #else - smmu->s1_output_size = min(32UL, size); + smmu->s2_input_size = min(32UL, size); #endif - } else { - smmu->s1_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, - size); - } /* The stage-2 output mask is also applied for bypass */ size = arm_smmu_id_size_to_bits((id >> ID2_OAS_SHIFT) & ID2_OAS_MASK); smmu->s2_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, size); if (smmu->version == 1) { - smmu->input_size = 32; + smmu->s1_input_size = 32; } else { #ifdef CONFIG_64BIT size = (id >> ID2_UBS_SHIFT) & ID2_UBS_MASK; @@ -1867,7 +1862,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) #else size = 32; #endif - smmu->input_size = size; + smmu->s1_input_size = size; if ((PAGE_SIZE == SZ_4K && !(id & ID2_PTFS_4K)) || (PAGE_SIZE == SZ_64K && !(id & ID2_PTFS_64K)) || @@ -1878,10 +1873,14 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) } } - dev_notice(smmu->dev, - "\t%lu-bit VA, %lu-bit IPA, %lu-bit PA\n", - smmu->input_size, smmu->s1_output_size, - smmu->s2_output_size); + if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) + dev_notice(smmu->dev, "\tStage-1: %lu-bit VA -> %lu-bit IPA\n", + smmu->s1_input_size, smmu->s1_output_size); + + if (smmu->features & ARM_SMMU_FEAT_TRANS_S2) + dev_notice(smmu->dev, "\tStage-2: %lu-bit IPA -> %lu-bit PA\n", + smmu->s2_input_size, smmu->s2_output_size); + return 0; } From 093604033361928f7f355b4d1766d0179ae747fb Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Thu, 28 Aug 2014 17:51:59 +0100 Subject: [PATCH 6/8] iommu/arm-smmu: fix architecture version detection The SMMU driver was relying on a quirk of MMU-500 r2px to identify the correct architecture version. Since this does not apply to other implementations, make the architecture version for each supported implementation explicit. While we're at it, remove the unnecessary #ifdef since the dependencies for CONFIG_ARM_SMMU already imply CONFIG_OF. Signed-off-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 47 +++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 27fd3d7ba8860..c00f483e106f0 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -331,6 +331,11 @@ module_param_named(force_stage, force_stage, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(force_stage, "Force SMMU mappings to be installed at a particular stage of translation. A value of '1' or '2' forces the corresponding stage. All other values are ignored (i.e. no stage is forced). Note that selecting a specific stage will disable support for nested translation."); +enum arm_smmu_arch_version { + ARM_SMMU_V1 = 1, + ARM_SMMU_V2, +}; + struct arm_smmu_smr { u8 idx; u16 mask; @@ -365,7 +370,7 @@ struct arm_smmu_device { #define ARM_SMMU_OPT_SECURE_CFG_ACCESS (1 << 0) u32 options; - int version; + enum arm_smmu_arch_version version; u32 num_context_banks; u32 num_s2_context_banks; @@ -737,7 +742,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) /* CBAR */ reg = cfg->cbar; - if (smmu->version == 1) + if (smmu->version == ARM_SMMU_V1) reg |= cfg->irptndx << CBAR_IRPTNDX_SHIFT; /* @@ -752,7 +757,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) } writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBAR(cfg->cbndx)); - if (smmu->version > 1) { + if (smmu->version > ARM_SMMU_V1) { /* CBA2R */ #ifdef CONFIG_64BIT reg = CBA2R_RW64_64BIT; @@ -825,7 +830,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) * TTBCR * We use long descriptor, with inner-shareable WBWA tables in TTBR0. */ - if (smmu->version > 1) { + if (smmu->version > ARM_SMMU_V1) { if (PAGE_SIZE == SZ_4K) reg = TTBCR_TG0_4K; else @@ -922,7 +927,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, goto out_unlock; cfg->cbndx = ret; - if (smmu->version == 1) { + if (smmu->version == ARM_SMMU_V1) { cfg->irptndx = atomic_inc_return(&smmu->irptndx); cfg->irptndx %= smmu->num_context_irqs; } else { @@ -1733,10 +1738,6 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) u32 id; dev_notice(smmu->dev, "probing hardware configuration...\n"); - - /* Primecell ID */ - id = readl_relaxed(gr0_base + ARM_SMMU_GR0_PIDR2); - smmu->version = ((id >> PIDR2_ARCH_SHIFT) & PIDR2_ARCH_MASK) + 1; dev_notice(smmu->dev, "SMMUv%d with:\n", smmu->version); /* ID0 */ @@ -1853,7 +1854,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) size = arm_smmu_id_size_to_bits((id >> ID2_OAS_SHIFT) & ID2_OAS_MASK); smmu->s2_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, size); - if (smmu->version == 1) { + if (smmu->version == ARM_SMMU_V1) { smmu->s1_input_size = 32; } else { #ifdef CONFIG_64BIT @@ -1884,8 +1885,18 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return 0; } +static struct of_device_id arm_smmu_of_match[] = { + { .compatible = "arm,smmu-v1", .data = (void *)ARM_SMMU_V1 }, + { .compatible = "arm,smmu-v2", .data = (void *)ARM_SMMU_V2 }, + { .compatible = "arm,mmu-400", .data = (void *)ARM_SMMU_V1 }, + { .compatible = "arm,mmu-500", .data = (void *)ARM_SMMU_V2 }, + { }, +}; +MODULE_DEVICE_TABLE(of, arm_smmu_of_match); + static int arm_smmu_device_dt_probe(struct platform_device *pdev) { + const struct of_device_id *of_id; struct resource *res; struct arm_smmu_device *smmu; struct device *dev = &pdev->dev; @@ -1900,6 +1911,9 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) } smmu->dev = dev; + of_id = of_match_node(arm_smmu_of_match, dev->of_node); + smmu->version = (enum arm_smmu_arch_version)of_id->data; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); smmu->base = devm_ioremap_resource(dev, res); if (IS_ERR(smmu->base)) @@ -1964,7 +1978,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) parse_driver_options(smmu); - if (smmu->version > 1 && + if (smmu->version > ARM_SMMU_V1 && smmu->num_context_banks != smmu->num_context_irqs) { dev_err(dev, "found only %d context interrupt(s) but %d required\n", @@ -2045,17 +2059,6 @@ static int arm_smmu_device_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_OF -static struct of_device_id arm_smmu_of_match[] = { - { .compatible = "arm,smmu-v1", }, - { .compatible = "arm,smmu-v2", }, - { .compatible = "arm,mmu-400", }, - { .compatible = "arm,mmu-500", }, - { }, -}; -MODULE_DEVICE_TABLE(of, arm_smmu_of_match); -#endif - static struct platform_driver arm_smmu_driver = { .driver = { .owner = THIS_MODULE, From d3aba0460a2e13b49892f7a12237f82658c44257 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Thu, 28 Aug 2014 17:52:00 +0100 Subject: [PATCH 7/8] iommu/arm-smmu: support MMU-401 MMU-401 is similar to MMU-400, but updated with limited ARMv8 support. Signed-off-by: Robin Murphy Signed-off-by: Will Deacon --- Documentation/devicetree/bindings/iommu/arm,smmu.txt | 1 + drivers/iommu/arm-smmu.c | 1 + 2 files changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.txt b/Documentation/devicetree/bindings/iommu/arm,smmu.txt index 2d0f7cd867eae..06760503a819f 100644 --- a/Documentation/devicetree/bindings/iommu/arm,smmu.txt +++ b/Documentation/devicetree/bindings/iommu/arm,smmu.txt @@ -14,6 +14,7 @@ conditions. "arm,smmu-v1" "arm,smmu-v2" "arm,mmu-400" + "arm,mmu-401" "arm,mmu-500" depending on the particular implementation and/or the diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index c00f483e106f0..939242c411002 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -1889,6 +1889,7 @@ static struct of_device_id arm_smmu_of_match[] = { { .compatible = "arm,smmu-v1", .data = (void *)ARM_SMMU_V1 }, { .compatible = "arm,smmu-v2", .data = (void *)ARM_SMMU_V2 }, { .compatible = "arm,mmu-400", .data = (void *)ARM_SMMU_V1 }, + { .compatible = "arm,mmu-401", .data = (void *)ARM_SMMU_V1 }, { .compatible = "arm,mmu-500", .data = (void *)ARM_SMMU_V2 }, { }, }; From ccd359f219bee914501a8892b148e2a1315066d3 Mon Sep 17 00:00:00 2001 From: Mitchel Humpherys Date: Fri, 19 Sep 2014 22:58:42 +0100 Subject: [PATCH 8/8] iommu/arm-smmu: fix bug in pmd construction We are using the same pfn for every pte we create while constructing the pmd. Fix this by actually updating the pfn on each iteration of the pmd construction loop. It's not clear if we can actually hit this bug right now since iommu_map splits up the calls to .map based on the page size, so we only ever seem to iterate this loop once. However, things might change in the future that might cause us to hit this. Signed-off-by: Mitchel Humpherys Signed-off-by: Will Deacon --- drivers/iommu/arm-smmu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 939242c411002..37dc3dd0df962 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -1408,6 +1408,7 @@ static int arm_smmu_alloc_init_pmd(struct arm_smmu_device *smmu, pud_t *pud, ret = arm_smmu_alloc_init_pte(smmu, pmd, addr, next, pfn, prot, stage); phys += next - addr; + pfn = __phys_to_pfn(phys); } while (pmd++, addr = next, addr < end); return ret;