Skip to content

Commit

Permalink
iommu/vt-d: Always enable PASID/PRI PCI capabilities before ATS
Browse files Browse the repository at this point in the history
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: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
David Woodhouse authored and David Woodhouse committed Oct 15, 2015
1 parent 8a94ade commit b16d0cb
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 35 deletions.
1 change: 1 addition & 0 deletions drivers/iommu/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 75 additions & 35 deletions drivers/iommu/intel-iommu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
return NULL;
}

static void iommu_enable_dev_iotlb(struct device_domain_info *info)
Expand All @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit b16d0cb

Please sign in to comment.