@@ -26,6 +26,7 @@ static const struct of_device_id qcom_smmu_client_of_match[] __maybe_unused = {
static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu)
{
unsigned int last_s2cr = ARM_SMMU_GR0_S2CR(smmu->num_mapping_groups - 1);
+ u32 smr;
u32 reg;
int i;
@@ -56,6 +57,16 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu)
}
}
+ for (i = 0; i < smmu->num_mapping_groups; i++) {
+ smr = arm_smmu_gr0_read(smmu, ARM_SMMU_GR0_SMR(i));
+
+ if (FIELD_GET(ARM_SMMU_SMR_VALID, smr)) {
+ smmu->smrs[i].id = FIELD_GET(ARM_SMMU_SMR_ID, smr);
+ smmu->smrs[i].mask = FIELD_GET(ARM_SMMU_SMR_MASK, smr);
+ smmu->smrs[i].valid = true;
+ }
+ }
+
return 0;
}
@@ -652,7 +652,8 @@ static void arm_smmu_write_context_bank(struct arm_smmu_device *smmu, int idx)
}
static int arm_smmu_init_domain_context(struct iommu_domain *domain,
- struct arm_smmu_device *smmu)
+ struct arm_smmu_device *smmu,
+ bool boot_domain)
{
int irq, start, ret = 0;
unsigned long ias, oas;
@@ -770,6 +771,15 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
ret = -EINVAL;
goto out_unlock;
}
+
+ /*
+ * Use the last context bank for identity mappings during boot, to
+ * avoid overwriting in-use bank configuration while we're setting up
+ * the new mappings.
+ */
+ if (boot_domain)
+ start = smmu->num_context_banks - 1;
+
ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
smmu->num_context_banks);
if (ret < 0)
@@ -1149,7 +1159,10 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
struct arm_smmu_master_cfg *cfg;
struct arm_smmu_device *smmu;
+ bool free_identity_domain = false;
+ int idx;
int ret;
+ int i;
if (!fwspec || fwspec->ops != &arm_smmu_ops) {
dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n");
@@ -1174,7 +1187,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
return ret;
/* Ensure that the domain is finalised */
- ret = arm_smmu_init_domain_context(domain, smmu);
+ ret = arm_smmu_init_domain_context(domain, smmu, false);
if (ret < 0)
goto rpm_put;
@@ -1190,9 +1203,33 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
goto rpm_put;
}
+ /* Decrement use counter for any references to the identity domain */
+ mutex_lock(&smmu->stream_map_mutex);
+ if (smmu->identity) {
+ struct arm_smmu_domain *identity = to_smmu_domain(smmu->identity);
+
+ for_each_cfg_sme(cfg, fwspec, i, idx) {
+ if (smmu->s2crs[idx].cbndx == identity->cfg.cbndx) {
+ smmu->num_identity_masters--;
+ if (smmu->num_identity_masters == 0)
+ free_identity_domain = true;
+ }
+ }
+ }
+ mutex_unlock(&smmu->stream_map_mutex);
+
/* Looks ok, so add the device to the domain */
ret = arm_smmu_domain_add_master(smmu_domain, cfg, fwspec);
+ /*
+ * The last stream map to reference the identity domain has been
+ * overwritten, so it's now okay to free it.
+ */
+ if (free_identity_domain) {
+ arm_smmu_domain_free(smmu->identity);
+ smmu->identity = NULL;
+ }
+
/*
* Setup an autosuspend delay to avoid bouncing runpm state.
* Otherwise, if a driver for a suspended consumer device
@@ -1922,17 +1959,51 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu)
static int arm_smmu_setup_identity(struct arm_smmu_device *smmu)
{
+ struct device *dev = smmu->dev;
+ int cbndx = 0xff;
+ int type = S2CR_TYPE_BYPASS;
+ int ret;
int i;
+ if (smmu->qcom_bypass_quirk) {
+ /* Create a IDENTITY domain to use for all inherited streams */
+ smmu->identity = arm_smmu_domain_alloc(IOMMU_DOMAIN_IDENTITY);
+ if (!smmu->identity) {
+ dev_err(dev, "failed to create identity domain\n");
+ return -ENOMEM;
+ }
+
+ smmu->identity->pgsize_bitmap = smmu->pgsize_bitmap;
+ smmu->identity->type = IOMMU_DOMAIN_IDENTITY;
+ smmu->identity->ops = &arm_smmu_ops;
+
+ ret = arm_smmu_init_domain_context(smmu->identity, smmu, true);
+ if (ret < 0) {
+ dev_err(dev, "failed to initialize identity domain: %d\n", ret);
+ return ret;
+ }
+
+ type = S2CR_TYPE_TRANS;
+ cbndx = to_smmu_domain(smmu->identity)->cfg.cbndx;
+ }
+
for (i = 0; i < smmu->num_mapping_groups; i++) {
if (smmu->smrs[i].valid) {
- smmu->s2crs[i].type = S2CR_TYPE_BYPASS;
+ smmu->s2crs[i].type = type;
smmu->s2crs[i].privcfg = S2CR_PRIVCFG_DEFAULT;
- smmu->s2crs[i].cbndx = 0xff;
+ smmu->s2crs[i].cbndx = cbndx;
smmu->s2crs[i].count++;
+
+ smmu->num_identity_masters++;
}
}
+ /* If no mappings where found, free the identiy domain again */
+ if (smmu->identity && !smmu->num_identity_masters) {
+ arm_smmu_domain_free(smmu->identity);
+ smmu->identity = NULL;
+ }
+
return 0;
}
@@ -321,6 +321,9 @@ struct arm_smmu_device {
/* IOMMU core code handle */
struct iommu_device iommu;
+ struct iommu_domain *identity;
+ unsigned int num_identity_masters;
+
bool qcom_bypass_quirk;
};