[RFC PATCH v1 4/8] iommu/arm-smmu-v3: check smmu compatibility on attach

Michael Shavit
2023-08-17T18:24:07Z

Record the domain's pgtbl_cfg when it's being prepared so that it can
later be compared to the features an smmu supports.
Verify a domain's compatibility with the smmu when it's being attached
to a master belong to a different smmu device.

Signed-off-by: Michael Shavit <mshavit@google.com>
---

drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 103 ++++++++++++++++----
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 +
2 files changed, 86 insertions(+), 19 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 447af74dbe280..c0943cf3c09ca 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2195,17 +2195,48 @@ static int arm_smmu_domain_finalise_s2(struct arm_smmu_domain *smmu_domain,
return 0;
}

+static int arm_smmu_prepare_pgtbl_cfg(struct arm_smmu_device *smmu,
+ enum arm_smmu_domain_stage stage,
+ struct io_pgtable_cfg *pgtbl_cfg)
+{
+ unsigned long ias, oas;
+
+ switch (stage) {
+ case ARM_SMMU_DOMAIN_S1:
+ ias = (smmu->features & ARM_SMMU_FEAT_VAX) ? 52 : 48;
+ ias = min_t(unsigned long, ias, VA_BITS);
+ oas = smmu->ias;
+ break;
+ case ARM_SMMU_DOMAIN_NESTED:
+ case ARM_SMMU_DOMAIN_S2:
+ ias = smmu->ias;
+ oas = smmu->oas;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *pgtbl_cfg = (struct io_pgtable_cfg) {
+ .pgsize_bitmap = smmu->pgsize_bitmap,
+ .ias = ias,
+ .oas = oas,
+ .coherent_walk = smmu->features & ARM_SMMU_FEAT_COHERENCY,
+ .tlb = &arm_smmu_flush_ops,
+ .iommu_dev = smmu->dev,
+ };
+ return 0;
+}
+
static int arm_smmu_domain_finalise(struct iommu_domain *domain)
{
int ret;
- unsigned long ias, oas;
enum io_pgtable_fmt fmt;
- struct io_pgtable_cfg pgtbl_cfg;
struct io_pgtable_ops *pgtbl_ops;
int (*finalise_stage_fn)(struct arm_smmu_domain *,
struct io_pgtable_cfg *);
struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
struct arm_smmu_device *smmu = smmu_domain->smmu;
+ struct io_pgtable_cfg *pgtbl_cfg = &smmu_domain->pgtbl_cfg;

if (domain->type == IOMMU_DOMAIN_IDENTITY) {
smmu_domain->stage = ARM_SMMU_DOMAIN_BYPASS;
@@ -2220,16 +2251,11 @@ static int arm_smmu_domain_finalise(struct iommu_domain *domain)

switch (smmu_domain->stage) {
case ARM_SMMU_DOMAIN_S1:
- ias = (smmu->features & ARM_SMMU_FEAT_VAX) ? 52 : 48;
- ias = min_t(unsigned long, ias, VA_BITS);
- oas = smmu->ias;
fmt = ARM_64_LPAE_S1;
finalise_stage_fn = arm_smmu_domain_finalise_s1;
break;
case ARM_SMMU_DOMAIN_NESTED:
case ARM_SMMU_DOMAIN_S2:
- ias = smmu->ias;
- oas = smmu->oas;
fmt = ARM_64_LPAE_S2;
finalise_stage_fn = arm_smmu_domain_finalise_s2;
break;
@@ -2237,24 +2263,19 @@ static int arm_smmu_domain_finalise(struct iommu_domain *domain)
return -EINVAL;
}

- pgtbl_cfg = (struct io_pgtable_cfg) {
- .pgsize_bitmap = smmu->pgsize_bitmap,
- .ias = ias,
- .oas = oas,
- .coherent_walk = smmu->features & ARM_SMMU_FEAT_COHERENCY,
- .tlb = &arm_smmu_flush_ops,
- .iommu_dev = smmu->dev,
- };
+ ret = arm_smmu_prepare_pgtbl_cfg(smmu, smmu_domain->stage, pgtbl_cfg);
+ if (ret)
+ return ret;

- pgtbl_ops = alloc_io_pgtable_ops(fmt, &pgtbl_cfg, smmu_domain);
+ pgtbl_ops = alloc_io_pgtable_ops(fmt, pgtbl_cfg, smmu_domain);
if (!pgtbl_ops)
return -ENOMEM;

- domain->pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
- domain->geometry.aperture_end = (1UL << pgtbl_cfg.ias) - 1;
+ domain->pgsize_bitmap = pgtbl_cfg->pgsize_bitmap;
+ domain->geometry.aperture_end = (1UL << pgtbl_cfg->ias) - 1;
domain->geometry.force_aperture = true;

- ret = finalise_stage_fn(smmu_domain, &pgtbl_cfg);
+ ret = finalise_stage_fn(smmu_domain, pgtbl_cfg);
if (ret < 0) {
free_io_pgtable_ops(pgtbl_ops);
return ret;
@@ -2406,6 +2427,46 @@ static void arm_smmu_disable_pasid(struct arm_smmu_master *master)
pci_disable_pasid(pdev);
}

+static int
+arm_smmu_verify_domain_compatible(struct arm_smmu_device *smmu,
+ struct arm_smmu_domain *smmu_domain)
+{
+ struct io_pgtable_cfg pgtbl_cfg;
+ int ret;
+
+ if (smmu_domain->domain.type == IOMMU_DOMAIN_IDENTITY)
+ return 0;
+
+ if (smmu_domain->stage == ARM_SMMU_DOMAIN_S2) {
+ if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S2))
+ return -EINVAL;
+ if (smmu_domain->s2_cfg.vmid >> smmu->vmid_bits)
+ return -EINVAL;
+ } else if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1) {
+ if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S1))
+ return -EINVAL;
+ if (smmu_domain->cd.asid >> smmu->asid_bits)
+ return -EINVAL;
+ }
+
+ ret = arm_smmu_prepare_pgtbl_cfg(smmu, smmu_domain->stage, &pgtbl_cfg);
+ if (ret)
+ return ret;
+
+ if (smmu_domain->pgtbl_cfg.ias > pgtbl_cfg.ias ||
+ smmu_domain->pgtbl_cfg.oas > pgtbl_cfg.oas ||
+ /*
+ * The supported pgsize_bitmap must be a superset of the domain's
+ * pgsize_bitmap.
+ */
+ (smmu_domain->pgtbl_cfg.pgsize_bitmap ^ pgtbl_cfg.pgsize_bitmap) &
+ smmu_domain->pgtbl_cfg.pgsize_bitmap ||
+ smmu_domain->pgtbl_cfg.coherent_walk != pgtbl_cfg.coherent_walk)
+ return -EINVAL;
+
+ return 0;
+}
+
static void arm_smmu_installed_smmus_remove_device(
struct arm_smmu_domain *smmu_domain,
struct arm_smmu_master *master)
@@ -2449,6 +2510,10 @@ arm_smmu_installed_smmus_add_device(struct arm_smmu_domain *smmu_domain,
}
}
if (!list_entry_found) {
+ ret = arm_smmu_verify_domain_compatible(smmu, smmu_domain);
+ if (ret)
+ goto unlock;
+
installed_smmu = kzalloc(sizeof(*installed_smmu), GFP_KERNEL);
if (!installed_smmu) {
ret = -ENOMEM;
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 2ab23139c796e..143b287be2f8b 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -9,6 +9,7 @@
#define _ARM_SMMU_V3_H

#include <linux/bitfield.h>
+#include <linux/io-pgtable.h>
#include <linux/iommu.h>
#include <linux/kernel.h>
#include <linux/mmzone.h>
@@ -729,6 +730,7 @@ struct arm_smmu_domain {
struct mutex init_mutex; /* Protects smmu pointer */

struct io_pgtable_ops *pgtbl_ops;
+ struct io_pgtable_cfg pgtbl_cfg;
atomic_t nr_ats_masters;

enum arm_smmu_domain_stage stage;
--
2.42.0.rc1.204.g551eb34607-goog