File xsa373-5.patch of Package xen.25143

AMD/IOMMU: drop command completion timeout

First and foremost - such timeouts were not signaled to callers, making
them believe they're fine to e.g. free previously unmapped pages.

Mirror VT-d's behavior: A fixed number of loop iterations is not a
suitable way to detect timeouts in an environment (CPU and bus speeds)
independent manner anyway. Furthermore, leaving an in-progress operation
pending when it appears to take too long is problematic: If a command
completed later, the signaling of its completion may instead be
understood to signal a subsequently started command's completion.

Log excessively long processing times (with a progressive threshold) to
have some indication of problems in this area. Allow callers to specify
a non-default timeout bias for this logging, using the same values as
VT-d does, which in particular means a (by default) much larger value
for device IO TLB invalidation.

This is part of XSA-373.

Signed-off-by: Jan Beulich <jbeulich@suse.com>
Reviewed-by: Paul Durrant <paul@xen.org>

--- a/xen/drivers/passthrough/amd/iommu_cmd.c
+++ b/xen/drivers/passthrough/amd/iommu_cmd.c
@@ -57,8 +57,9 @@ static void send_iommu_command(struct am
 
 static void flush_command_buffer(struct amd_iommu *iommu)
 {
-    u32 cmd[4], status;
-    int loop_count, comp_wait;
+    uint32_t cmd[4];
+    s_time_t start, timeout;
+    static unsigned int __read_mostly threshold = 1;
 
     /* RW1C 'ComWaitInt' in status register */
     writel(IOMMU_STATUS_COMP_WAIT_INT_MASK,
@@ -74,24 +75,29 @@ static void flush_command_buffer(struct
                          IOMMU_COMP_WAIT_I_FLAG_SHIFT, &cmd[0]);
     send_iommu_command(iommu, cmd);
 
-    /* Make loop_count long enough for polling completion wait bit */
-    loop_count = 1000;
-    do {
-        status = readl(iommu->mmio_base + IOMMU_STATUS_MMIO_OFFSET);
-        comp_wait = get_field_from_reg_u32(status,
-                                           IOMMU_STATUS_COMP_WAIT_INT_MASK,
-                                           IOMMU_STATUS_COMP_WAIT_INT_SHIFT);
-        --loop_count;
-    } while ( !comp_wait && loop_count );
-
-    if ( comp_wait )
+    start = NOW();
+    timeout = start + 100 * MILLISECS(threshold);
+    while ( !(readl(iommu->mmio_base + IOMMU_STATUS_MMIO_OFFSET) &
+              IOMMU_STATUS_COMP_WAIT_INT_MASK) )
     {
-        /* RW1C 'ComWaitInt' in status register */
-        writel(IOMMU_STATUS_COMP_WAIT_INT_MASK,
-               iommu->mmio_base + IOMMU_STATUS_MMIO_OFFSET);
-        return;
+        if ( timeout && NOW() > timeout )
+        {
+            threshold |= threshold << 1;
+            printk(XENLOG_WARNING
+                   "AMD IOMMU %04x:%02x:%02x.%u: completion wait taking too long\n",
+                   iommu->seg, PCI_BUS(iommu->bdf),
+                   PCI_SLOT(iommu->bdf), PCI_FUNC(iommu->bdf));
+            timeout = 0;
+        }
+        cpu_relax();
     }
-    AMD_IOMMU_DEBUG("Warning: ComWaitInt bit did not assert!\n");
+
+    if ( !timeout )
+        printk(XENLOG_WARNING
+               "AMD IOMMU %04x:%02x:%02x.%u: completion wait took %lums\n",
+               iommu->seg, PCI_BUS(iommu->bdf),
+               PCI_SLOT(iommu->bdf), PCI_FUNC(iommu->bdf),
+               (NOW() - start) / 10000000);
 }
 
 /* Build low level iommu command messages */
openSUSE Build Service is sponsored by