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:
committed by
Treehugger Robot
parent
c0a46be9dc
commit
6d4b5281a6
@@ -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). */
|
||||
|
||||
Reference in New Issue
Block a user