From 495bcce9ef20364310a60ca6c480f778ab387947 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 19 Oct 2020 11:23:21 -0700 Subject: iommu/arm-smmu: Allow implementation specific write_s2cr The firmware found in some Qualcomm platforms intercepts writes to the S2CR register in order to replace the BYPASS type with FAULT. Further more it treats faults at this level as catastrophic and restarts the device. Add support for providing implementation specific versions of the S2CR write function, to allow the Qualcomm driver to work around this behavior. Reviewed-by: Robin Murphy Signed-off-by: Bjorn Andersson Signed-off-by: Vinod Koul --- drivers/iommu/arm/arm-smmu/arm-smmu.c | 13 ++++++++++--- drivers/iommu/arm/arm-smmu/arm-smmu.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c index dad7fa86fbd4..bcbacf22331d 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c @@ -929,9 +929,16 @@ static void arm_smmu_write_smr(struct arm_smmu_device *smmu, int idx) static void arm_smmu_write_s2cr(struct arm_smmu_device *smmu, int idx) { struct arm_smmu_s2cr *s2cr = smmu->s2crs + idx; - u32 reg = FIELD_PREP(ARM_SMMU_S2CR_TYPE, s2cr->type) | - FIELD_PREP(ARM_SMMU_S2CR_CBNDX, s2cr->cbndx) | - FIELD_PREP(ARM_SMMU_S2CR_PRIVCFG, s2cr->privcfg); + u32 reg; + + if (smmu->impl && smmu->impl->write_s2cr) { + smmu->impl->write_s2cr(smmu, idx); + return; + } + + reg = FIELD_PREP(ARM_SMMU_S2CR_TYPE, s2cr->type) | + FIELD_PREP(ARM_SMMU_S2CR_CBNDX, s2cr->cbndx) | + FIELD_PREP(ARM_SMMU_S2CR_PRIVCFG, s2cr->privcfg); if (smmu->features & ARM_SMMU_FEAT_EXIDS && smmu->smrs && smmu->smrs[idx].valid) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.h b/drivers/iommu/arm/arm-smmu/arm-smmu.h index 1a746476927c..b71647eaa319 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.h +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.h @@ -436,6 +436,7 @@ struct arm_smmu_impl { int (*alloc_context_bank)(struct arm_smmu_domain *smmu_domain, struct arm_smmu_device *smmu, struct device *dev, int start); + void (*write_s2cr)(struct arm_smmu_device *smmu, int idx); }; #define INVALID_SMENDX -1 -- cgit v1.2.3 From 023dece8abc893d97c05b3f66953875680ef9b77 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 19 Oct 2020 11:23:22 -0700 Subject: iommu/arm-smmu-qcom: Read back stream mappings The Qualcomm boot loader configures stream mapping for the peripherals that it accesses and in particular it sets up the stream mapping for the display controller to be allowed to scan out a splash screen or EFI framebuffer. Read back the stream mappings during initialization and make the arm-smmu driver maintain the streams in bypass mode. Signed-off-by: Bjorn Andersson Signed-off-by: Vinod Koul --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index be4318044f96..48627fcf6bed 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -23,6 +23,28 @@ static const struct of_device_id qcom_smmu_client_of_match[] __maybe_unused = { { } }; +static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) +{ + u32 smr; + int i; + + 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; + + smmu->s2crs[i].type = S2CR_TYPE_BYPASS; + smmu->s2crs[i].privcfg = S2CR_PRIVCFG_DEFAULT; + smmu->s2crs[i].cbndx = 0xff; + } + } + + return 0; +} + static int qcom_smmu_def_domain_type(struct device *dev) { const struct of_device_id *match = @@ -61,6 +83,7 @@ static int qcom_smmu500_reset(struct arm_smmu_device *smmu) } static const struct arm_smmu_impl qcom_smmu_impl = { + .cfg_probe = qcom_smmu_cfg_probe, .def_domain_type = qcom_smmu_def_domain_type, .reset = qcom_smmu500_reset, }; -- cgit v1.2.3 From 641d0e623c1acf346358126d4f5957921f99533e Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 19 Oct 2020 11:23:23 -0700 Subject: iommu/arm-smmu-qcom: Implement S2CR quirk The firmware found in some Qualcomm platforms intercepts writes to S2CR in order to replace bypass type streams with fault; and ignore S2CR updates of type fault. Detect this behavior and implement a custom write_s2cr function in order to trick the firmware into supporting bypass streams by the means of configuring the stream for translation using a reserved and disabled context bank. Also circumvent the problem of configuring faulting streams by configuring the stream as bypass. Signed-off-by: Bjorn Andersson Signed-off-by: Vinod Koul --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 67 ++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index 48627fcf6bed..66ba4870659f 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -10,8 +10,15 @@ struct qcom_smmu { struct arm_smmu_device smmu; + bool bypass_quirk; + u8 bypass_cbndx; }; +static struct qcom_smmu *to_qcom_smmu(struct arm_smmu_device *smmu) +{ + return container_of(smmu, struct qcom_smmu, smmu); +} + static const struct of_device_id qcom_smmu_client_of_match[] __maybe_unused = { { .compatible = "qcom,adreno" }, { .compatible = "qcom,mdp4" }, @@ -25,9 +32,33 @@ 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); + struct qcom_smmu *qsmmu = to_qcom_smmu(smmu); + u32 reg; u32 smr; int i; + /* + * With some firmware versions writes to S2CR of type FAULT are + * ignored, and writing BYPASS will end up written as FAULT in the + * register. Perform a write to S2CR to detect if this is the case and + * if so reserve a context bank to emulate bypass streams. + */ + reg = FIELD_PREP(ARM_SMMU_S2CR_TYPE, S2CR_TYPE_BYPASS) | + FIELD_PREP(ARM_SMMU_S2CR_CBNDX, 0xff) | + FIELD_PREP(ARM_SMMU_S2CR_PRIVCFG, S2CR_PRIVCFG_DEFAULT); + arm_smmu_gr0_write(smmu, last_s2cr, reg); + reg = arm_smmu_gr0_read(smmu, last_s2cr); + if (FIELD_GET(ARM_SMMU_S2CR_TYPE, reg) != S2CR_TYPE_BYPASS) { + qsmmu->bypass_quirk = true; + qsmmu->bypass_cbndx = smmu->num_context_banks - 1; + + set_bit(qsmmu->bypass_cbndx, smmu->context_map); + + reg = FIELD_PREP(ARM_SMMU_CBAR_TYPE, CBAR_TYPE_S1_TRANS_S2_BYPASS); + arm_smmu_gr1_write(smmu, ARM_SMMU_GR1_CBAR(qsmmu->bypass_cbndx), reg); + } + for (i = 0; i < smmu->num_mapping_groups; i++) { smr = arm_smmu_gr0_read(smmu, ARM_SMMU_GR0_SMR(i)); @@ -45,6 +76,41 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) return 0; } +static void qcom_smmu_write_s2cr(struct arm_smmu_device *smmu, int idx) +{ + struct arm_smmu_s2cr *s2cr = smmu->s2crs + idx; + struct qcom_smmu *qsmmu = to_qcom_smmu(smmu); + u32 cbndx = s2cr->cbndx; + u32 type = s2cr->type; + u32 reg; + + if (qsmmu->bypass_quirk) { + if (type == S2CR_TYPE_BYPASS) { + /* + * Firmware with quirky S2CR handling will substitute + * BYPASS writes with FAULT, so point the stream to the + * reserved context bank and ask for translation on the + * stream + */ + type = S2CR_TYPE_TRANS; + cbndx = qsmmu->bypass_cbndx; + } else if (type == S2CR_TYPE_FAULT) { + /* + * Firmware with quirky S2CR handling will ignore FAULT + * writes, so trick it to write FAULT by asking for a + * BYPASS. + */ + type = S2CR_TYPE_BYPASS; + cbndx = 0xff; + } + } + + reg = FIELD_PREP(ARM_SMMU_S2CR_TYPE, type) | + FIELD_PREP(ARM_SMMU_S2CR_CBNDX, cbndx) | + FIELD_PREP(ARM_SMMU_S2CR_PRIVCFG, s2cr->privcfg); + arm_smmu_gr0_write(smmu, ARM_SMMU_GR0_S2CR(idx), reg); +} + static int qcom_smmu_def_domain_type(struct device *dev) { const struct of_device_id *match = @@ -86,6 +152,7 @@ static const struct arm_smmu_impl qcom_smmu_impl = { .cfg_probe = qcom_smmu_cfg_probe, .def_domain_type = qcom_smmu_def_domain_type, .reset = qcom_smmu500_reset, + .write_s2cr = qcom_smmu_write_s2cr, }; struct arm_smmu_device *qcom_smmu_impl_init(struct arm_smmu_device *smmu) -- cgit v1.2.3