From 992b5f98cad8a8bd0da106a113c25429aeb183cd Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Thu, 13 Oct 2022 14:20:53 +0100 Subject: [PATCH] ANDROID: KVM: arm64: s2mpu: Add SysMMU_SYNC timeout The SysMMU_SYNC provides an invalidation-complete signal to the hypervisor. Currently the hypervisor will wait indefinitely for the SYNC to set the SYNC_COMP_COMPLETE bit. In practice, this case deadlock as the hypervisor holds the host lock while waiting for the SYNC. To avoid deadlock, adjust the algorithm to time out after a given number of reads of the SYNC_COMP register (new constant SYNC_TIMEOUT_BASE). This can be a small number as most attempts succeed after a single read of the SFR. If the wait-loop times out, the hypervisor will try again, multiplying the maximum number of SFR reads with SYNC_TIMEOUT_MULTIPLIER each time. This number was selected to grow quickly, in case there is a lot of DMA traffic that would be slowing down the SYNC request. Finally, if the hardware does not set the bit even after SYNC_MAX_RETRIES, the algorithm will give up to avoid deadlock. The value was selected so that the worst-case time spent in __wait_for_invalidation_complete() remains tolerable. Bug: 250727777 Signed-off-by: David Brazdil Change-Id: I00098753bcc46a894943bbdb3a61acc3a8e5e5d2 --- arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c b/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c index ff5d7d1044e5..20c65f7489ed 100644 --- a/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c +++ b/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c @@ -24,6 +24,10 @@ #define PA_MAX ((phys_addr_t)SZ_1G * NR_GIGABYTES) +#define SYNC_MAX_RETRIES 5 +#define SYNC_TIMEOUT 5 +#define SYNC_TIMEOUT_MULTIPLIER 3 + #define CTX_CFG_ENTRY(ctxid, nr_ctx, vid) \ (CONTEXT_CFG_VALID_VID_CTX_VID(ctxid, vid) \ | (((ctxid) < (nr_ctx)) ? CONTEXT_CFG_VALID_VID_CTX_VALID(ctxid) : 0)) @@ -158,11 +162,20 @@ static void __set_control_regs(struct pkvm_iommu *dev) writel_relaxed(ctrl0, dev->va + REG_NS_CTRL0); } -/* Poll the given SFR until its value has all bits of a given mask set. */ -static void __wait_until(void __iomem *addr, u32 mask) +/* + * Poll the given SFR until its value has all bits of a given mask set. + * Returns true if successful, false if not successful after a given number of + * attempts. + */ +static bool __wait_until(void __iomem *addr, u32 mask, size_t max_attempts) { - while ((readl_relaxed(addr) & mask) != mask) - continue; + size_t i; + + for (i = 0; i < max_attempts; i++) { + if ((readl_relaxed(addr) & mask) == mask) + return true; + } + return false; } /* Poll the given SFR as long as its value has all bits of a given mask set. */ @@ -175,14 +188,27 @@ static void __wait_while(void __iomem *addr, u32 mask) static void __wait_for_invalidation_complete(struct pkvm_iommu *dev) { struct pkvm_iommu *sync; + size_t i, timeout; /* * Wait for transactions to drain if SysMMU_SYNCs were registered. * Assumes that they are in the same power domain as the S2MPU. + * + * The algorithm will try initiating the SYNC if the SYNC_COMP_COMPLETE + * bit has not been set after a given number of attempts, increasing the + * timeout exponentially each time. If this cycle fails a given number + * of times, the algorithm will give up completely to avoid deadlock. */ for_each_child(sync, dev) { - writel_relaxed(SYNC_CMD_SYNC, sync->va + REG_NS_SYNC_CMD); - __wait_until(sync->va + REG_NS_SYNC_COMP, SYNC_COMP_COMPLETE); + timeout = SYNC_TIMEOUT; + for (i = 0; i < SYNC_MAX_RETRIES; i++) { + writel_relaxed(SYNC_CMD_SYNC, sync->va + REG_NS_SYNC_CMD); + if (__wait_until(sync->va + REG_NS_SYNC_COMP, + SYNC_COMP_COMPLETE, timeout)) { + break; + } + timeout *= SYNC_TIMEOUT_MULTIPLIER; + } } /* Must not access SFRs while S2MPU is busy invalidating (v9 only). */