]> git.karo-electronics.de Git - linux-beck.git/commitdiff
iommu/arm-smmu: add support for DOMAIN_ATTR_NESTING attribute
authorWill Deacon <will.deacon@arm.com>
Wed, 25 Jun 2014 21:46:31 +0000 (22:46 +0100)
committerWill Deacon <will.deacon@arm.com>
Fri, 14 Nov 2014 14:41:39 +0000 (14:41 +0000)
When domains are set with the DOMAIN_ATTR_NESTING flag, we must ensure
that we allocate them to stage-2 context banks if the hardware permits
it.

This patch adds support for the attribute to the ARM SMMU driver, with
the actual stage being determined depending on the features supported
by the hardware.

Signed-off-by: Will Deacon <will.deacon@arm.com>
drivers/iommu/arm-smmu.c

index 217581469f79cfbed5c1e793f1baa8f8affa4cf0..7a80f710ba2d4ee972fcc2cf8de9c48ec4ae81ce 100644 (file)
@@ -404,9 +404,16 @@ struct arm_smmu_cfg {
 #define ARM_SMMU_CB_ASID(cfg)          ((cfg)->cbndx)
 #define ARM_SMMU_CB_VMID(cfg)          ((cfg)->cbndx + 1)
 
+enum arm_smmu_domain_stage {
+       ARM_SMMU_DOMAIN_S1 = 0,
+       ARM_SMMU_DOMAIN_S2,
+       ARM_SMMU_DOMAIN_NESTED,
+};
+
 struct arm_smmu_domain {
        struct arm_smmu_device          *smmu;
        struct arm_smmu_cfg             cfg;
+       enum arm_smmu_domain_stage      stage;
        spinlock_t                      lock;
 };
 
@@ -906,19 +913,46 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
        if (smmu_domain->smmu)
                goto out_unlock;
 
-       if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) {
+       /*
+        * Mapping the requested stage onto what we support is surprisingly
+        * complicated, mainly because the spec allows S1+S2 SMMUs without
+        * support for nested translation. That means we end up with the
+        * following table:
+        *
+        * Requested        Supported        Actual
+        *     S1               N              S1
+        *     S1             S1+S2            S1
+        *     S1               S2             S2
+        *     S1               S1             S1
+        *     N                N              N
+        *     N              S1+S2            S2
+        *     N                S2             S2
+        *     N                S1             S1
+        *
+        * Note that you can't actually request stage-2 mappings.
+        */
+       if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S1))
+               smmu_domain->stage = ARM_SMMU_DOMAIN_S2;
+       if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S2))
+               smmu_domain->stage = ARM_SMMU_DOMAIN_S1;
+
+       switch (smmu_domain->stage) {
+       case ARM_SMMU_DOMAIN_S1:
+               cfg->cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS;
+               start = smmu->num_s2_context_banks;
+               break;
+       case ARM_SMMU_DOMAIN_NESTED:
                /*
                 * We will likely want to change this if/when KVM gets
                 * involved.
                 */
-               cfg->cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS;
-               start = smmu->num_s2_context_banks;
-       } else if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) {
-               cfg->cbar = CBAR_TYPE_S1_TRANS_S2_BYPASS;
-               start = smmu->num_s2_context_banks;
-       } else {
+       case ARM_SMMU_DOMAIN_S2:
                cfg->cbar = CBAR_TYPE_S2_TRANS;
                start = 0;
+               break;
+       default:
+               ret = -EINVAL;
+               goto out_unlock;
        }
 
        ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
@@ -1647,20 +1681,56 @@ static void arm_smmu_remove_device(struct device *dev)
        iommu_group_remove_device(dev);
 }
 
+static int arm_smmu_domain_get_attr(struct iommu_domain *domain,
+                                   enum iommu_attr attr, void *data)
+{
+       struct arm_smmu_domain *smmu_domain = domain->priv;
+
+       switch (attr) {
+       case DOMAIN_ATTR_NESTING:
+               *(int *)data = (smmu_domain->stage == ARM_SMMU_DOMAIN_NESTED);
+               return 0;
+       default:
+               return -ENODEV;
+       }
+}
+
+static int arm_smmu_domain_set_attr(struct iommu_domain *domain,
+                                   enum iommu_attr attr, void *data)
+{
+       struct arm_smmu_domain *smmu_domain = domain->priv;
+
+       switch (attr) {
+       case DOMAIN_ATTR_NESTING:
+               if (smmu_domain->smmu)
+                       return -EPERM;
+               if (*(int *)data)
+                       smmu_domain->stage = ARM_SMMU_DOMAIN_NESTED;
+               else
+                       smmu_domain->stage = ARM_SMMU_DOMAIN_S1;
+
+               return 0;
+       default:
+               return -ENODEV;
+       }
+}
+
 static const struct iommu_ops arm_smmu_ops = {
-       .capable        = arm_smmu_capable,
-       .domain_init    = arm_smmu_domain_init,
-       .domain_destroy = arm_smmu_domain_destroy,
-       .attach_dev     = arm_smmu_attach_dev,
-       .detach_dev     = arm_smmu_detach_dev,
-       .map            = arm_smmu_map,
-       .unmap          = arm_smmu_unmap,
-       .iova_to_phys   = arm_smmu_iova_to_phys,
-       .add_device     = arm_smmu_add_device,
-       .remove_device  = arm_smmu_remove_device,
-       .pgsize_bitmap  = (SECTION_SIZE |
-                          ARM_SMMU_PTE_CONT_SIZE |
-                          PAGE_SIZE),
+       .capable                = arm_smmu_capable,
+       .domain_init            = arm_smmu_domain_init,
+       .domain_destroy         = arm_smmu_domain_destroy,
+       .attach_dev             = arm_smmu_attach_dev,
+       .detach_dev             = arm_smmu_detach_dev,
+       .map                    = arm_smmu_map,
+       .unmap                  = arm_smmu_unmap,
+       .iova_to_phys           = arm_smmu_iova_to_phys,
+       .add_device             = arm_smmu_add_device,
+       .remove_device          = arm_smmu_remove_device,
+       .domain_get_attr        = arm_smmu_domain_get_attr,
+       .domain_set_attr        = arm_smmu_domain_set_attr,
+       .pgsize_bitmap          = (SECTION_SIZE |
+                                  ARM_SMMU_PTE_CONT_SIZE |
+                                  PAGE_SIZE),
 };
 
 static void arm_smmu_device_reset(struct arm_smmu_device *smmu)