Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b16d0cb9 authored by David Woodhouse's avatar David Woodhouse
Browse files

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
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+75 −35
Original line number Diff line number Diff line
@@ -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);