summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Gebben <jgebben@codeaurora.org>2015-07-10 16:43:23 -0600
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:13:38 -0700
commit13ae3bcaca0fc47f4709b72cdba50ac3eed80198 (patch)
treec2db6950161f33a67ffe4b9ba91c6ffb0e0bad89
parent36e4ea4ac1063624ee470f9769f47cf93a13e777 (diff)
iommu/arm-smmu: add support for DOMAIN_ATTR_DYNAMIC
Implement support for dynamic domain switching. This feature is only enabled when the qcom,dynamic device tree attribute for an smmu instance. In order to use dynamic domains, a non-dynamic domain must first be created and attached. This non-dynamic domain must remain attached while the device is in use. All domains must be attached before calling any mapping functions, such as iommu_map(). This allows the pagetable setup to be set up during attach based on the hardware configuration for the smmu. Before attaching a dynamic domain, the DOMAIN_ATTR_CONTEXT_BANK must be set to indicate which context bank registers should be used for any register programming. Attaching dynamic domain doesn't cause hardware register programming, but mapping operations may cause TLBI operations. Additionally, the upstream driver or hardware may do other register programming. Because the arm-smmu driver assigns context banks dynamically, the upstream driver should query DOMAIN_ATTR_CONTEXT_BANK on its non-dynamic domain, to ensure the correct value is used for all dynamic domains created. To switch domains dynamically, the upstream driver or hardware must program the TTBR0 and CONTEXTIDR registers with the values from the DOMAIN_ATTR_TTBR0 and DOMAIN_ATTR_CONTEXTIDR attributes for the desired domain. The upstream driver may also need to do other hardware specific register programming to properly synchronize the domain switch. It must ensure that all register state, except for CONTEXTIDR and TTBR0 registers, is restored at the end of the domain switch operation. DOMAIN_ATTR_PROCID may be set to any value for each domain before it is attached. This value is part of the CONTEXTIDR register, but it is not used by the SMMU hardware. Setting a unique value for this attribute in every domain can be useful for debugging. Change-Id: Ib92d06db06832700bdf56265b169ccddfb192698 Signed-off-by: Jeremy Gebben <jgebben@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/iommu/arm,smmu.txt4
-rw-r--r--drivers/iommu/arm-smmu.c154
2 files changed, 158 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.txt b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
index c55b5499fcfe..186524c16312 100644
--- a/Documentation/devicetree/bindings/iommu/arm,smmu.txt
+++ b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
@@ -112,6 +112,10 @@ conditions.
support, the stream matching table is programmed before
control is even turned over to Linux.
+- qcom,dynamic : Allow dynamic domains to be attached. This is only
+ useful if the upstream hardware is capable of switching
+ between multiple domains within a single context bank.
+
- clocks : List of clocks to be used during SMMU register access. See
Documentation/devicetree/bindings/clock/clock-bindings.txt
for information about the format. For each clock specified
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c
index 945724dcdd0a..db6de14c7d29 100644
--- a/drivers/iommu/arm-smmu.c
+++ b/drivers/iommu/arm-smmu.c
@@ -363,6 +363,7 @@ struct arm_smmu_device {
#define ARM_SMMU_OPT_ERRATA_TZ_ATOS (1 << 7)
#define ARM_SMMU_OPT_NO_M (1 << 8)
#define ARM_SMMU_OPT_NO_SMR_CHECK (1 << 9)
+#define ARM_SMMU_OPT_DYNAMIC (1 << 10)
u32 options;
enum arm_smmu_arch_version version;
@@ -393,6 +394,7 @@ struct arm_smmu_device {
/* Protects against domains attaching to the same SMMU concurrently */
struct mutex attach_lock;
unsigned int attach_count;
+ struct idr asid_idr;
struct arm_smmu_impl_def_reg *impl_def_attach_registers;
unsigned int num_impl_def_attach_registers;
@@ -414,6 +416,11 @@ struct arm_smmu_cfg {
#define INVALID_CBNDX 0xff
#define INVALID_ASID 0xffff
#define INVALID_VMID 0xff
+/*
+ * In V7L and V8L with TTBCR2.AS == 0, ASID is 8 bits.
+ * V8L 16 with TTBCR2.AS == 1 (16 bit ASID) isn't supported yet.
+ */
+#define MAX_ASID 0xff
#define ARM_SMMU_CB_ASID(cfg) ((cfg)->asid)
#define ARM_SMMU_CB_VMID(cfg) ((cfg)->vmid)
@@ -463,6 +470,7 @@ static struct arm_smmu_option_prop arm_smmu_options[] = {
{ ARM_SMMU_OPT_ERRATA_TZ_ATOS, "qcom,errata-tz-atos" },
{ ARM_SMMU_OPT_NO_M, "qcom,no-mmu-enable" },
{ ARM_SMMU_OPT_NO_SMR_CHECK, "qcom,no-smr-check" },
+ { ARM_SMMU_OPT_DYNAMIC, "qcom,dynamic" },
{ 0, NULL},
};
@@ -1632,6 +1640,79 @@ static void arm_smmu_impl_def_programming(struct arm_smmu_device *smmu)
static void arm_smmu_device_reset(struct arm_smmu_device *smmu);
+static int arm_smmu_attach_dynamic(struct iommu_domain *domain,
+ struct arm_smmu_device *smmu)
+{
+ int ret;
+ struct arm_smmu_domain *smmu_domain = domain->priv;
+ enum io_pgtable_fmt fmt;
+ struct io_pgtable_ops *pgtbl_ops;
+ struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
+
+ if (!(smmu->options & ARM_SMMU_OPT_DYNAMIC)) {
+ dev_err(smmu->dev, "dynamic domains not supported\n");
+ return -EPERM;
+ }
+
+ if (smmu_domain->smmu != NULL) {
+ dev_err(smmu->dev, "domain is already attached\n");
+ return -EBUSY;
+ }
+
+ if (smmu_domain->cfg.cbndx >= smmu->num_context_banks) {
+ dev_err(smmu->dev, "invalid context bank\n");
+ return -ENODEV;
+ }
+
+ if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) {
+ smmu_domain->cfg.cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS;
+ } else if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) {
+ smmu_domain->cfg.cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS;
+ } else {
+ /* dynamic only makes sense for S1. */
+ return -EINVAL;
+ }
+
+ smmu_domain->pgtbl_cfg = (struct io_pgtable_cfg) {
+ .pgsize_bitmap = arm_smmu_ops.pgsize_bitmap,
+ .ias = smmu->va_size,
+ .oas = smmu->ipa_size,
+ .tlb = &arm_smmu_gather_ops,
+ };
+
+ fmt = IS_ENABLED(CONFIG_64BIT) ? ARM_64_LPAE_S1 : ARM_32_LPAE_S1;
+
+ pgtbl_ops = alloc_io_pgtable_ops(fmt, &smmu_domain->pgtbl_cfg,
+ smmu_domain);
+ if (!pgtbl_ops)
+ return -ENOMEM;
+
+ cfg->vmid = cfg->cbndx + 2;
+ smmu_domain->smmu = smmu;
+
+ mutex_lock(&smmu->attach_lock);
+ /* try to avoid reusing an old ASID right away */
+ ret = idr_alloc_cyclic(&smmu->asid_idr, domain,
+ smmu->num_context_banks + 2,
+ MAX_ASID + 1, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(smmu->dev, "dynamic ASID allocation failed: %d\n",
+ ret);
+ goto out;
+ }
+
+ smmu_domain->cfg.asid = ret;
+ smmu_domain->smmu = smmu;
+ smmu_domain->pgtbl_ops = pgtbl_ops;
+ ret = 0;
+out:
+ if (ret)
+ kfree(pgtbl_ops);
+ mutex_unlock(&smmu->attach_lock);
+
+ return ret;
+}
+
static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
{
int ret;
@@ -1648,7 +1729,14 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
return -ENXIO;
}
+ if (smmu_domain->attributes & (1 << DOMAIN_ATTR_DYNAMIC)) {
+ ret = arm_smmu_attach_dynamic(domain, smmu);
+ mutex_unlock(&smmu_domain->init_mutex);
+ return ret;
+ }
+
mutex_lock(&smmu->attach_lock);
+
if (dev->archdata.iommu) {
dev_err(dev, "already attached to IOMMU domain\n");
mutex_unlock(&smmu->attach_lock);
@@ -1743,6 +1831,23 @@ static void arm_smmu_power_off(struct arm_smmu_device *smmu,
arm_smmu_disable_regulators(smmu);
}
+static void arm_smmu_detach_dynamic(struct iommu_domain *domain,
+ struct arm_smmu_device *smmu)
+{
+ struct arm_smmu_domain *smmu_domain = domain->priv;
+
+ mutex_lock(&smmu->attach_lock);
+ if (smmu->attach_count > 0) {
+ arm_smmu_enable_clocks(smmu_domain->smmu);
+ arm_smmu_tlb_inv_context(smmu_domain);
+ arm_smmu_disable_clocks(smmu_domain->smmu);
+ }
+ idr_remove(&smmu->asid_idr, smmu_domain->cfg.asid);
+ smmu_domain->cfg.asid = INVALID_ASID;
+ smmu_domain->smmu = NULL;
+ mutex_unlock(&smmu->attach_lock);
+}
+
static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
{
struct arm_smmu_domain *smmu_domain = domain->priv;
@@ -1758,6 +1863,13 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
return;
}
+
+ if (smmu_domain->attributes & (1 << DOMAIN_ATTR_DYNAMIC)) {
+ arm_smmu_detach_dynamic(domain, smmu);
+ mutex_unlock(&smmu_domain->init_mutex);
+ return;
+ }
+
mutex_lock(&smmu->attach_lock);
cfg = find_smmu_master_cfg(dev);
@@ -2204,6 +2316,11 @@ static int arm_smmu_domain_get_attr(struct iommu_domain *domain,
*((u32 *)data) = smmu_domain->cfg.procid;
ret = 0;
break;
+ case DOMAIN_ATTR_DYNAMIC:
+ *((int *)data) = !!(smmu_domain->attributes
+ & (1 << DOMAIN_ATTR_DYNAMIC));
+ ret = 0;
+ break;
default:
ret = -ENODEV;
break;
@@ -2278,8 +2395,42 @@ static int arm_smmu_domain_set_attr(struct iommu_domain *domain,
smmu_domain->cfg.procid = *((u32 *)data);
ret = 0;
break;
+ case DOMAIN_ATTR_DYNAMIC: {
+ int dynamic = *((int *)data);
+
+ if (smmu_domain->smmu != NULL) {
+ dev_err(smmu_domain->smmu->dev,
+ "cannot change dynamic attribute while attached\n");
+ ret = -EBUSY;
+ break;
+ }
+
+ if (dynamic)
+ smmu_domain->attributes |= 1 << DOMAIN_ATTR_DYNAMIC;
+ else
+ smmu_domain->attributes &= ~(1 << DOMAIN_ATTR_DYNAMIC);
+ ret = 0;
+ break;
+ }
+ case DOMAIN_ATTR_CONTEXT_BANK:
+ /* context bank can't be set while attached */
+ if (smmu_domain->smmu != NULL) {
+ ret = -EBUSY;
+ break;
+ }
+ /* ... and it can only be set for dynamic contexts. */
+ if (!(smmu_domain->attributes & (1 << DOMAIN_ATTR_DYNAMIC))) {
+ ret = -EINVAL;
+ break;
+ }
+
+ /* this will be validated during attach */
+ smmu_domain->cfg.cbndx = *((unsigned int *)data);
+ ret = 0;
+ break;
default:
ret = -ENODEV;
+ break;
}
out_unlock:
@@ -2795,6 +2946,8 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
}
}
+ idr_init(&smmu->asid_idr);
+
INIT_LIST_HEAD(&smmu->list);
spin_lock(&arm_smmu_devices_lock);
list_add(&smmu->list, &arm_smmu_devices);
@@ -2849,6 +3002,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev)
free_irq(smmu->irqs[i], smmu);
mutex_lock(&smmu->attach_lock);
+ idr_destroy(&smmu->asid_idr);
/*
* If all devices weren't detached for some reason, we're
* still powered on. Power off now.