OSDN Git Service

iommu/arm-smmu: add support for DOMAIN_ATTR_DYNAMIC
authorJeremy Gebben <jgebben@codeaurora.org>
Fri, 10 Jul 2015 22:43:23 +0000 (16:43 -0600)
committerDavid Keitel <dkeitel@codeaurora.org>
Tue, 22 Mar 2016 18:13:38 +0000 (11:13 -0700)
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>
Documentation/devicetree/bindings/iommu/arm,smmu.txt
drivers/iommu/arm-smmu.c

index c55b549..186524c 100644 (file)
@@ -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
index 945724d..db6de14 100644 (file)
@@ -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.