Skip to contentSkip to conversation

Enable PASID for DMA API users

PATCH Superseded Jacob Pan 3 participants 22 messages May 10, 2022 – May 19, 2022

Subsystems (5)

v3 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Jacob Pan

author
#0 lore raw

v3 cover Enable PASID for DMA API users

Some modern accelerators such as Intel's Data Streaming Accelerator (DSA) require PASID in DMA requests to be operational. Specifically, the work submissions with ENQCMD on shared work queues require PASIDs. The use cases include both user DMA with shared virtual addressing (SVA) and in-kernel DMA similar to legacy DMA w/o PASID. Here we address the latter.

DMA mapping API is the de facto standard for in-kernel DMA. However, it operates on a per device or Requester ID(RID) basis which is not PASID-aware. To leverage DMA API for devices relies on PASIDs, this patchset introduces the following APIs

1. A driver facing API that enables DMA API PASID usage:
iommu_enable_pasid_dma(struct device *dev, ioasid_t &pasid);

2. An IOMMU op that allows attaching device-domain-PASID generically (will be used beyond DMA API PASID support)

Once PASID DMA is enabled and attached to the appropriate IOMMU domain, device drivers can continue to use DMA APIs as-is. There is no difference in terms of mapping in dma_handle between without PASID and with PASID. The DMA mapping performed by IOMMU will be identical for both requests, let it be IOVA or PA in case of pass-through.

