Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1ab48c37 authored by Patrick Daly's avatar Patrick Daly Committed by Sudarshan Rajagopalan
Browse files

iommu: arm-smmu: Support ECATS during context fault



The TBU will complete the halt sequence only if there are no pending
transactions. This includes transactions stalled by an ongoing context
fault. When halting the TBU, disable stall on fault and fault
interrupt reporting. Clear any pending faults via terminate register.

Additionally, add an error message for maximum address range.

Change-Id: If1a6059594f8ec98c91ea0be59d8c7c6bf2f55a3
Signed-off-by: default avatarPatrick Daly <pdaly@codeaurora.org>
Signed-off-by: default avatarSudarshan Rajagopalan <sudaraja@codeaurora.org>
parent 1529c90d
Loading
Loading
Loading
Loading
+68 −32
Original line number Original line Diff line number Diff line
@@ -4441,7 +4441,7 @@ IOMMU_OF_DECLARE(cavium_smmuv2, "cavium,smmu-v2", arm_smmu_of_init);
#define DEBUG_PAR_PA_SHIFT		12
#define DEBUG_PAR_PA_SHIFT		12
#define DEBUG_PAR_FAULT_VAL		0x1
#define DEBUG_PAR_FAULT_VAL		0x1


#define TBU_DBG_TIMEOUT_US		30000
#define TBU_DBG_TIMEOUT_US		100


#define QSMMUV500_ACTLR_DEEP_PREFETCH_MASK	0x3
#define QSMMUV500_ACTLR_DEEP_PREFETCH_MASK	0x3
#define QSMMUV500_ACTLR_DEEP_PREFETCH_SHIFT	0x8
#define QSMMUV500_ACTLR_DEEP_PREFETCH_SHIFT	0x8
@@ -4504,11 +4504,12 @@ static bool arm_smmu_fwspec_match_smr(struct iommu_fwspec *fwspec,
	return false;
	return false;
}
}


static int qsmmuv500_tbu_halt(struct qsmmuv500_tbu_device *tbu)
static int qsmmuv500_tbu_halt(struct qsmmuv500_tbu_device *tbu,
				struct arm_smmu_domain *smmu_domain)
{
{
	unsigned long flags;
	unsigned long flags;
	u32 val;
	u32 halt, fsr, sctlr_orig, sctlr, status;
	void __iomem *base;
	void __iomem *base, *cb_base;


	spin_lock_irqsave(&tbu->halt_lock, flags);
	spin_lock_irqsave(&tbu->halt_lock, flags);
	if (tbu->halt_count) {
	if (tbu->halt_count) {
@@ -4517,19 +4518,48 @@ static int qsmmuv500_tbu_halt(struct qsmmuv500_tbu_device *tbu)
		return 0;
		return 0;
	}
	}


	cb_base = ARM_SMMU_CB(smmu_domain->smmu, smmu_domain->cfg.cbndx);
	base = tbu->base;
	base = tbu->base;
	val = readl_relaxed(base + DEBUG_SID_HALT_REG);
	halt = readl_relaxed(base + DEBUG_SID_HALT_REG);
	val |= DEBUG_SID_HALT_VAL;
	halt |= DEBUG_SID_HALT_VAL;
	writel_relaxed(val, base + DEBUG_SID_HALT_REG);
	writel_relaxed(halt, base + DEBUG_SID_HALT_REG);


	if (readl_poll_timeout_atomic(base + DEBUG_SR_HALT_ACK_REG,
	if (!readl_poll_timeout_atomic(base + DEBUG_SR_HALT_ACK_REG, status,
					val, (val & DEBUG_SR_HALT_ACK_VAL),
					(status & DEBUG_SR_HALT_ACK_VAL),
					0, TBU_DBG_TIMEOUT_US)) {
					0, TBU_DBG_TIMEOUT_US))
		goto out;

	fsr = readl_relaxed(cb_base + ARM_SMMU_CB_FSR);
	if (!(fsr & FSR_FAULT)) {
		dev_err(tbu->dev, "Couldn't halt TBU!\n");
		dev_err(tbu->dev, "Couldn't halt TBU!\n");
		spin_unlock_irqrestore(&tbu->halt_lock, flags);
		spin_unlock_irqrestore(&tbu->halt_lock, flags);
		return -ETIMEDOUT;
		return -ETIMEDOUT;
	}
	}


	/*
	 * We are in a fault; Our request to halt the bus will not complete
	 * until transactions in front of us (such as the fault itself) have
	 * completed. Disable iommu faults and terminate any existing
	 * transactions.
	 */
	sctlr_orig = readl_relaxed(cb_base + ARM_SMMU_CB_SCTLR);
	sctlr = sctlr_orig & ~(SCTLR_CFCFG | SCTLR_CFIE);
	writel_relaxed(sctlr, cb_base + ARM_SMMU_CB_SCTLR);

	writel_relaxed(fsr, cb_base + ARM_SMMU_CB_FSR);
	writel_relaxed(RESUME_TERMINATE, cb_base + ARM_SMMU_CB_RESUME);

	if (readl_poll_timeout_atomic(base + DEBUG_SR_HALT_ACK_REG, status,
					(status & DEBUG_SR_HALT_ACK_VAL),
					0, TBU_DBG_TIMEOUT_US)) {
		dev_err(tbu->dev, "Couldn't halt TBU from fault context!\n");
		writel_relaxed(sctlr_orig, cb_base + ARM_SMMU_CB_SCTLR);
		spin_unlock_irqrestore(&tbu->halt_lock, flags);
		return -ETIMEDOUT;
	}

	writel_relaxed(sctlr_orig, cb_base + ARM_SMMU_CB_SCTLR);
out:
	tbu->halt_count = 1;
	tbu->halt_count = 1;
	spin_unlock_irqrestore(&tbu->halt_lock, flags);
	spin_unlock_irqrestore(&tbu->halt_lock, flags);
	return 0;
	return 0;
@@ -4630,6 +4660,14 @@ static phys_addr_t qsmmuv500_iova_to_phys(
	void __iomem *cb_base;
	void __iomem *cb_base;
	u32 sctlr_orig, sctlr;
	u32 sctlr_orig, sctlr;
	int needs_redo = 0;
	int needs_redo = 0;
	ktime_t timeout;

	/* only 36 bit iova is supported */
	if (iova >= (1ULL << 36)) {
		dev_err_ratelimited(smmu->dev, "ECATS: address too large: %pad\n",
					&iova);
		return 0;
	}


	cb_base = ARM_SMMU_CB(smmu, cfg->cbndx);
	cb_base = ARM_SMMU_CB(smmu, cfg->cbndx);
	tbu = qsmmuv500_find_tbu(smmu, sid);
	tbu = qsmmuv500_find_tbu(smmu, sid);
@@ -4640,35 +4678,23 @@ static phys_addr_t qsmmuv500_iova_to_phys(
	if (ret)
	if (ret)
		return 0;
		return 0;


	/*
	ret = qsmmuv500_tbu_halt(tbu, smmu_domain);
	 * Disable client transactions & wait for existing operations to
	 * complete.
	 */
	ret = qsmmuv500_tbu_halt(tbu);
	if (ret)
	if (ret)
		goto out_power_off;
		goto out_power_off;


	/* Only one concurrent atos operation */
	ret = qsmmuv500_ecats_lock(smmu_domain, tbu, &flags);
	if (ret)
		goto out_resume;

	/*
	/*
	 * We can be called from an interrupt handler with FSR already set
	 * so terminate the faulting transaction prior to starting ecats.
	 * No new racing faults can occur since we in the halted state.
	 * ECATS can trigger the fault interrupt, so disable it temporarily
	 * ECATS can trigger the fault interrupt, so disable it temporarily
	 * and check for an interrupt manually.
	 * and check for an interrupt manually.
	 */
	 */
	fsr = readl_relaxed(cb_base + ARM_SMMU_CB_FSR);
	if (fsr & FSR_FAULT) {
		writel_relaxed(fsr, cb_base + ARM_SMMU_CB_FSR);
		writel_relaxed(RESUME_TERMINATE, cb_base + ARM_SMMU_CB_RESUME);
	}
	sctlr_orig = readl_relaxed(cb_base + ARM_SMMU_CB_SCTLR);
	sctlr_orig = readl_relaxed(cb_base + ARM_SMMU_CB_SCTLR);
	sctlr = sctlr_orig & ~(SCTLR_CFCFG | SCTLR_CFIE);
	sctlr = sctlr_orig & ~(SCTLR_CFCFG | SCTLR_CFIE);
	writel_relaxed(sctlr, cb_base + ARM_SMMU_CB_SCTLR);
	writel_relaxed(sctlr, cb_base + ARM_SMMU_CB_SCTLR);


	/* Only one concurrent atos operation */
	ret = qsmmuv500_ecats_lock(smmu_domain, tbu, &flags);
	if (ret)
		goto out_resume;

redo:
redo:
	/* Set address and stream-id */
	/* Set address and stream-id */
	val = readq_relaxed(tbu->base + DEBUG_SID_HALT_REG);
	val = readq_relaxed(tbu->base + DEBUG_SID_HALT_REG);
@@ -4687,10 +4713,20 @@ static phys_addr_t qsmmuv500_iova_to_phys(
	writeq_relaxed(val, tbu->base + DEBUG_TXN_TRIGG_REG);
	writeq_relaxed(val, tbu->base + DEBUG_TXN_TRIGG_REG);


	ret = 0;
	ret = 0;
	if (readl_poll_timeout_atomic(tbu->base + DEBUG_SR_HALT_ACK_REG,
	//based on readx_poll_timeout_atomic
				val, !(val & DEBUG_SR_ECATS_RUNNING_VAL),
	timeout = ktime_add_us(ktime_get(), TBU_DBG_TIMEOUT_US);
				0, TBU_DBG_TIMEOUT_US)) {
	for (;;) {
		val = readl_relaxed(tbu->base + DEBUG_SR_HALT_ACK_REG);
		if (!(val & DEBUG_SR_ECATS_RUNNING_VAL))
			break;
		val = readl_relaxed(cb_base + ARM_SMMU_CB_FSR);
		if (val & FSR_FAULT)
			break;
		if (ktime_compare(ktime_get(), timeout) > 0) {
			dev_err(tbu->dev, "ECATS translation timed out!\n");
			dev_err(tbu->dev, "ECATS translation timed out!\n");
			ret = -ETIMEDOUT;
			break;
		}
	}
	}


	fsr = readl_relaxed(cb_base + ARM_SMMU_CB_FSR);
	fsr = readl_relaxed(cb_base + ARM_SMMU_CB_FSR);