From: Zhen Lei thunder.leizhen@huawei.com
hulk inclusion category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I5TE5L CVE: NA
-------------------------------------------------------------------------
We are currently not doing anything in arm_smmu_suspend(), but ECMDQ may have executed some commands. In arm_smmu_device_reset(), we assume that the index value of prod and cons are zero. Therefore, when ecmdq is enabled again, the index values of prod and cons are inconsistent. As a result, the hardware mistakenly considers that there are commands in the queue and executes them and reports invalid commands.
On the other hand, when we disable ecmdq, we need to wait for ENACK to become 0 before writing cons.
Disable ECMDQ in arm_smmu_suspend() to save energy.
Fixes: 4b009f708c37 ("iommu/arm-smmu-v3: Add suspend and resume support") Signed-off-by: Zhen Lei thunder.leizhen@huawei.com Tested-by: Liyan Liu liuliyan6@h-partners.com Reviewed-by: Hanjun Guo guohanjun@huawei.com Signed-off-by: Zheng Zengkai zhengzengkai@huawei.com --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 62 ++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-)
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 46c15673788b..8b941089aa33 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -5003,12 +5003,16 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool resume) ecmdq = *per_cpu_ptr(smmu->ecmdq, i); q = &ecmdq->cmdq.q;
+ if (WARN_ON(q->llq.prod != q->llq.cons)) { + q->llq.prod = 0; + q->llq.cons = 0; + } writeq_relaxed(q->q_base, ecmdq->base + ARM_SMMU_ECMDQ_BASE); writel_relaxed(q->llq.prod, ecmdq->base + ARM_SMMU_ECMDQ_PROD); writel_relaxed(q->llq.cons, ecmdq->base + ARM_SMMU_ECMDQ_CONS);
/* enable ecmdq */ - writel(ECMDQ_PROD_EN, q->prod_reg); + writel(ECMDQ_PROD_EN | q->llq.prod, q->prod_reg); ret = readl_relaxed_poll_timeout(q->cons_reg, reg, reg & ECMDQ_CONS_ENACK, 1, ARM_SMMU_POLL_TIMEOUT_US); if (ret) { @@ -5707,8 +5711,64 @@ static void __iomem *arm_smmu_ioremap(struct device *dev, resource_size_t start, }
#ifdef CONFIG_PM_SLEEP + +static int arm_smmu_ecmdq_disable(struct device *dev) +{ + int i, j; + int ret, nr_fail = 0, n = 100; + u32 reg, prod, cons; + struct arm_smmu_ecmdq *ecmdq; + struct arm_smmu_queue *q; + struct arm_smmu_device *smmu = dev_get_drvdata(dev); + + for (i = 0; i < smmu->nr_ecmdq; i++) { + ecmdq = *per_cpu_ptr(smmu->ecmdq, i); + q = &ecmdq->cmdq.q; + + prod = readl_relaxed(q->prod_reg); + cons = readl_relaxed(q->cons_reg); + if ((prod & ECMDQ_PROD_EN) == 0) + continue; + + for (j = 0; j < n; j++) { + if (Q_IDX(&q->llq, prod) == Q_IDX(&q->llq, cons) && + Q_WRP(&q->llq, prod) == Q_WRP(&q->llq, cons)) + break; + + /* Wait a moment, so ECMDQ has a chance to finish */ + udelay(1); + cons = readl_relaxed(q->cons_reg); + } + WARN_ON(prod != readl_relaxed(q->prod_reg)); + if (j >= n) + dev_warn(smmu->dev, + "Forcibly disabling ecmdq[%d]: prod=%08x, cons=%08x\n", + i, prod, cons); + + /* disable ecmdq */ + prod &= ~ECMDQ_PROD_EN; + writel(prod, q->prod_reg); + ret = readl_relaxed_poll_timeout(q->cons_reg, reg, !(reg & ECMDQ_CONS_ENACK), + 1, ARM_SMMU_POLL_TIMEOUT_US); + if (ret) { + nr_fail++; + dev_err(smmu->dev, "ecmdq[%d] disable failed\n", i); + } + } + + if (nr_fail) { + smmu->ecmdq_enabled = 0; + pr_warn("Suppress ecmdq feature, switch to normal cmdq\n"); + return -EIO; + } + + return 0; +} + static int arm_smmu_suspend(struct device *dev) { + arm_smmu_ecmdq_disable(dev); + /* * The smmu is powered off and related registers are automatically * cleared when suspend. No need to do anything.