|
@@ -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,
|
|
@@ -1281,7 +1315,7 @@ static int arm_smmu_alloc_init_pte(struct arm_smmu_device *smmu, pmd_t *pmd,
|
|
|
unsigned long pfn, int prot, int stage)
|
|
|
{
|
|
|
pte_t *pte, *start;
|
|
|
- pteval_t pteval = ARM_SMMU_PTE_PAGE | ARM_SMMU_PTE_AF | ARM_SMMU_PTE_XN;
|
|
|
+ pteval_t pteval = ARM_SMMU_PTE_PAGE | ARM_SMMU_PTE_AF;
|
|
|
|
|
|
if (pmd_none(*pmd)) {
|
|
|
/* Allocate a new set of tables */
|
|
@@ -1315,10 +1349,11 @@ static int arm_smmu_alloc_init_pte(struct arm_smmu_device *smmu, pmd_t *pmd,
|
|
|
pteval |= ARM_SMMU_PTE_MEMATTR_NC;
|
|
|
}
|
|
|
|
|
|
+ if (prot & IOMMU_NOEXEC)
|
|
|
+ pteval |= ARM_SMMU_PTE_XN;
|
|
|
+
|
|
|
/* If no access, create a faulting entry to avoid TLB fills */
|
|
|
- if (prot & IOMMU_EXEC)
|
|
|
- pteval &= ~ARM_SMMU_PTE_XN;
|
|
|
- else if (!(prot & (IOMMU_READ | IOMMU_WRITE)))
|
|
|
+ if (!(prot & (IOMMU_READ | IOMMU_WRITE)))
|
|
|
pteval &= ~ARM_SMMU_PTE_PAGE;
|
|
|
|
|
|
pteval |= ARM_SMMU_PTE_SH_IS;
|
|
@@ -1568,6 +1603,8 @@ static bool arm_smmu_capable(enum iommu_cap cap)
|
|
|
return true;
|
|
|
case IOMMU_CAP_INTR_REMAP:
|
|
|
return true; /* MSIs are just memory writes */
|
|
|
+ case IOMMU_CAP_NOEXEC:
|
|
|
+ return true;
|
|
|
default:
|
|
|
return false;
|
|
|
}
|
|
@@ -1644,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)
|
|
@@ -2072,8 +2145,20 @@ static struct platform_driver arm_smmu_driver = {
|
|
|
|
|
|
static int __init arm_smmu_init(void)
|
|
|
{
|
|
|
+ struct device_node *np;
|
|
|
int ret;
|
|
|
|
|
|
+ /*
|
|
|
+ * Play nice with systems that don't have an ARM SMMU by checking that
|
|
|
+ * an ARM SMMU exists in the system before proceeding with the driver
|
|
|
+ * and IOMMU bus operation registration.
|
|
|
+ */
|
|
|
+ np = of_find_matching_node(NULL, arm_smmu_of_match);
|
|
|
+ if (!np)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ of_node_put(np);
|
|
|
+
|
|
|
ret = platform_driver_register(&arm_smmu_driver);
|
|
|
if (ret)
|
|
|
return ret;
|