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 <dbrazdil@google.com>
Change-Id: I00098753bcc46a894943bbdb3a61acc3a8e5e5d2
This commit is contained in:
David Brazdil
2022-10-13 14:20:53 +01:00
committed by Treehugger Robot
parent c0a46be9dc
commit 6d4b5281a6

View File

@@ -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). */