forked from mirrors/linux
		
	iommu/vt-d: Add basic SVM PASID support
This provides basic PASID support for endpoint devices, tested with a version of the i915 driver. Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
This commit is contained in:
		
							parent
							
								
									b16d0cb9e2
								
							
						
					
					
						commit
						2f26e0a9c9
					
				
					 6 changed files with 548 additions and 5 deletions
				
			
		|  | @ -139,6 +139,7 @@ config INTEL_IOMMU_SVM | |||
| 	bool "Support for Shared Virtual Memory with Intel IOMMU" | ||||
| 	depends on INTEL_IOMMU && X86 | ||||
| 	select PCI_PASID | ||||
| 	select MMU_NOTIFIER | ||||
| 	help | ||||
| 	  Shared Virtual Memory (SVM) provides a facility for devices | ||||
| 	  to access DMA resources through process address space by | ||||
|  |  | |||
|  | @ -4929,6 +4929,110 @@ static void intel_iommu_remove_device(struct device *dev) | |||
| 	iommu_device_unlink(iommu->iommu_dev, dev); | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_INTEL_IOMMU_SVM | ||||
| int intel_iommu_enable_pasid(struct intel_iommu *iommu, struct intel_svm_dev *sdev) | ||||
| { | ||||
| 	struct device_domain_info *info; | ||||
| 	struct context_entry *context; | ||||
| 	struct dmar_domain *domain; | ||||
| 	unsigned long flags; | ||||
| 	u64 ctx_lo; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	domain = get_valid_domain_for_dev(sdev->dev); | ||||
| 	if (!domain) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	spin_lock_irqsave(&device_domain_lock, flags); | ||||
| 	spin_lock(&iommu->lock); | ||||
| 
 | ||||
| 	ret = -EINVAL; | ||||
| 	info = sdev->dev->archdata.iommu; | ||||
| 	if (!info || !info->pasid_supported) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	context = iommu_context_addr(iommu, info->bus, info->devfn, 0); | ||||
| 	if (WARN_ON(!context)) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	ctx_lo = context[0].lo; | ||||
| 
 | ||||
| 	sdev->did = domain->iommu_did[iommu->seq_id]; | ||||
| 	sdev->sid = PCI_DEVID(info->bus, info->devfn); | ||||
| 
 | ||||
| 	if (!(ctx_lo & CONTEXT_PASIDE)) { | ||||
| 		context[1].hi = (u64)virt_to_phys(iommu->pasid_state_table); | ||||
| 		context[1].lo = (u64)virt_to_phys(iommu->pasid_table) | ecap_pss(iommu->ecap); | ||||
| 		wmb(); | ||||
| 		/* CONTEXT_TT_MULTI_LEVEL and CONTEXT_TT_DEV_IOTLB are both
 | ||||
| 		 * extended to permit requests-with-PASID if the PASIDE bit | ||||
| 		 * is set. which makes sense. For CONTEXT_TT_PASS_THROUGH, | ||||
| 		 * however, the PASIDE bit is ignored and requests-with-PASID | ||||
| 		 * are unconditionally blocked. Which makes less sense. | ||||
| 		 * So convert from CONTEXT_TT_PASS_THROUGH to one of the new | ||||
| 		 * "guest mode" translation types depending on whether ATS | ||||
| 		 * is available or not. Annoyingly, we can't use the new | ||||
| 		 * modes *unless* PASIDE is set. */ | ||||
| 		if ((ctx_lo & CONTEXT_TT_MASK) == (CONTEXT_TT_PASS_THROUGH << 2)) { | ||||
| 			ctx_lo &= ~CONTEXT_TT_MASK; | ||||
| 			if (info->ats_supported) | ||||
| 				ctx_lo |= CONTEXT_TT_PT_PASID_DEV_IOTLB << 2; | ||||
| 			else | ||||
| 				ctx_lo |= CONTEXT_TT_PT_PASID << 2; | ||||
| 		} | ||||
| 		ctx_lo |= CONTEXT_PASIDE; | ||||
| 		context[0].lo = ctx_lo; | ||||
| 		wmb(); | ||||
| 		iommu->flush.flush_context(iommu, sdev->did, sdev->sid, | ||||
| 					   DMA_CCMD_MASK_NOBIT, | ||||
| 					   DMA_CCMD_DEVICE_INVL); | ||||
| 	} | ||||
| 
 | ||||
| 	/* Enable PASID support in the device, if it wasn't already */ | ||||
| 	if (!info->pasid_enabled) | ||||
| 		iommu_enable_dev_iotlb(info); | ||||
| 
 | ||||
| 	if (info->ats_enabled) { | ||||
| 		sdev->dev_iotlb = 1; | ||||
| 		sdev->qdep = info->ats_qdep; | ||||
| 		if (sdev->qdep >= QI_DEV_EIOTLB_MAX_INVS) | ||||
| 			sdev->qdep = 0; | ||||
| 	} | ||||
| 	ret = 0; | ||||
| 
 | ||||
|  out: | ||||
| 	spin_unlock(&iommu->lock); | ||||
| 	spin_unlock_irqrestore(&device_domain_lock, flags); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| struct intel_iommu *intel_svm_device_to_iommu(struct device *dev) | ||||
| { | ||||
| 	struct intel_iommu *iommu; | ||||
| 	u8 bus, devfn; | ||||
| 
 | ||||
| 	if (iommu_dummy(dev)) { | ||||
| 		dev_warn(dev, | ||||
| 			 "No IOMMU translation for device; cannot enable SVM\n"); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	iommu = device_to_iommu(dev, &bus, &devfn); | ||||
| 	if ((!iommu)) { | ||||
| 		dev_dbg(dev, "No IOMMU for device; cannot enable SVM\n"); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!iommu->pasid_table) { | ||||
| 		dev_dbg(dev, "PASID not enabled on IOMMU; cannot enable SVM\n"); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	return iommu; | ||||
| } | ||||
| #endif /* CONFIG_INTEL_IOMMU_SVM */ | ||||
| 
 | ||||
| static const struct iommu_ops intel_iommu_ops = { | ||||
| 	.capable	= intel_iommu_capable, | ||||
| 	.domain_alloc	= intel_iommu_domain_alloc, | ||||
|  |  | |||
|  | @ -14,6 +14,17 @@ | |||
|  */ | ||||
| 
 | ||||
| #include <linux/intel-iommu.h> | ||||
| #include <linux/mmu_notifier.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/intel-svm.h> | ||||
| #include <linux/rculist.h> | ||||
| #include <linux/pci.h> | ||||
| #include <linux/pci-ats.h> | ||||
| 
 | ||||
| struct pasid_entry { | ||||
| 	u64 val; | ||||
| }; | ||||
| 
 | ||||
| int intel_svm_alloc_pasid_tables(struct intel_iommu *iommu) | ||||
| { | ||||
|  | @ -42,6 +53,8 @@ int intel_svm_alloc_pasid_tables(struct intel_iommu *iommu) | |||
| 				iommu->name); | ||||
| 	} | ||||
| 
 | ||||
| 	idr_init(&iommu->pasid_idr); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -61,5 +74,283 @@ int intel_svm_free_pasid_tables(struct intel_iommu *iommu) | |||
| 		free_pages((unsigned long)iommu->pasid_state_table, order); | ||||
| 		iommu->pasid_state_table = NULL; | ||||
| 	} | ||||
| 	idr_destroy(&iommu->pasid_idr); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void intel_flush_svm_range_dev (struct intel_svm *svm, struct intel_svm_dev *sdev, | ||||
| 				       unsigned long address, int pages, int ih) | ||||
| { | ||||
| 	struct qi_desc desc; | ||||
| 	int mask = ilog2(__roundup_pow_of_two(pages)); | ||||
| 
 | ||||
| 	if (pages == -1 || !cap_pgsel_inv(svm->iommu->cap) || | ||||
| 	    mask > cap_max_amask_val(svm->iommu->cap)) { | ||||
| 		desc.low = QI_EIOTLB_PASID(svm->pasid) | QI_EIOTLB_DID(sdev->did) | | ||||
| 			QI_EIOTLB_GRAN(QI_GRAN_NONG_PASID) | QI_EIOTLB_TYPE; | ||||
| 		desc.high = 0; | ||||
| 	} else { | ||||
| 		desc.low = QI_EIOTLB_PASID(svm->pasid) | QI_EIOTLB_DID(sdev->did) | | ||||
| 			QI_EIOTLB_GRAN(QI_GRAN_PSI_PASID) | QI_EIOTLB_TYPE; | ||||
| 		desc.high = QI_EIOTLB_ADDR(address) | QI_EIOTLB_GL(1) | | ||||
| 			QI_EIOTLB_IH(ih) | QI_EIOTLB_AM(mask); | ||||
| 	} | ||||
| 
 | ||||
| 	qi_submit_sync(&desc, svm->iommu); | ||||
| 
 | ||||
| 	if (sdev->dev_iotlb) { | ||||
| 		desc.low = QI_DEV_EIOTLB_PASID(svm->pasid) | QI_DEV_EIOTLB_SID(sdev->sid) | | ||||
| 			QI_DEV_EIOTLB_QDEP(sdev->qdep) | QI_DEIOTLB_TYPE; | ||||
| 		if (mask) { | ||||
| 			unsigned long adr, delta; | ||||
| 
 | ||||
| 			/* Least significant zero bits in the address indicate the
 | ||||
| 			 * range of the request. So mask them out according to the | ||||
| 			 * size. */ | ||||
| 			adr = address & ((1<<(VTD_PAGE_SHIFT + mask)) - 1); | ||||
| 
 | ||||
| 			/* Now ensure that we round down further if the original
 | ||||
| 			 * request was not aligned w.r.t. its size */ | ||||
| 			delta = address - adr; | ||||
| 			if (delta + (pages << VTD_PAGE_SHIFT) >= (1 << (VTD_PAGE_SHIFT + mask))) | ||||
| 				adr &= ~(1 << (VTD_PAGE_SHIFT + mask)); | ||||
| 			desc.high = QI_DEV_EIOTLB_ADDR(adr) | QI_DEV_EIOTLB_SIZE; | ||||
| 		} else { | ||||
| 			desc.high = QI_DEV_EIOTLB_ADDR(address); | ||||
| 		} | ||||
| 		qi_submit_sync(&desc, svm->iommu); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void intel_flush_svm_range(struct intel_svm *svm, unsigned long address, | ||||
| 				  int pages, int ih) | ||||
| { | ||||
| 	struct intel_svm_dev *sdev; | ||||
| 
 | ||||
| 	rcu_read_lock(); | ||||
| 	list_for_each_entry_rcu(sdev, &svm->devs, list) | ||||
| 		intel_flush_svm_range_dev(svm, sdev, address, pages, ih); | ||||
| 	rcu_read_unlock(); | ||||
| } | ||||
| 
 | ||||
| static void intel_change_pte(struct mmu_notifier *mn, struct mm_struct *mm, | ||||
| 			     unsigned long address, pte_t pte) | ||||
| { | ||||
| 	struct intel_svm *svm = container_of(mn, struct intel_svm, notifier); | ||||
| 
 | ||||
| 	intel_flush_svm_range(svm, address, 1, 1); | ||||
| } | ||||
| 
 | ||||
| static void intel_invalidate_page(struct mmu_notifier *mn, struct mm_struct *mm, | ||||
| 				  unsigned long address) | ||||
| { | ||||
| 	struct intel_svm *svm = container_of(mn, struct intel_svm, notifier); | ||||
| 
 | ||||
| 	intel_flush_svm_range(svm, address, 1, 1); | ||||
| } | ||||
| 
 | ||||
| /* Pages have been freed at this point */ | ||||
| static void intel_invalidate_range(struct mmu_notifier *mn, | ||||
| 				   struct mm_struct *mm, | ||||
| 				   unsigned long start, unsigned long end) | ||||
| { | ||||
| 	struct intel_svm *svm = container_of(mn, struct intel_svm, notifier); | ||||
| 
 | ||||
| 	intel_flush_svm_range(svm, start, | ||||
| 			      (end - start + PAGE_SIZE - 1) >> VTD_PAGE_SHIFT , 0); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void intel_flush_pasid_dev(struct intel_svm *svm, struct intel_svm_dev *sdev) | ||||
| { | ||||
| 	struct qi_desc desc; | ||||
| 
 | ||||
| 	desc.high = 0; | ||||
| 	desc.low = QI_PC_TYPE | QI_PC_DID(sdev->did) | QI_PC_PASID_SEL | QI_PC_PASID(svm->pasid); | ||||
| 
 | ||||
| 	qi_submit_sync(&desc, svm->iommu); | ||||
| } | ||||
| 
 | ||||
| static void intel_mm_release(struct mmu_notifier *mn, struct mm_struct *mm) | ||||
| { | ||||
| 	struct intel_svm *svm = container_of(mn, struct intel_svm, notifier); | ||||
| 
 | ||||
| 	svm->iommu->pasid_table[svm->pasid].val = 0; | ||||
| 
 | ||||
| 	/* There's no need to do any flush because we can't get here if there
 | ||||
| 	 * are any devices left anyway. */ | ||||
| 	WARN_ON(!list_empty(&svm->devs)); | ||||
| } | ||||
| 
 | ||||
| static const struct mmu_notifier_ops intel_mmuops = { | ||||
| 	.release = intel_mm_release, | ||||
| 	.change_pte = intel_change_pte, | ||||
| 	.invalidate_page = intel_invalidate_page, | ||||
| 	.invalidate_range = intel_invalidate_range, | ||||
| }; | ||||
| 
 | ||||
| static DEFINE_MUTEX(pasid_mutex); | ||||
| 
 | ||||
| int intel_svm_bind_mm(struct device *dev, int *pasid) | ||||
| { | ||||
| 	struct intel_iommu *iommu = intel_svm_device_to_iommu(dev); | ||||
| 	struct intel_svm_dev *sdev; | ||||
| 	struct intel_svm *svm = NULL; | ||||
| 	int pasid_max; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	BUG_ON(pasid && !current->mm); | ||||
| 
 | ||||
| 	if (WARN_ON(!iommu)) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (dev_is_pci(dev)) { | ||||
| 		pasid_max = pci_max_pasids(to_pci_dev(dev)); | ||||
| 		if (pasid_max < 0) | ||||
| 			return -EINVAL; | ||||
| 	} else | ||||
| 		pasid_max = 1 << 20; | ||||
| 
 | ||||
| 	mutex_lock(&pasid_mutex); | ||||
| 	if (pasid) { | ||||
| 		int i; | ||||
| 
 | ||||
| 		idr_for_each_entry(&iommu->pasid_idr, svm, i) { | ||||
| 			if (svm->mm != current->mm) | ||||
| 				continue; | ||||
| 
 | ||||
| 			if (svm->pasid >= pasid_max) { | ||||
| 				dev_warn(dev, | ||||
| 					 "Limited PASID width. Cannot use existing PASID %d\n", | ||||
| 					 svm->pasid); | ||||
| 				ret = -ENOSPC; | ||||
| 				goto out; | ||||
| 			} | ||||
| 
 | ||||
| 			list_for_each_entry(sdev, &svm->devs, list) { | ||||
| 				if (dev == sdev->dev) { | ||||
| 					sdev->users++; | ||||
| 					goto success; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); | ||||
| 	if (!sdev) { | ||||
| 		ret = -ENOMEM; | ||||
| 		goto out; | ||||
| 	} | ||||
| 	sdev->dev = dev; | ||||
| 
 | ||||
| 	ret = intel_iommu_enable_pasid(iommu, sdev); | ||||
| 	if (ret || !pasid) { | ||||
| 		/* If they don't actually want to assign a PASID, this is
 | ||||
| 		 * just an enabling check/preparation. */ | ||||
| 		kfree(sdev); | ||||
| 		goto out; | ||||
| 	} | ||||
| 	/* Finish the setup now we know we're keeping it */ | ||||
| 	sdev->users = 1; | ||||
| 	init_rcu_head(&sdev->rcu); | ||||
| 
 | ||||
| 	if (!svm) { | ||||
| 		svm = kzalloc(sizeof(*svm), GFP_KERNEL); | ||||
| 		if (!svm) { | ||||
| 			ret = -ENOMEM; | ||||
| 			kfree(sdev); | ||||
| 			goto out; | ||||
| 		} | ||||
| 		svm->iommu = iommu; | ||||
| 
 | ||||
| 		if (pasid_max > 2 << ecap_pss(iommu->ecap)) | ||||
| 			pasid_max = 2 << ecap_pss(iommu->ecap); | ||||
| 
 | ||||
| 		ret = idr_alloc(&iommu->pasid_idr, svm, 0, pasid_max - 1, | ||||
| 				GFP_KERNEL); | ||||
| 		if (ret < 0) { | ||||
| 			kfree(svm); | ||||
| 			goto out; | ||||
| 		} | ||||
| 		svm->pasid = ret; | ||||
| 		svm->notifier.ops = &intel_mmuops; | ||||
| 		svm->mm = get_task_mm(current); | ||||
| 		INIT_LIST_HEAD_RCU(&svm->devs); | ||||
| 		ret = -ENOMEM; | ||||
| 		if (!svm->mm || (ret = mmu_notifier_register(&svm->notifier, svm->mm))) { | ||||
| 			idr_remove(&svm->iommu->pasid_idr, svm->pasid); | ||||
| 			kfree(svm); | ||||
| 			kfree(sdev); | ||||
| 			goto out; | ||||
| 		} | ||||
| 		iommu->pasid_table[svm->pasid].val = (u64)__pa(svm->mm->pgd) | 1; | ||||
| 		wmb(); | ||||
| 	} | ||||
| 	list_add_rcu(&sdev->list, &svm->devs); | ||||
| 
 | ||||
|  success: | ||||
| 	*pasid = svm->pasid; | ||||
| 	ret = 0; | ||||
|  out: | ||||
| 	mutex_unlock(&pasid_mutex); | ||||
| 	return ret; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(intel_svm_bind_mm); | ||||
| 
 | ||||
| int intel_svm_unbind_mm(struct device *dev, int pasid) | ||||
| { | ||||
| 	struct intel_svm_dev *sdev; | ||||
| 	struct intel_iommu *iommu; | ||||
| 	struct intel_svm *svm; | ||||
| 	int ret = -EINVAL; | ||||
| 
 | ||||
| 	mutex_lock(&pasid_mutex); | ||||
| 	iommu = intel_svm_device_to_iommu(dev); | ||||
| 	if (!iommu || !iommu->pasid_table) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	svm = idr_find(&iommu->pasid_idr, pasid); | ||||
| 	if (!svm) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	list_for_each_entry(sdev, &svm->devs, list) { | ||||
| 		if (dev == sdev->dev) { | ||||
| 			ret = 0; | ||||
| 			sdev->users--; | ||||
| 			if (!sdev->users) { | ||||
| 				list_del_rcu(&sdev->list); | ||||
| 				/* Flush the PASID cache and IOTLB for this device.
 | ||||
| 				 * Note that we do depend on the hardware *not* using | ||||
| 				 * the PASID any more. Just as we depend on other | ||||
| 				 * devices never using PASIDs that they have no right | ||||
| 				 * to use. We have a *shared* PASID table, because it's | ||||
| 				 * large and has to be physically contiguous. So it's | ||||
| 				 * hard to be as defensive as we might like. */ | ||||
| 				intel_flush_pasid_dev(svm, sdev); | ||||
| 				intel_flush_svm_range_dev(svm, sdev, 0, -1, 0); | ||||
| 				kfree_rcu(sdev, rcu); | ||||
| 
 | ||||
| 				if (list_empty(&svm->devs)) { | ||||
| 					mmu_notifier_unregister(&svm->notifier, svm->mm); | ||||
| 
 | ||||
| 					idr_remove(&svm->iommu->pasid_idr, svm->pasid); | ||||
| 					mmput(svm->mm); | ||||
| 					/* We mandate that no page faults may be outstanding
 | ||||
| 					 * for the PASID when intel_svm_unbind_mm() is called. | ||||
| 					 * If that is not obeyed, subtle errors will happen. | ||||
| 					 * Let's make them less subtle... */ | ||||
| 					memset(svm, 0x6b, sizeof(*svm)); | ||||
| 					kfree(svm); | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  out: | ||||
| 	mutex_unlock(&pasid_mutex); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(intel_svm_unbind_mm); | ||||
|  |  | |||
|  | @ -20,6 +20,13 @@ | |||
| #define CONTEXT_TT_MULTI_LEVEL	0 | ||||
| #define CONTEXT_TT_DEV_IOTLB	1 | ||||
| #define CONTEXT_TT_PASS_THROUGH 2 | ||||
| /* Extended context entry types */ | ||||
| #define CONTEXT_TT_PT_PASID	4 | ||||
| #define CONTEXT_TT_PT_PASID_DEV_IOTLB 5 | ||||
| #define CONTEXT_TT_MASK (7ULL << 2) | ||||
| 
 | ||||
| #define CONTEXT_PRS		(1ULL << 9) | ||||
| #define CONTEXT_PASIDE		(1ULL << 11) | ||||
| 
 | ||||
| struct intel_iommu; | ||||
| struct dmar_domain; | ||||
|  |  | |||
|  | @ -1,5 +1,9 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2006, Intel Corporation. | ||||
|  * Copyright © 2006-2015, Intel Corporation. | ||||
|  * | ||||
|  * Authors: Ashok Raj <ashok.raj@intel.com> | ||||
|  *          Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> | ||||
|  *          David Woodhouse <David.Woodhouse@intel.com> | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  | @ -13,10 +17,6 @@ | |||
|  * You should have received a copy of the GNU General Public License along with | ||||
|  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple | ||||
|  * Place - Suite 330, Boston, MA 02111-1307 USA. | ||||
|  * | ||||
|  * Copyright (C) 2006-2008 Intel Corporation | ||||
|  * Author: Ashok Raj <ashok.raj@intel.com> | ||||
|  * Author: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _INTEL_IOMMU_H_ | ||||
|  | @ -25,7 +25,10 @@ | |||
| #include <linux/types.h> | ||||
| #include <linux/iova.h> | ||||
| #include <linux/io.h> | ||||
| #include <linux/idr.h> | ||||
| #include <linux/dma_remapping.h> | ||||
| #include <linux/mmu_notifier.h> | ||||
| #include <linux/list.h> | ||||
| #include <asm/cacheflush.h> | ||||
| #include <asm/iommu.h> | ||||
| 
 | ||||
|  | @ -251,6 +254,9 @@ enum { | |||
| #define QI_DIOTLB_TYPE		0x3 | ||||
| #define QI_IEC_TYPE		0x4 | ||||
| #define QI_IWD_TYPE		0x5 | ||||
| #define QI_EIOTLB_TYPE		0x6 | ||||
| #define QI_PC_TYPE		0x7 | ||||
| #define QI_DEIOTLB_TYPE		0x8 | ||||
| 
 | ||||
| #define QI_IEC_SELECTIVE	(((u64)1) << 4) | ||||
| #define QI_IEC_IIDEX(idx)	(((u64)(idx & 0xffff) << 32)) | ||||
|  | @ -278,6 +284,34 @@ enum { | |||
| #define QI_DEV_IOTLB_SIZE	1 | ||||
| #define QI_DEV_IOTLB_MAX_INVS	32 | ||||
| 
 | ||||
| #define QI_PC_PASID(pasid)	(((u64)pasid) << 32) | ||||
| #define QI_PC_DID(did)		(((u64)did) << 16) | ||||
| #define QI_PC_GRAN(gran)	(((u64)gran) << 4) | ||||
| 
 | ||||
| #define QI_PC_ALL_PASIDS	(QI_PC_TYPE | QI_PC_GRAN(0)) | ||||
| #define QI_PC_PASID_SEL		(QI_PC_TYPE | QI_PC_GRAN(1)) | ||||
| 
 | ||||
| #define QI_EIOTLB_ADDR(addr)	((u64)(addr) & VTD_PAGE_MASK) | ||||
| #define QI_EIOTLB_GL(gl)	(((u64)gl) << 7) | ||||
| #define QI_EIOTLB_IH(ih)	(((u64)ih) << 6) | ||||
| #define QI_EIOTLB_AM(am)	(((u64)am)) | ||||
| #define QI_EIOTLB_PASID(pasid) 	(((u64)pasid) << 32) | ||||
| #define QI_EIOTLB_DID(did)	(((u64)did) << 16) | ||||
| #define QI_EIOTLB_GRAN(gran) 	(((u64)gran) << 4) | ||||
| 
 | ||||
| #define QI_DEV_EIOTLB_ADDR(a)	((u64)(a) & VTD_PAGE_MASK) | ||||
| #define QI_DEV_EIOTLB_SIZE	(((u64)1) << 11) | ||||
| #define QI_DEV_EIOTLB_GLOB(g)	((u64)g) | ||||
| #define QI_DEV_EIOTLB_PASID(p)	(((u64)p) << 32) | ||||
| #define QI_DEV_EIOTLB_SID(sid)	((u64)((sid) & 0xffff) << 32) | ||||
| #define QI_DEV_EIOTLB_QDEP(qd)	(((qd) & 0x1f) << 16) | ||||
| #define QI_DEV_EIOTLB_MAX_INVS	32 | ||||
| 
 | ||||
| #define QI_GRAN_ALL_ALL			0 | ||||
| #define QI_GRAN_NONG_ALL		1 | ||||
| #define QI_GRAN_NONG_PASID		2 | ||||
| #define QI_GRAN_PSI_PASID		3 | ||||
| 
 | ||||
| struct qi_desc { | ||||
| 	u64 low, high; | ||||
| }; | ||||
|  | @ -359,6 +393,7 @@ struct intel_iommu { | |||
| 	 * told to. But while it's all driver-arbitrated, we're fine. */ | ||||
| 	struct pasid_entry *pasid_table; | ||||
| 	struct pasid_state_entry *pasid_state_table; | ||||
| 	struct idr pasid_idr; | ||||
| #endif | ||||
| 	struct q_inval  *qi;            /* Queued invalidation info */ | ||||
| 	u32 *iommu_state; /* Store iommu states between suspend and resume.*/ | ||||
|  | @ -399,9 +434,32 @@ extern int qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu); | |||
| 
 | ||||
| extern int dmar_ir_support(void); | ||||
| 
 | ||||
| #ifdef CONFIG_INTEL_IOMMU_SVM | ||||
| extern int intel_svm_alloc_pasid_tables(struct intel_iommu *iommu); | ||||
| extern int intel_svm_free_pasid_tables(struct intel_iommu *iommu); | ||||
| 
 | ||||
| struct intel_svm_dev { | ||||
| 	struct list_head list; | ||||
| 	struct rcu_head rcu; | ||||
| 	struct device *dev; | ||||
| 	int users; | ||||
| 	u16 did; | ||||
| 	u16 dev_iotlb:1; | ||||
| 	u16 sid, qdep; | ||||
| }; | ||||
| 
 | ||||
| struct intel_svm { | ||||
| 	struct mmu_notifier notifier; | ||||
| 	struct mm_struct *mm; | ||||
| 	struct intel_iommu *iommu; | ||||
| 	int pasid; | ||||
| 	struct list_head devs; | ||||
| }; | ||||
| 
 | ||||
| extern int intel_iommu_enable_pasid(struct intel_iommu *iommu, struct intel_svm_dev *sdev); | ||||
| extern struct intel_iommu *intel_svm_device_to_iommu(struct device *dev); | ||||
| #endif | ||||
| 
 | ||||
| extern const struct attribute_group *intel_iommu_groups[]; | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
							
								
								
									
										82
									
								
								include/linux/intel-svm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								include/linux/intel-svm.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,82 @@ | |||
| /*
 | ||||
|  * Copyright © 2015 Intel Corporation. | ||||
|  * | ||||
|  * Authors: David Woodhouse <David.Woodhouse@intel.com> | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | ||||
|  * more details. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef __INTEL_SVM_H__ | ||||
| #define __INTEL_SVM_H__ | ||||
| 
 | ||||
| #ifdef CONFIG_INTEL_IOMMU_SVM | ||||
| 
 | ||||
| struct device; | ||||
| 
 | ||||
| /**
 | ||||
|  * intel_svm_bind_mm() - Bind the current process to a PASID | ||||
|  * @dev:	Device to be granted acccess | ||||
|  * @pasid:	Address for allocated PASID | ||||
|  * | ||||
|  * This function attempts to enable PASID support for the given device. | ||||
|  * If the @pasid argument is non-%NULL, a PASID is allocated for access | ||||
|  * to the MM of the current process. | ||||
|  * | ||||
|  * By using a %NULL value for the @pasid argument, this function can | ||||
|  * be used to simply validate that PASID support is available for the | ||||
|  * given device — i.e. that it is behind an IOMMU which has the | ||||
|  * requisite support, and is enabled. | ||||
|  * | ||||
|  * Page faults are handled transparently by the IOMMU code, and there | ||||
|  * should be no need for the device driver to be involved. If a page | ||||
|  * fault cannot be handled (i.e. is an invalid address rather than | ||||
|  * just needs paging in), then the page request will be completed by | ||||
|  * the core IOMMU code with appropriate status, and the device itself | ||||
|  * can then report the resulting fault to its driver via whatever | ||||
|  * mechanism is appropriate. | ||||
|  * | ||||
|  * Multiple calls from the same process may result in the same PASID | ||||
|  * being re-used. A reference count is kept. | ||||
|  */ | ||||
| extern int intel_svm_bind_mm(struct device *dev, int *pasid); | ||||
| 
 | ||||
| /**
 | ||||
|  * intel_svm_unbind_mm() - Unbind a specified PASID | ||||
|  * @dev:	Device for which PASID was allocated | ||||
|  * @pasid:	PASID value to be unbound | ||||
|  * | ||||
|  * This function allows a PASID to be retired when the device no | ||||
|  * longer requires access to the address space of a given process. | ||||
|  * | ||||
|  * If the use count for the PASID in question reaches zero, the | ||||
|  * PASID is revoked and may no longer be used by hardware. | ||||
|  * | ||||
|  * Device drivers are required to ensure that no access (including | ||||
|  * page requests) is currently outstanding for the PASID in question, | ||||
|  * before calling this function. | ||||
|  */ | ||||
| extern int intel_svm_unbind_mm(struct device *dev, int pasid); | ||||
| 
 | ||||
| #else /* CONFIG_INTEL_IOMMU_SVM */ | ||||
| 
 | ||||
| static inline int intel_svm_bind_mm(struct device *dev, int *pasid) | ||||
| { | ||||
| 	return -ENOSYS; | ||||
| } | ||||
| 
 | ||||
| static inline int intel_svm_unbind_mm(struct device *dev, int pasid) | ||||
| { | ||||
| 	BUG(); | ||||
| } | ||||
| #endif /* CONFIG_INTEL_IOMMU_SVM */ | ||||
| 
 | ||||
| #define intel_svm_available(dev) (!intel_svm_bind_mm((dev), NULL)) | ||||
| 
 | ||||
| #endif /* __INTEL_SVM_H__ */ | ||||
		Loading…
	
		Reference in a new issue
	
	 David Woodhouse
						David Woodhouse