110 lines
4.3 KiB
Diff
110 lines
4.3 KiB
Diff
|
# Commit 84c340ba4c3eb99278b6ba885616bb183b88ad67
|
||
|
# Date 2014-06-18 15:50:02 +0200
|
||
|
# Author Malcolm Crossley <malcolm.crossley@citrix.com>
|
||
|
# Committer Jan Beulich <jbeulich@suse.com>
|
||
|
IOMMU: prevent VT-d device IOTLB operations on wrong IOMMU
|
||
|
|
||
|
PCIe ATS allows for devices to contain IOTLBs, the VT-d code was iterating
|
||
|
around all ATS capable devices and issuing IOTLB operations for all IOMMUs,
|
||
|
even though each ATS device is only accessible via one particular IOMMU.
|
||
|
|
||
|
Issuing an IOMMU operation to a device not accessible via that IOMMU results
|
||
|
in an IOMMU timeout because the device does not reply. VT-d IOMMU timeouts
|
||
|
result in a Xen panic.
|
||
|
|
||
|
Therefore this bug prevents any Intel system with 2 or more ATS enabled IOMMUs,
|
||
|
each with an ATS device connected to them, from booting Xen.
|
||
|
|
||
|
The patch adds a IOMMU pointer to the ATS device struct so the VT-d code can
|
||
|
ensure it does not issue IOMMU ATS operations on the wrong IOMMU. A void
|
||
|
pointer has to be used because AMD and Intel IOMMU implementations do not have
|
||
|
a common IOMMU structure or indexing mechanism.
|
||
|
|
||
|
Signed-off-by: Malcolm Crossley <malcolm.crossley@citrix.com>
|
||
|
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||
|
Acked-by: Kevin Tian <kevin.tian@intel.com>
|
||
|
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||
|
|
||
|
--- a/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||
|
+++ b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||
|
@@ -163,7 +163,7 @@ static void amd_iommu_setup_domain_devic
|
||
|
!pci_ats_enabled(iommu->seg, bus, pdev->devfn) )
|
||
|
{
|
||
|
if ( devfn == pdev->devfn )
|
||
|
- enable_ats_device(iommu->seg, bus, devfn);
|
||
|
+ enable_ats_device(iommu->seg, bus, devfn, iommu);
|
||
|
|
||
|
amd_iommu_flush_iotlb(devfn, pdev, INV_IOMMU_ALL_PAGES_ADDRESS, 0);
|
||
|
}
|
||
|
--- a/xen/drivers/passthrough/ats.h
|
||
|
+++ b/xen/drivers/passthrough/ats.h
|
||
|
@@ -24,6 +24,7 @@ struct pci_ats_dev {
|
||
|
u8 bus;
|
||
|
u8 devfn;
|
||
|
u16 ats_queue_depth; /* ATS device invalidation queue depth */
|
||
|
+ const void *iommu; /* No common IOMMU struct so use void pointer */
|
||
|
};
|
||
|
|
||
|
#define ATS_REG_CAP 4
|
||
|
@@ -34,7 +35,7 @@ struct pci_ats_dev {
|
||
|
extern struct list_head ats_devices;
|
||
|
extern bool_t ats_enabled;
|
||
|
|
||
|
-int enable_ats_device(int seg, int bus, int devfn);
|
||
|
+int enable_ats_device(int seg, int bus, int devfn, const void *iommu);
|
||
|
void disable_ats_device(int seg, int bus, int devfn);
|
||
|
struct pci_ats_dev *get_ats_device(int seg, int bus, int devfn);
|
||
|
|
||
|
--- a/xen/drivers/passthrough/vtd/iommu.c
|
||
|
+++ b/xen/drivers/passthrough/vtd/iommu.c
|
||
|
@@ -1442,7 +1442,7 @@ static int domain_context_mapping(
|
||
|
ret = domain_context_mapping_one(domain, drhd->iommu, bus, devfn,
|
||
|
pdev);
|
||
|
if ( !ret && devfn == pdev->devfn && ats_device(pdev, drhd) > 0 )
|
||
|
- enable_ats_device(seg, bus, devfn);
|
||
|
+ enable_ats_device(seg, bus, devfn, drhd->iommu);
|
||
|
|
||
|
break;
|
||
|
|
||
|
@@ -1930,7 +1930,7 @@ static int intel_iommu_enable_device(str
|
||
|
if ( ret <= 0 )
|
||
|
return ret;
|
||
|
|
||
|
- ret = enable_ats_device(pdev->seg, pdev->bus, pdev->devfn);
|
||
|
+ ret = enable_ats_device(pdev->seg, pdev->bus, pdev->devfn, drhd->iommu);
|
||
|
|
||
|
return ret >= 0 ? 0 : ret;
|
||
|
}
|
||
|
--- a/xen/drivers/passthrough/vtd/x86/ats.c
|
||
|
+++ b/xen/drivers/passthrough/vtd/x86/ats.c
|
||
|
@@ -120,6 +120,10 @@ int dev_invalidate_iotlb(struct iommu *i
|
||
|
{
|
||
|
sid = (pdev->bus << 8) | pdev->devfn;
|
||
|
|
||
|
+ /* Only invalidate devices that belong to this IOMMU */
|
||
|
+ if ( pdev->iommu != iommu )
|
||
|
+ continue;
|
||
|
+
|
||
|
switch ( type ) {
|
||
|
case DMA_TLB_DSI_FLUSH:
|
||
|
if ( !device_in_domain(iommu, pdev, did) )
|
||
|
--- a/xen/drivers/passthrough/x86/ats.c
|
||
|
+++ b/xen/drivers/passthrough/x86/ats.c
|
||
|
@@ -23,7 +23,7 @@ LIST_HEAD(ats_devices);
|
||
|
bool_t __read_mostly ats_enabled = 1;
|
||
|
boolean_param("ats", ats_enabled);
|
||
|
|
||
|
-int enable_ats_device(int seg, int bus, int devfn)
|
||
|
+int enable_ats_device(int seg, int bus, int devfn, const void *iommu)
|
||
|
{
|
||
|
struct pci_ats_dev *pdev = NULL;
|
||
|
u32 value;
|
||
|
@@ -66,6 +66,7 @@ int enable_ats_device(int seg, int bus,
|
||
|
pdev->seg = seg;
|
||
|
pdev->bus = bus;
|
||
|
pdev->devfn = devfn;
|
||
|
+ pdev->iommu = iommu;
|
||
|
value = pci_conf_read16(seg, bus, PCI_SLOT(devfn),
|
||
|
PCI_FUNC(devfn), pos + ATS_REG_CAP);
|
||
|
pdev->ats_queue_depth = value & ATS_QUEUE_DEPTH_MASK ?:
|