mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
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 / CVE-2021-28692. Signed-off-by: Jan Beulich <jbeulich@suse.com> Reviewed-by: Paul Durrant <paul@xen.org>
This commit is contained in:
1 parent
c4e4434357
commit
e4fee66043
1 file changed
+33
-23
@@ -46,10 +46,12 @@ static void send_iommu_command(struct amd_iommu *iommu,
|
||||
writel(tail, iommu->mmio_base + IOMMU_CMD_BUFFER_TAIL_OFFSET);
|
||||
}
|
||||
|
||||
static void flush_command_buffer(struct amd_iommu *iommu)
|
||||
static void flush_command_buffer(struct amd_iommu *iommu,
|
||||
unsigned int timeout_base)
|
||||
{
|
||||
unsigned int cmd[4], status, loop_count;
|
||||
bool 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,
|
||||
@@ -65,22 +67,29 @@ static void flush_command_buffer(struct amd_iommu *iommu)
|
||||
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 = status & IOMMU_STATUS_COMP_WAIT_INT;
|
||||
--loop_count;
|
||||
} while ( !comp_wait && loop_count );
|
||||
|
||||
if ( comp_wait )
|
||||
start = NOW();
|
||||
timeout = start + (timeout_base ?: 100) * MILLISECS(threshold);
|
||||
while ( !(readl(iommu->mmio_base + IOMMU_STATUS_MMIO_OFFSET) &
|
||||
IOMMU_STATUS_COMP_WAIT_INT) )
|
||||
{
|
||||
/* RW1C 'ComWaitInt' in status register */
|
||||
writel(IOMMU_STATUS_COMP_WAIT_INT,
|
||||
iommu->mmio_base + IOMMU_STATUS_MMIO_OFFSET);
|
||||
return;
|
||||
if ( timeout && NOW() > timeout )
|
||||
{
|
||||
threshold |= threshold << 1;
|
||||
printk(XENLOG_WARNING
|
||||
"AMD IOMMU %pp: %scompletion wait taking too long\n",
|
||||
&PCI_SBDF2(iommu->seg, iommu->bdf),
|
||||
timeout_base ? "iotlb " : "");
|
||||
timeout = 0;
|
||||
}
|
||||
cpu_relax();
|
||||
}
|
||||
AMD_IOMMU_DEBUG("Warning: ComWaitInt bit did not assert!\n");
|
||||
|
||||
if ( !timeout )
|
||||
printk(XENLOG_WARNING
|
||||
"AMD IOMMU %pp: %scompletion wait took %lums\n",
|
||||
&PCI_SBDF2(iommu->seg, iommu->bdf),
|
||||
timeout_base ? "iotlb " : "",
|
||||
(NOW() - start) / 10000000);
|
||||
}
|
||||
|
||||
/* Build low level iommu command messages */
|
||||
@@ -291,7 +300,7 @@ void amd_iommu_flush_iotlb(u8 devfn, const struct pci_dev *pdev,
|
||||
/* send INVALIDATE_IOTLB_PAGES command */
|
||||
spin_lock_irqsave(&iommu->lock, flags);
|
||||
invalidate_iotlb_pages(iommu, maxpend, 0, queueid, daddr, req_id, order);
|
||||
flush_command_buffer(iommu);
|
||||
flush_command_buffer(iommu, iommu_dev_iotlb_timeout);
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
}
|
||||
|
||||
@@ -328,7 +337,7 @@ static void _amd_iommu_flush_pages(struct domain *d,
|
||||
{
|
||||
spin_lock_irqsave(&iommu->lock, flags);
|
||||
invalidate_iommu_pages(iommu, daddr, dom_id, order);
|
||||
flush_command_buffer(iommu);
|
||||
flush_command_buffer(iommu, 0);
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
}
|
||||
|
||||
@@ -352,7 +361,7 @@ void amd_iommu_flush_device(struct amd_iommu *iommu, uint16_t bdf)
|
||||
ASSERT( spin_is_locked(&iommu->lock) );
|
||||
|
||||
invalidate_dev_table_entry(iommu, bdf);
|
||||
flush_command_buffer(iommu);
|
||||
flush_command_buffer(iommu, 0);
|
||||
}
|
||||
|
||||
void amd_iommu_flush_intremap(struct amd_iommu *iommu, uint16_t bdf)
|
||||
@@ -360,7 +369,7 @@ void amd_iommu_flush_intremap(struct amd_iommu *iommu, uint16_t bdf)
|
||||
ASSERT( spin_is_locked(&iommu->lock) );
|
||||
|
||||
invalidate_interrupt_table(iommu, bdf);
|
||||
flush_command_buffer(iommu);
|
||||
flush_command_buffer(iommu, 0);
|
||||
}
|
||||
|
||||
void amd_iommu_flush_all_caches(struct amd_iommu *iommu)
|
||||
@@ -368,7 +377,7 @@ void amd_iommu_flush_all_caches(struct amd_iommu *iommu)
|
||||
ASSERT( spin_is_locked(&iommu->lock) );
|
||||
|
||||
invalidate_iommu_all(iommu);
|
||||
flush_command_buffer(iommu);
|
||||
flush_command_buffer(iommu, 0);
|
||||
}
|
||||
|
||||
void amd_iommu_send_guest_cmd(struct amd_iommu *iommu, u32 cmd[])
|
||||
@@ -378,7 +387,8 @@ void amd_iommu_send_guest_cmd(struct amd_iommu *iommu, u32 cmd[])
|
||||
spin_lock_irqsave(&iommu->lock, flags);
|
||||
|
||||
send_iommu_command(iommu, cmd);
|
||||
flush_command_buffer(iommu);
|
||||
/* TBD: Timeout selection may require peeking into cmd[]. */
|
||||
flush_command_buffer(iommu, 0);
|
||||
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
}
|
||||