In addition, this set converts DSA driver in-kernel DMA with PASID from SVA lib to DMA API. There have been security and functional issues with the kernel SVA approach:
(https://lore.kernel.org/linux-iommu/[email protected]/)
The highlights are as the following:
- The lack of IOTLB synchronization upon kernel page table updates. (vmalloc, module/BPF loading, CONFIG_DEBUG_PAGEALLOC etc.)
- Other than slight more protection, using kernel virtual address (KVA)
has little advantage over physical address. There are also no use cases yet where DMA engines need kernel virtual addresses for in-kernel DMA.

Subsequently, cleanup is done around the usage of sva_bind_device() for in-kernel DMA. Removing special casing code in VT-d driver and tightening SVA lib API.

This work and idea behind it is a collaboration with many people, many thanks to Baolu Lu, Jason Gunthorpe, Dave Jiang, and others.

ChangeLog:
v3
- Rebased on "Baolu's SVA and IOPF refactoring" series v5.
(https://github.com/LuBaolu/intel-iommu/commits/iommu-sva-refactoring-v5)
This version is significantly simplified by leveraging IOMMU domain ops, attach_dev_pasid() op is implemented differently on a DMA domain than on a SVA domain.
We currently have no need to support multiple PASIDs per DMA domain.
(https://lore.kernel.org/lkml/[email protected]/).
Removed PASID-device list from V2, a PASID field is introduced to struct iommu_domain instead. It is intended for DMA requests with PASID by all devices attached to the domain.

v2
- Do not reserve a special PASID for DMA API usage. Use IOASID allocation instead.
- Introduced a generic device-pasid-domain attachment IOMMU op. Replaced the DMA API only IOMMU op.
- Removed supervisor SVA support in VT-d
- Removed unused sva_bind_device parameters
- Use IOMMU specific data instead of struct device to store PASID info

Jacob Pan (4):
iommu/vt-d: Implement domain ops for attach_dev_pasid
iommu: Add PASID support for DMA mapping API users
dmaengine: idxd: Use DMA API for in-kernel DMA with PASID iommu/vt-d: Delete unused SVM flag

 drivers/dma/idxd/idxd.h     |   1 -
 drivers/dma/idxd/init.c     |  34 +++---------
 drivers/dma/idxd/sysfs.c    |   7 ---
 drivers/iommu/dma-iommu.c   | 107 ++++++++++++++++++++++++++++++++++++
 drivers/iommu/intel/iommu.c |  81 ++++++++++++++++++++++++++-
 drivers/iommu/intel/svm.c   |   2 +-
 include/linux/dma-iommu.h   |   3 +
 include/linux/intel-iommu.h |   1 +
 include/linux/intel-svm.h   |  13 -----
 include/linux/iommu.h       |   2 +
 10 files changed, 202 insertions(+), 49 deletions(-)
Signature · 1 line
2.25.1
author
#1 lore raw

PATCH v3 1/4 iommu/vt-d: Implement domain ops for attach_dev_pasid

On VT-d platforms with scalable mode enabled, devices issue DMA requests with PASID need to attach PASIDs to given IOMMU domains. The attach operation involves the following:
- Programming the PASID into the device's PASID table
- Tracking device domain and the PASID relationship
- Managing IOTLB and device TLB invalidations

This patch add attach_dev_pasid functions to the default domain ops which is used by DMA and identity domain types. It could be extended to support other domain types whenever necessary.

---
 drivers/iommu/intel/iommu.c | 81 ++++++++++++++++++++++++++++++++++++-
 include/linux/intel-iommu.h |  1 +
 2 files changed, 80 insertions(+), 2 deletions(-)
drivers/iommu/intel/iommu.c +79 -2
index a51b96fa9b3a..5408418f4f4b 100644
@@ -1562,6 +1562,10 @@ static void __iommu_flush_dev_iotlb(struct device_domain_info *info,
 
 	sid = info->bus << 8 | info->devfn;
 	qdep = info->ats_qdep;
+	if (info->pasid) {
+		qi_flush_dev_iotlb_pasid(info->iommu, sid, info->pfsid,
+					 info->pasid, qdep, addr, mask);
+	}
 	qi_flush_dev_iotlb(info->iommu, sid, info->pfsid,
 			   qdep, addr, mask);
 }
@@ -1591,6 +1595,7 @@ static void iommu_flush_iotlb_psi(struct intel_iommu *iommu,
 	unsigned int mask = ilog2(aligned_pages);
 	uint64_t addr = (uint64_t)pfn << VTD_PAGE_SHIFT;
 	u16 did = domain->iommu_did[iommu->seq_id];
+	struct iommu_domain *iommu_domain = &domain->domain;
 
 	BUG_ON(pages == 0);
 
@@ -1599,6 +1604,9 @@ static void iommu_flush_iotlb_psi(struct intel_iommu *iommu,
 
 	if (domain_use_first_level(domain)) {
 		qi_flush_piotlb(iommu, did, PASID_RID2PASID, addr, pages, ih);
+		/* flush additional kernel DMA PASIDs attached */
+		if (iommu_domain->pasid)
+			qi_flush_piotlb(iommu, did, iommu_domain->pasid, addr, pages, ih);
 	} else {
 		unsigned long bitmask = aligned_pages - 1;
 
@@ -4265,10 +4273,13 @@ static void __dmar_remove_one_dev_info(struct device_domain_info *info)
 	domain = info->domain;
 
 	if (info->dev && !dev_is_real_dma_subdevice(info->dev)) {
-		if (dev_is_pci(info->dev) && sm_supported(iommu))
+		if (dev_is_pci(info->dev) && sm_supported(iommu)) {
 			intel_pasid_tear_down_entry(iommu, info->dev,
 					PASID_RID2PASID, false);
-
+			if (info->pasid)
+				intel_pasid_tear_down_entry(iommu, info->dev,
+							    info->pasid, false);
+		}
 		iommu_disable_dev_iotlb(info);
 		domain_context_clear(info);
 		intel_pasid_free_table(info->dev);
@@ -4912,6 +4923,70 @@ static void intel_iommu_iotlb_sync_map(struct iommu_domain *domain,
 	}
 }
 
+static int intel_iommu_attach_dev_pasid(struct iommu_domain *domain,
+					struct device *dev,
+					ioasid_t pasid)
+{
+	struct device_domain_info *info = dev_iommu_priv_get(dev);
+	struct dmar_domain *dmar_domain = to_dmar_domain(domain);
+	struct intel_iommu *iommu = info->iommu;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!sm_supported(iommu) || !info)
+		return -ENODEV;
+
+	spin_lock_irqsave(&device_domain_lock, flags);
+	/*
+	 * If the same device already has a PASID attached, just return.
+	 * DMA layer will return the PASID value to the caller.
+	 */
+	if (pasid != PASID_RID2PASID && info->pasid) {
+		if (info->pasid == pasid)
+			ret = 0;
+		else {
+			dev_warn(dev, "Cannot attach PASID %u, %u already attached\n",
+				 pasid, info->pasid);
+			ret = -EBUSY;
+		}
+		goto out_unlock_domain;
+	}
+
+	spin_lock(&iommu->lock);
+	if (hw_pass_through && domain_type_is_si(dmar_domain))
+		ret = intel_pasid_setup_pass_through(iommu, dmar_domain,
+						     dev, pasid);
+	else if (domain_use_first_level(dmar_domain))
+		ret = domain_setup_first_level(iommu, dmar_domain,
+					       dev, pasid);
+	else
+		ret = intel_pasid_setup_second_level(iommu, dmar_domain,
+						     dev, pasid);
+
+	spin_unlock(&iommu->lock);
+out_unlock_domain:
+	spin_unlock_irqrestore(&device_domain_lock, flags);
+	if (!ret)
+		info->pasid = pasid;
+
+	return ret;
+}
+
+static void intel_iommu_detach_dev_pasid(struct iommu_domain *domain,
+					struct device *dev,
+					ioasid_t pasid)
+{
+	struct device_domain_info *info = dev_iommu_priv_get(dev);
+	struct intel_iommu *iommu = info->iommu;
+	unsigned long flags;
+
+	WARN_ON(info->pasid != pasid);
+	spin_lock_irqsave(&iommu->lock, flags);
+	intel_pasid_tear_down_entry(iommu, dev, pasid, false);
+	info->pasid = 0;
+	spin_unlock_irqrestore(&iommu->lock, flags);
+}
+
 const struct iommu_ops intel_iommu_ops = {
 	.capable		= intel_iommu_capable,
 	.domain_alloc		= intel_iommu_domain_alloc,
@@ -4940,6 +5015,8 @@ const struct iommu_ops intel_iommu_ops = {
 		.iova_to_phys		= intel_iommu_iova_to_phys,
 		.free			= intel_iommu_domain_free,
 		.enforce_cache_coherency = intel_iommu_enforce_cache_coherency,
+		.attach_dev_pasid	= intel_iommu_attach_dev_pasid,
+		.detach_dev_pasid	= intel_iommu_detach_dev_pasid,
 	}
 };
 
include/linux/intel-iommu.h +1 -0
index 5af24befc9f1..55845a8c4f4d 100644
@@ -627,6 +627,7 @@ struct device_domain_info {
 	struct intel_iommu *iommu; /* IOMMU used by this device */
 	struct dmar_domain *domain; /* pointer to domain */
 	struct pasid_table *pasid_table; /* pasid table */
+	ioasid_t pasid; /* DMA request with PASID */
 };
 
 static inline void __iommu_flush_cache(
Signature · 1 line
2.25.1
#2 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Quoted 9 lines · Jacob Pan
drivers/iommu/intel/iommu.c +9 -0
+static int intel_iommu_attach_dev_pasid(struct iommu_domain *domain,
+					struct device *dev,
+					ioasid_t pasid)
+{
+	struct device_domain_info *info = dev_iommu_priv_get(dev);
+	struct dmar_domain *dmar_domain = to_dmar_domain(domain);
+	struct intel_iommu *iommu = info->iommu;
+	unsigned long flags;
+	int ret = 0;

drivers/iommu/intel/iommu.c static void intel_iommu_iotlb_sync_map(struct iommu_domain *domain, · line 4944

Quoted 10 lines
drivers/iommu/intel/iommu.c +10 -0
+
+	if (!sm_supported(iommu) || !info)
+		return -ENODEV;
+
+	spin_lock_irqsave(&device_domain_lock, flags);
+	/*
+	 * If the same device already has a PASID attached, just return.
+	 * DMA layer will return the PASID value to the caller.
+	 */
+	if (pasid != PASID_RID2PASID && info->pasid) {

Why check for PASID == 0 like this? Shouldn't pasid == 0 be rejected as an invalid argument?

drivers/iommu/intel/iommu.c static void intel_iommu_iotlb_sync_map(struct iommu_domain *domain, · line 4946

Quoted 2 lines
drivers/iommu/intel/iommu.c +2 -0
+		if (info->pasid == pasid)
+			ret = 0;

Doesn't this need to check that the current domain is the requested domain as well? How can this happen anyhow - isn't it an error to double attach?

include/linux/intel-iommu.h struct device_domain_info { · line 630

Quoted 8 lines
include/linux/intel-iommu.h +2 -0
index 5af24befc9f1..55845a8c4f4d 100644
+++ b/include/linux/intel-iommu.h
@@ -627,6 +627,7 @@ struct device_domain_info {
 	struct intel_iommu *iommu; /* IOMMU used by this device */
 	struct dmar_domain *domain; /* pointer to domain */
 	struct pasid_table *pasid_table; /* pasid table */
+	ioasid_t pasid; /* DMA request with PASID */

And this seems wrong - the DMA API is not the only user of attach_dev_pasid, so there should not be any global pasid for the device.

I suspect this should be a counter of # of pasid domains attached so that the special flush logic triggers

And rely on the core code to worry about assigning only one domain per pasid - this should really be a 'set' function.

Jason

author
#3 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Hi Jason,

Quoted 16 lines · Jason Gunthorpe
Quoted 15 lines · Jacob Pan
drivers/iommu/intel/iommu.c +15 -0
+static int intel_iommu_attach_dev_pasid(struct iommu_domain *domain,
+					struct device *dev,
+					ioasid_t pasid)
+{
+	struct device_domain_info *info = dev_iommu_priv_get(dev);
+	struct dmar_domain *dmar_domain = to_dmar_domain(domain);
+	struct intel_iommu *iommu = info->iommu;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!sm_supported(iommu) || !info)
+		return -ENODEV;
+
+	spin_lock_irqsave(&device_domain_lock, flags);
+	/*
Quoted 8 lines

drivers/iommu/intel/iommu.c static void intel_iommu_iotlb_sync_map(struct iommu_domain *domain, · line 4944

Quoted 5 lines
(quoted diff) +1 -0
+	 * If the same device already has a PASID attached, just

return.

drivers/iommu/intel/iommu.c +3 -0
+	 * DMA layer will return the PASID value to the caller.
+	 */
+	if (pasid != PASID_RID2PASID && info->pasid) {  

Why check for PASID == 0 like this? Shouldn't pasid == 0 be rejected as an invalid argument?

Right, I was planning on reuse the attach function for RIDPASID as clean up, but didn't include here. Will fix.

Quoted 8 lines

drivers/iommu/intel/iommu.c static void intel_iommu_iotlb_sync_map(struct iommu_domain *domain, · line 4946

Quoted 3 lines
drivers/iommu/intel/iommu.c +2 -0
+		if (info->pasid == pasid)
+			ret = 0;  

Doesn't this need to check that the current domain is the requested domain as well? How can this happen anyhow - isn't it an error to double attach?

Quoted 13 lines

include/linux/intel-iommu.h struct device_domain_info { · line 630

Quoted 8 lines
include/linux/intel-iommu.h +2 -0
index 5af24befc9f1..55845a8c4f4d 100644
+++ b/include/linux/intel-iommu.h
@@ -627,6 +627,7 @@ struct device_domain_info {
 	struct intel_iommu *iommu; /* IOMMU used by this device */
 	struct dmar_domain *domain; /* pointer to domain */
 	struct pasid_table *pasid_table; /* pasid table */
+	ioasid_t pasid; /* DMA request with PASID */  

And this seems wrong - the DMA API is not the only user of attach_dev_pasid, so there should not be any global pasid for the device.

True but the attach_dev_pasid() op is domain type specific. i.e. DMA API has its own attach_dev_pasid which is different than sva domain attach_dev_pasid().
device_domain_info is only used by DMA API.

Quoted 3 lines

I suspect this should be a counter of # of pasid domains attached so that the special flush logic triggers

This field is only used for devTLB, so it is per domain-device. struct device_domain_info is allocated per device-domain as well. Sorry, I might have totally missed your point.

Quoted 3 lines

And rely on the core code to worry about assigning only one domain per pasid - this should really be a 'set' function.

Yes, in this set the core code (in dma-iommu.c) only assign one PASID per DMA domain type.

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

Thanks a lot for the quick review!

Jacob

#4 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Quoted 13 lines · Jacob Pan
Quoted 4 lines
Quoted 4 lines
include/linux/intel-iommu.h +1 -0
index 5af24befc9f1..55845a8c4f4d 100644
+++ b/include/linux/intel-iommu.h
@@ -627,6 +627,7 @@ struct device_domain_info {
Quoted 9 lines

include/linux/intel-iommu.h struct device_domain_info { · line 630

Quoted 4 lines
include/linux/intel-iommu.h +1 -0
 	struct intel_iommu *iommu; /* IOMMU used by this device */
 	struct dmar_domain *domain; /* pointer to domain */
 	struct pasid_table *pasid_table; /* pasid table */
+	ioasid_t pasid; /* DMA request with PASID */  

And this seems wrong - the DMA API is not the only user of attach_dev_pasid, so there should not be any global pasid for the device.

Quoted 3 lines

True but the attach_dev_pasid() op is domain type specific. i.e. DMA API has its own attach_dev_pasid which is different than sva domain attach_dev_pasid().

Huh? The intel driver shares the same ops between UNMANAGED and DMA - and in general I do not think we should be putting special knowledge about the DMA domains in the drivers. Drivers should continue to treat them identically to UNMANAGED.

Quoted 1 line

device_domain_info is only used by DMA API.

Huh?

Quoted 6 lines
Quoted 3 lines

I suspect this should be a counter of # of pasid domains attached so that the special flush logic triggers

This field is only used for devTLB, so it is per domain-device. struct device_domain_info is allocated per device-domain as well. Sorry, I might have totally missed your point.

You can't store a single pasid in the driver like this, since the only thing it does is trigger the flush logic just count how many pasids are used by the device-domain and trigger pasid flush if any pasids are attached

Quoted 8 lines
Quoted 2 lines

And rely on the core code to worry about assigning only one domain per pasid - this should really be a 'set' function.

Yes, in this set the core code (in dma-iommu.c) only assign one PASID per DMA domain type.

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

No that API is Ok - the driver ops API should be 'set' not attach/detach

Jason

author
#5 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Hi Jason,

Quoted 15 lines · Jason Gunthorpe
Quoted 13 lines · Jacob Pan
Quoted 13 lines

include/linux/intel-iommu.h struct device_domain_info { · line 630

Quoted 8 lines
(quoted diff) +0 -0

b/include/linux/intel-iommu.h index 5af24befc9f1..55845a8c4f4d 100644 +++ b/include/linux/intel-iommu.h

include/linux/intel-iommu.h +1 -0
@@ -627,6 +627,7 @@ struct device_domain_info {
 	struct intel_iommu *iommu; /* IOMMU used by this device */
 	struct dmar_domain *domain; /* pointer to domain */
 	struct pasid_table *pasid_table; /* pasid table */
+	ioasid_t pasid; /* DMA request with PASID */    

And this seems wrong - the DMA API is not the only user of attach_dev_pasid, so there should not be any global pasid for the device.

Quoted 9 lines
Quoted 3 lines

True but the attach_dev_pasid() op is domain type specific. i.e. DMA API has its own attach_dev_pasid which is different than sva domain attach_dev_pasid().

Huh? The intel driver shares the same ops between UNMANAGED and DMA - and in general I do not think we should be putting special knowledge about the DMA domains in the drivers. Drivers should continue to treat them identically to UNMANAGED.

OK, other than SVA domain, the rest domain types share the same default ops. I agree that the default ops should be the same for UNMANAGED, IDENTITY, and DMA domain types. Minor detail is that we need to treat IDENTITY domain slightly different when it comes down to PASID entry programming.

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Quoted 3 lines
Quoted 1 line

device_domain_info is only used by DMA API.

Huh?

My mistake, i meant the device_domain_info.pasid is only used by DMA API

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

I suspect this should be a counter of # of pasid domains attached so that the special flush logic triggers

Quoted 9 lines
Quoted 3 lines

This field is only used for devTLB, so it is per domain-device. struct device_domain_info is allocated per device-domain as well. Sorry, I might have totally missed your point.

You can't store a single pasid in the driver like this, since the only thing it does is trigger the flush logic just count how many pasids are used by the device-domain and trigger pasid flush if any pasids are attached

Got it, will put the pasids in an xa as described above.

Quoted 11 lines
Quoted 8 lines
Quoted 2 lines

And rely on the core code to worry about assigning only one domain per pasid - this should really be a 'set' function.

Yes, in this set the core code (in dma-iommu.c) only assign one PASID per DMA domain type.

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

No that API is Ok - the driver ops API should be 'set' not attach/detach

Sounds good, this operation has little in common with domain_ops.dev_attach_pasid() used by SVA domain. So I will add a new domain_ops.dev_set_pasid()

Thanks,

Jacob

#6 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Quoted 9 lines · Jacob Pan
Quoted 5 lines

Huh? The intel driver shares the same ops between UNMANAGED and DMA - and in general I do not think we should be putting special knowledge about the DMA domains in the drivers. Drivers should continue to treat them identically to UNMANAGED.

OK, other than SVA domain, the rest domain types share the same default ops. I agree that the default ops should be the same for UNMANAGED, IDENTITY, and DMA domain types. Minor detail is that we need to treat IDENTITY domain slightly different when it comes down to PASID entry programming.

I would be happy if IDENTITY had its own ops, if that makes sense

Quoted 3 lines

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Sort of, but we shouldn't duplicate xarrays - the group already has this xarray - need to find some way to allow access to it from the driver.

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

No that API is Ok - the driver ops API should be 'set' not attach/detach

Sounds good, this operation has little in common with domain_ops.dev_attach_pasid() used by SVA domain. So I will add a new domain_ops.dev_set_pasid()

What? No, their should only be one operation, 'dev_set_pasid' and it is exactly the same as the SVA operation. It configures things so that any existing translation on the PASID is removed and the PASID translates according to the given domain.

SVA given domain or UNMANAGED given domain doesn't matter to the higher level code. The driver should implement per-domain ops as required to get the different behaviors.

Jason
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

author
#7 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Hi Jason,

Quoted 7 lines · Jason Gunthorpe
Quoted 5 lines · Jacob Pan
Quoted 5 lines

Huh? The intel driver shares the same ops between UNMANAGED and DMA - and in general I do not think we should be putting special knowledge about the DMA domains in the drivers. Drivers should continue to treat them identically to UNMANAGED.

Quoted 8 lines
Quoted 5 lines

OK, other than SVA domain, the rest domain types share the same default ops. I agree that the default ops should be the same for UNMANAGED, IDENTITY, and DMA domain types. Minor detail is that we need to treat IDENTITY domain slightly different when it comes down to PASID entry programming.

I would be happy if IDENTITY had its own ops, if that makes sense

I have tried to have its own ops but there are complications around checking if a domain has ops. It would be a logic thing to clean up next.

Quoted 9 lines
Quoted 4 lines

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Sort of, but we shouldn't duplicate xarrays - the group already has this xarray - need to find some way to allow access to it from the driver.

I am not following, here are the PASIDs for devTLB flush which is per device. Why group?
We could retrieve PASIDs from the device PASID table but xa would be more efficient.

Quoted 9 lines
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

No that API is Ok - the driver ops API should be 'set' not attach/detach

Sounds good, this operation has little in common with domain_ops.dev_attach_pasid() used by SVA domain. So I will add a new domain_ops.dev_set_pasid()

Quoted 8 lines

What? No, their should only be one operation, 'dev_set_pasid' and it is exactly the same as the SVA operation. It configures things so that any existing translation on the PASID is removed and the PASID translates according to the given domain.

SVA given domain or UNMANAGED given domain doesn't matter to the higher level code. The driver should implement per-domain ops as required to get the different behaviors.

Perhaps some code to clarify, we have sva_domain_ops.dev_attach_pasid() = intel_svm_attach_dev_pasid; default_domain_ops.dev_attach_pasid() = intel_iommu_attach_dev_pasid;

Consolidate pasid programming into dev_set_pasid() then called by both intel_svm_attach_dev_pasid() and intel_iommu_attach_dev_pasid(), right?

Thanks,

Jacob

#8 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Quoted 11 lines · Jacob Pan
Quoted 9 lines
Quoted 4 lines

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Sort of, but we shouldn't duplicate xarrays - the group already has this xarray - need to find some way to allow access to it from the driver.

I am not following, here are the PASIDs for devTLB flush which is per device. Why group?

Because group is where the core code stores it.

Quoted 17 lines

We could retrieve PASIDs from the device PASID table but xa would be more efficient.

Quoted 14 lines
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

No that API is Ok - the driver ops API should be 'set' not attach/detach

Sounds good, this operation has little in common with domain_ops.dev_attach_pasid() used by SVA domain. So I will add a new domain_ops.dev_set_pasid()

What? No, their should only be one operation, 'dev_set_pasid' and it is exactly the same as the SVA operation. It configures things so that any existing translation on the PASID is removed and the PASID translates according to the given domain.

Quoted 6 lines
Quoted 3 lines

SVA given domain or UNMANAGED given domain doesn't matter to the higher level code. The driver should implement per-domain ops as required to get the different behaviors.

Perhaps some code to clarify, we have sva_domain_ops.dev_attach_pasid() = intel_svm_attach_dev_pasid; default_domain_ops.dev_attach_pasid() = intel_iommu_attach_dev_pasid;

Yes, keep that structure

Quoted 2 lines

Consolidate pasid programming into dev_set_pasid() then called by both intel_svm_attach_dev_pasid() and intel_iommu_attach_dev_pasid(), right?

I was only suggesting that really dev_attach_pasid() op is misnamed, it should be called set_dev_pasid() and act like a set, not a paired attach/detach - same as the non-PASID ops.

Jason

author
#9 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Hi Jason,

Quoted 6 lines · Jason Gunthorpe
Quoted 5 lines · Jacob Pan
Quoted 5 lines
Quoted 4 lines

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Quoted 8 lines
Quoted 6 lines
Quoted 4 lines

Sort of, but we shouldn't duplicate xarrays - the group already has this xarray - need to find some way to allow access to it from the driver.

I am not following, here are the PASIDs for devTLB flush which is per device. Why group?

Because group is where the core code stores it.

I see, with singleton group. I guess I can let dma-iommu code call

iommu_attach_dma_pasid {
	iommu_attach_device_pasid();

Then the PASID will be stored in the group xa.
The flush code can retrieve PASIDs from device_domain_info.device -> group -> pasid_array.
Thanks for pointing it out, I missed the new pasid_array.

Quoted 27 lines
Quoted 18 lines

We could retrieve PASIDs from the device PASID table but xa would be more efficient.

Quoted 14 lines
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

No that API is Ok - the driver ops API should be 'set' not attach/detach

Sounds good, this operation has little in common with domain_ops.dev_attach_pasid() used by SVA domain. So I will add a new domain_ops.dev_set_pasid()

What? No, their should only be one operation, 'dev_set_pasid' and it is exactly the same as the SVA operation. It configures things so that any existing translation on the PASID is removed and the PASID translates according to the given domain.

Quoted 6 lines
Quoted 3 lines

SVA given domain or UNMANAGED given domain doesn't matter to the higher level code. The driver should implement per-domain ops as required to get the different behaviors.

Perhaps some code to clarify, we have sva_domain_ops.dev_attach_pasid() = intel_svm_attach_dev_pasid; default_domain_ops.dev_attach_pasid() = intel_iommu_attach_dev_pasid;

Yes, keep that structure

Quoted 8 lines
Quoted 3 lines

Consolidate pasid programming into dev_set_pasid() then called by both intel_svm_attach_dev_pasid() and intel_iommu_attach_dev_pasid(), right?

I was only suggesting that really dev_attach_pasid() op is misnamed, it should be called set_dev_pasid() and act like a set, not a paired attach/detach - same as the non-PASID ops.

Got it. Perhaps another patch to rename, Baolu?

Thanks,

Jacob

#10 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Quoted 17 lines · Jacob Pan

Hi Jason,

Quoted 13 lines · Jason Gunthorpe
Quoted 11 lines · Jacob Pan
Quoted 9 lines
Quoted 4 lines

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Sort of, but we shouldn't duplicate xarrays - the group already has this xarray - need to find some way to allow access to it from the driver.

I am not following, here are the PASIDs for devTLB flush which is per device. Why group?

Quoted 6 lines
Quoted 1 line

Because group is where the core code stores it.

I see, with singleton group. I guess I can let dma-iommu code call

iommu_attach_dma_pasid {
	iommu_attach_device_pasid();

Then the PASID will be stored in the group xa.

Yes, again, the dma-iommu should not be any different from the normal unmanaged path. At this point there is no longer any difference, we should not invent new ones.

Quoted 3 lines

The flush code can retrieve PASIDs from device_domain_info.device -> group -> pasid_array. Thanks for pointing it out, I missed the new pasid_array.

Yes.. It seems inefficient to iterate over that xarray multiple times on the flush hot path, but maybe there is little choice. Try to use use the xas iterators under the xa_lock spinlock..

The challenge will be accessing the group xa in the first place, but maybe the core code can gain a function call to return a pointer to that XA or something..

Jason

author
#11 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Hi Jason,

Quoted 29 lines · Jason Gunthorpe
Quoted 17 lines · Jacob Pan

Hi Jason,

Quoted 13 lines · Jason Gunthorpe
Quoted 11 lines · Jacob Pan
Quoted 9 lines
Quoted 4 lines

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Sort of, but we shouldn't duplicate xarrays - the group already has this xarray - need to find some way to allow access to it from the driver.

I am not following, here are the PASIDs for devTLB flush which is per device. Why group?

Quoted 6 lines
Quoted 1 line

Because group is where the core code stores it.

I see, with singleton group. I guess I can let dma-iommu code call

iommu_attach_dma_pasid {
	iommu_attach_device_pasid();

Then the PASID will be stored in the group xa.

Yes, again, the dma-iommu should not be any different from the normal unmanaged path. At this point there is no longer any difference, we should not invent new ones.

Quoted 8 lines
Quoted 3 lines

The flush code can retrieve PASIDs from device_domain_info.device -> group -> pasid_array. Thanks for pointing it out, I missed the new pasid_array.

Yes.. It seems inefficient to iterate over that xarray multiple times on the flush hot path, but maybe there is little choice. Try to use use the xas iterators under the xa_lock spinlock..

xas_for_each takes a max range, here we don't really have one. So I posted v4 w/o using the xas advanced API. Please let me know if you have suggestions.
xa_for_each takes RCU read lock, it should be fast for tlb flush, right? The worst case maybe over flush when we have stale data but should be very rare.

Quoted 4 lines

The challenge will be accessing the group xa in the first place, but maybe the core code can gain a function call to return a pointer to that XA or something..

I added a helper function to find the matching DMA API PASID in v4.

Thanks,

Jacob
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

#12 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Quoted 7 lines · Jacob Pan
Quoted 4 lines

Yes.. It seems inefficient to iterate over that xarray multiple times on the flush hot path, but maybe there is little choice. Try to use use the xas iterators under the xa_lock spinlock..

xas_for_each takes a max range, here we don't really have one. So I posted v4 w/o using the xas advanced API. Please let me know if you have suggestions.

You are supposed to use ULONG_MAX in cases like that.

Quoted 2 lines

xa_for_each takes RCU read lock, it should be fast for tlb flush, right? The worst case maybe over flush when we have stale data but should be very rare.

Not really, xa_for_each walks the tree for every iteration, it is slower than a linked list walk in any cases where the xarray is multi-node. xas_for_each is able to retain a pointer where it is in the tree so each iteration is usually just a pointer increment.

The downside is you cannot sleep while doing xas_for_each

Quoted 5 lines
Quoted 3 lines

The challenge will be accessing the group xa in the first place, but maybe the core code can gain a function call to return a pointer to that XA or something..

I added a helper function to find the matching DMA API PASID in v4.

Again, why are we focused on DMA API? Nothing you build here should be DMA API beyond the fact that the iommu_domain being attached is the default domain.

Jason

author
#13 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Hi Jason,

Quoted 6 lines · Jason Gunthorpe
Quoted 4 lines · Jacob Pan
Quoted 4 lines

Yes.. It seems inefficient to iterate over that xarray multiple times on the flush hot path, but maybe there is little choice. Try to use use the xas iterators under the xa_lock spinlock..

Quoted 6 lines
Quoted 3 lines

xas_for_each takes a max range, here we don't really have one. So I posted v4 w/o using the xas advanced API. Please let me know if you have suggestions.

You are supposed to use ULONG_MAX in cases like that.

got it.

Quoted 9 lines
Quoted 3 lines

xa_for_each takes RCU read lock, it should be fast for tlb flush, right? The worst case maybe over flush when we have stale data but should be very rare.

Not really, xa_for_each walks the tree for every iteration, it is slower than a linked list walk in any cases where the xarray is multi-node. xas_for_each is able to retain a pointer where it is in the tree so each iteration is usually just a pointer increment.

Thanks for explaining, yeah if we have to iterate multiple times xas_for_each() is better.

Quoted 2 lines

The downside is you cannot sleep while doing xas_for_each

will do under RCU read lock

Quoted 9 lines
Quoted 5 lines
Quoted 3 lines

The challenge will be accessing the group xa in the first place, but maybe the core code can gain a function call to return a pointer to that XA or something..

I added a helper function to find the matching DMA API PASID in v4.

Again, why are we focused on DMA API? Nothing you build here should be DMA API beyond the fact that the iommu_domain being attached is the default domain.

The helper is not DMA API specific. Just a domain-PASID look up. Sorry for the confusion.

Thanks,

Jacob
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

maintainer
#14 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

In reply to Jacob Pan
Quoted 57 lines · Jacob Pan

Hi Jason,

Quoted 6 lines · Jason Gunthorpe
Quoted 5 lines · Jacob Pan
Quoted 5 lines
Quoted 4 lines

If not global, perhaps we could have a list of pasids (e.g. xarray) attached to the device_domain_info. The TLB flush logic would just go through the list w/o caring what the PASIDs are for. Does it make sense to you?

Quoted 8 lines
Quoted 6 lines
Quoted 4 lines

Sort of, but we shouldn't duplicate xarrays - the group already has this xarray - need to find some way to allow access to it from the driver.

I am not following, here are the PASIDs for devTLB flush which is per device. Why group?

Because group is where the core code stores it.

I see, with singleton group. I guess I can let dma-iommu code call

iommu_attach_dma_pasid {
	iommu_attach_device_pasid();

Then the PASID will be stored in the group xa.
The flush code can retrieve PASIDs from device_domain_info.device -> group -> pasid_array.
Thanks for pointing it out, I missed the new pasid_array.

Quoted 31 lines
Quoted 18 lines

We could retrieve PASIDs from the device PASID table but xa would be more efficient.

Quoted 14 lines
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

Are you suggesting the dma-iommu API should be called iommu_set_dma_pasid instead of iommu_attach_dma_pasid?

No that API is Ok - the driver ops API should be 'set' not attach/detach

Sounds good, this operation has little in common with domain_ops.dev_attach_pasid() used by SVA domain. So I will add a new domain_ops.dev_set_pasid()

What? No, their should only be one operation, 'dev_set_pasid' and it is exactly the same as the SVA operation. It configures things so that any existing translation on the PASID is removed and the PASID translates according to the given domain.

Quoted 6 lines
Quoted 3 lines

SVA given domain or UNMANAGED given domain doesn't matter to the higher level code. The driver should implement per-domain ops as required to get the different behaviors.

Perhaps some code to clarify, we have sva_domain_ops.dev_attach_pasid() = intel_svm_attach_dev_pasid; default_domain_ops.dev_attach_pasid() = intel_iommu_attach_dev_pasid;

Yes, keep that structure

Quoted 3 lines

Consolidate pasid programming into dev_set_pasid() then called by both intel_svm_attach_dev_pasid() and intel_iommu_attach_dev_pasid(), right?

Quoted 5 lines
Quoted 4 lines

I was only suggesting that really dev_attach_pasid() op is misnamed, it should be called set_dev_pasid() and act like a set, not a paired attach/detach - same as the non-PASID ops.

Got it. Perhaps another patch to rename, Baolu?

Yes. I can rename it in my sva series if others are also happy with this naming.

Best regards,
baolu

maintainer
#15 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

In reply to Jason Gunthorpe

Hi Jason,

Quoted 5 lines · Jason Gunthorpe
Quoted 2 lines

Consolidate pasid programming into dev_set_pasid() then called by both intel_svm_attach_dev_pasid() and intel_iommu_attach_dev_pasid(), right?

I was only suggesting that really dev_attach_pasid() op is misnamed, it should be called set_dev_pasid() and act like a set, not a paired attach/detach - same as the non-PASID ops.

So,

"set_dev_pasid(domain, device, pasid)" equals to dev_attach_pasid()

and

"set_dev_pasid(NULL, device, pasid)" equals to dev_detach_pasid()?

do I understand it right?

Best regards,
baolu

#16 lore raw

Re: [PATCH v3 1/4] iommu/vt-d: Implement domain ops for attach_dev_pasid

Quoted 11 lines · Baolu Lu

Hi Jason,

Quoted 5 lines · Jason Gunthorpe
Quoted 2 lines

Consolidate pasid programming into dev_set_pasid() then called by both intel_svm_attach_dev_pasid() and intel_iommu_attach_dev_pasid(), right?

I was only suggesting that really dev_attach_pasid() op is misnamed, it should be called set_dev_pasid() and act like a set, not a paired attach/detach - same as the non-PASID ops.

So,

Quoted 7 lines

"set_dev_pasid(domain, device, pasid)" equals to dev_attach_pasid()

and

"set_dev_pasid(NULL, device, pasid)" equals to dev_detach_pasid()?

do I understand it right?

blocking_domain should be passed, not null

Jason
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

author
#17 lore raw

PATCH v3 2/4 iommu: Add PASID support for DMA mapping API users

In reply to Jacob Pan

DMA mapping API is the de facto standard for in-kernel DMA. It operates on a per device/RID basis which is not PASID-aware.

Some modern devices such as Intel Data Streaming Accelerator, PASID is required for certain work submissions. To allow such devices use DMA mapping API, we need the following functionalities:
1. Provide device a way to retrieve a PASID for work submission within the kernel
2. Enable the kernel PASID on the IOMMU for the device
3. Attach the kernel PASID to the device's default DMA domain, let it be IOVA or physical address in case of pass-through.

This patch introduces a driver facing API that enables DMA API PASID usage. Once enabled, device drivers can continue to use DMA APIs as is. There is no difference in dma_handle between without PASID and with PASID.

---
 drivers/iommu/dma-iommu.c | 107 ++++++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h |   3 ++
 include/linux/iommu.h     |   2 +
 3 files changed, 112 insertions(+)
drivers/iommu/dma-iommu.c +107 -0
index 1ca85d37eeab..5984f3129fa2 100644
@@ -34,6 +34,8 @@ struct iommu_dma_msi_page {
 	phys_addr_t		phys;
 };
 
+static DECLARE_IOASID_SET(iommu_dma_pasid);
+
 enum iommu_dma_cookie_type {
 	IOMMU_DMA_IOVA_COOKIE,
 	IOMMU_DMA_MSI_COOKIE,
@@ -370,6 +372,111 @@ void iommu_put_dma_cookie(struct iommu_domain *domain)
 	domain->iova_cookie = NULL;
 }
 
+/**
+ * iommu_attach_dma_pasid --Attach a PASID for in-kernel DMA. Use the device's
+ * DMA domain.
+ * @dev: Device to be enabled
+ * @pasid: The returned kernel PASID to be used for DMA
+ *
+ * DMA request with PASID will be mapped the same way as the legacy DMA.
+ * If the device is in pass-through, PASID will also pass-through. If the
+ * device is in IOVA, the PASID will point to the same IOVA page table.
+ *
+ * @return err code or 0 on success
+ */
+int iommu_attach_dma_pasid(struct device *dev, ioasid_t *pasid)
+{
+	struct iommu_domain *dom;
+	ioasid_t id, max;
+	int ret = 0;
+
+	dom = iommu_get_domain_for_dev(dev);
+	if (!dom || !dom->ops || !dom->ops->attach_dev_pasid)
+		return -ENODEV;
+
+	/* Only support domain types that DMA API can be used */
+	if (dom->type == IOMMU_DOMAIN_UNMANAGED ||
+	    dom->type == IOMMU_DOMAIN_BLOCKED) {
+		dev_warn(dev, "Invalid domain type %d", dom->type);
+		return -EPERM;
+	}
+
+	id = dom->pasid;
+	if (!id) {
+		/*
+		 * First device to use PASID in its DMA domain, allocate
+		 * a single PASID per DMA domain is all we need, it is also
+		 * good for performance when it comes down to IOTLB flush.
+		 */
+		max = 1U << dev->iommu->pasid_bits;
+		if (!max)
+			return -EINVAL;
+
+		id = ioasid_alloc(&iommu_dma_pasid, 1, max, dev);
+		if (id == INVALID_IOASID)
+			return -ENOMEM;
+
+		dom->pasid = id;
+		atomic_set(&dom->pasid_users, 1);
+	}
+
+	ret = dom->ops->attach_dev_pasid(dom, dev, id);
+	if (!ret) {
+		*pasid = id;
+		atomic_inc(&dom->pasid_users);
+		return 0;
+	}
+
+	if (atomic_dec_and_test(&dom->pasid_users)) {
+		ioasid_free(id);
+		dom->pasid = 0;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(iommu_attach_dma_pasid);
+
+/**
+ * iommu_detach_dma_pasid --Disable in-kernel DMA request with PASID
+ * @dev:	Device's PASID DMA to be disabled
+ *
+ * It is the device driver's responsibility to ensure no more incoming DMA
+ * requests with the kernel PASID before calling this function. IOMMU driver
+ * ensures PASID cache, IOTLBs related to the kernel PASID are cleared and
+ * drained.
+ *
+ */
+void iommu_detach_dma_pasid(struct device *dev)
+{
+	struct iommu_domain *dom;
+	ioasid_t pasid;
+
+	dom = iommu_get_domain_for_dev(dev);
+	if (!dom || !dom->ops || !dom->ops->detach_dev_pasid) {
+		dev_warn(dev, "No ops for detaching PASID %u", pasid);
+		return;
+	}
+	/* Only support DMA API managed domain type */
+	if (dom->type == IOMMU_DOMAIN_UNMANAGED ||
+	    dom->type == IOMMU_DOMAIN_BLOCKED) {
+		dev_err(dev, "Invalid domain type %d to detach DMA PASID %u\n",
+			 dom->type, pasid);
+		return;
+	}
+
+	pasid = dom->pasid;
+	if (!pasid) {
+		dev_err(dev, "No DMA PASID attached\n");
+		return;
+	}
+	dom->ops->detach_dev_pasid(dom, dev, pasid);
+	if (atomic_dec_and_test(&dom->pasid_users)) {
+		ioasid_free(pasid);
+		dom->pasid = 0;
+	}
+}
+EXPORT_SYMBOL(iommu_detach_dma_pasid);
+
 /**
  * iommu_dma_get_resv_regions - Reserved region driver helper
  * @dev: Device from iommu_get_resv_regions()
include/linux/dma-iommu.h +3 -0
index 24607dc3c2ac..538650b9cb75 100644
@@ -18,6 +18,9 @@ int iommu_get_dma_cookie(struct iommu_domain *domain);
 int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base);
 void iommu_put_dma_cookie(struct iommu_domain *domain);
 
+int iommu_attach_dma_pasid(struct device *dev, ioasid_t *pasid);
+void iommu_detach_dma_pasid(struct device *dev);
+
 /* Setup call for arch DMA mapping code */
 void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit);
 int iommu_dma_init_fq(struct iommu_domain *domain);
include/linux/iommu.h +2 -0
index 1164524814cb..281a87fdce77 100644
@@ -105,6 +105,8 @@ struct iommu_domain {
 	enum iommu_page_response_code (*iopf_handler)(struct iommu_fault *fault,
 						      void *data);
 	void *fault_data;
+	ioasid_t pasid;		/* Used for DMA requests with PASID */
+	atomic_t pasid_users;
 };
 
 static inline bool iommu_is_dma_domain(struct iommu_domain *domain)
Signature · 1 line
2.25.1
#18 lore raw

Re: [PATCH v3 2/4] iommu: Add PASID support for DMA mapping API users

Quoted 55 lines · Jacob Pan

DMA mapping API is the de facto standard for in-kernel DMA. It operates on a per device/RID basis which is not PASID-aware.

Some modern devices such as Intel Data Streaming Accelerator, PASID is required for certain work submissions. To allow such devices use DMA mapping API, we need the following functionalities:
1. Provide device a way to retrieve a PASID for work submission within the kernel
2. Enable the kernel PASID on the IOMMU for the device
3. Attach the kernel PASID to the device's default DMA domain, let it be IOVA or physical address in case of pass-through.

This patch introduces a driver facing API that enables DMA API PASID usage. Once enabled, device drivers can continue to use DMA APIs as is. There is no difference in dma_handle between without PASID and with PASID.

 drivers/iommu/dma-iommu.c | 107 ++++++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h |   3 ++
 include/linux/iommu.h     |   2 +
 3 files changed, 112 insertions(+)
drivers/iommu/dma-iommu.c +19 -0
index 1ca85d37eeab..5984f3129fa2 100644
+++ b/drivers/iommu/dma-iommu.c
@@ -34,6 +34,8 @@ struct iommu_dma_msi_page {
 	phys_addr_t		phys;
 };
 
+static DECLARE_IOASID_SET(iommu_dma_pasid);
+
 enum iommu_dma_cookie_type {
 	IOMMU_DMA_IOVA_COOKIE,
 	IOMMU_DMA_MSI_COOKIE,
@@ -370,6 +372,111 @@ void iommu_put_dma_cookie(struct iommu_domain *domain)
 	domain->iova_cookie = NULL;
 }
 
+/**
+ * iommu_attach_dma_pasid --Attach a PASID for in-kernel DMA. Use the device's
+ * DMA domain.
+ * @dev: Device to be enabled
+ * @pasid: The returned kernel PASID to be used for DMA
+ *
+ * DMA request with PASID will be mapped the same way as the legacy DMA.
+ * If the device is in pass-through, PASID will also pass-through. If the
+ * device is in IOVA, the PASID will point to the same IOVA page table.
+ *
+ * @return err code or 0 on success
+ */
+int iommu_attach_dma_pasid(struct device *dev, ioasid_t *pasid)
+{
+	struct iommu_domain *dom;
+	ioasid_t id, max;

drivers/iommu/dma-iommu.c void iommu_put_dma_cookie(struct iommu_domain *domain) · line 400

Quoted 10 lines
drivers/iommu/dma-iommu.c +10 -0
+	int ret = 0;
+
+	dom = iommu_get_domain_for_dev(dev);
+	if (!dom || !dom->ops || !dom->ops->attach_dev_pasid)
+		return -ENODEV;
+
+	/* Only support domain types that DMA API can be used */
+	if (dom->type == IOMMU_DOMAIN_UNMANAGED ||
+	    dom->type == IOMMU_DOMAIN_BLOCKED) {
+		dev_warn(dev, "Invalid domain type %d", dom->type);

This should be a WARN_ON

Quoted 10 lines
drivers/iommu/dma-iommu.c +10 -0
+		return -EPERM;
+	}
+
+	id = dom->pasid;
+	if (!id) {
+		/*
+		 * First device to use PASID in its DMA domain, allocate
+		 * a single PASID per DMA domain is all we need, it is also
+		 * good for performance when it comes down to IOTLB flush.
+		 */

drivers/iommu/dma-iommu.c void iommu_put_dma_cookie(struct iommu_domain *domain) · line 420

Quoted 10 lines
drivers/iommu/dma-iommu.c +10 -0
+		max = 1U << dev->iommu->pasid_bits;
+		if (!max)
+			return -EINVAL;
+
+		id = ioasid_alloc(&iommu_dma_pasid, 1, max, dev);
+		if (id == INVALID_IOASID)
+			return -ENOMEM;
+
+		dom->pasid = id;
+		atomic_set(&dom->pasid_users, 1);

All of this needs proper locking.

Quoted 40 lines
drivers/iommu/dma-iommu.c +40 -0
+	}
+
+	ret = dom->ops->attach_dev_pasid(dom, dev, id);
+	if (!ret) {
+		*pasid = id;
+		atomic_inc(&dom->pasid_users);
+		return 0;
+	}
+
+	if (atomic_dec_and_test(&dom->pasid_users)) {
+		ioasid_free(id);
+		dom->pasid = 0;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(iommu_attach_dma_pasid);
+
+/**
+ * iommu_detach_dma_pasid --Disable in-kernel DMA request with PASID
+ * @dev:	Device's PASID DMA to be disabled
+ *
+ * It is the device driver's responsibility to ensure no more incoming DMA
+ * requests with the kernel PASID before calling this function. IOMMU driver
+ * ensures PASID cache, IOTLBs related to the kernel PASID are cleared and
+ * drained.
+ *
+ */
+void iommu_detach_dma_pasid(struct device *dev)
+{
+	struct iommu_domain *dom;
+	ioasid_t pasid;
+
+	dom = iommu_get_domain_for_dev(dev);
+	if (!dom || !dom->ops || !dom->ops->detach_dev_pasid) {
+		dev_warn(dev, "No ops for detaching PASID %u", pasid);
+		return;
+	}
+	/* Only support DMA API managed domain type */
+	if (dom->type == IOMMU_DOMAIN_UNMANAGED ||

drivers/iommu/dma-iommu.c void iommu_put_dma_cookie(struct iommu_domain *domain) · line 471

Quoted 10 lines
drivers/iommu/dma-iommu.c +10 -0
+	    dom->type == IOMMU_DOMAIN_BLOCKED) {
+		dev_err(dev, "Invalid domain type %d to detach DMA PASID %u\n",
+			 dom->type, pasid);
+		return;
+	}
+	pasid = dom->pasid;
+	if (!pasid) {
+		dev_err(dev, "No DMA PASID attached\n");
+		return;
+	}

All WARN_ON's too

Quoted 28 lines
drivers/iommu/dma-iommu.c +8 -0
+	dom->ops->detach_dev_pasid(dom, dev, pasid);
+	if (atomic_dec_and_test(&dom->pasid_users)) {
+		ioasid_free(pasid);
+		dom->pasid = 0;
+	}
+}
+EXPORT_SYMBOL(iommu_detach_dma_pasid);
+
 /**
  * iommu_dma_get_resv_regions - Reserved region driver helper
  * @dev: Device from iommu_get_resv_regions()
include/linux/dma-iommu.h +4 -0
index 24607dc3c2ac..538650b9cb75 100644
+++ b/include/linux/dma-iommu.h
@@ -18,6 +18,9 @@ int iommu_get_dma_cookie(struct iommu_domain *domain);
 int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base);
 void iommu_put_dma_cookie(struct iommu_domain *domain);
 
+int iommu_attach_dma_pasid(struct device *dev, ioasid_t *pasid);
+void iommu_detach_dma_pasid(struct device *dev);
+
 /* Setup call for arch DMA mapping code */
 void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit);
 int iommu_dma_init_fq(struct iommu_domain *domain);
include/linux/iommu.h +1 -0
index 1164524814cb..281a87fdce77 100644
+++ b/include/linux/iommu.h

include/linux/iommu.h struct iommu_domain { · line 109

Quoted 5 lines
include/linux/iommu.h +2 -0
@@ -105,6 +105,8 @@ struct iommu_domain {
 	enum iommu_page_response_code (*iopf_handler)(struct iommu_fault *fault,
 						      void *data);
 	void *fault_data;
+	ioasid_t pasid;		/* Used for DMA requests with PASID */
+	atomic_t pasid_users;

These are poorly named, this is really the DMA API global PASID and shouldn't be used for other things.

Jason
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

author
#19 lore raw

Re: [PATCH v3 2/4] iommu: Add PASID support for DMA mapping API users

Hi Jason,

Quoted 63 lines · Jason Gunthorpe
Quoted 62 lines · Jacob Pan

DMA mapping API is the de facto standard for in-kernel DMA. It operates on a per device/RID basis which is not PASID-aware.

Some modern devices such as Intel Data Streaming Accelerator, PASID is required for certain work submissions. To allow such devices use DMA mapping API, we need the following functionalities:
1. Provide device a way to retrieve a PASID for work submission within the kernel
2. Enable the kernel PASID on the IOMMU for the device
3. Attach the kernel PASID to the device's default DMA domain, let it be IOVA or physical address in case of pass-through.

This patch introduces a driver facing API that enables DMA API PASID usage. Once enabled, device drivers can continue to use DMA APIs as is. There is no difference in dma_handle between without PASID and with PASID.

 drivers/iommu/dma-iommu.c | 107 ++++++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h |   3 ++
 include/linux/iommu.h     |   2 +
 3 files changed, 112 insertions(+)
drivers/iommu/dma-iommu.c +5 -0
index 1ca85d37eeab..5984f3129fa2 100644
+++ b/drivers/iommu/dma-iommu.c
@@ -34,6 +34,8 @@ struct iommu_dma_msi_page {
 	phys_addr_t		phys;
 };
 
+static DECLARE_IOASID_SET(iommu_dma_pasid);
+
 enum iommu_dma_cookie_type {
 	IOMMU_DMA_IOVA_COOKIE,
 	IOMMU_DMA_MSI_COOKIE,
@@ -370,6 +372,111 @@ void iommu_put_dma_cookie(struct iommu_domain
 *domain) domain->iova_cookie = NULL;
 }
 
+/**
+ * iommu_attach_dma_pasid --Attach a PASID for in-kernel DMA. Use the

device's
+ * DMA domain.
+ * @dev: Device to be enabled
+ * @pasid: The returned kernel PASID to be used for DMA
+ *
+ * DMA request with PASID will be mapped the same way as the legacy DMA.
+ * If the device is in pass-through, PASID will also pass-through. If the
+ * device is in IOVA, the PASID will point to the same IOVA page table.
+ *
+ * @return err code or 0 on success
+ */

drivers/iommu/dma-iommu.c +8 -0
+int iommu_attach_dma_pasid(struct device *dev, ioasid_t *pasid)
+{
+	struct iommu_domain *dom;
+	ioasid_t id, max;
+	int ret = 0;
+
+	dom = iommu_get_domain_for_dev(dev);
+	if (!dom || !dom->ops || !dom->ops->attach_dev_pasid)
Quoted 9 lines

drivers/iommu/dma-iommu.c void iommu_put_dma_cookie(struct iommu_domain *domain) · line 400

Quoted 6 lines
drivers/iommu/dma-iommu.c +6 -0
+		return -ENODEV;
+
+	/* Only support domain types that DMA API can be used */
+	if (dom->type == IOMMU_DOMAIN_UNMANAGED ||
+	    dom->type == IOMMU_DOMAIN_BLOCKED) {
+		dev_warn(dev, "Invalid domain type %d", dom->type);  

This should be a WARN_ON

will do, thanks

Quoted 17 lines
Quoted 17 lines
drivers/iommu/dma-iommu.c +7 -0
+		return -EPERM;
+	}
+
+	id = dom->pasid;
+	if (!id) {
+		/*
+		 * First device to use PASID in its DMA domain,

allocate

drivers/iommu/dma-iommu.c +1 -0
+		 * a single PASID per DMA domain is all we need, it is

also

drivers/iommu/dma-iommu.c +1 -0
+		 * good for performance when it comes down to IOTLB

flush.

drivers/iommu/dma-iommu.c +5 -0
+		 */
+		max = 1U << dev->iommu->pasid_bits;
+		if (!max)
+			return -EINVAL;
+
Quoted 9 lines

drivers/iommu/dma-iommu.c void iommu_put_dma_cookie(struct iommu_domain *domain) · line 420

Quoted 6 lines
drivers/iommu/dma-iommu.c +6 -0
+		id = ioasid_alloc(&iommu_dma_pasid, 1, max, dev);
+		if (id == INVALID_IOASID)
+			return -ENOMEM;
+
+		dom->pasid = id;
+		atomic_set(&dom->pasid_users, 1);  

All of this needs proper locking.

good catch, will add a mutex for domain updates, detach as well.

Quoted 48 lines
Quoted 48 lines
drivers/iommu/dma-iommu.c +23 -0
+	}
+
+	ret = dom->ops->attach_dev_pasid(dom, dev, id);
+	if (!ret) {
+		*pasid = id;
+		atomic_inc(&dom->pasid_users);
+		return 0;
+	}
+
+	if (atomic_dec_and_test(&dom->pasid_users)) {
+		ioasid_free(id);
+		dom->pasid = 0;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(iommu_attach_dma_pasid);
+
+/**
+ * iommu_detach_dma_pasid --Disable in-kernel DMA request with PASID
+ * @dev:	Device's PASID DMA to be disabled
+ *
+ * It is the device driver's responsibility to ensure no more incoming

DMA
+ * requests with the kernel PASID before calling this function. IOMMU driver
+ * ensures PASID cache, IOTLBs related to the kernel PASID are cleared and
+ * drained.
+ *
+ */

drivers/iommu/dma-iommu.c +14 -0
+void iommu_detach_dma_pasid(struct device *dev)
+{
+	struct iommu_domain *dom;
+	ioasid_t pasid;
+
+	dom = iommu_get_domain_for_dev(dev);
+	if (!dom || !dom->ops || !dom->ops->detach_dev_pasid) {
+		dev_warn(dev, "No ops for detaching PASID %u", pasid);
+		return;
+	}
+	/* Only support DMA API managed domain type */
+	if (dom->type == IOMMU_DOMAIN_UNMANAGED ||
+	    dom->type == IOMMU_DOMAIN_BLOCKED) {
+		dev_err(dev, "Invalid domain type %d to detach DMA

PASID %u\n",

drivers/iommu/dma-iommu.c +2 -0
+			 dom->type, pasid);
+		return;
Quoted 9 lines

drivers/iommu/dma-iommu.c void iommu_put_dma_cookie(struct iommu_domain *domain) · line 471

Quoted 6 lines
drivers/iommu/dma-iommu.c +6 -0
+	}
+	pasid = dom->pasid;
+	if (!pasid) {
+		dev_err(dev, "No DMA PASID attached\n");
+		return;
+	}  

All WARN_ON's too

will do.

Quoted 29 lines
Quoted 29 lines
drivers/iommu/dma-iommu.c +8 -0
+	dom->ops->detach_dev_pasid(dom, dev, pasid);
+	if (atomic_dec_and_test(&dom->pasid_users)) {
+		ioasid_free(pasid);
+		dom->pasid = 0;
+	}
+}
+EXPORT_SYMBOL(iommu_detach_dma_pasid);
+
 /**
  * iommu_dma_get_resv_regions - Reserved region driver helper
  * @dev: Device from iommu_get_resv_regions()
include/linux/dma-iommu.h +4 -0
index 24607dc3c2ac..538650b9cb75 100644
+++ b/include/linux/dma-iommu.h
@@ -18,6 +18,9 @@ int iommu_get_dma_cookie(struct iommu_domain *domain);
 int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base);
 void iommu_put_dma_cookie(struct iommu_domain *domain);
 
+int iommu_attach_dma_pasid(struct device *dev, ioasid_t *pasid);
+void iommu_detach_dma_pasid(struct device *dev);
+
 /* Setup call for arch DMA mapping code */
 void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64
 dma_limit); int iommu_dma_init_fq(struct iommu_domain *domain);
include/linux/iommu.h +1 -0
index 1164524814cb..281a87fdce77 100644
+++ b/include/linux/iommu.h
@@ -105,6 +105,8 @@ struct iommu_domain {
 	enum iommu_page_response_code (*iopf_handler)(struct
Quoted 9 lines

include/linux/iommu.h struct iommu_domain { · line 109

Quoted 5 lines
include/linux/iommu.h +1 -0
 iommu_fault *fault, void *data);
 	void *fault_data;
+	ioasid_t pasid;		/* Used for DMA requests with

PASID */

include/linux/iommu.h +1 -0
+	atomic_t pasid_users;  

These are poorly named, this is really the DMA API global PASID and shouldn't be used for other things.

I was hoping it can be generic since sva_cookie also has a pasid field but it looks like sva uses mm->pasid now.

Shall we call it dma_api_pasid, dma_pasid, or something else?

Quoted 1 line

Jason

Thanks,

Jacob

author
#20 lore raw

PATCH v3 3/4 dmaengine: idxd: Use DMA API for in-kernel DMA with PASID

In reply to Jacob Pan

The current in-kernel supervisor PASID support is based on the SVM/SVA machinery in SVA lib. The binding between a kernel PASID and kernel mapping has many flaws. See discussions in the link below.

This patch enables in-kernel DMA by switching from SVA lib to the standard DMA mapping APIs. Since both DMA requests with and without PASIDs are mapped identically, there is no change to how DMA APIs are used after the kernel PASID is enabled.

---
 drivers/dma/idxd/idxd.h  |  1 -
 drivers/dma/idxd/init.c  | 34 +++++++++-------------------------
 drivers/dma/idxd/sysfs.c |  7 -------
 3 files changed, 9 insertions(+), 33 deletions(-)
drivers/dma/idxd/idxd.h +0 -1
index ccbefd0be617..190b08bd7c08 100644
@@ -277,7 +277,6 @@ struct idxd_device {
 	struct idxd_wq **wqs;
 	struct idxd_engine **engines;
 
-	struct iommu_sva *sva;
 	unsigned int pasid;
 
 	int num_groups;
drivers/dma/idxd/init.c +9 -25
index e1b5d1e4a949..e2e1c0eae6d6 100644
@@ -16,6 +16,7 @@
 #include <linux/idr.h>
 #include <linux/intel-svm.h>
 #include <linux/iommu.h>
+#include <linux/dma-iommu.h>
 #include <uapi/linux/idxd.h>
 #include <linux/dmaengine.h>
 #include "../dmaengine.h"
@@ -466,36 +467,22 @@ static struct idxd_device *idxd_alloc(struct pci_dev *pdev, struct idxd_driver_d
 
 static int idxd_enable_system_pasid(struct idxd_device *idxd)
 {
-	int flags;
-	unsigned int pasid;
-	struct iommu_sva *sva;
+	u32 pasid;
+	int ret;
 
-	flags = SVM_FLAG_SUPERVISOR_MODE;
-
-	sva = iommu_sva_bind_device(&idxd->pdev->dev, NULL, &flags);
-	if (IS_ERR(sva)) {
-		dev_warn(&idxd->pdev->dev,
-			 "iommu sva bind failed: %ld\n", PTR_ERR(sva));
-		return PTR_ERR(sva);
-	}
-
-	pasid = iommu_sva_get_pasid(sva);
-	if (pasid == IOMMU_PASID_INVALID) {
-		iommu_sva_unbind_device(sva);
-		return -ENODEV;
+	ret = iommu_attach_dma_pasid(&idxd->pdev->dev, &pasid);
+	if (ret) {
+		dev_err(&idxd->pdev->dev, "No DMA PASID %d\n", ret);
+		return ret;
 	}
-
-	idxd->sva = sva;
 	idxd->pasid = pasid;
-	dev_dbg(&idxd->pdev->dev, "system pasid: %u\n", pasid);
+
 	return 0;
 }
 
 static void idxd_disable_system_pasid(struct idxd_device *idxd)
 {
-
-	iommu_sva_unbind_device(idxd->sva);
-	idxd->sva = NULL;
+	iommu_detach_dma_pasid(&idxd->pdev->dev);
 }
 
 static int idxd_probe(struct idxd_device *idxd)
@@ -527,10 +514,7 @@ static int idxd_probe(struct idxd_device *idxd)
 			else
 				set_bit(IDXD_FLAG_PASID_ENABLED, &idxd->flags);
 		}
-	} else if (!sva) {
-		dev_warn(dev, "User forced SVA off via module param.\n");
 	}
-
 	idxd_read_caps(idxd);
 	idxd_read_table_offsets(idxd);
 
drivers/dma/idxd/sysfs.c +0 -7
index dfd549685c46..a48928973bd4 100644
@@ -839,13 +839,6 @@ static ssize_t wq_name_store(struct device *dev,
 	if (strlen(buf) > WQ_NAME_SIZE || strlen(buf) == 0)
 		return -EINVAL;
 
-	/*
-	 * This is temporarily placed here until we have SVM support for
-	 * dmaengine.
-	 */
-	if (wq->type == IDXD_WQT_KERNEL && device_pasid_enabled(wq->idxd))
-		return -EOPNOTSUPP;
-
 	memset(wq->name, 0, WQ_NAME_SIZE + 1);
 	strncpy(wq->name, buf, WQ_NAME_SIZE);
 	strreplace(wq->name, '\n', '\0');
Signature · 1 line
2.25.1
author
#21 lore raw

PATCH v3 4/4 iommu/vt-d: Delete unused SVM flag

In reply to Jacob Pan

Supervisor PASID for SVA/SVM is no longer supported, delete the unused flag.

---
 drivers/iommu/intel/svm.c |  2 +-
 include/linux/intel-svm.h | 13 -------------
 2 files changed, 1 insertion(+), 14 deletions(-)
drivers/iommu/intel/svm.c +1 -1
index 38c33cde177e..98ec77415770 100644
@@ -750,7 +750,7 @@ static irqreturn_t prq_event_thread(int irq, void *d)
 			 * to unbind the mm while any page faults are outstanding.
 			 */
 			svm = pasid_private_find(req->pasid);
-			if (IS_ERR_OR_NULL(svm) || (svm->flags & SVM_FLAG_SUPERVISOR_MODE))
+			if (IS_ERR_OR_NULL(svm))
 				goto bad_req;
 		}
 
include/linux/intel-svm.h +0 -13
index b3b125b332aa..6835a665c195 100644
@@ -13,17 +13,4 @@
 #define PRQ_RING_MASK	((0x1000 << PRQ_ORDER) - 0x20)
 #define PRQ_DEPTH	((0x1000 << PRQ_ORDER) >> 5)
 
-/*
- * The SVM_FLAG_SUPERVISOR_MODE flag requests a PASID which can be used only
- * for access to kernel addresses. No IOTLB flushes are automatically done
- * for kernel mappings; it is valid only for access to the kernel's static
- * 1:1 mapping of physical memory — not to vmalloc or even module mappings.
- * A future API addition may permit the use of such ranges, by means of an
- * explicit IOTLB flush call (akin to the DMA API's unmap method).
- *
- * It is unlikely that we will ever hook into flush_tlb_kernel_range() to
- * do such IOTLB flushes automatically.
- */
-#define SVM_FLAG_SUPERVISOR_MODE	BIT(0)
-
 #endif /* __INTEL_SVM_H__ */
Signature · 1 line
2.25.1