Commit b16d0cb9 authored by David Woodhouse's avatar David Woodhouse

iommu/vt-d: Always enable PASID/PRI PCI capabilities before ATS

The behaviour if you enable PASID support after ATS is undefined. So we
have to enable it first, even if we don't know whether we'll need it.

This is safe enough; unless we set up a context that permits it, the device
can't actually *do* anything with it.

Also shift the feature detction to dmar_insert_one_dev_info() as it only
needs to happen once.
Signed-off-by: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
parent 8a94ade4
......@@ -138,6 +138,7 @@ config INTEL_IOMMU
config INTEL_IOMMU_SVM
bool "Support for Shared Virtual Memory with Intel IOMMU"
depends on INTEL_IOMMU && X86
select PCI_PASID
help
Shared Virtual Memory (SVM) provides a facility for devices
to access DMA resources through process address space by
......
......@@ -418,10 +418,13 @@ struct device_domain_info {
struct list_head global; /* link to global list */
u8 bus; /* PCI bus number */
u8 devfn; /* PCI devfn number */
struct {
u8 enabled:1;
u8 qdep;
} ats; /* ATS state */
u8 pasid_supported:3;
u8 pasid_enabled:1;
u8 pri_supported:1;
u8 pri_enabled:1;
u8 ats_supported:1;
u8 ats_enabled:1;
u8 ats_qdep;
struct device *dev; /* it's NULL for PCIe-to-PCI bridge */
struct intel_iommu *iommu; /* IOMMU used by this device */
struct dmar_domain *domain; /* pointer to domain */
......@@ -1420,37 +1423,22 @@ static struct device_domain_info *
iommu_support_dev_iotlb (struct dmar_domain *domain, struct intel_iommu *iommu,
u8 bus, u8 devfn)
{
bool found = false;
struct device_domain_info *info;
struct pci_dev *pdev;
assert_spin_locked(&device_domain_lock);
if (!ecap_dev_iotlb_support(iommu->ecap))
return NULL;
if (!iommu->qi)
return NULL;
list_for_each_entry(info, &domain->devices, link)
if (info->iommu == iommu && info->bus == bus &&
info->devfn == devfn) {
found = true;
if (info->ats_supported && info->dev)
return info;
break;
}
if (!found || !info->dev || !dev_is_pci(info->dev))
return NULL;
pdev = to_pci_dev(info->dev);
if (!pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ATS))
return NULL;
if (!dmar_find_matched_atsr_unit(pdev))
return NULL;
return info;
}
static void iommu_enable_dev_iotlb(struct device_domain_info *info)
......@@ -1461,20 +1449,48 @@ static void iommu_enable_dev_iotlb(struct device_domain_info *info)
return;
pdev = to_pci_dev(info->dev);
if (pci_enable_ats(pdev, VTD_PAGE_SHIFT))
return;
info->ats.enabled = 1;
info->ats.qdep = pci_ats_queue_depth(pdev);
#ifdef CONFIG_INTEL_IOMMU_SVM
/* The PCIe spec, in its wisdom, declares that the behaviour of
the device if you enable PASID support after ATS support is
undefined. So always enable PASID support on devices which
have it, even if we can't yet know if we're ever going to
use it. */
if (info->pasid_supported && !pci_enable_pasid(pdev, info->pasid_supported & ~1))
info->pasid_enabled = 1;
if (info->pri_supported && !pci_reset_pri(pdev) && !pci_enable_pri(pdev, 32))
info->pri_enabled = 1;
#endif
if (info->ats_supported && !pci_enable_ats(pdev, VTD_PAGE_SHIFT)) {
info->ats_enabled = 1;
info->ats_qdep = pci_ats_queue_depth(pdev);
}
}
static void iommu_disable_dev_iotlb(struct device_domain_info *info)
{
if (!info->ats.enabled)
struct pci_dev *pdev;
if (dev_is_pci(info->dev))
return;
pci_disable_ats(to_pci_dev(info->dev));
info->ats.enabled = 0;
pdev = to_pci_dev(info->dev);
if (info->ats_enabled) {
pci_disable_ats(pdev);
info->ats_enabled = 0;
}
#ifdef CONFIG_INTEL_IOMMU_SVM
if (info->pri_enabled) {
pci_disable_pri(pdev);
info->pri_enabled = 0;
}
if (info->pasid_enabled) {
pci_disable_pasid(pdev);
info->pasid_enabled = 0;
}
#endif
}
static void iommu_flush_dev_iotlb(struct dmar_domain *domain,
......@@ -1486,11 +1502,11 @@ static void iommu_flush_dev_iotlb(struct dmar_domain *domain,
spin_lock_irqsave(&device_domain_lock, flags);
list_for_each_entry(info, &domain->devices, link) {
if (!info->ats.enabled)
if (!info->ats_enabled)
continue;
sid = info->bus << 8 | info->devfn;
qdep = info->ats.qdep;
qdep = info->ats_qdep;
qi_flush_dev_iotlb(info->iommu, sid, qdep, addr, mask);
}
spin_unlock_irqrestore(&device_domain_lock, flags);
......@@ -1952,8 +1968,10 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
}
info = iommu_support_dev_iotlb(domain, iommu, bus, devfn);
translation = info ? CONTEXT_TT_DEV_IOTLB :
CONTEXT_TT_MULTI_LEVEL;
if (info && info->ats_supported)
translation = CONTEXT_TT_DEV_IOTLB;
else
translation = CONTEXT_TT_MULTI_LEVEL;
context_set_address_root(context, virt_to_phys(pgd));
context_set_address_width(context, iommu->agaw);
......@@ -2291,12 +2309,34 @@ static struct dmar_domain *dmar_insert_one_dev_info(struct intel_iommu *iommu,
info->bus = bus;
info->devfn = devfn;
info->ats.enabled = 0;
info->ats.qdep = 0;
info->ats_supported = info->pasid_supported = info->pri_supported = 0;
info->ats_enabled = info->pasid_enabled = info->pri_enabled = 0;
info->ats_qdep = 0;
info->dev = dev;
info->domain = domain;
info->iommu = iommu;
if (dev && dev_is_pci(dev)) {
struct pci_dev *pdev = to_pci_dev(info->dev);
if (ecap_dev_iotlb_support(iommu->ecap) &&
pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ATS) &&
dmar_find_matched_atsr_unit(pdev))
info->ats_supported = 1;
if (ecs_enabled(iommu)) {
if (pasid_enabled(iommu)) {
int features = pci_pasid_features(pdev);
if (features >= 0)
info->pasid_supported = features | 1;
}
if (info->ats_supported && ecap_prs(iommu->ecap) &&
pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_PRI))
info->pri_supported = 1;
}
}
spin_lock_irqsave(&device_domain_lock, flags);
if (dev)
found = find_domain(dev);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment