Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [RFC PATCH] Optimize VFIO and IOMMU mapping traversal
@ 2026-05-29  7:09 Guanghui Feng
  2026-05-29  7:52 ` sashiko-bot
  2026-05-29 11:51 ` Jason Gunthorpe
  0 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-05-29  7:09 UTC (permalink / raw)
  To: joro, suravee.suthikulpanit, will, robin.murphy, dwmw2, baolu.lu,
	alex, jgg, kevin.tian, skhawaja, iommu, linux-kernel, kvm

In VFIO, vfio_unmap_unpin requires performing iommu unmap and mm
unpin on the address space. However, VFIO doesn't record the PHY
address corresponding to iova, but instead obtains the iova-PHY
mapping through iommu_iommu_iova_to_phys.

In IOMMU, under conditions such as address alignment, it prioritizes
mapping iova-PHY based on bigpages. Therefore, during the
vfio_unmap_unpin process, traversal can be performed at the
granularity of the IOMMU map, reducing the number of
iommu_iova_to_phys queries and significantly improving conversion
efficiency.

Therefore, an iommu_iova_to_pgsize implementation is added to the
IOMMU driver to return the pagesize used for the iova mapping.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Signed-off-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Signed-off-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/amd/iommu.c           |  2 ++
 drivers/iommu/generic_pt/iommu_pt.h | 53 +++++++++++++++++++++++++++++
 drivers/iommu/intel/iommu.c         |  2 ++
 drivers/iommu/iommu.c               | 25 ++++++++++++++
 drivers/vfio/vfio_iommu_type1.c     | 17 +++++++--
 include/linux/generic_pt/iommu.h    |  4 +++
 include/linux/iommu.h               |  3 ++
 7 files changed, 104 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 57dc8fabc7d9..36ffeb96c454 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -2662,6 +2662,7 @@ static const struct pt_iommu_driver_ops amd_hw_driver_ops_v1 = {
 
 static const struct iommu_domain_ops amdv1_ops = {
 	IOMMU_PT_DOMAIN_OPS(amdv1),
+	IOMMU_PT_PGSIZE_OPS(amdv1),
 	.iotlb_sync_map = amd_iommu_iotlb_sync_map,
 	.flush_iotlb_all = amd_iommu_flush_iotlb_all,
 	.iotlb_sync = amd_iommu_iotlb_sync,
@@ -2740,6 +2741,7 @@ static struct iommu_domain *amd_iommu_domain_alloc_paging_v1(struct device *dev,
 
 static const struct iommu_domain_ops amdv2_ops = {
 	IOMMU_PT_DOMAIN_OPS(x86_64),
+	IOMMU_PT_PGSIZE_OPS(x86_64),
 	.iotlb_sync_map = amd_iommu_iotlb_sync_map,
 	.flush_iotlb_all = amd_iommu_flush_iotlb_all,
 	.iotlb_sync = amd_iommu_iotlb_sync,
diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
index dc91fb4e2f61..de861d8b6ce2 100644
--- a/drivers/iommu/generic_pt/iommu_pt.h
+++ b/drivers/iommu/generic_pt/iommu_pt.h
@@ -199,6 +199,59 @@ phys_addr_t DOMAIN_NS(iova_to_phys)(struct iommu_domain *domain,
 }
 EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys), "GENERIC_PT_IOMMU");
 
+static __always_inline int __do_iova_to_pgsize(struct pt_range *range,
+					       void *arg, unsigned int level,
+					       struct pt_table_p *table,
+					       pt_level_fn_t descend_fn)
+{
+	struct pt_state pts = pt_init(range, level, table);
+	size_t *pgsize = arg;
+
+	switch (pt_load_single_entry(&pts)) {
+	case PT_ENTRY_EMPTY:
+		return -ENOENT;
+	case PT_ENTRY_TABLE:
+		return pt_descend(&pts, arg, descend_fn);
+	case PT_ENTRY_OA:
+		*pgsize = BIT(pt_entry_oa_lg2sz(&pts));
+		return 0;
+	}
+	return -ENOENT;
+}
+PT_MAKE_LEVELS(__iova_to_pgsize, __do_iova_to_pgsize);
+
+/**
+ * iova_to_pgsize() - Return the page size of the mapping at the given IOVA
+ * @domain: Table to query
+ * @iova: IO virtual address to query
+ *
+ * Walk the IOMMU page table to determine the actual page size of the PTE
+ * entry that maps the given IOVA.
+ *
+ * Context: The caller must hold a read range lock that includes @iova.
+ *
+ * Return: The page size in bytes, or 0 if there is no translation.
+ */
+size_t DOMAIN_NS(iova_to_pgsize)(struct iommu_domain *domain,
+				 dma_addr_t iova)
+{
+	struct pt_iommu *iommu_table =
+		container_of(domain, struct pt_iommu, domain);
+	struct pt_range range;
+	size_t pgsize;
+	int ret;
+
+	ret = make_range(common_from_iommu(iommu_table), &range, iova, 1);
+	if (ret)
+		return 0;
+
+	ret = pt_walk_range(&range, __iova_to_pgsize, &pgsize);
+	if (ret)
+		return 0;
+	return pgsize;
+}
+EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_pgsize), "GENERIC_PT_IOMMU");
+
 struct pt_iommu_dirty_args {
 	struct iommu_dirty_bitmap *dirty;
 	unsigned int flags;
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 4d0e65bc131d..f992162cfa67 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -3890,6 +3890,7 @@ static struct iommu_domain identity_domain = {
 
 const struct iommu_domain_ops intel_fs_paging_domain_ops = {
 	IOMMU_PT_DOMAIN_OPS(x86_64),
+	IOMMU_PT_PGSIZE_OPS(x86_64),
 	.attach_dev = intel_iommu_attach_device,
 	.set_dev_pasid = intel_iommu_set_dev_pasid,
 	.iotlb_sync_map = intel_iommu_iotlb_sync_map,
@@ -3901,6 +3902,7 @@ const struct iommu_domain_ops intel_fs_paging_domain_ops = {
 
 const struct iommu_domain_ops intel_ss_paging_domain_ops = {
 	IOMMU_PT_DOMAIN_OPS(vtdss),
+	IOMMU_PT_PGSIZE_OPS(vtdss),
 	.attach_dev = intel_iommu_attach_device,
 	.set_dev_pasid = intel_iommu_set_dev_pasid,
 	.iotlb_sync_map = intel_iommu_iotlb_sync_map,
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index d1a9e713d3a0..e27f26bc1851 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2557,6 +2557,31 @@ phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
 }
 EXPORT_SYMBOL_GPL(iommu_iova_to_phys);
 
+/**
+ * iommu_iova_to_pgsize - Get the page size of the mapping at a given IOVA
+ * @domain: IOMMU domain to query
+ * @iova: IO virtual address to query
+ *
+ * Walk the IOMMU page table to determine the actual page size of the PTE
+ * entry that maps the given IOVA. This reflects the real mapping granularity,
+ * not an inferred value from alignment.
+ *
+ * Returns the page size in bytes, or 0 if the mapping doesn't exist or the
+ * domain doesn't support this query.
+ */
+size_t iommu_iova_to_pgsize(struct iommu_domain *domain, dma_addr_t iova)
+{
+	if (domain->type == IOMMU_DOMAIN_IDENTITY ||
+	    domain->type == IOMMU_DOMAIN_BLOCKED)
+		return 0;
+
+	if (!domain->ops->iova_to_pgsize)
+		return 0;
+
+	return domain->ops->iova_to_pgsize(domain, iova);
+}
+EXPORT_SYMBOL_GPL(iommu_iova_to_pgsize);
+
 static size_t iommu_pgsize(struct iommu_domain *domain, unsigned long iova,
 			   phys_addr_t paddr, size_t size, size_t *count)
 {
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
index c8151ba54de3..bf918a93a159 100644
--- a/drivers/vfio/vfio_iommu_type1.c
+++ b/drivers/vfio/vfio_iommu_type1.c
@@ -1177,7 +1177,7 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
 
 	iommu_iotlb_gather_init(&iotlb_gather);
 	while (pos < dma->size) {
-		size_t unmapped, len;
+		size_t unmapped, len, pgsize;
 		phys_addr_t phys, next;
 		dma_addr_t iova = dma->iova + pos;
 
@@ -1191,11 +1191,24 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
 		 * To optimize for fewer iommu_unmap() calls, each of which
 		 * may require hardware cache flushing, try to find the
 		 * largest contiguous physical memory chunk to unmap.
+		 *
+		 * Query the actual IOMMU PTE mapping granularity at this IOVA
+		 * to determine the guaranteed contiguous range. Use only the
+		 * remaining portion within the current PTE from our position,
+		 * in case we start from the middle of a large page mapping.
 		 */
-		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
+		pgsize = iommu_iova_to_pgsize(domain->domain, iova);
+		if (!pgsize)
+			pgsize = PAGE_SIZE;
+		len = pgsize - (iova & (pgsize - 1));
+		for (; pos + len < dma->size; len += pgsize) {
 			next = iommu_iova_to_phys(domain->domain, iova + len);
 			if (next != phys + len)
 				break;
+			pgsize = iommu_iova_to_pgsize(domain->domain,
+						      iova + len);
+			if (!pgsize)
+				pgsize = PAGE_SIZE;
 		}
 
 		/*
diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
index dd0edd02a48a..2f30ae73a9eb 100644
--- a/include/linux/generic_pt/iommu.h
+++ b/include/linux/generic_pt/iommu.h
@@ -251,6 +251,8 @@ struct pt_iommu_cfg {
 #define IOMMU_PROTOTYPES(fmt)                                                  \
 	phys_addr_t pt_iommu_##fmt##_iova_to_phys(struct iommu_domain *domain, \
 						  dma_addr_t iova);            \
+	size_t pt_iommu_##fmt##_iova_to_pgsize(struct iommu_domain *domain,    \
+					       dma_addr_t iova);               \
 	int pt_iommu_##fmt##_read_and_clear_dirty(                             \
 		struct iommu_domain *domain, unsigned long iova, size_t size,  \
 		unsigned long flags, struct iommu_dirty_bitmap *dirty);        \
@@ -272,6 +274,8 @@ struct pt_iommu_cfg {
  */
 #define IOMMU_PT_DOMAIN_OPS(fmt)                        \
 	.iova_to_phys = &pt_iommu_##fmt##_iova_to_phys
+#define IOMMU_PT_PGSIZE_OPS(fmt)                        \
+	.iova_to_pgsize = &pt_iommu_##fmt##_iova_to_pgsize
 #define IOMMU_PT_DIRTY_OPS(fmt) \
 	.read_and_clear_dirty = &pt_iommu_##fmt##_read_and_clear_dirty
 
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e587d4ac4d33..d04dc7dcfb1e 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -776,6 +776,8 @@ struct iommu_domain_ops {
 
 	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
 				    dma_addr_t iova);
+	size_t (*iova_to_pgsize)(struct iommu_domain *domain,
+				 dma_addr_t iova);
 
 	bool (*enforce_cache_coherency)(struct iommu_domain *domain);
 	int (*set_pgtable_quirks)(struct iommu_domain *domain,
@@ -930,6 +932,7 @@ extern ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
 			    struct scatterlist *sg, unsigned int nents,
 			    int prot, gfp_t gfp);
 extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova);
+extern size_t iommu_iova_to_pgsize(struct iommu_domain *domain, dma_addr_t iova);
 extern void iommu_set_fault_handler(struct iommu_domain *domain,
 			iommu_fault_handler_t handler, void *token);
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* Re: [RFC PATCH] Optimize VFIO and IOMMU mapping traversal
  2026-05-29  7:09 [RFC PATCH] Optimize VFIO and IOMMU mapping traversal Guanghui Feng
@ 2026-05-29  7:52 ` sashiko-bot
  2026-05-29 11:51 ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-29  7:52 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] The computed chunk size `len` in `vfio_unmap_unpin` is not clamped to the remaining size of the target DMA region (`dma->size - pos`). This allows `len` to overshoot the DMA region boundary, causing out-of-bounds unmapping and unpinning of adjacent memory.
--

commit 0f5b376f7b38b02730cdbd93b618b39e5e93f1d0
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

Optimize VFIO and IOMMU mapping traversal

This commit adds an iommu_iova_to_pgsize() implementation to query the
hardware mapping page size. It uses this in VFIO to optimize
vfio_unmap_unpin() by traversing the mappings at the IOMMU map granularity
rather than PAGE_SIZE.

> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index c8151ba54de33..bf918a93a1591 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c

[ ... ]

> @@ -1191,11 +1191,24 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
>  		 * To optimize for fewer iommu_unmap() calls, each of which
>  		 * may require hardware cache flushing, try to find the
>  		 * largest contiguous physical memory chunk to unmap.
> +		 *
> +		 * Query the actual IOMMU PTE mapping granularity at this IOVA
> +		 * to determine the guaranteed contiguous range. Use only the
> +		 * remaining portion within the current PTE from our position,
> +		 * in case we start from the middle of a large page mapping.
>  		 */
> -		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
> +		pgsize = iommu_iova_to_pgsize(domain->domain, iova);
> +		if (!pgsize)
> +			pgsize = PAGE_SIZE;
> +		len = pgsize - (iova & (pgsize - 1));
> +		for (; pos + len < dma->size; len += pgsize) {

[Severity: Critical]
Could this len calculation exceed the boundaries of the DMA region?

If the hardware mapped a large page that extends beyond the target vfio_dma
region, or if a partial unmap split the vfio_dma region but left the hardware
mapping intact, the calculated len can be larger than dma->size - pos.

The loop condition pos + len < dma->size prevents further iterations but does
not clamp the oversized len. Does this result in unmap_unpin_fast() being
called with an out-of-bounds len, which would unmap and unpin pages belonging
to adjacent memory regions?

Would it be better to clamp len to the remaining size before the unmap?
len = min_t(size_t, len, dma->size - pos);

>  			next = iommu_iova_to_phys(domain->domain, iova + len);
>  			if (next != phys + len)
>  				break;
> +			pgsize = iommu_iova_to_pgsize(domain->domain,
> +						      iova + len);
> +			if (!pgsize)
> +				pgsize = PAGE_SIZE;
>  		}
>  
>  		/*

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260529070932.2632907-1-guanghuifeng@linux.alibaba.com?part=1

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [RFC PATCH] Optimize VFIO and IOMMU mapping traversal
  2026-05-29  7:09 [RFC PATCH] Optimize VFIO and IOMMU mapping traversal Guanghui Feng
  2026-05-29  7:52 ` sashiko-bot
@ 2026-05-29 11:51 ` Jason Gunthorpe
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
  1 sibling, 1 reply; 144+ messages in thread
From: Jason Gunthorpe @ 2026-05-29 11:51 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: joro, suravee.suthikulpanit, will, robin.murphy, dwmw2, baolu.lu,
	alex, kevin.tian, skhawaja, iommu, linux-kernel, kvm

On Fri, May 29, 2026 at 03:09:32PM +0800, Guanghui Feng wrote:
> In VFIO, vfio_unmap_unpin requires performing iommu unmap and mm
> unpin on the address space. However, VFIO doesn't record the PHY
> address corresponding to iova, but instead obtains the iova-PHY
> mapping through iommu_iommu_iova_to_phys.
> 
> In IOMMU, under conditions such as address alignment, it prioritizes
> mapping iova-PHY based on bigpages. Therefore, during the
> vfio_unmap_unpin process, traversal can be performed at the
> granularity of the IOMMU map, reducing the number of
> iommu_iova_to_phys queries and significantly improving conversion
> efficiency.
> 
> Therefore, an iommu_iova_to_pgsize implementation is added to the
> IOMMU driver to return the pagesize used for the iova mapping.

This is the wrong API, what we need here is an iova_to_phys variation
that returns a size so physically contiguous IOPTEs can be joined.

>  drivers/iommu/amd/iommu.c           |  2 ++
>  drivers/iommu/generic_pt/iommu_pt.h | 53 +++++++++++++++++++++++++++++
>  drivers/iommu/intel/iommu.c         |  2 ++
>  drivers/iommu/iommu.c               | 25 ++++++++++++++
>  drivers/vfio/vfio_iommu_type1.c     | 17 +++++++--

If you care about performance use iommufd, and patches like this have
to update iommfd too.

>  static const struct iommu_domain_ops amdv1_ops = {
>  	IOMMU_PT_DOMAIN_OPS(amdv1),
> +	IOMMU_PT_PGSIZE_OPS(amdv1),
>  	.iotlb_sync_map = amd_iommu_iotlb_sync_map,
>  	.flush_iotlb_all = amd_iommu_flush_iotlb_all,
>  	.iotlb_sync = amd_iommu_iotlb_sync,

It should be routed through the private ops structure, and even if not
no reason to make another macro.

This would also need to be split into a patch adding the core API
function and then updating callers.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation
  2026-05-29 11:51 ` Jason Gunthorpe
@ 2026-05-31  9:36   ` Guanghui Feng
  2026-05-31  9:36     ` [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
                       ` (9 more replies)
  0 siblings, 10 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

This series introduces a new iova_to_phys_length() interface across the
IOMMU subsystem that returns both the physical address and the PTE mapping
page size in a single page-table walk.

Motivation:

The existing iova_to_phys() only returns a physical address without any
information about the page size of the underlying PTE mapping. Callers
like VFIO and iommufd that need to traverse IOVA ranges are forced to
walk one PAGE_SIZE step at a time, even when the IOMMU has mapped large
regions using 2MB or 1GB huge pages. This results in O(n) page-table
walks where O(1) would suffice per mapping entry.

Approach:

The new callback .iova_to_phys_length returns the physical address and
sets *mapped_length to the PTE page size (e.g. 4KB, 2MB, 1GB). This
allows callers to skip ahead by the actual mapping granularity.

The migration follows an add-migrate-remove pattern to ensure every
commit in this series compiles independently (bisectable):

  1. Add the new interface with fallback to legacy iova_to_phys
  2. Migrate all IOMMU drivers (io-pgtable, generic_pt, per-SoC)
  3. Migrate callers (VFIO, iommufd, DRM)
  4. Remove the deprecated iova_to_phys from ops structs

Performance impact:

For VFIO unmap of a 2MB region mapped with 2MB pages, this reduces
page-table walks from 512 (PAGE_SIZE steps) to 1 (one per 2MB PTE),
a ~512x improvement in that path.

Guanghui Feng (9):
  iommu: introduce iova_to_phys_length in iommu_domain_ops
  iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops
  iommu/generic_pt: implement iova_to_phys_length
  iommu/arm-smmu: implement iova_to_phys_length
  iommu: apple-dart/ipmmu/mtk_iommu implement iova_to_phys_length
  iommu: direct page-table drivers implement iova_to_phys_length
  vfio/iommufd: use iova_to_phys_length for efficient unmap
  drm/gpu, iommu/io-pgtable: switch to iova_to_phys_length
  iommu: remove deprecated iova_to_phys from domain_ops and
    io_pgtable_ops

 drivers/gpu/drm/panfrost/panfrost_mmu.c       |  2 +-
 drivers/gpu/drm/panthor/panthor_mmu.c         |  2 +-
 drivers/iommu/apple-dart.c                    | 11 +--
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c   | 10 ++-
 .../iommu/arm/arm-smmu/arm-smmu-qcom-debug.c  |  2 +-
 drivers/iommu/arm/arm-smmu/arm-smmu.c         | 13 ++--
 drivers/iommu/arm/arm-smmu/qcom_iommu.c       | 11 +--
 drivers/iommu/exynos-iommu.c                  | 21 ++++--
 drivers/iommu/fsl_pamu_domain.c               | 26 ++++++-
 drivers/iommu/generic_pt/iommu_pt.h           | 49 ++++++++-----
 drivers/iommu/io-pgtable-arm-selftests.c      | 12 ++--
 drivers/iommu/io-pgtable-arm-v7s.c            | 25 ++++---
 drivers/iommu/io-pgtable-arm.c                | 14 ++--
 drivers/iommu/io-pgtable-dart.c               | 17 +++--
 drivers/iommu/iommu.c                         | 30 +++++++-
 drivers/iommu/iommufd/pages.c                 | 71 +++++++++++++++----
 drivers/iommu/iommufd/selftest.c              |  2 +-
 drivers/iommu/ipmmu-vmsa.c                    | 12 ++--
 drivers/iommu/msm_iommu.c                     | 25 +++++--
 drivers/iommu/mtk_iommu.c                     | 12 ++--
 drivers/iommu/mtk_iommu_v1.c                  | 15 +++-
 drivers/iommu/omap-iommu.c                    | 32 ++++++---
 drivers/iommu/rockchip-iommu.c                | 11 ++-
 drivers/iommu/s390-iommu.c                    | 15 ++--
 drivers/iommu/sprd-iommu.c                    | 13 +++-
 drivers/iommu/sun50i-iommu.c                  | 13 +++-
 drivers/iommu/tegra-smmu.c                    | 12 +++-
 drivers/iommu/virtio-iommu.c                  | 13 +++-
 drivers/vfio/vfio_iommu_type1.c               | 24 +++++--
 include/linux/generic_pt/iommu.h              | 13 ++--
 include/linux/io-pgtable.h                    | 10 ++-
 include/linux/iommu.h                         | 12 +++-
 32 files changed, 404 insertions(+), 146 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 144+ messages in thread

* [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31  9:54       ` sashiko-bot
  2026-05-31 23:51       ` Jason Gunthorpe
  2026-05-31  9:36     ` [PATCH 2/9] iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
                       ` (8 subsequent siblings)
  9 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Add iova_to_phys_length callback to struct iommu_domain_ops alongside
the existing iova_to_phys. The new callback returns both the physical
address and the PTE mapping page size in a single page table walk.

Add iommu_iova_to_phys_length() core function that:
- Checks ops->iova_to_phys_length first (preferred path)
- Falls back to ops->iova_to_phys for unmigrated drivers

This enables callers like VFIO to efficiently traverse IOVA space
by actual mapping granularity instead of fixed PAGE_SIZE steps.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/iommu.c | 34 ++++++++++++++++++++++++++++++++--
 include/linux/iommu.h |  9 +++++++++
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index d1a9e713d3a0..43323229a1df 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2545,15 +2545,45 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
 }
 EXPORT_SYMBOL_GPL(iommu_detach_group);
 
-phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+/**
+ * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
+ * @domain: IOMMU domain to query
+ * @iova: IO virtual address to translate
+ * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
+ *
+ * Like iommu_iova_to_phys() but additionally returns the page size of the
+ * PTE mapping at @iova through @mapped_length.
+ *
+ * Return: The physical address for the given IOVA, or 0 if no translation.
+ */
+phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
+				       dma_addr_t iova,
+				       size_t *mapped_length)
 {
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (domain->type == IOMMU_DOMAIN_IDENTITY)
 		return iova;
 
 	if (domain->type == IOMMU_DOMAIN_BLOCKED)
 		return 0;
 
-	return domain->ops->iova_to_phys(domain, iova);
+	if (domain->ops->iova_to_phys_length)
+		return domain->ops->iova_to_phys_length(domain, iova,
+							mapped_length);
+
+	/* Fallback to legacy iova_to_phys without length info */
+	if (domain->ops->iova_to_phys)
+		return domain->ops->iova_to_phys(domain, iova);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iommu_iova_to_phys_length);
+
+phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+{
+	return iommu_iova_to_phys_length(domain, iova, NULL);
 }
 EXPORT_SYMBOL_GPL(iommu_iova_to_phys);
 
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e587d4ac4d33..19da84c2922c 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -747,6 +747,9 @@ struct iommu_ops {
  *                         invalidation requests. The driver data structure
  *                         must be defined in include/uapi/linux/iommufd.h
  * @iova_to_phys: translate iova to physical address
+ * @iova_to_phys_length: translate iova to physical address and additionally
+ *                       return the page size of the PTE mapping at @iova
+ *                       through @mapped_length.
  * @enforce_cache_coherency: Prevent any kind of DMA from bypassing IOMMU_CACHE,
  *                           including no-snoop TLPs on PCIe or other platform
  *                           specific mechanisms.
@@ -776,6 +779,9 @@ struct iommu_domain_ops {
 
 	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
 				    dma_addr_t iova);
+	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
+					    dma_addr_t iova,
+					    size_t *mapped_length);
 
 	bool (*enforce_cache_coherency)(struct iommu_domain *domain);
 	int (*set_pgtable_quirks)(struct iommu_domain *domain,
@@ -930,6 +936,9 @@ extern ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
 			    struct scatterlist *sg, unsigned int nents,
 			    int prot, gfp_t gfp);
 extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova);
+extern phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
+					      dma_addr_t iova,
+					      size_t *mapped_length);
 extern void iommu_set_fault_handler(struct iommu_domain *domain,
 			iommu_fault_handler_t handler, void *token);
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 2/9] iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
  2026-05-31  9:36     ` [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31 10:03       ` sashiko-bot
  2026-05-31  9:36     ` [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
                       ` (7 subsequent siblings)
  9 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Add iova_to_phys_length to struct io_pgtable_ops alongside iova_to_phys.
The new callback additionally returns the block/page size of the PTE
entry through the mapped_length parameter.

Implement in all three io-pgtable backends:
- ARM LPAE: returns ARM_LPAE_BLOCK_SIZE at the resolved level
- ARM v7s: returns block size derived from level mask
- DART: returns pgsize_bitmap (single fixed page size)

The old iova_to_phys is kept as a thin wrapper for backward compat.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-v7s.c | 30 ++++++++++++++++++++++++------
 drivers/iommu/io-pgtable-arm.c     | 19 ++++++++++++++++++-
 drivers/iommu/io-pgtable-dart.c    | 26 +++++++++++++++++++++-----
 include/linux/io-pgtable.h         |  7 +++++++
 4 files changed, 70 insertions(+), 12 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
index 40e33257d3c2..54e8cc673eb6 100644
--- a/drivers/iommu/io-pgtable-arm-v7s.c
+++ b/drivers/iommu/io-pgtable-arm-v7s.c
@@ -641,13 +641,25 @@ static size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova
 	return unmapped;
 }
 
+static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
+						unsigned long iova,
+						size_t *mapped_length);
+
 static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
 					unsigned long iova)
+{
+	return arm_v7s_iova_to_phys_length(ops, iova, NULL);
+}
+
+static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
+						unsigned long iova,
+						size_t *mapped_length)
 {
 	struct arm_v7s_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	arm_v7s_iopte *ptep = data->pgd, pte;
 	int lvl = 0;
 	u32 mask;
+	size_t blk_size;
 
 	do {
 		ptep += ARM_V7S_LVL_IDX(iova, ++lvl, &data->iop.cfg);
@@ -661,6 +673,11 @@ static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
 	mask = ARM_V7S_LVL_MASK(lvl);
 	if (arm_v7s_pte_is_cont(pte, lvl))
 		mask *= ARM_V7S_CONT_PAGES;
+
+	blk_size = ~mask + 1U;
+	if (mapped_length)
+		*mapped_length = blk_size;
+
 	return iopte_to_paddr(pte, lvl, &data->iop.cfg) | (iova & ~mask);
 }
 
@@ -714,6 +731,7 @@ static struct io_pgtable *arm_v7s_alloc_pgtable(struct io_pgtable_cfg *cfg,
 		.map_pages	= arm_v7s_map_pages,
 		.unmap_pages	= arm_v7s_unmap_pages,
 		.iova_to_phys	= arm_v7s_iova_to_phys,
+		.iova_to_phys_length	= arm_v7s_iova_to_phys_length,
 	};
 
 	/* We have to do this early for __arm_v7s_alloc_table to work... */
@@ -837,13 +855,13 @@ static int __init arm_v7s_do_selftests(void)
 	 * Initial sanity checks.
 	 * Empty page tables shouldn't provide any translations.
 	 */
-	if (ops->iova_to_phys(ops, 42))
+	if (ops->iova_to_phys_length(ops, 42, NULL))
 		return __FAIL(ops);
 
-	if (ops->iova_to_phys(ops, SZ_1G + 42))
+	if (ops->iova_to_phys_length(ops, SZ_1G + 42, NULL))
 		return __FAIL(ops);
 
-	if (ops->iova_to_phys(ops, SZ_2G + 42))
+	if (ops->iova_to_phys_length(ops, SZ_2G + 42, NULL))
 		return __FAIL(ops);
 
 	/*
@@ -864,7 +882,7 @@ static int __init arm_v7s_do_selftests(void)
 				    &mapped))
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 			return __FAIL(ops);
 
 		iova += SZ_16M;
@@ -878,7 +896,7 @@ static int __init arm_v7s_do_selftests(void)
 		if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL))
 			return __FAIL(ops);
 
 		/* Remap full block */
@@ -886,7 +904,7 @@ static int __init arm_v7s_do_selftests(void)
 				   GFP_KERNEL, &mapped))
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 			return __FAIL(ops);
 
 		iova += SZ_16M;
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 0208e5897c29..e2ea84669fbf 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -731,8 +731,19 @@ static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
 	return 0;
 }
 
+static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
+						 unsigned long iova,
+						 size_t *mapped_length);
+
 static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
 					 unsigned long iova)
+{
+	return arm_lpae_iova_to_phys_length(ops, iova, NULL);
+}
+
+static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
+						 unsigned long iova,
+						 size_t *mapped_length)
 {
 	struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	struct iova_to_phys_data d;
@@ -742,13 +753,18 @@ static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
 		.addr = iova,
 		.end = iova + 1,
 	};
+	size_t block_size;
 	int ret;
 
 	ret = __arm_lpae_iopte_walk(data, &walk_data, data->pgd, data->start_level);
 	if (ret)
 		return 0;
 
-	iova &= (ARM_LPAE_BLOCK_SIZE(d.lvl, data) - 1);
+	block_size = ARM_LPAE_BLOCK_SIZE(d.lvl, data);
+	if (mapped_length)
+		*mapped_length = block_size;
+
+	iova &= (block_size - 1);
 	return iopte_to_paddr(d.pte, data) | iova;
 }
 
@@ -948,6 +964,7 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
 		.map_pages	= arm_lpae_map_pages,
 		.unmap_pages	= arm_lpae_unmap_pages,
 		.iova_to_phys	= arm_lpae_iova_to_phys,
+		.iova_to_phys_length	= arm_lpae_iova_to_phys_length,
 		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
 		.pgtable_walk	= arm_lpae_pgtable_walk,
 	};
diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index cbc5d6aa2daa..fa250f5d16ed 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -333,11 +333,23 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	return i * pgsize;
 }
 
+static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
+					    unsigned long iova,
+					    size_t *mapped_length);
+
 static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
-					 unsigned long iova)
+				     unsigned long iova)
+{
+	return dart_iova_to_phys_length(ops, iova, NULL);
+}
+
+static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
+					    unsigned long iova,
+					    size_t *mapped_length)
 {
 	struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	dart_iopte pte, *ptep;
+	size_t pgsize;
 
 	ptep = dart_get_last(data, iova);
 
@@ -350,7 +362,10 @@ static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
 	pte = READ_ONCE(*ptep);
 	/* Found translation */
 	if (pte) {
-		iova &= (data->iop.cfg.pgsize_bitmap - 1);
+		pgsize = data->iop.cfg.pgsize_bitmap;
+		if (mapped_length)
+			*mapped_length = pgsize;
+		iova &= (pgsize - 1);
 		return iopte_to_paddr(pte, data) | iova;
 	}
 
@@ -397,9 +412,10 @@ dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->bits_per_level = bits_per_level;
 
 	data->iop.ops = (struct io_pgtable_ops) {
-		.map_pages	= dart_map_pages,
-		.unmap_pages	= dart_unmap_pages,
-		.iova_to_phys	= dart_iova_to_phys,
+		.map_pages		= dart_map_pages,
+		.unmap_pages		= dart_unmap_pages,
+		.iova_to_phys		= dart_iova_to_phys,
+		.iova_to_phys_length	= dart_iova_to_phys_length,
 	};
 
 	return data;
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index e19872e37e06..42bcdd309b88 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -203,6 +203,10 @@ struct arm_lpae_io_pgtable_walk_data {
  * @map_pages:    Map a physically contiguous range of pages of the same size.
  * @unmap_pages:  Unmap a range of virtually contiguous pages of the same size.
  * @iova_to_phys: Translate iova to physical address.
+ * @iova_to_phys_length: Translate iova to physical address and return the
+ *			  remaining mapped length from iova to the end of the
+ *			  mapping entry via @mapped_length. If @mapped_length is
+ *			  NULL, only the physical address is returned.
  * @pgtable_walk: (optional) Perform a page table walk for a given iova.
  * @read_and_clear_dirty: Record dirty info per IOVA. If an IOVA is dirty,
  *			  clear its dirty state from the PTE unless the
@@ -220,6 +224,9 @@ struct io_pgtable_ops {
 			      struct iommu_iotlb_gather *gather);
 	phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,
 				    unsigned long iova);
+	phys_addr_t (*iova_to_phys_length)(struct io_pgtable_ops *ops,
+					   unsigned long iova,
+					   size_t *mapped_length);
 	int (*pgtable_walk)(struct io_pgtable_ops *ops, unsigned long iova, void *wd);
 	int (*read_and_clear_dirty)(struct io_pgtable_ops *ops,
 				    unsigned long iova, size_t size,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
  2026-05-31  9:36     ` [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
  2026-05-31  9:36     ` [PATCH 2/9] iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31 10:12       ` sashiko-bot
  2026-05-31 23:54       ` Jason Gunthorpe
  2026-05-31  9:36     ` [PATCH 4/9] iommu/arm-smmu: " Guanghui Feng
                       ` (6 subsequent siblings)
  9 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Extend the Generic Page Table framework to implement iova_to_phys_length.
Use pt_entry_oa_lg2sz() to determine PTE block size. Update
IOMMU_PT_DOMAIN_OPS macro to set .iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/generic_pt/iommu_pt.h | 49 ++++++++++++++++++-----------
 include/linux/generic_pt/iommu.h    | 13 ++++----
 2 files changed, 37 insertions(+), 25 deletions(-)

diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
index dc91fb4e2f61..b4213b4947f2 100644
--- a/drivers/iommu/generic_pt/iommu_pt.h
+++ b/drivers/iommu/generic_pt/iommu_pt.h
@@ -145,13 +145,18 @@ static inline unsigned int compute_best_pgsize(struct pt_state *pts,
 				      pts->range->va, pts->range->last_va, oa);
 }
 
-static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
-					     unsigned int level,
-					     struct pt_table_p *table,
-					     pt_level_fn_t descend_fn)
+struct iova_to_phys_length_data {
+	pt_oaddr_t phys;
+	size_t length;
+};
+
+static __always_inline int __do_iova_to_phys_length(struct pt_range *range,
+					       void *arg, unsigned int level,
+					       struct pt_table_p *table,
+					       pt_level_fn_t descend_fn)
 {
 	struct pt_state pts = pt_init(range, level, table);
-	pt_oaddr_t *res = arg;
+	struct iova_to_phys_length_data *data = arg;
 
 	switch (pt_load_single_entry(&pts)) {
 	case PT_ENTRY_EMPTY:
@@ -159,45 +164,51 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
 	case PT_ENTRY_TABLE:
 		return pt_descend(&pts, arg, descend_fn);
 	case PT_ENTRY_OA:
-		*res = pt_entry_oa_exact(&pts);
+		data->phys = pt_entry_oa_exact(&pts);
+		data->length = BIT(pt_entry_oa_lg2sz(&pts));
 		return 0;
 	}
 	return -ENOENT;
 }
-PT_MAKE_LEVELS(__iova_to_phys, __do_iova_to_phys);
+PT_MAKE_LEVELS(__iova_to_phys_length, __do_iova_to_phys_length);
 
 /**
- * iova_to_phys() - Return the output address for the given IOVA
+ * iova_to_phys_length() - Translate IOVA returning both phys and page size
  * @domain: Table to query
  * @iova: IO virtual address to query
+ * @mapped_length: Output for the PTE page size in bytes
  *
- * Determine the output address from the given IOVA. @iova may have any
- * alignment, the returned physical will be adjusted with any sub page offset.
+ * Walk the IOMMU page table to translate @iova to a physical address while
+ * also returning the page size of the PTE entry through @mapped_length.
+ * This combines iova_to_phys and page size query into a single page table walk.
  *
  * Context: The caller must hold a read range lock that includes @iova.
  *
- * Return: 0 if there is no translation for the given iova.
+ * Return: The physical address, or 0 if there is no translation.
  */
-phys_addr_t DOMAIN_NS(iova_to_phys)(struct iommu_domain *domain,
-				    dma_addr_t iova)
+phys_addr_t DOMAIN_NS(iova_to_phys_length)(struct iommu_domain *domain,
+					    dma_addr_t iova,
+					    size_t *mapped_length)
 {
 	struct pt_iommu *iommu_table =
 		container_of(domain, struct pt_iommu, domain);
 	struct pt_range range;
-	pt_oaddr_t res;
+	struct iova_to_phys_length_data data;
 	int ret;
 
 	ret = make_range(common_from_iommu(iommu_table), &range, iova, 1);
 	if (ret)
-		return ret;
+		return 0;
 
-	ret = pt_walk_range(&range, __iova_to_phys, &res);
-	/* PHYS_ADDR_MAX would be a better error code */
+	ret = pt_walk_range(&range, __iova_to_phys_length, &data);
 	if (ret)
 		return 0;
-	return res;
+
+	if (mapped_length)
+		*mapped_length = data.length;
+	return data.phys;
 }
-EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys), "GENERIC_PT_IOMMU");
+EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys_length), "GENERIC_PT_IOMMU");
 
 struct pt_iommu_dirty_args {
 	struct iommu_dirty_bitmap *dirty;
diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
index dd0edd02a48a..859b853e9dc7 100644
--- a/include/linux/generic_pt/iommu.h
+++ b/include/linux/generic_pt/iommu.h
@@ -249,8 +249,9 @@ struct pt_iommu_cfg {
 
 /* Generate the exported function signatures from iommu_pt.h */
 #define IOMMU_PROTOTYPES(fmt)                                                  \
-	phys_addr_t pt_iommu_##fmt##_iova_to_phys(struct iommu_domain *domain, \
-						  dma_addr_t iova);            \
+	phys_addr_t pt_iommu_##fmt##_iova_to_phys_length(			\
+		struct iommu_domain *domain, dma_addr_t iova,			\
+		size_t *mapped_length);						\
 	int pt_iommu_##fmt##_read_and_clear_dirty(                             \
 		struct iommu_domain *domain, unsigned long iova, size_t size,  \
 		unsigned long flags, struct iommu_dirty_bitmap *dirty);        \
@@ -267,11 +268,11 @@ struct pt_iommu_cfg {
 	IOMMU_PROTOTYPES(fmt)
 
 /*
- * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the
- * iommu_pt
+ * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for
+ * the iommu_pt
  */
-#define IOMMU_PT_DOMAIN_OPS(fmt)                        \
-	.iova_to_phys = &pt_iommu_##fmt##_iova_to_phys
+#define IOMMU_PT_DOMAIN_OPS(fmt)					\
+	.iova_to_phys_length = &pt_iommu_##fmt##_iova_to_phys_length
 #define IOMMU_PT_DIRTY_OPS(fmt) \
 	.read_and_clear_dirty = &pt_iommu_##fmt##_read_and_clear_dirty
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 4/9] iommu/arm-smmu: implement iova_to_phys_length
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                       ` (2 preceding siblings ...)
  2026-05-31  9:36     ` [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31 10:22       ` sashiko-bot
  2026-05-31  9:36     ` [PATCH 5/9] iommu: apple-dart/ipmmu/mtk_iommu " Guanghui Feng
                       ` (5 subsequent siblings)
  9 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Migrate ARM SMMU drivers to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer to get both phys
and PTE page size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c      | 10 +++++++---
 drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c |  2 +-
 drivers/iommu/arm/arm-smmu/arm-smmu.c            | 13 ++++++++-----
 drivers/iommu/arm/arm-smmu/qcom_iommu.c          | 11 +++++++----
 4 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index e8d7dbe495f0..c9f356c75bc5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4069,14 +4069,18 @@ static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
 }
 
 static phys_addr_t
-arm_smmu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+arm_smmu_iova_to_phys_length(struct iommu_domain *domain, dma_addr_t iova,
+			     size_t *mapped_length)
 {
 	struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
 		return 0;
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static struct platform_driver arm_smmu_driver;
@@ -4396,7 +4400,7 @@ static const struct iommu_ops arm_smmu_ops = {
 		.unmap_pages		= arm_smmu_unmap_pages,
 		.flush_iotlb_all	= arm_smmu_flush_iotlb_all,
 		.iotlb_sync		= arm_smmu_iotlb_sync,
-		.iova_to_phys		= arm_smmu_iova_to_phys,
+		.iova_to_phys_length	= arm_smmu_iova_to_phys_length,
 		.free			= arm_smmu_domain_free_paging,
 	}
 };
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
index 65e0ef6539fe..4fd01341157f 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
@@ -415,7 +415,7 @@ irqreturn_t qcom_smmu_context_fault(int irq, void *dev)
 		return IRQ_HANDLED;
 	}
 
-	phys_soft = ops->iova_to_phys(ops, cfi.iova);
+	phys_soft = ops->iova_to_phys_length(ops, cfi.iova, NULL);
 
 	tmp = report_iommu_fault(&smmu_domain->domain, NULL, cfi.iova,
 				 cfi.fsynr & ARM_SMMU_CB_FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ);
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
index 0bd21d206eb3..e29e0ea12f24 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
@@ -1366,7 +1366,7 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
 			"iova to phys timed out on %pad. Falling back to software table walk.\n",
 			&iova);
 		arm_smmu_rpm_put(smmu);
-		return ops->iova_to_phys(ops, iova);
+		return ops->iova_to_phys_length(ops, iova, NULL);
 	}
 
 	phys = arm_smmu_cb_readq(smmu, idx, ARM_SMMU_CB_PAR);
@@ -1384,12 +1384,15 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
 	return addr;
 }
 
-static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
-					dma_addr_t iova)
+static phys_addr_t arm_smmu_iova_to_phys_length(struct iommu_domain *domain,
+					dma_addr_t iova, size_t *mapped_length)
 {
 	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
 	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
 		return 0;
 
@@ -1397,7 +1400,7 @@ static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
 			smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
 		return arm_smmu_iova_to_phys_hard(domain, iova);
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static bool arm_smmu_capable(struct device *dev, enum iommu_cap cap)
@@ -1652,7 +1655,7 @@ static const struct iommu_ops arm_smmu_ops = {
 		.unmap_pages		= arm_smmu_unmap_pages,
 		.flush_iotlb_all	= arm_smmu_flush_iotlb_all,
 		.iotlb_sync		= arm_smmu_iotlb_sync,
-		.iova_to_phys		= arm_smmu_iova_to_phys,
+		.iova_to_phys_length	= arm_smmu_iova_to_phys_length,
 		.set_pgtable_quirks	= arm_smmu_set_pgtable_quirks,
 		.free			= arm_smmu_domain_free,
 	}
diff --git a/drivers/iommu/arm/arm-smmu/qcom_iommu.c b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
index a1e8cf29f594..08bb6fce992f 100644
--- a/drivers/iommu/arm/arm-smmu/qcom_iommu.c
+++ b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
@@ -489,19 +489,22 @@ static void qcom_iommu_iotlb_sync(struct iommu_domain *domain,
 	qcom_iommu_flush_iotlb_all(domain);
 }
 
-static phys_addr_t qcom_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t qcom_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	phys_addr_t ret;
 	unsigned long flags;
 	struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain);
 	struct io_pgtable_ops *ops = qcom_domain->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
 		return 0;
 
 	spin_lock_irqsave(&qcom_domain->pgtbl_lock, flags);
-	ret = ops->iova_to_phys(ops, iova);
+	ret = ops->iova_to_phys_length(ops, iova, mapped_length);
 	spin_unlock_irqrestore(&qcom_domain->pgtbl_lock, flags);
 
 	return ret;
@@ -602,7 +605,7 @@ static const struct iommu_ops qcom_iommu_ops = {
 		.unmap_pages	= qcom_iommu_unmap,
 		.flush_iotlb_all = qcom_iommu_flush_iotlb_all,
 		.iotlb_sync	= qcom_iommu_iotlb_sync,
-		.iova_to_phys	= qcom_iommu_iova_to_phys,
+		.iova_to_phys_length = qcom_iommu_iova_to_phys_length,
 		.free		= qcom_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 5/9] iommu: apple-dart/ipmmu/mtk_iommu implement iova_to_phys_length
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                       ` (3 preceding siblings ...)
  2026-05-31  9:36     ` [PATCH 4/9] iommu/arm-smmu: " Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31 10:32       ` sashiko-bot
  2026-05-31  9:36     ` [PATCH 6/9] iommu: direct page-table drivers " Guanghui Feng
                       ` (4 subsequent siblings)
  9 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Migrate remaining io-pgtable user drivers to implement
iova_to_phys_length, passing through mapped_length from io-pgtable.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/apple-dart.c | 11 +++++++----
 drivers/iommu/ipmmu-vmsa.c | 12 ++++++++----
 drivers/iommu/mtk_iommu.c  | 12 ++++++++----
 3 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 17bdadb6b504..6f78bd28cee7 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -528,16 +528,19 @@ static int apple_dart_iotlb_sync_map(struct iommu_domain *domain,
 	return 0;
 }
 
-static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t apple_dart_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	struct apple_dart_domain *dart_domain = to_dart_domain(domain);
 	struct io_pgtable_ops *ops = dart_domain->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
 		return 0;
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
@@ -1018,7 +1021,7 @@ static const struct iommu_ops apple_dart_iommu_ops = {
 		.flush_iotlb_all = apple_dart_flush_iotlb_all,
 		.iotlb_sync	= apple_dart_iotlb_sync,
 		.iotlb_sync_map	= apple_dart_iotlb_sync_map,
-		.iova_to_phys	= apple_dart_iova_to_phys,
+		.iova_to_phys_length = apple_dart_iova_to_phys_length,
 		.free		= apple_dart_domain_free,
 	}
 };
diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c
index 9386b752dea2..a1b659ddbdb5 100644
--- a/drivers/iommu/ipmmu-vmsa.c
+++ b/drivers/iommu/ipmmu-vmsa.c
@@ -699,14 +699,18 @@ static void ipmmu_iotlb_sync(struct iommu_domain *io_domain,
 	ipmmu_flush_iotlb_all(io_domain);
 }
 
-static phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain,
-				      dma_addr_t iova)
+static phys_addr_t ipmmu_iova_to_phys_length(struct iommu_domain *io_domain,
+				      dma_addr_t iova, size_t *mapped_length)
 {
 	struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	/* TODO: Is locking needed ? */
 
-	return domain->iop->iova_to_phys(domain->iop, iova);
+	return domain->iop->iova_to_phys_length(domain->iop, iova,
+						mapped_length);
 }
 
 static int ipmmu_init_platform_device(struct device *dev,
@@ -892,7 +896,7 @@ static const struct iommu_ops ipmmu_ops = {
 		.unmap_pages	= ipmmu_unmap,
 		.flush_iotlb_all = ipmmu_flush_iotlb_all,
 		.iotlb_sync	= ipmmu_iotlb_sync,
-		.iova_to_phys	= ipmmu_iova_to_phys,
+		.iova_to_phys_length = ipmmu_iova_to_phys_length,
 		.free		= ipmmu_domain_free,
 	}
 };
diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
index 2be990c108de..214cab76e853 100644
--- a/drivers/iommu/mtk_iommu.c
+++ b/drivers/iommu/mtk_iommu.c
@@ -858,13 +858,17 @@ static int mtk_iommu_sync_map(struct iommu_domain *domain, unsigned long iova,
 	return 0;
 }
 
-static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain,
-					  dma_addr_t iova)
+static phys_addr_t mtk_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					  dma_addr_t iova, size_t *mapped_length)
 {
 	struct mtk_iommu_domain *dom = to_mtk_domain(domain);
 	phys_addr_t pa;
 
-	pa = dom->iop->iova_to_phys(dom->iop, iova);
+	if (mapped_length)
+		*mapped_length = 0;
+
+	pa = dom->iop->iova_to_phys_length(dom->iop, iova, mapped_length);
+
 	if (IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT) &&
 	    dom->bank->parent_data->enable_4GB &&
 	    pa >= MTK_IOMMU_4GB_MODE_REMAP_BASE)
@@ -1070,7 +1074,7 @@ static const struct iommu_ops mtk_iommu_ops = {
 		.flush_iotlb_all = mtk_iommu_flush_iotlb_all,
 		.iotlb_sync	= mtk_iommu_iotlb_sync,
 		.iotlb_sync_map	= mtk_iommu_sync_map,
-		.iova_to_phys	= mtk_iommu_iova_to_phys,
+		.iova_to_phys_length = mtk_iommu_iova_to_phys_length,
 		.free		= mtk_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 6/9] iommu: direct page-table drivers implement iova_to_phys_length
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                       ` (4 preceding siblings ...)
  2026-05-31  9:36     ` [PATCH 5/9] iommu: apple-dart/ipmmu/mtk_iommu " Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31 10:47       ` sashiko-bot
  2026-05-31  9:36     ` [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
                       ` (3 subsequent siblings)
  9 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Implement iova_to_phys_length for all IOMMU drivers with direct page
tables. Each returns the actual PTE mapping size.

Also fix mtk_iommu_v1 pre-existing bug: add page offset to PA.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/exynos-iommu.c    | 21 ++++++++++++++++-----
 drivers/iommu/fsl_pamu_domain.c | 26 +++++++++++++++++++++++---
 drivers/iommu/msm_iommu.c       | 25 +++++++++++++++++++------
 drivers/iommu/mtk_iommu_v1.c    | 15 +++++++++++++--
 drivers/iommu/omap-iommu.c      | 32 +++++++++++++++++++++++---------
 drivers/iommu/rockchip-iommu.c  | 11 ++++++++---
 drivers/iommu/s390-iommu.c      | 15 +++++++++++----
 drivers/iommu/sprd-iommu.c      | 13 ++++++++++---
 drivers/iommu/sun50i-iommu.c    | 13 ++++++++++---
 drivers/iommu/tegra-smmu.c      | 12 +++++++++---
 drivers/iommu/virtio-iommu.c    | 13 ++++++++++---
 11 files changed, 152 insertions(+), 44 deletions(-)

diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c
index 874d05f4b396..00ab813ed436 100644
--- a/drivers/iommu/exynos-iommu.c
+++ b/drivers/iommu/exynos-iommu.c
@@ -1372,27 +1372,38 @@ static size_t exynos_iommu_unmap(struct iommu_domain *iommu_domain,
 	return 0;
 }
 
-static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain,
-					  dma_addr_t iova)
+static phys_addr_t exynos_iommu_iova_to_phys_length(struct iommu_domain *iommu_domain,
+						 dma_addr_t iova,
+						 size_t *mapped_length)
 {
 	struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain);
 	sysmmu_pte_t *entry;
 	unsigned long flags;
 	phys_addr_t phys = 0;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&domain->pgtablelock, flags);
 
 	entry = section_entry(domain->pgtable, iova);
 
 	if (lv1ent_section(entry)) {
 		phys = section_phys(entry) + section_offs(iova);
+		if (mapped_length)
+			*mapped_length = SECT_SIZE;
 	} else if (lv1ent_page(entry)) {
 		entry = page_entry(entry, iova);
 
-		if (lv2ent_large(entry))
+		if (lv2ent_large(entry)) {
 			phys = lpage_phys(entry) + lpage_offs(iova);
-		else if (lv2ent_small(entry))
+			if (mapped_length)
+				*mapped_length = LPAGE_SIZE;
+		} else if (lv2ent_small(entry)) {
 			phys = spage_phys(entry) + spage_offs(iova);
+			if (mapped_length)
+				*mapped_length = SPAGE_SIZE;
+		}
 	}
 
 	spin_unlock_irqrestore(&domain->pgtablelock, flags);
@@ -1484,7 +1495,7 @@ static const struct iommu_ops exynos_iommu_ops = {
 		.attach_dev	= exynos_iommu_attach_device,
 		.map_pages	= exynos_iommu_map,
 		.unmap_pages	= exynos_iommu_unmap,
-		.iova_to_phys	= exynos_iommu_iova_to_phys,
+		.iova_to_phys_length	= exynos_iommu_iova_to_phys_length,
 		.free		= exynos_iommu_domain_free,
 	}
 };
diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c
index 9664ef9840d2..48a072074b56 100644
--- a/drivers/iommu/fsl_pamu_domain.c
+++ b/drivers/iommu/fsl_pamu_domain.c
@@ -169,12 +169,32 @@ static void attach_device(struct fsl_dma_domain *dma_domain, int liodn, struct d
 	spin_unlock_irqrestore(&device_domain_lock, flags);
 }
 
-static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain,
-					 dma_addr_t iova)
+static phys_addr_t fsl_pamu_iova_to_phys_length(struct iommu_domain *domain,
+						dma_addr_t iova,
+						size_t *mapped_length)
 {
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (iova < domain->geometry.aperture_start ||
 	    iova > domain->geometry.aperture_end)
 		return 0;
+
+	/*
+	 * PAMU configures exactly one Primary PAACE entry per LIODN with the
+	 * Multi-Window (MW) bit cleared and ATM = PAACE_ATM_WINDOW_XLATE,
+	 * WBAL/TWBAL = 0. That is, every LIODN is backed by a single hardware
+	 * mapping window of fixed size (1ULL << 36, i.e. 64GB, see
+	 * pamu_config_ppaace()) performing an identity translation. The driver
+	 * does not split this window into SPAACE sub-windows, so the entire
+	 * aperture is one PAACE "PTE" entry. Return that single entry's size
+	 * as mapped_length, matching the iova_to_phys_length contract that
+	 * mapped_length reports the full size of the mapping entry which
+	 * covers iova (not the remaining bytes from iova to its end).
+	 */
+	if (mapped_length)
+		*mapped_length = 1ULL << 36;
+
 	return iova;
 }
 
@@ -435,7 +455,7 @@ static const struct iommu_ops fsl_pamu_ops = {
 	.device_group   = fsl_pamu_device_group,
 	.default_domain_ops = &(const struct iommu_domain_ops) {
 		.attach_dev	= fsl_pamu_attach_device,
-		.iova_to_phys	= fsl_pamu_iova_to_phys,
+		.iova_to_phys_length	= fsl_pamu_iova_to_phys_length,
 		.free		= fsl_pamu_domain_free,
 	}
 };
diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c
index 0ad5ff431d5b..f36fdbf8076f 100644
--- a/drivers/iommu/msm_iommu.c
+++ b/drivers/iommu/msm_iommu.c
@@ -523,8 +523,9 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
 	return ret;
 }
 
-static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
-					  dma_addr_t va)
+static phys_addr_t msm_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						 dma_addr_t va,
+						 size_t *mapped_length)
 {
 	struct msm_priv *priv;
 	struct msm_iommu_dev *iommu;
@@ -533,6 +534,9 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
 	unsigned long flags;
 	phys_addr_t ret = 0;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&msm_iommu_lock, flags);
 
 	priv = to_msm_priv(domain);
@@ -558,13 +562,22 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
 	par = GET_PAR(iommu->base, master->num);
 
 	/* We are dealing with a supersection */
-	if (GET_NOFAULT_SS(iommu->base, master->num))
+	if (GET_NOFAULT_SS(iommu->base, master->num)) {
 		ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
-	else	/* Upper 20 bits from PAR, lower 12 from VA */
+		if (mapped_length)
+			*mapped_length = SZ_16M;
+	} else {
+		/* Upper 20 bits from PAR, lower 12 from VA */
 		ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
+		if (mapped_length)
+			*mapped_length = SZ_4K;
+	}
 
-	if (GET_FAULT(iommu->base, master->num))
+	if (GET_FAULT(iommu->base, master->num)) {
 		ret = 0;
+		if (mapped_length)
+			*mapped_length = 0;
+	}
 
 	__disable_clocks(iommu);
 fail:
@@ -706,7 +719,7 @@ static struct iommu_ops msm_iommu_ops = {
 		 */
 		.iotlb_sync	= NULL,
 		.iotlb_sync_map	= msm_iommu_sync_map,
-		.iova_to_phys	= msm_iommu_iova_to_phys,
+		.iova_to_phys_length	= msm_iommu_iova_to_phys_length,
 		.free		= msm_iommu_domain_free,
 	}
 };
diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c
index ac97dd2868d4..a451de7d669e 100644
--- a/drivers/iommu/mtk_iommu_v1.c
+++ b/drivers/iommu/mtk_iommu_v1.c
@@ -393,17 +393,28 @@ static size_t mtk_iommu_v1_unmap(struct iommu_domain *domain, unsigned long iova
 	return size;
 }
 
-static phys_addr_t mtk_iommu_v1_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+static phys_addr_t mtk_iommu_v1_iova_to_phys_length(struct iommu_domain *domain,
+						    dma_addr_t iova,
+						    size_t *mapped_length)
 {
 	struct mtk_iommu_v1_domain *dom = to_mtk_domain(domain);
 	unsigned long flags;
 	phys_addr_t pa;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&dom->pgtlock, flags);
 	pa = *(dom->pgt_va + (iova >> MT2701_IOMMU_PAGE_SHIFT));
 	pa = pa & (~(MT2701_IOMMU_PAGE_SIZE - 1));
 	spin_unlock_irqrestore(&dom->pgtlock, flags);
 
+	if (pa) {
+		pa |= (iova & (MT2701_IOMMU_PAGE_SIZE - 1));
+		if (mapped_length)
+			*mapped_length = MT2701_IOMMU_PAGE_SIZE;
+	}
+
 	return pa;
 }
 
@@ -590,7 +601,7 @@ static const struct iommu_ops mtk_iommu_v1_ops = {
 		.attach_dev	= mtk_iommu_v1_attach_device,
 		.map_pages	= mtk_iommu_v1_map,
 		.unmap_pages	= mtk_iommu_v1_unmap,
-		.iova_to_phys	= mtk_iommu_v1_iova_to_phys,
+		.iova_to_phys_length = mtk_iommu_v1_iova_to_phys_length,
 		.free		= mtk_iommu_v1_domain_free,
 	}
 };
diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c
index 8231d7d6bb6a..91daa69decc9 100644
--- a/drivers/iommu/omap-iommu.c
+++ b/drivers/iommu/omap-iommu.c
@@ -1592,8 +1592,9 @@ static void omap_iommu_domain_free(struct iommu_domain *domain)
 	kfree(omap_domain);
 }
 
-static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t da)
+static phys_addr_t omap_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t da,
+						  size_t *mapped_length)
 {
 	struct omap_iommu_domain *omap_domain = to_omap_domain(domain);
 	struct omap_iommu_device *iommu = omap_domain->iommus;
@@ -1602,6 +1603,9 @@ static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
 	u32 *pgd, *pte;
 	phys_addr_t ret = 0;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	/*
 	 * all the iommus within the domain will have identical programming,
 	 * so perform the lookup using just the first iommu
@@ -1609,21 +1613,31 @@ static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
 	iopgtable_lookup_entry(oiommu, da, &pgd, &pte);
 
 	if (pte) {
-		if (iopte_is_small(*pte))
+		if (iopte_is_small(*pte)) {
 			ret = omap_iommu_translate(*pte, da, IOPTE_MASK);
-		else if (iopte_is_large(*pte))
+			if (ret && mapped_length)
+				*mapped_length = IOPTE_SIZE;
+		} else if (iopte_is_large(*pte)) {
 			ret = omap_iommu_translate(*pte, da, IOLARGE_MASK);
-		else
+			if (ret && mapped_length)
+				*mapped_length = IOLARGE_SIZE;
+		} else {
 			dev_err(dev, "bogus pte 0x%x, da 0x%llx", *pte,
 				(unsigned long long)da);
+		}
 	} else {
-		if (iopgd_is_section(*pgd))
+		if (iopgd_is_section(*pgd)) {
 			ret = omap_iommu_translate(*pgd, da, IOSECTION_MASK);
-		else if (iopgd_is_super(*pgd))
+			if (ret && mapped_length)
+				*mapped_length = IOSECTION_SIZE;
+		} else if (iopgd_is_super(*pgd)) {
 			ret = omap_iommu_translate(*pgd, da, IOSUPER_MASK);
-		else
+			if (ret && mapped_length)
+				*mapped_length = IOSUPER_SIZE;
+		} else {
 			dev_err(dev, "bogus pgd 0x%x, da 0x%llx", *pgd,
 				(unsigned long long)da);
+		}
 	}
 
 	return ret;
@@ -1723,7 +1737,7 @@ static const struct iommu_ops omap_iommu_ops = {
 		.attach_dev	= omap_iommu_attach_dev,
 		.map_pages	= omap_iommu_map,
 		.unmap_pages	= omap_iommu_unmap,
-		.iova_to_phys	= omap_iommu_iova_to_phys,
+		.iova_to_phys_length	= omap_iommu_iova_to_phys_length,
 		.free		= omap_iommu_domain_free,
 	}
 };
diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c
index 0013cf196c57..dad3ff38fe2c 100644
--- a/drivers/iommu/rockchip-iommu.c
+++ b/drivers/iommu/rockchip-iommu.c
@@ -648,8 +648,8 @@ static irqreturn_t rk_iommu_irq(int irq, void *dev_id)
 	return ret;
 }
 
-static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
-					 dma_addr_t iova)
+static phys_addr_t rk_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					 dma_addr_t iova, size_t *mapped_length)
 {
 	struct rk_iommu_domain *rk_domain = to_rk_domain(domain);
 	unsigned long flags;
@@ -657,6 +657,9 @@ static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
 	u32 dte, pte;
 	u32 *page_table;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&rk_domain->dt_lock, flags);
 
 	dte = rk_domain->dt[rk_iova_dte_index(iova)];
@@ -670,6 +673,8 @@ static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
 		goto out;
 
 	phys = rk_ops->pt_address(pte) + rk_iova_page_offset(iova);
+	if (mapped_length)
+		*mapped_length = SPAGE_SIZE;
 out:
 	spin_unlock_irqrestore(&rk_domain->dt_lock, flags);
 
@@ -1187,7 +1192,7 @@ static const struct iommu_ops rk_iommu_ops = {
 		.attach_dev	= rk_iommu_attach_device,
 		.map_pages	= rk_iommu_map,
 		.unmap_pages	= rk_iommu_unmap,
-		.iova_to_phys	= rk_iommu_iova_to_phys,
+		.iova_to_phys_length = rk_iommu_iova_to_phys_length,
 		.free		= rk_iommu_domain_free,
 	}
 };
diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c
index f148f559ac56..8df98aceb5f7 100644
--- a/drivers/iommu/s390-iommu.c
+++ b/drivers/iommu/s390-iommu.c
@@ -986,8 +986,9 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,
 	}
 }
 
-static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t s390_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t iova,
+						  size_t *mapped_length)
 {
 	struct s390_domain *s390_domain = to_s390_domain(domain);
 	unsigned long *rto, *sto, *pto;
@@ -995,6 +996,9 @@ static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
 	unsigned int rtx, sx, px;
 	phys_addr_t phys = 0;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (iova < domain->geometry.aperture_start ||
 	    iova > domain->geometry.aperture_end)
 		return 0;
@@ -1014,8 +1018,11 @@ static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
 		if (reg_entry_isvalid(ste)) {
 			pto = get_st_pto(ste);
 			pte = READ_ONCE(pto[px]);
-			if (pt_entry_isvalid(pte))
+			if (pt_entry_isvalid(pte)) {
 				phys = pte & ZPCI_PTE_ADDR_MASK;
+				if (mapped_length)
+					*mapped_length = SZ_4K;
+			}
 		}
 	}
 
@@ -1183,7 +1190,7 @@ static struct iommu_domain blocking_domain = {
 		.flush_iotlb_all = s390_iommu_flush_iotlb_all, \
 		.iotlb_sync      = s390_iommu_iotlb_sync, \
 		.iotlb_sync_map  = s390_iommu_iotlb_sync_map, \
-		.iova_to_phys	= s390_iommu_iova_to_phys, \
+		.iova_to_phys_length	= s390_iommu_iova_to_phys_length, \
 		.free		= s390_domain_free, \
 	}
 
diff --git a/drivers/iommu/sprd-iommu.c b/drivers/iommu/sprd-iommu.c
index c1a34445d244..da14574a6430 100644
--- a/drivers/iommu/sprd-iommu.c
+++ b/drivers/iommu/sprd-iommu.c
@@ -366,8 +366,9 @@ static void sprd_iommu_sync(struct iommu_domain *domain,
 	sprd_iommu_sync_map(domain, 0, 0);
 }
 
-static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t sprd_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t iova,
+						  size_t *mapped_length)
 {
 	struct sprd_iommu_domain *dom = to_sprd_domain(domain);
 	unsigned long flags;
@@ -375,6 +376,9 @@ static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
 	unsigned long start = domain->geometry.aperture_start;
 	unsigned long end = domain->geometry.aperture_end;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (WARN_ON(iova < start || iova > end))
 		return 0;
 
@@ -383,6 +387,9 @@ static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
 	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
 	spin_unlock_irqrestore(&dom->pgtlock, flags);
 
+	if (pa && mapped_length)
+		*mapped_length = SPRD_IOMMU_PAGE_SIZE;
+
 	return pa;
 }
 
@@ -420,7 +427,7 @@ static const struct iommu_ops sprd_iommu_ops = {
 		.unmap_pages	= sprd_iommu_unmap,
 		.iotlb_sync_map	= sprd_iommu_sync_map,
 		.iotlb_sync	= sprd_iommu_sync,
-		.iova_to_phys	= sprd_iommu_iova_to_phys,
+		.iova_to_phys_length	= sprd_iommu_iova_to_phys_length,
 		.free		= sprd_iommu_domain_free,
 	}
 };
diff --git a/drivers/iommu/sun50i-iommu.c b/drivers/iommu/sun50i-iommu.c
index be3f1ce696ba..66c354baaca1 100644
--- a/drivers/iommu/sun50i-iommu.c
+++ b/drivers/iommu/sun50i-iommu.c
@@ -659,14 +659,18 @@ static size_t sun50i_iommu_unmap(struct iommu_domain *domain, unsigned long iova
 	return SZ_4K;
 }
 
-static phys_addr_t sun50i_iommu_iova_to_phys(struct iommu_domain *domain,
-					     dma_addr_t iova)
+static phys_addr_t sun50i_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						    dma_addr_t iova,
+						    size_t *mapped_length)
 {
 	struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
 	phys_addr_t pt_phys;
 	u32 *page_table;
 	u32 dte, pte;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	dte = sun50i_domain->dt[sun50i_iova_get_dte_index(iova)];
 	if (!sun50i_dte_is_pt_valid(dte))
 		return 0;
@@ -677,6 +681,9 @@ static phys_addr_t sun50i_iommu_iova_to_phys(struct iommu_domain *domain,
 	if (!sun50i_pte_is_page_valid(pte))
 		return 0;
 
+	if (mapped_length)
+		*mapped_length = SZ_4K;
+
 	return sun50i_pte_get_page_address(pte) +
 		sun50i_iova_get_page_offset(iova);
 }
@@ -857,7 +864,7 @@ static const struct iommu_ops sun50i_iommu_ops = {
 		.flush_iotlb_all = sun50i_iommu_flush_iotlb_all,
 		.iotlb_sync_map = sun50i_iommu_iotlb_sync_map,
 		.iotlb_sync	= sun50i_iommu_iotlb_sync,
-		.iova_to_phys	= sun50i_iommu_iova_to_phys,
+		.iova_to_phys_length	= sun50i_iommu_iova_to_phys_length,
 		.map_pages	= sun50i_iommu_map,
 		.unmap_pages	= sun50i_iommu_unmap,
 		.free		= sun50i_iommu_domain_free,
diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
index 67e7a7b925f0..c2545d577fdc 100644
--- a/drivers/iommu/tegra-smmu.c
+++ b/drivers/iommu/tegra-smmu.c
@@ -803,20 +803,26 @@ static size_t tegra_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
 	return size;
 }
 
-static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t tegra_smmu_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	struct tegra_smmu_as *as = to_smmu_as(domain);
 	unsigned long pfn;
 	dma_addr_t pte_dma;
 	u32 *pte;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	pte = tegra_smmu_pte_lookup(as, iova, &pte_dma);
 	if (!pte || !*pte)
 		return 0;
 
 	pfn = *pte & as->smmu->pfn_mask;
 
+	if (mapped_length)
+		*mapped_length = SZ_4K;
+
 	return SMMU_PFN_PHYS(pfn) + SMMU_OFFSET_IN_PAGE(iova);
 }
 
@@ -1007,7 +1013,7 @@ static const struct iommu_ops tegra_smmu_ops = {
 		.attach_dev	= tegra_smmu_attach_dev,
 		.map_pages	= tegra_smmu_map,
 		.unmap_pages	= tegra_smmu_unmap,
-		.iova_to_phys	= tegra_smmu_iova_to_phys,
+		.iova_to_phys_length = tegra_smmu_iova_to_phys_length,
 		.free		= tegra_smmu_domain_free,
 	}
 };
diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c
index 587fc13197f1..80c9c06a1380 100644
--- a/drivers/iommu/virtio-iommu.c
+++ b/drivers/iommu/virtio-iommu.c
@@ -912,8 +912,9 @@ static size_t viommu_unmap_pages(struct iommu_domain *domain, unsigned long iova
 	return ret ? 0 : unmapped;
 }
 
-static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
-				       dma_addr_t iova)
+static phys_addr_t viommu_iova_to_phys_length(struct iommu_domain *domain,
+					      dma_addr_t iova,
+					      size_t *mapped_length)
 {
 	u64 paddr = 0;
 	unsigned long flags;
@@ -921,11 +922,17 @@ static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
 	struct interval_tree_node *node;
 	struct viommu_domain *vdomain = to_viommu_domain(domain);
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&vdomain->mappings_lock, flags);
 	node = interval_tree_iter_first(&vdomain->mappings, iova, iova);
 	if (node) {
 		mapping = container_of(node, struct viommu_mapping, iova);
 		paddr = mapping->paddr + (iova - mapping->iova.start);
+		if (mapped_length)
+			*mapped_length = mapping->iova.last -
+					 mapping->iova.start + 1;
 	}
 	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
 
@@ -1102,7 +1109,7 @@ static const struct iommu_ops viommu_ops = {
 		.attach_dev		= viommu_attach_dev,
 		.map_pages		= viommu_map_pages,
 		.unmap_pages		= viommu_unmap_pages,
-		.iova_to_phys		= viommu_iova_to_phys,
+		.iova_to_phys_length	= viommu_iova_to_phys_length,
 		.flush_iotlb_all	= viommu_flush_iotlb_all,
 		.iotlb_sync		= viommu_iotlb_sync,
 		.iotlb_sync_map		= viommu_iotlb_sync_map,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                       ` (5 preceding siblings ...)
  2026-05-31  9:36     ` [PATCH 6/9] iommu: direct page-table drivers " Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31 11:01       ` sashiko-bot
  2026-05-31 23:58       ` Jason Gunthorpe
  2026-05-31  9:36     ` [PATCH 8/9] drm/gpu, iommu/io-pgtable: switch to iova_to_phys_length Guanghui Feng
                       ` (2 subsequent siblings)
  9 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Use iommu_iova_to_phys_length() to get PTE page size, allowing
traversal by actual mapping granularity instead of PAGE_SIZE steps.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/iommufd/pages.c    | 71 ++++++++++++++++++++++++++------
 drivers/iommu/iommufd/selftest.c |  2 +-
 drivers/vfio/vfio_iommu_type1.c  | 24 +++++++++--
 3 files changed, 80 insertions(+), 17 deletions(-)

diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index 9bdb2945afe1..d67e564035b4 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -417,17 +417,42 @@ static void batch_from_domain(struct pfn_batch *batch,
 	if (start_index == iopt_area_index(area))
 		page_offset = area->page_offset;
 	while (start_index <= last_index) {
+		size_t pgsize;
+		unsigned long npages;
+		unsigned long i;
+
 		/*
-		 * This is pretty slow, it would be nice to get the page size
-		 * back from the driver, or have the driver directly fill the
-		 * batch.
+		 * Use iova_to_phys_length to get both the physical address
+		 * and the PTE page size in a single page table walk, allowing
+		 * us to skip ahead by the contiguous region size instead of
+		 * walking the page tables for every PAGE_SIZE step.
 		 */
-		phys = iommu_iova_to_phys(domain, iova) - page_offset;
-		if (!batch_add_pfn(batch, PHYS_PFN(phys)))
-			return;
-		iova += PAGE_SIZE - page_offset;
+		phys = iommu_iova_to_phys_length(domain, iova, &pgsize) -
+		       page_offset;
+		if (!pgsize || pgsize < PAGE_SIZE)
+			pgsize = PAGE_SIZE;
+
+		/*
+		 * Calculate contiguous pages within this PTE from our
+		 * position. phys points to the page-aligned start (backed
+		 * up by page_offset), so pages available = bytes from phys
+		 * to PTE end divided by PAGE_SIZE.
+		 */
+		npages = (pgsize - (iova & (pgsize - 1)) + page_offset) /
+			 PAGE_SIZE;
+		npages = min_t(unsigned long, npages,
+			       last_index - start_index + 1);
+		if (!npages)
+			npages = 1;
+
+		for (i = 0; i < npages; i++) {
+			if (!batch_add_pfn(batch, PHYS_PFN(phys) + i))
+				return;
+		}
+
+		iova += npages * PAGE_SIZE - page_offset;
 		page_offset = 0;
-		start_index++;
+		start_index += npages;
 	}
 }
 
@@ -445,11 +470,33 @@ static struct page **raw_pages_from_domain(struct iommu_domain *domain,
 	if (start_index == iopt_area_index(area))
 		page_offset = area->page_offset;
 	while (start_index <= last_index) {
-		phys = iommu_iova_to_phys(domain, iova) - page_offset;
-		*(out_pages++) = pfn_to_page(PHYS_PFN(phys));
-		iova += PAGE_SIZE - page_offset;
+		size_t pgsize;
+		unsigned long npages;
+		unsigned long i;
+
+		/*
+		 * Resolve the PTE page size together with the physical
+		 * address so we can fill multiple struct page pointers per
+		 * page table walk when the IOMMU uses large pages.
+		 */
+		phys = iommu_iova_to_phys_length(domain, iova, &pgsize) -
+		       page_offset;
+		if (!pgsize || pgsize < PAGE_SIZE)
+			pgsize = PAGE_SIZE;
+
+		npages = (pgsize - (iova & (pgsize - 1)) + page_offset) /
+			 PAGE_SIZE;
+		npages = min_t(unsigned long, npages,
+			       last_index - start_index + 1);
+		if (!npages)
+			npages = 1;
+
+		for (i = 0; i < npages; i++)
+			*(out_pages++) = pfn_to_page(PHYS_PFN(phys) + i);
+
+		iova += npages * PAGE_SIZE - page_offset;
 		page_offset = 0;
-		start_index++;
+		start_index += npages;
 	}
 	return out_pages;
 }
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index af07c642a526..4b9c3ffc9523 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -1214,7 +1214,7 @@ static int iommufd_test_md_check_pa(struct iommufd_ucmd *ucmd,
 		pfn = page_to_pfn(pages[0]);
 		put_page(pages[0]);
 
-		io_phys = mock->domain.ops->iova_to_phys(&mock->domain, iova);
+		io_phys = iommu_iova_to_phys(&mock->domain, iova);
 		if (io_phys !=
 		    pfn * PAGE_SIZE + ((uintptr_t)uptr % PAGE_SIZE)) {
 			rc = -EINVAL;
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
index c8151ba54de3..393f9e8f1511 100644
--- a/drivers/vfio/vfio_iommu_type1.c
+++ b/drivers/vfio/vfio_iommu_type1.c
@@ -1177,25 +1177,41 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
 
 	iommu_iotlb_gather_init(&iotlb_gather);
 	while (pos < dma->size) {
-		size_t unmapped, len;
+		size_t unmapped, len, pgsize;
 		phys_addr_t phys, next;
 		dma_addr_t iova = dma->iova + pos;
 
-		phys = iommu_iova_to_phys(domain->domain, iova);
+		/* Single page table walk returns both phys and PTE size */
+		phys = iommu_iova_to_phys_length(domain->domain, iova,
+						  &pgsize);
 		if (WARN_ON(!phys)) {
 			pos += PAGE_SIZE;
 			continue;
 		}
+		if (!pgsize || pgsize < PAGE_SIZE)
+			pgsize = PAGE_SIZE;
 
 		/*
 		 * To optimize for fewer iommu_unmap() calls, each of which
 		 * may require hardware cache flushing, try to find the
 		 * largest contiguous physical memory chunk to unmap.
+		 *
+		 * Calculate remaining contiguous bytes within this PTE from
+		 * our position, then try to join following physically
+		 * contiguous PTEs.
 		 */
-		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
-			next = iommu_iova_to_phys(domain->domain, iova + len);
+		len = pgsize - (iova & (pgsize - 1));
+		for (; pos + len < dma->size; ) {
+			size_t next_pgsize;
+
+			next = iommu_iova_to_phys_length(domain->domain,
+							  iova + len,
+							  &next_pgsize);
 			if (next != phys + len)
 				break;
+			if (!next_pgsize || next_pgsize < PAGE_SIZE)
+				next_pgsize = PAGE_SIZE;
+			len += next_pgsize;
 		}
 
 		/*
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 8/9] drm/gpu, iommu/io-pgtable: switch to iova_to_phys_length
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                       ` (6 preceding siblings ...)
  2026-05-31  9:36     ` [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31  9:36     ` [PATCH 9/9] iommu: remove deprecated iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
  9 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Migrate remaining callers of io_pgtable_ops.iova_to_phys:
- panthor_mmu: use ops->iova_to_phys_length(ops, iova, NULL)
- panfrost_mmu: same
- io-pgtable selftests: use iova_to_phys_length with NULL

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/gpu/drm/panfrost/panfrost_mmu.c  |  2 +-
 drivers/gpu/drm/panthor/panthor_mmu.c    |  2 +-
 drivers/iommu/io-pgtable-arm-selftests.c | 12 ++++++------
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/panfrost/panfrost_mmu.c b/drivers/gpu/drm/panfrost/panfrost_mmu.c
index 4a3162c3b659..b7c420eb76ec 100644
--- a/drivers/gpu/drm/panfrost/panfrost_mmu.c
+++ b/drivers/gpu/drm/panfrost/panfrost_mmu.c
@@ -514,7 +514,7 @@ void panfrost_mmu_unmap(struct panfrost_gem_mapping *mapping)
 
 		if (bo->is_heap)
 			pgcount = 1;
-		if (!bo->is_heap || ops->iova_to_phys(ops, iova)) {
+		if (!bo->is_heap || ops->iova_to_phys_length(ops, iova, NULL)) {
 			unmapped_page = ops->unmap_pages(ops, iova, pgsize, pgcount, NULL);
 			WARN_ON(unmapped_page != pgsize * pgcount);
 		}
diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
index 75d98dad7b1d..05bc7ec95931 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.c
+++ b/drivers/gpu/drm/panthor/panthor_mmu.c
@@ -903,7 +903,7 @@ static void panthor_vm_unmap_pages(struct panthor_vm *vm, u64 iova, u64 size)
 			 * are out-of-sync. This is not supposed to happen, hence the
 			 * above WARN_ON().
 			 */
-			while (!ops->iova_to_phys(ops, iova + unmapped_sz) &&
+			while (!ops->iova_to_phys_length(ops, iova + unmapped_sz, NULL) &&
 			       unmapped_sz < pgsize * pgcount)
 				unmapped_sz += SZ_4K;
 
diff --git a/drivers/iommu/io-pgtable-arm-selftests.c b/drivers/iommu/io-pgtable-arm-selftests.c
index 334e70350924..d1d0529f711d 100644
--- a/drivers/iommu/io-pgtable-arm-selftests.c
+++ b/drivers/iommu/io-pgtable-arm-selftests.c
@@ -72,13 +72,13 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 		 * Initial sanity checks.
 		 * Empty page tables shouldn't provide any translations.
 		 */
-		if (ops->iova_to_phys(ops, 42))
+		if (ops->iova_to_phys_length(ops, 42, NULL))
 			return __FAIL(test, i);
 
-		if (ops->iova_to_phys(ops, SZ_1G + 42))
+		if (ops->iova_to_phys_length(ops, SZ_1G + 42, NULL))
 			return __FAIL(test, i);
 
-		if (ops->iova_to_phys(ops, SZ_2G + 42))
+		if (ops->iova_to_phys_length(ops, SZ_2G + 42, NULL))
 			return __FAIL(test, i);
 
 		/*
@@ -100,7 +100,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 					    GFP_KERNEL, &mapped))
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 				return __FAIL(test, i);
 
 			iova += SZ_1G;
@@ -114,7 +114,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 			if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL))
 				return __FAIL(test, i);
 
 			/* Remap full block */
@@ -122,7 +122,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 					   IOMMU_WRITE, GFP_KERNEL, &mapped))
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 				return __FAIL(test, i);
 
 			iova += SZ_1G;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH 9/9] iommu: remove deprecated iova_to_phys from domain_ops and io_pgtable_ops
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                       ` (7 preceding siblings ...)
  2026-05-31  9:36     ` [PATCH 8/9] drm/gpu, iommu/io-pgtable: switch to iova_to_phys_length Guanghui Feng
@ 2026-05-31  9:36     ` Guanghui Feng
  2026-05-31 11:17       ` sashiko-bot
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
  9 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-05-31  9:36 UTC (permalink / raw)
  To: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, jgg, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2
  Cc: xlpang, oliver.yang, shiyu.zsq, wei.guo.simon

Now that all drivers implement iova_to_phys_length and all callers
have migrated, remove the deprecated interfaces:

- Remove .iova_to_phys from struct iommu_domain_ops
- Remove .iova_to_phys from struct io_pgtable_ops
- Remove fallback in iommu_iova_to_phys_length()
- Make iommu_iova_to_phys() a thin wrapper
- Remove old wrapper functions from io-pgtable implementations

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-v7s.c | 11 -----------
 drivers/iommu/io-pgtable-arm.c     | 11 -----------
 drivers/iommu/io-pgtable-dart.c    | 11 -----------
 drivers/iommu/iommu.c              |  4 ----
 include/linux/io-pgtable.h         |  3 ---
 include/linux/iommu.h              |  3 ---
 6 files changed, 43 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
index 54e8cc673eb6..00c09710b01b 100644
--- a/drivers/iommu/io-pgtable-arm-v7s.c
+++ b/drivers/iommu/io-pgtable-arm-v7s.c
@@ -641,16 +641,6 @@ static size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova
 	return unmapped;
 }
 
-static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
-						unsigned long iova,
-						size_t *mapped_length);
-
-static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
-					unsigned long iova)
-{
-	return arm_v7s_iova_to_phys_length(ops, iova, NULL);
-}
-
 static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
 						unsigned long iova,
 						size_t *mapped_length)
@@ -730,7 +720,6 @@ static struct io_pgtable *arm_v7s_alloc_pgtable(struct io_pgtable_cfg *cfg,
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages	= arm_v7s_map_pages,
 		.unmap_pages	= arm_v7s_unmap_pages,
-		.iova_to_phys	= arm_v7s_iova_to_phys,
 		.iova_to_phys_length	= arm_v7s_iova_to_phys_length,
 	};
 
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index e2ea84669fbf..c7419f812e79 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -731,16 +731,6 @@ static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
 	return 0;
 }
 
-static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
-						 unsigned long iova,
-						 size_t *mapped_length);
-
-static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
-					 unsigned long iova)
-{
-	return arm_lpae_iova_to_phys_length(ops, iova, NULL);
-}
-
 static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
 						 unsigned long iova,
 						 size_t *mapped_length)
@@ -963,7 +953,6 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages	= arm_lpae_map_pages,
 		.unmap_pages	= arm_lpae_unmap_pages,
-		.iova_to_phys	= arm_lpae_iova_to_phys,
 		.iova_to_phys_length	= arm_lpae_iova_to_phys_length,
 		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
 		.pgtable_walk	= arm_lpae_pgtable_walk,
diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index fa250f5d16ed..f71c04a4c15e 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -333,16 +333,6 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	return i * pgsize;
 }
 
-static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
-					    unsigned long iova,
-					    size_t *mapped_length);
-
-static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
-				     unsigned long iova)
-{
-	return dart_iova_to_phys_length(ops, iova, NULL);
-}
-
 static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
 					    unsigned long iova,
 					    size_t *mapped_length)
@@ -414,7 +404,6 @@ dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages		= dart_map_pages,
 		.unmap_pages		= dart_unmap_pages,
-		.iova_to_phys		= dart_iova_to_phys,
 		.iova_to_phys_length	= dart_iova_to_phys_length,
 	};
 
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 43323229a1df..df56635de39d 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2573,10 +2573,6 @@ phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
 		return domain->ops->iova_to_phys_length(domain, iova,
 							mapped_length);
 
-	/* Fallback to legacy iova_to_phys without length info */
-	if (domain->ops->iova_to_phys)
-		return domain->ops->iova_to_phys(domain, iova);
-
 	return 0;
 }
 EXPORT_SYMBOL_GPL(iommu_iova_to_phys_length);
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index 42bcdd309b88..c595f5b51e61 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -202,7 +202,6 @@ struct arm_lpae_io_pgtable_walk_data {
  *
  * @map_pages:    Map a physically contiguous range of pages of the same size.
  * @unmap_pages:  Unmap a range of virtually contiguous pages of the same size.
- * @iova_to_phys: Translate iova to physical address.
  * @iova_to_phys_length: Translate iova to physical address and return the
  *			  remaining mapped length from iova to the end of the
  *			  mapping entry via @mapped_length. If @mapped_length is
@@ -222,8 +221,6 @@ struct io_pgtable_ops {
 	size_t (*unmap_pages)(struct io_pgtable_ops *ops, unsigned long iova,
 			      size_t pgsize, size_t pgcount,
 			      struct iommu_iotlb_gather *gather);
-	phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,
-				    unsigned long iova);
 	phys_addr_t (*iova_to_phys_length)(struct io_pgtable_ops *ops,
 					   unsigned long iova,
 					   size_t *mapped_length);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 19da84c2922c..ca585647180b 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -746,7 +746,6 @@ struct iommu_ops {
  *                         array->entry_num to report the number of handled
  *                         invalidation requests. The driver data structure
  *                         must be defined in include/uapi/linux/iommufd.h
- * @iova_to_phys: translate iova to physical address
  * @iova_to_phys_length: translate iova to physical address and additionally
  *                       return the page size of the PTE mapping at @iova
  *                       through @mapped_length.
@@ -777,8 +776,6 @@ struct iommu_domain_ops {
 	int (*cache_invalidate_user)(struct iommu_domain *domain,
 				     struct iommu_user_data_array *array);
 
-	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
-				    dma_addr_t iova);
 	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
 					    dma_addr_t iova,
 					    size_t *mapped_length);
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* Re: [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-05-31  9:36     ` [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
@ 2026-05-31  9:54       ` sashiko-bot
  2026-05-31 23:51       ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31  9:54 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Out-of-bounds physical memory access in IOMMUFD due to non-power-of-2 mapping size returned by virtio-iommu.
- [Medium] Missing static inline stub for iommu_iova_to_phys_length when CONFIG_IOMMU_API is disabled.
--

commit 1d85ac7114cff28b195e3c83a6653359f5181460
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu: introduce iova_to_phys_length in iommu_domain_ops

Add iova_to_phys_length callback to struct iommu_domain_ops alongside
the existing iova_to_phys. The new callback returns both the physical
address and the PTE mapping page size in a single page table walk.

> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index d1a9e713d3a05..43323229a1df3 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
[ ... ]
> @@ -2545,15 +2545,45 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
>  }
>  EXPORT_SYMBOL_GPL(iommu_detach_group);
>  
> -phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
> +/**
> + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
> + * @domain: IOMMU domain to query
> + * @iova: IO virtual address to translate
> + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
> + *
> + * Like iommu_iova_to_phys() but additionally returns the page size of the
> + * PTE mapping at @iova through @mapped_length.
> + *
> + * Return: The physical address for the given IOVA, or 0 if no translation.
> + */
> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> +				       dma_addr_t iova,
> +				       size_t *mapped_length)

[Severity: Critical]
Is it possible for the underlying domain implementation to return a
non-power-of-2 length here?

If a driver like virtio-iommu implements iova_to_phys_length by returning
the size of the user mapping region stored in its interval tree
(e.g., mapping->iova.last - mapping->iova.start + 1), it can return an
unaligned or non-power-of-2 value (like 12KB).

Callers like IOMMUFD use bitwise math assuming the length is a power-of-2:

drivers/iommu/iommufd/pages.c:batch_from_domain() {
    ...
    npages = (pgsize - (iova & (pgsize - 1)) + page_offset) / PAGE_SIZE;
    for (i = 0; i < npages; i++) {
        if (!batch_add_pfn(batch, PHYS_PFN(phys) + i))
            return;
    }
    ...
}

VFIO does the same:

drivers/vfio/vfio_iommu_type1.c:vfio_unmap_unpin() {
    ...
    len = pgsize - (iova & (pgsize - 1));
    ...
}

When an unaligned or non-power-of-2 length is returned, pgsize - 1 is not
a valid bitmask, which can cause the formula to overestimate the remaining
contiguous length. This exposes unmapped physical memory, and could allow
an out-of-bounds physical memory access or unmapping lengths that exceed
the actual mapping.

Could this be documented to strictly require power-of-2 PTE page sizes,
or could the callers be updated to handle non-power-of-2 region lengths?

[ ... ]
> diff --git a/include/linux/iommu.h b/include/linux/iommu.h
> index bf8a77a164e4d..29c7c042d400d 100644
> --- a/include/linux/iommu.h
> +++ b/include/linux/iommu.h
[ ... ]
> @@ -958,6 +964,9 @@ extern ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
>  			    struct scatterlist *sg, unsigned int nents,
>  			    int prot, gfp_t gfp);
>  extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova);
> +extern phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> +					      dma_addr_t iova,
> +					      size_t *mapped_length);

[Severity: Medium]
Will this cause build failures for configurations without CONFIG_IOMMU_API?

It appears that the static inline stub for iommu_iova_to_phys_length() is
missing in the #else block for CONFIG_IOMMU_API in this header. Generic
drivers that use this API might encounter compilation and linkage failures
when IOMMU_API is disabled.

>  extern void iommu_set_fault_handler(struct iommu_domain *domain,
>  			iommu_fault_handler_t handler, void *token);
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=1

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 2/9] iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops
  2026-05-31  9:36     ` [PATCH 2/9] iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
@ 2026-05-31 10:03       ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31 10:03 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [Critical] The `iova_to_phys_length` implementations return the total block/page size instead of the remaining mapped length, violating the API contract and risking out-of-bounds traversal.

Pre-existing issues:
- [High] Uninitialized stack variable usage in `arm_lpae_iova_to_phys_length` when `iova` is `ULONG_MAX`.
- [High] Data race and TOCTOU vulnerability due to unprotected double-reading of the PTE in the page table walk callback.
--

commit 3219288135ef40cd65a3a4a3c01d71fbe03eb398
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Subject: iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops

This patch adds iova_to_phys_length to struct io_pgtable_ops. The new
callback returns the mapped length from iova to the end of the mapping
entry through the mapped_length parameter, and is implemented for the
ARM LPAE, ARM v7s, and DART backends.

> diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
> --- a/drivers/iommu/io-pgtable-arm-v7s.c
> +++ b/drivers/iommu/io-pgtable-arm-v7s.c

[ ... ]

> @@ -661,6 +673,11 @@ static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
>  	mask = ARM_V7S_LVL_MASK(lvl);
>  	if (arm_v7s_pte_is_cont(pte, lvl))
>  		mask *= ARM_V7S_CONT_PAGES;
> +
> +	blk_size = ~mask + 1U;
> +	if (mapped_length)
> +		*mapped_length = blk_size;

[Severity: Critical]
Does this return the total block size instead of the remaining mapped length?

The io_pgtable_ops documentation requires returning the remaining mapped length
from the iova to the end of the mapping entry. However, this seems to return
the full block size without subtracting the intra-block offset.

If a caller passes an unaligned iova and uses mapped_length to advance its
traversal iterator, could this cause it to overshoot the current block and
skip subsequent page table regions?

This same pattern appears to be used in the ARM LPAE and DART implementations
added in this patch as well.

> diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
> --- a/drivers/iommu/io-pgtable-arm.c
> +++ b/drivers/iommu/io-pgtable-arm.c

[ ... ]

>  static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
>  			      arm_lpae_iopte *ptep, size_t size)
>  {
>  	struct iova_to_phys_data *data = walk_data->data;
>  	data->pte = *ptep;

[Severity: High]
This isn't a bug introduced by this patch, but could this unprotected read
of *ptep lead to a torn read or a Time-of-Check to Time-of-Use issue?

The caller io_pgtable_visit() safely reads the PTE into a local variable
using READ_ONCE() to validate it, but passes the raw ptep pointer to this
callback.

If the PTE is modified concurrently between the READ_ONCE() in the caller
and the direct dereference here, could we end up with torn reads on 32-bit
ARM systems or return a garbage physical address translation?

>  	data->lvl = lvl;
>  	return 0;
>  }

[ ... ]

> +static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
> +						 unsigned long iova,
> +						 size_t *mapped_length)
>  {
>  	struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
>  	struct iova_to_phys_data d;
>  	struct io_pgtable_walk_data walk_data = {
>  		.data = &d,
>  		.visit = visit_iova_to_phys,
>  		.addr = iova,
>  		.end = iova + 1,
>  	};

[Severity: High]
This is a pre-existing issue, but can walk_data.end overflow if iova is
ULONG_MAX?

If iova is ULONG_MAX, iova + 1 would wrap around to 0. During the walk in
__arm_lpae_iopte_walk(), the loop condition checks walk_data->addr <
walk_data->end. With end being 0, wouldn't this condition be immediately false?

If the walk returns 0 (success) without calling the visit callback, could we
end up using the uninitialized d.lvl and d.pte fields to compute block_size
and the physical address?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=2

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length
  2026-05-31  9:36     ` [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
@ 2026-05-31 10:12       ` sashiko-bot
  2026-05-31 23:54       ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31 10:12 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Direct calls to `ops->iova_to_phys` in the iommufd selftests will cause a NULL pointer dereference because `IOMMU_PT_DOMAIN_OPS` no longer initializes this function pointer.
--

commit 3d28cd1b26aa1da26180cde7d750a84030c72a61
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/generic_pt: implement iova_to_phys_length

Extend the Generic Page Table framework to implement iova_to_phys_length
by determining the PTE block size during the page table walk. This also
updates the IOMMU_PT_DOMAIN_OPS macro to configure the new function.

> diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
> index dd0edd02a48a2..859b853e9dc7e 100644
> --- a/include/linux/generic_pt/iommu.h
> +++ b/include/linux/generic_pt/iommu.h
[ ... ]
> @@ -267,11 +268,11 @@ struct pt_iommu_cfg {
>  	IOMMU_PROTOTYPES(fmt)
>  
>  /*
> - * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the
> - * iommu_pt
> + * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for
> + * the iommu_pt
>   */
> -#define IOMMU_PT_DOMAIN_OPS(fmt)                        \
> -	.iova_to_phys = &pt_iommu_##fmt##_iova_to_phys
> +#define IOMMU_PT_DOMAIN_OPS(fmt)					\
> +	.iova_to_phys_length = &pt_iommu_##fmt##_iova_to_phys_length

[Severity: High]
Will removing .iova_to_phys here cause a NULL pointer dereference in the
iommufd selftests?

The amdv1_mock_ops in drivers/iommu/iommufd/selftest.c uses this macro,
so its .iova_to_phys field will now be implicitly NULL.

However, the selftest directly dereferences this function pointer without
checking:

drivers/iommu/iommufd/selftest.c:iommufd_test_md_check_pa() {
    ...
    io_phys = mock->domain.ops->iova_to_phys(&mock->domain, iova);
    ...
}

While I understand this is addressed later in the patch series when the
code properly switches to using the iommu_iova_to_phys() wrapper, will
this intermediate state break git bisect by crashing the kernel during
testing?

>  #define IOMMU_PT_DIRTY_OPS(fmt) \
>  	.read_and_clear_dirty = &pt_iommu_##fmt##_read_and_clear_dirty
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=3

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 4/9] iommu/arm-smmu: implement iova_to_phys_length
  2026-05-31  9:36     ` [PATCH 4/9] iommu/arm-smmu: " Guanghui Feng
@ 2026-05-31 10:22       ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31 10:22 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] `arm_smmu_iova_to_phys_length` fails to populate `mapped_length` when using the hardware translation path, silently defeating the page table walk optimization.

Pre-existing issues:
- [High] The `qcom_smmu_context_fault` interrupt handler can dereference a NULL `pgtbl_ops` pointer if a context fault interrupt fires before domain initialization completes.
--

commit 3fdd57d4dce5ad5d48f3b578b79029642d87bc3e
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/arm-smmu: implement iova_to_phys_length

Migrate ARM SMMU drivers to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer to get both phys
and PTE page size.

> diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> index 65e0ef6539fe7..4fd01341157f3 100644
> --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> @@ -415,7 +415,7 @@ irqreturn_t qcom_smmu_context_fault(int irq, void *dev)
>  		return IRQ_HANDLED;
>  	}
>  
> -	phys_soft = ops->iova_to_phys(ops, cfi.iova);
> +	phys_soft = ops->iova_to_phys_length(ops, cfi.iova, NULL);

[Severity: High]
This is a pre-existing issue, but is it possible for ops to be NULL here?

If a context fault interrupt fires early during domain initialization, before
pgtbl_ops is safely published in arm_smmu_init_domain_context():

    ret = devm_request_irq(smmu->dev, irq, context_fault, ...);
    ...
    mutex_unlock(&smmu_domain->init_mutex);
    smmu_domain->pgtbl_ops = pgtbl_ops;

Could an unhandled fault on a reused context bank cause an immediate
interrupt, leading to a NULL pointer dereference when
ops->iova_to_phys_length() is called?

> diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> index 0bd21d206eb3e..e29e0ea12f24a 100644
> --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
> +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> @@ -1366,7 +1366,7 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
>  			"iova to phys timed out on %pad. Falling back to software table walk.\n",
>  			&iova);
>  		arm_smmu_rpm_put(smmu);
> -		return ops->iova_to_phys(ops, iova);
> +		return ops->iova_to_phys_length(ops, iova, NULL);

[Severity: High]
Does this hardware translation fallback path fail to populate mapped_length?

Since mapped_length is passed as NULL, callers like VFIO attempting to unmap
large regions might receive a length of 0. This could force them to fall back
to PAGE_SIZE iterations, which would trigger hundreds of slow ATOS MMIO
operations and severely impact performance.

[ ... ]

> @@ -1384,12 +1384,15 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
>  	return addr;
>  }
>  
> -static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
> -					dma_addr_t iova)
> +static phys_addr_t arm_smmu_iova_to_phys_length(struct iommu_domain *domain,
> +					dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
>  	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
>  
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	if (!ops)
>  		return 0;
>  
> @@ -1397,7 +1400,7 @@ static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
>  			smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
>  		return arm_smmu_iova_to_phys_hard(domain, iova);

[Severity: High]
Does the hardware translation path here also defeat the page table walk
optimization by failing to populate mapped_length?

When arm_smmu_iova_to_phys_hard() is called, mapped_length is left as 0 since
the function doesn't accept or update it. Will this result in callers
receiving a length of 0 and performing inefficient PAGE_SIZE iterations?

> -	return ops->iova_to_phys(ops, iova);
> +	return ops->iova_to_phys_length(ops, iova, mapped_length);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=4

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 5/9] iommu: apple-dart/ipmmu/mtk_iommu implement iova_to_phys_length
  2026-05-31  9:36     ` [PATCH 5/9] iommu: apple-dart/ipmmu/mtk_iommu " Guanghui Feng
@ 2026-05-31 10:32       ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31 10:32 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Unsafe dereference of `iop` and `bank` pointers without NULL checks when querying an unattached domain.
--

commit ed347a72ff4d98b66b4b150fbb2b517fe7bcb883
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu: apple-dart/ipmmu/mtk_iommu implement iova_to_phys_length

Migrate remaining io-pgtable user drivers to implement
iova_to_phys_length, passing through mapped_length from io-pgtable.

> diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c
> --- a/drivers/iommu/ipmmu-vmsa.c
> +++ b/drivers/iommu/ipmmu-vmsa.c

[ ... ]

> @@ -699,14 +699,18 @@ static void ipmmu_iotlb_sync(struct iommu_domain *io_domain,
> -static phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain,
> -				      dma_addr_t iova)
> +static phys_addr_t ipmmu_iova_to_phys_length(struct iommu_domain *io_domain,
> +				      dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
>  
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	/* TODO: Is locking needed ? */
>  
> -	return domain->iop->iova_to_phys(domain->iop, iova);
> +	return domain->iop->iova_to_phys_length(domain->iop, iova,
> +						mapped_length);

[Severity: High]
This is a pre-existing issue, but could this unconditional dereference of
domain->iop cause a panic for unattached domains?

The IOMMUFD framework allows userspace to allocate a hardware page table
which creates an IOMMU domain without immediately attaching it. If a
translation is triggered on this unattached domain, domain->iop will be NULL.

>  }

[ ... ]

> diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
> --- a/drivers/iommu/mtk_iommu.c
> +++ b/drivers/iommu/mtk_iommu.c

[ ... ]

> @@ -858,13 +858,17 @@ static int mtk_iommu_sync_map(struct iommu_domain *domain, unsigned long iova,
> -static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain,
> -					  dma_addr_t iova)
> +static phys_addr_t mtk_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +					  dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct mtk_iommu_domain *dom = to_mtk_domain(domain);
>  	phys_addr_t pa;
>  
> -	pa = dom->iop->iova_to_phys(dom->iop, iova);
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
> +	pa = dom->iop->iova_to_phys_length(dom->iop, iova, mapped_length);
> +
>  	if (IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT) &&
>  	    dom->bank->parent_data->enable_4GB &&

[Severity: High]
This is also a pre-existing issue, but does this function safely handle querying
unattached domains without dereferencing NULL dom->iop and dom->bank pointers?

Similar to the ipmmu-vmsa driver, domain initialization is deferred until
attach_dev. If an unattached domain is queried, both pointers would be NULL,
causing a kernel panic. The apple_dart driver prevents this by explicitly
checking the operations pointer first before querying it.

>  	    pa >= MTK_IOMMU_4GB_MODE_REMAP_BASE)

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=5

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 6/9] iommu: direct page-table drivers implement iova_to_phys_length
  2026-05-31  9:36     ` [PATCH 6/9] iommu: direct page-table drivers " Guanghui Feng
@ 2026-05-31 10:47       ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31 10:47 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Unmapped IOVAs return fake physical address and length
- [High] viommu_iova_to_phys_length returns non-power-of-two size, corrupting VFIO unmap
- [Medium] Integer truncation on 32-bit systems defeats unmap optimization

Pre-existing issues:
- [Critical] NULL pointer dereference when querying unmapped IOVAs on s390
--

commit 2222000e24f667e099e4484bdedb23cb1c3c3cfd
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu: direct page-table drivers implement iova_to_phys_length

Implement iova_to_phys_length for all IOMMU drivers with direct page tables.
Each returns the actual PTE mapping size. Also fixes an mtk_iommu_v1
pre-existing bug.

> diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c
> index 9664ef9840d2c..48a072074b56c 100644
> --- a/drivers/iommu/fsl_pamu_domain.c
> +++ b/drivers/iommu/fsl_pamu_domain.c
[ ... ]
> +	/*
> +	 * PAMU configures exactly one Primary PAACE entry per LIODN with the
> +	 * Multi-Window (MW) bit cleared and ATM = PAACE_ATM_WINDOW_XLATE,
> +	 * WBAL/TWBAL = 0. That is, every LIODN is backed by a single hardware
> +	 * mapping window of fixed size (1ULL << 36, i.e. 64GB, see
> +	 * pamu_config_ppaace()) performing an identity translation. The driver
> +	 * does not split this window into SPAACE sub-windows, so the entire
> +	 * aperture is one PAACE "PTE" entry. Return that single entry's size
> +	 * as mapped_length, matching the iova_to_phys_length contract that
> +	 * mapped_length reports the full size of the mapping entry which
> +	 * covers iova (not the remaining bytes from iova to its end).
> +	 */
> +	if (mapped_length)
> +		*mapped_length = 1ULL << 36;

[Severity: Medium]
Will this assignment truncate on 32-bit systems?

On 32-bit architectures like PPC32 (which PAMU supports), size_t is 32 bits.
Assigning 1ULL << 36 to mapped_length will silently truncate to 0. While
callers like VFIO might check for 0 and safely fall back to PAGE_SIZE, doesn't
this entirely break the performance optimization intended for PAMU?

> diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c
> index f148f559ac564..8df98aceb5f72 100644
> --- a/drivers/iommu/s390-iommu.c
> +++ b/drivers/iommu/s390-iommu.c
[ ... ]
> @@ -986,8 +986,9 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,
>  	}
>  }
>  
> -static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
> -					   dma_addr_t iova)
> +static phys_addr_t s390_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +						  dma_addr_t iova,
> +						  size_t *mapped_length)
>  {

[Severity: Critical]
This is a pre-existing issue, but does get_rto_from_iova() suffer from a NULL
pointer dereference when queried with unmapped IOVAs?

If the first-level table entry is invalid (which is normal for an unmapped
IOVA), get_rso_from_iova() returns NULL. When get_rto_from_iova() fails to
check this and immediately evaluates READ_ONCE(rso[rsx]), does it cause a
kernel panic?

Since the iova_to_phys_length API is exposed to external callers like VFIO,
could this be triggered by a guest or userspace?

> diff --git a/drivers/iommu/sprd-iommu.c b/drivers/iommu/sprd-iommu.c
> index c1a34445d244f..da14574a64308 100644
> --- a/drivers/iommu/sprd-iommu.c
> +++ b/drivers/iommu/sprd-iommu.c
[ ... ]
>  	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
>  	spin_unlock_irqrestore(&dom->pgtlock, flags);
>  
> +	if (pa && mapped_length)
> +		*mapped_length = SPRD_IOMMU_PAGE_SIZE;
> +

[Severity: High]
Does this code assign a fake mapped_length for unmapped IOVAs?

In sprd_iommu_iova_to_phys_length(), if the IOVA is unmapped (PTE is 0) but
not page-aligned, pa is incorrectly evaluated to a non-zero offset. By adding
this check, does the new API now formally claim a valid 4KB mapping exists?

Could this mislead the IOMMU core into treating unmapped addresses as valid,
potentially leading to memory corruption or undefined behavior in callers
like VFIO?

> diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c
> index 587fc13197f12..80c9c06a13804 100644
> --- a/drivers/iommu/virtio-iommu.c
> +++ b/drivers/iommu/virtio-iommu.c
[ ... ]
>  	spin_lock_irqsave(&vdomain->mappings_lock, flags);
>  	node = interval_tree_iter_first(&vdomain->mappings, iova, iova);
>  	if (node) {
>  		mapping = container_of(node, struct viommu_mapping, iova);
>  		paddr = mapping->paddr + (iova - mapping->iova.start);
> +		if (mapped_length)
> +			*mapped_length = mapping->iova.last -
> +					 mapping->iova.start + 1;

[Severity: High]
Can returning a non-power-of-two size here break API consumers?

virtio-iommu computes mapped_length as the exact interval size, which might not
be a power-of-two (e.g., 3 pages). However, callers like vfio_iommu_type1 rely
on mapped_length being a valid power-of-two PTE size to calculate remaining
contiguous bytes via bitwise math:

    pgsize - (iova & (pgsize - 1))

Does providing a non-power-of-two size produce an invalid bitmask resulting in
garbage offset values? If this causes VFIO to overestimate the contiguous block
size and unmap adjacent, potentially unrelated memory, could it lead to memory
corruption?

Additionally, would an identity mapping spanning the 64-bit address space
overflow this calculation to 0?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=6

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap
  2026-05-31  9:36     ` [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
@ 2026-05-31 11:01       ` sashiko-bot
  2026-05-31 23:58       ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31 11:01 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Missing bounds check in `vfio_unmap_unpin` allows out-of-bounds unmap and unpin
--

commit 400f16aa1c5788ec3ad51067e2b226d2e48c53d6
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

vfio/iommufd: use iova_to_phys_length for efficient unmap
    
Use iommu_iova_to_phys_length() to get PTE page size, allowing
traversal by actual mapping granularity instead of PAGE_SIZE steps.

> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index c8151ba54de33..393f9e8f1511e 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
[ ... ]
> @@ -1177,25 +1177,41 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
[ ... ]
>  		/*
>  		 * To optimize for fewer iommu_unmap() calls, each of which
>  		 * may require hardware cache flushing, try to find the
>  		 * largest contiguous physical memory chunk to unmap.
> +		 *
> +		 * Calculate remaining contiguous bytes within this PTE from
> +		 * our position, then try to join following physically
> +		 * contiguous PTEs.
>  		 */
> -		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
> -			next = iommu_iova_to_phys(domain->domain, iova + len);
> +		len = pgsize - (iova & (pgsize - 1));

[Severity: Critical]
Is it possible for the hardware PTE size to extend beyond the remaining size
of the logical DMA region?

If the IOMMU driver returns a page size that extends beyond dma->size - pos,
len is never clamped to the remaining size. This would cause len to overshoot
the region boundary.

> +		for (; pos + len < dma->size; ) {
> +			size_t next_pgsize;
> +
> +			next = iommu_iova_to_phys_length(domain->domain,
> +							  iova + len,
> +							  &next_pgsize);
>  			if (next != phys + len)
>  				break;
> +			if (!next_pgsize || next_pgsize < PAGE_SIZE)
> +				next_pgsize = PAGE_SIZE;
> +			len += next_pgsize;

[Severity: Critical]
Could unconditionally adding next_pgsize to len cause it to overshoot the
region boundary before the loop condition is evaluated?

If the next page size extends beyond the end of the region, len will again
overshoot the target boundary.

When an oversized len is subsequently passed to unmap_unpin_fast(), will it
unmap and drop pin references for physical memory outside the target region?
Dropping pin counts for active adjacent memory regions could corrupt page
refcounts and lead to physical use-after-free conditions.

>  		}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=7

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 9/9] iommu: remove deprecated iova_to_phys from domain_ops and io_pgtable_ops
  2026-05-31  9:36     ` [PATCH 9/9] iommu: remove deprecated iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
@ 2026-05-31 11:17       ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-05-31 11:17 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] Unmigrated driver vsi-iommu.c causes a compilation error because `.iova_to_phys` was removed from `struct iommu_domain_ops`.

Pre-existing issues:
- [High] The hardirq handler `vsi_iommu_irq` calls `pm_runtime_resume_and_get()`, which sleeps waiting for the device to resume, causing a kernel panic.
--

commit 6b44c3a91e8a25d542a9e22178e54c5a5807b633
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu: remove deprecated iova_to_phys from domain_ops and io_pgtable_ops

This commit removes the deprecated iova_to_phys interface from both
struct iommu_domain_ops and struct io_pgtable_ops. It cleans up the fallback
logic in iommu_iova_to_phys_length() and removes wrapper functions across
various io-pgtable implementations.

[ ... ]

> diff --git a/include/linux/iommu.h b/include/linux/iommu.h
> index 29c7c042d400d..e95b7491b5aac 100644
> --- a/include/linux/iommu.h
> +++ b/include/linux/iommu.h
> @@ -805,8 +804,6 @@ struct iommu_domain_ops {
>  	int (*cache_invalidate_user)(struct iommu_domain *domain,
>  				     struct iommu_user_data_array *array);
>  
> -	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
> -				    dma_addr_t iova);

[Severity: High]
The commit message states that all drivers implement iova_to_phys_length and
have migrated, but was drivers/iommu/vsi-iommu.c missed?

It looks like this driver still initializes this member:

drivers/iommu/vsi-iommu.c:vsi_iommu_ops {
    .iova_to_phys = vsi_iommu_iova_to_phys,
}

Will removing this member cause a build failure for that driver?


[Severity: High]
This is a pre-existing issue, but while looking at drivers/iommu/vsi-iommu.c,
can the hardirq handler sleep?

The interrupt is registered as a shared hardirq:

drivers/iommu/vsi-iommu.c:vsi_iommu_probe() {
    ...
    err = devm_request_irq(iommu->dev, iommu->irq, vsi_iommu_irq,
                           IRQF_SHARED, dev_name(dev), iommu);
    ...
}

But the handler calls a sleeping function:

drivers/iommu/vsi-iommu.c:vsi_iommu_irq() {
    ...
    if (pm_runtime_resume_and_get(iommu->dev) < 0)
        return IRQ_NONE;
    ...
}

Since pm_runtime_resume_and_get() can block waiting for the device to
resume, will this trigger a system panic or might_sleep() warning when
another device sharing the IRQ line fires?

>  	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
>  					    dma_addr_t iova,
>  					    size_t *mapped_length);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260531093637.3893199-1-guanghuifeng@linux.alibaba.com?part=9

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-05-31  9:36     ` [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
  2026-05-31  9:54       ` sashiko-bot
@ 2026-05-31 23:51       ` Jason Gunthorpe
  2026-06-01  8:41         ` guanghuifeng
  1 sibling, 1 reply; 144+ messages in thread
From: Jason Gunthorpe @ 2026-05-31 23:51 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon

On Sun, May 31, 2026 at 05:36:29PM +0800, Guanghui Feng wrote:
> Add iova_to_phys_length callback to struct iommu_domain_ops alongside
> the existing iova_to_phys. The new callback returns both the physical
> address and the PTE mapping page size in a single page table walk.
> 
> Add iommu_iova_to_phys_length() core function that:
> - Checks ops->iova_to_phys_length first (preferred path)
> - Falls back to ops->iova_to_phys for unmigrated drivers
> 
> This enables callers like VFIO to efficiently traverse IOVA space
> by actual mapping granularity instead of fixed PAGE_SIZE steps.
> 
> Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
> Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
> Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
> ---
>  drivers/iommu/iommu.c | 34 ++++++++++++++++++++++++++++++++--
>  include/linux/iommu.h |  9 +++++++++
>  2 files changed, 41 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index d1a9e713d3a0..43323229a1df 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
> @@ -2545,15 +2545,45 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
>  }
>  EXPORT_SYMBOL_GPL(iommu_detach_group);
>  
> -phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
> +/**
> + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
> + * @domain: IOMMU domain to query
> + * @iova: IO virtual address to translate
> + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
> + *
> + * Like iommu_iova_to_phys() but additionally returns the page size of the
> + * PTE mapping at @iova through @mapped_length.
> + *
> + * Return: The physical address for the given IOVA, or 0 if no translation.
> + */

When introducing the new function I would like to fix this 0 error as
well, it should return PHYS_MAX for error

> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> +				       dma_addr_t iova,
> +				       size_t *mapped_length)
>  {
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	if (domain->type == IOMMU_DOMAIN_IDENTITY)
>  		return iova;
>  
>  	if (domain->type == IOMMU_DOMAIN_BLOCKED)
>  		return 0;

Any domain that doesn't have an op should fail, blocked is one example

>  
> -	return domain->ops->iova_to_phys(domain, iova);
> +	if (domain->ops->iova_to_phys_length)
> +		return domain->ops->iova_to_phys_length(domain, iova,
> +							mapped_length);
> +
> +	/* Fallback to legacy iova_to_phys without length info */
> +	if (domain->ops->iova_to_phys)
> +		return domain->ops->iova_to_phys(domain, iova);

If it falls back it should return something sensible for the length.

I suggest you approach the patch plan a little differently, the first
patches should implement the new function and an iommput
implementation

Arrange things so the normal iova_to_phys calls the new function if it
is available and discards the length.

Then convert callers that can take advantage of it. Have the fallback
path also compute the length by iterating internally.

Finally one patch per driver implementing the new op, this could even
be a second series.

Don't remove iova_to_phys(), it is fine for things that don't need the
length.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length
  2026-05-31  9:36     ` [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
  2026-05-31 10:12       ` sashiko-bot
@ 2026-05-31 23:54       ` Jason Gunthorpe
  2026-06-01  9:23         ` guanghuifeng
                           ` (2 more replies)
  1 sibling, 3 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-05-31 23:54 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon

On Sun, May 31, 2026 at 05:36:31PM +0800, Guanghui Feng wrote:
> @@ -159,45 +164,51 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
>  	case PT_ENTRY_TABLE:
>  		return pt_descend(&pts, arg, descend_fn);
>  	case PT_ENTRY_OA:
> -		*res = pt_entry_oa_exact(&pts);
> +		data->phys = pt_entry_oa_exact(&pts);
> +		data->length = BIT(pt_entry_oa_lg2sz(&pts));

BIT is the wrong function, it uses the wrong type. log2_to_int() is
type'd properly

This also needs to keep walking and accumulating length for
consecutive PTEs until it reaches a non-contiguity.

The other drivers don't need to have that complexity.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap
  2026-05-31  9:36     ` [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
  2026-05-31 11:01       ` sashiko-bot
@ 2026-05-31 23:58       ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-05-31 23:58 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon

On Sun, May 31, 2026 at 05:36:35PM +0800, Guanghui Feng wrote:
>  		/*
> -		 * This is pretty slow, it would be nice to get the page size
> -		 * back from the driver, or have the driver directly fill the
> -		 * batch.
> +		 * Use iova_to_phys_length to get both the physical address
> +		 * and the PTE page size in a single page table walk, allowing
> +		 * us to skip ahead by the contiguous region size instead of
> +		 * walking the page tables for every PAGE_SIZE step.
>  		 */
> -		phys = iommu_iova_to_phys(domain, iova) - page_offset;
> -		if (!batch_add_pfn(batch, PHYS_PFN(phys)))
> -			return;
> -		iova += PAGE_SIZE - page_offset;
> +		phys = iommu_iova_to_phys_length(domain, iova, &pgsize) -
> +		       page_offset;
> +		if (!pgsize || pgsize < PAGE_SIZE)
> +			pgsize = PAGE_SIZE;

It is actually a bug if it returns something < PAGE_SIZE, it should
WARN_ON and try to continue.

> @@ -1177,25 +1177,41 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
>  
>  	iommu_iotlb_gather_init(&iotlb_gather);
>  	while (pos < dma->size) {
> -		size_t unmapped, len;
> +		size_t unmapped, len, pgsize;
>  		phys_addr_t phys, next;
>  		dma_addr_t iova = dma->iova + pos;
>  
> -		phys = iommu_iova_to_phys(domain->domain, iova);
> +		/* Single page table walk returns both phys and PTE size */
> +		phys = iommu_iova_to_phys_length(domain->domain, iova,
> +						  &pgsize);
>  		if (WARN_ON(!phys)) {
>  			pos += PAGE_SIZE;
>  			continue;
>  		}
> +		if (!pgsize || pgsize < PAGE_SIZE)
> +			pgsize = PAGE_SIZE;
>  
>  		/*
>  		 * To optimize for fewer iommu_unmap() calls, each of which
>  		 * may require hardware cache flushing, try to find the
>  		 * largest contiguous physical memory chunk to unmap.
> +		 *
> +		 * Calculate remaining contiguous bytes within this PTE from
> +		 * our position, then try to join following physically
> +		 * contiguous PTEs.
>  		 */
> -		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
> -			next = iommu_iova_to_phys(domain->domain, iova + len);
> +		len = pgsize - (iova & (pgsize - 1));
> +		for (; pos + len < dma->size; ) {
> +			size_t next_pgsize;

Things should be arranged so the iommu_iova_to_phys_length() always
returns the best length, either because it called into iommupt to get
it or because it accumulated internally on an old driver.

Probably to make this work well the API should include the last
address to reach so it can stop iterating at the right point.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-05-31 23:51       ` Jason Gunthorpe
@ 2026-06-01  8:41         ` guanghuifeng
  2026-06-01 13:43           ` Jason Gunthorpe
  0 siblings, 1 reply; 144+ messages in thread
From: guanghuifeng @ 2026-06-01  8:41 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer


在 2026/6/1 7:51, Jason Gunthorpe 写道:
> On Sun, May 31, 2026 at 05:36:29PM +0800, Guanghui Feng wrote:
>> Add iova_to_phys_length callback to struct iommu_domain_ops alongside
>> the existing iova_to_phys. The new callback returns both the physical
>> address and the PTE mapping page size in a single page table walk.
>>
>> Add iommu_iova_to_phys_length() core function that:
>> - Checks ops->iova_to_phys_length first (preferred path)
>> - Falls back to ops->iova_to_phys for unmigrated drivers
>>
>> This enables callers like VFIO to efficiently traverse IOVA space
>> by actual mapping granularity instead of fixed PAGE_SIZE steps.
>>
>> Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
>> Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
>> Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
>> ---
>>   drivers/iommu/iommu.c | 34 ++++++++++++++++++++++++++++++++--
>>   include/linux/iommu.h |  9 +++++++++
>>   2 files changed, 41 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
>> index d1a9e713d3a0..43323229a1df 100644
>> --- a/drivers/iommu/iommu.c
>> +++ b/drivers/iommu/iommu.c
>> @@ -2545,15 +2545,45 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
>>   }
>>   EXPORT_SYMBOL_GPL(iommu_detach_group);
>>   
>> -phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
>> +/**
>> + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
>> + * @domain: IOMMU domain to query
>> + * @iova: IO virtual address to translate
>> + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
>> + *
>> + * Like iommu_iova_to_phys() but additionally returns the page size of the
>> + * PTE mapping at @iova through @mapped_length.
>> + *
>> + * Return: The physical address for the given IOVA, or 0 if no translation.
>> + */
> When introducing the new function I would like to fix this 0 error as
> well, it should return PHYS_MAX for error

Implementations such as arm_smmu_iova_to_phys/DOMAIN_NS(iova_to_phys)

all use a return value of 0 as an invalid state, so 0 is used as the 
representation

of an invalid state to maintain compatibility.

>
>> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
>> +				       dma_addr_t iova,
>> +				       size_t *mapped_length)
>>   {
>> +	if (mapped_length)
>> +		*mapped_length = 0;
>> +
>>   	if (domain->type == IOMMU_DOMAIN_IDENTITY)
>>   		return iova;
>>   
>>   	if (domain->type == IOMMU_DOMAIN_BLOCKED)
>>   		return 0;
> Any domain that doesn't have an op should fail, blocked is one example

In accordance with the implementation of iommu_iova_to_phys, it returns 
a phy value of 0 in invalid states.

>
>>   
>> -	return domain->ops->iova_to_phys(domain, iova);
>> +	if (domain->ops->iova_to_phys_length)
>> +		return domain->ops->iova_to_phys_length(domain, iova,
>> +							mapped_length);
>> +
>> +	/* Fallback to legacy iova_to_phys without length info */
>> +	if (domain->ops->iova_to_phys)
>> +		return domain->ops->iova_to_phys(domain, iova);
> If it falls back it should return something sensible for the length.
>
> I suggest you approach the patch plan a little differently, the first
> patches should implement the new function and an iommput
> implementation
>
> Arrange things so the normal iova_to_phys calls the new function if it
> is available and discards the length.
>
> Then convert callers that can take advantage of it. Have the fallback
> path also compute the length by iterating internally.
>
> Finally one patch per driver implementing the new op, this could even
> be a second series.
>
> Don't remove iova_to_phys(), it is fine for things that don't need the
> length.

Does this mean retaining the iommu_iova_to_phys implementation but

implementing it through domain->ops->iova_to_phys_length (mapped_length 
is NULL)?

>
> Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length
  2026-05-31 23:54       ` Jason Gunthorpe
@ 2026-06-01  9:23         ` guanghuifeng
       [not found]         ` <fa924b86-1ca9-4819-8330-0d5f6ede8923@linux.alibaba.com>
  2026-06-02  7:20         ` guanghuifeng
  2 siblings, 0 replies; 144+ messages in thread
From: guanghuifeng @ 2026-06-01  9:23 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer


在 2026/6/1 7:54, Jason Gunthorpe 写道:
> On Sun, May 31, 2026 at 05:36:31PM +0800, Guanghui Feng wrote:
>> @@ -159,45 +164,51 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
>>   	case PT_ENTRY_TABLE:
>>   		return pt_descend(&pts, arg, descend_fn);
>>   	case PT_ENTRY_OA:
>> -		*res = pt_entry_oa_exact(&pts);
>> +		data->phys = pt_entry_oa_exact(&pts);
>> +		data->length = BIT(pt_entry_oa_lg2sz(&pts));
> BIT is the wrong function, it uses the wrong type. log2_to_int() is
> type'd properly

Yes, log2_to_int should be used.

> This also needs to keep walking and accumulating length for
> consecutive PTEs until it reaches a non-contiguity.
>
> The other drivers don't need to have that complexity.

Yes

> Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-01  8:41         ` guanghuifeng
@ 2026-06-01 13:43           ` Jason Gunthorpe
  2026-06-01 14:14             ` guanghuifeng
  0 siblings, 1 reply; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-01 13:43 UTC (permalink / raw)
  To: guanghuifeng@linux.alibaba.com
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer

On Mon, Jun 01, 2026 at 04:41:48PM +0800, guanghuifeng@linux.alibaba.com wrote:
> > > +/**
> > > + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
> > > + * @domain: IOMMU domain to query
> > > + * @iova: IO virtual address to translate
> > > + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
> > > + *
> > > + * Like iommu_iova_to_phys() but additionally returns the page size of the
> > > + * PTE mapping at @iova through @mapped_length.
> > > + *
> > > + * Return: The physical address for the given IOVA, or 0 if no translation.
> > > + */
> > When introducing the new function I would like to fix this 0 error as
> > well, it should return PHYS_MAX for error
> 
> Implementations such as arm_smmu_iova_to_phys/DOMAIN_NS(iova_to_phys)
> all use a return value of 0 as an invalid state, so 0 is used as the
> representation of an invalid state to maintain compatibility.

I know, but this bad choice has already caused bugs so if we are
changing everything I would prefer we fix it.

> > > +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> > > +				       dma_addr_t iova,
> > > +				       size_t *mapped_length)
> > >   {
> > > +	if (mapped_length)
> > > +		*mapped_length = 0;
> > > +
> > >   	if (domain->type == IOMMU_DOMAIN_IDENTITY)
> > >   		return iova;
> > >   	if (domain->type == IOMMU_DOMAIN_BLOCKED)
> > >   		return 0;
> > Any domain that doesn't have an op should fail, blocked is one example
> 
> In accordance with the implementation of iommu_iova_to_phys, it returns a
> phy value of 0 in invalid states.

Detect the invalid states by looking at ops not domain->type

> > I suggest you approach the patch plan a little differently, the first
> > patches should implement the new function and an iommput
> > implementation
> > 
> > Arrange things so the normal iova_to_phys calls the new function if it
> > is available and discards the length.
> > 
> > Then convert callers that can take advantage of it. Have the fallback
> > path also compute the length by iterating internally.
> > 
> > Finally one patch per driver implementing the new op, this could even
> > be a second series.
> > 
> > Don't remove iova_to_phys(), it is fine for things that don't need the
> > length.
> 
> Does this mean retaining the iommu_iova_to_phys implementation but
> implementing it through domain->ops->iova_to_phys_length (mapped_length is
> NULL)?

Yes

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-01 13:43           ` Jason Gunthorpe
@ 2026-06-01 14:14             ` guanghuifeng
  2026-06-01 14:31               ` Jason Gunthorpe
  0 siblings, 1 reply; 144+ messages in thread
From: guanghuifeng @ 2026-06-01 14:14 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer


在 2026/6/1 21:43, Jason Gunthorpe 写道:
> On Mon, Jun 01, 2026 at 04:41:48PM +0800, guanghuifeng@linux.alibaba.com wrote:
>>>> +/**
>>>> + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
>>>> + * @domain: IOMMU domain to query
>>>> + * @iova: IO virtual address to translate
>>>> + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
>>>> + *
>>>> + * Like iommu_iova_to_phys() but additionally returns the page size of the
>>>> + * PTE mapping at @iova through @mapped_length.
>>>> + *
>>>> + * Return: The physical address for the given IOVA, or 0 if no translation.
>>>> + */
>>> When introducing the new function I would like to fix this 0 error as
>>> well, it should return PHYS_MAX for error
>> Implementations such as arm_smmu_iova_to_phys/DOMAIN_NS(iova_to_phys)
>> all use a return value of 0 as an invalid state, so 0 is used as the
>> representation of an invalid state to maintain compatibility.
> I know, but this bad choice has already caused bugs so if we are
> changing everything I would prefer we fix it.

OK, there are a lot of changes in the current commit. This issue will be 
fixed in a subsequent series patch.

>>>> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
>>>> +				       dma_addr_t iova,
>>>> +				       size_t *mapped_length)
>>>>    {
>>>> +	if (mapped_length)
>>>> +		*mapped_length = 0;
>>>> +
>>>>    	if (domain->type == IOMMU_DOMAIN_IDENTITY)
>>>>    		return iova;
>>>>    	if (domain->type == IOMMU_DOMAIN_BLOCKED)
>>>>    		return 0;
>>> Any domain that doesn't have an op should fail, blocked is one example
>> In accordance with the implementation of iommu_iova_to_phys, it returns a
>> phy value of 0 in invalid states.
> Detect the invalid states by looking at ops not domain->type
>
>>> I suggest you approach the patch plan a little differently, the first
>>> patches should implement the new function and an iommput
>>> implementation
>>>
>>> Arrange things so the normal iova_to_phys calls the new function if it
>>> is available and discards the length.
>>>
>>> Then convert callers that can take advantage of it. Have the fallback
>>> path also compute the length by iterating internally.
>>>
>>> Finally one patch per driver implementing the new op, this could even
>>> be a second series.
>>>
>>> Don't remove iova_to_phys(), it is fine for things that don't need the
>>> length.
>> Does this mean retaining the iommu_iova_to_phys implementation but
>> implementing it through domain->ops->iova_to_phys_length (mapped_length is
>> NULL)?
> Yes
>
> Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-01 14:14             ` guanghuifeng
@ 2026-06-01 14:31               ` Jason Gunthorpe
  0 siblings, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-01 14:31 UTC (permalink / raw)
  To: guanghuifeng@linux.alibaba.com
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer

On Mon, Jun 01, 2026 at 10:14:27PM +0800, guanghuifeng@linux.alibaba.com wrote:
> 
> 在 2026/6/1 21:43, Jason Gunthorpe 写道:
> > On Mon, Jun 01, 2026 at 04:41:48PM +0800, guanghuifeng@linux.alibaba.com wrote:
> > > > > +/**
> > > > > + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
> > > > > + * @domain: IOMMU domain to query
> > > > > + * @iova: IO virtual address to translate
> > > > > + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
> > > > > + *
> > > > > + * Like iommu_iova_to_phys() but additionally returns the page size of the
> > > > > + * PTE mapping at @iova through @mapped_length.
> > > > > + *
> > > > > + * Return: The physical address for the given IOVA, or 0 if no translation.
> > > > > + */
> > > > When introducing the new function I would like to fix this 0 error as
> > > > well, it should return PHYS_MAX for error
> > > Implementations such as arm_smmu_iova_to_phys/DOMAIN_NS(iova_to_phys)
> > > all use a return value of 0 as an invalid state, so 0 is used as the
> > > representation of an invalid state to maintain compatibility.
> > I know, but this bad choice has already caused bugs so if we are
> > changing everything I would prefer we fix it.
> 
> OK, there are a lot of changes in the current commit. This issue will be
> fixed in a subsequent series patch.

If you follow the plan I gave you then introduce the new function
using the new return code and only support it in iommupt, then you can
convert the other drivers to the new function one by one including the
return code.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length
       [not found]         ` <fa924b86-1ca9-4819-8330-0d5f6ede8923@linux.alibaba.com>
@ 2026-06-01 14:32           ` Jason Gunthorpe
  0 siblings, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-01 14:32 UTC (permalink / raw)
  To: guanghuifeng@linux.alibaba.com
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer

On Mon, Jun 01, 2026 at 10:24:14PM +0800, guanghuifeng@linux.alibaba.com wrote:
 
>    Therefore, currently only the PTE mapping size of the iova is returned.
>    The current commit has a large number of changes; this issue will be
>    fixed  in a subsequent series patch.

No, do not introduce the wrong API to start. The iommupt version
should be easy enough to do this.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length
  2026-05-31 23:54       ` Jason Gunthorpe
  2026-06-01  9:23         ` guanghuifeng
       [not found]         ` <fa924b86-1ca9-4819-8330-0d5f6ede8923@linux.alibaba.com>
@ 2026-06-02  7:20         ` guanghuifeng
  2026-06-02 12:32           ` Jason Gunthorpe
  2 siblings, 1 reply; 144+ messages in thread
From: guanghuifeng @ 2026-06-02  7:20 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer


在 2026/6/1 7:54, Jason Gunthorpe 写道:
> On Sun, May 31, 2026 at 05:36:31PM +0800, Guanghui Feng wrote:
>> @@ -159,45 +164,51 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
>>   	case PT_ENTRY_TABLE:
>>   		return pt_descend(&pts, arg, descend_fn);
>>   	case PT_ENTRY_OA:
>> -		*res = pt_entry_oa_exact(&pts);
>> +		data->phys = pt_entry_oa_exact(&pts);
>> +		data->length = BIT(pt_entry_oa_lg2sz(&pts));
> BIT is the wrong function, it uses the wrong type. log2_to_int() is
> type'd properly
>
> This also needs to keep walking and accumulating length for
> consecutive PTEs until it reaches a non-contiguity.
>
> The other drivers don't need to have that complexity.
>
> Jason

pt_entry_oa_lg2sz has already considered continuous PTEs.

Does this mean that multiple PTEs need to be traversed additionally for 
consecutive PA address mappings?


^ permalink raw reply	[flat|nested] 144+ messages in thread

* [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation
  2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                       ` (8 preceding siblings ...)
  2026-05-31  9:36     ` [PATCH 9/9] iommu: remove deprecated iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
@ 2026-06-02 10:46     ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
                         ` (30 more replies)
  9 siblings, 31 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

This series introduces the iova_to_phys_length interface across the IOMMU
subsystem. The new callback returns both the physical address and the PTE
mapping size in a single page table walk, enabling callers such as iommufd
and VFIO to traverse IOVA space efficiently by actual mapping granularity
instead of fixed PAGE_SIZE steps.

Motivation
==========

The current iova_to_phys interface only returns a physical address with no
indication of the mapping page size. This forces callers to iterate one
PAGE_SIZE at a time when collecting PFNs or unmapping IOVA ranges, which is
extremely inefficient for large mappings (e.g., 2MB or 1GB huge pages
require 512 or 262144 page-table walks respectively).

The new iova_to_phys_length interface solves this by providing the
contiguous mapping size alongside the physical address in a single walk.

Design
======

Core layer (patch 1):
  - Adds iova_to_phys_length to iommu_domain_ops
  - iommu_iova_to_phys_length() detects invalid states by checking
    ops->iova_to_phys_length (not domain->type), returns PHYS_ADDR_MAX
    on error
  - iommu_iova_to_phys() is preserved as a thin wrapper calling
    iova_to_phys_length internally, converting PHYS_ADDR_MAX back to 0
    for historical API compatibility

io-pgtable backends (patches 2-4):
  - ARM LPAE, ARM v7s, and DART each implement iova_to_phys_length
    returning phys + page size, with PHYS_ADDR_MAX for error paths
  - The old iova_to_phys is kept temporarily as a wrapper

generic_pt framework (patch 5):
  - Implements iova_to_phys_length using pt_entry_oa_lg2sz() which
    already accounts for contiguous PTE hints, returning the correct
    mapping size in a single leaf-entry lookup

Per-driver migration (patches 6-22):
  - Each IOMMU driver (arm-smmu-v3, arm-smmu, qcom, apple-dart,
    ipmmu-vmsa, mtk, exynos, fsl_pamu, msm, omap, rockchip, s390,
    sprd, sun50i, tegra-smmu, virtio) implements iova_to_phys_length
    in its own atomic commit
  - All error paths return PHYS_ADDR_MAX

Caller conversion (patches 23-25):
  - iommufd/pages.c uses iova_to_phys_length to batch PFN collection
    by actual mapping granularity
  - VFIO type1 uses iova_to_phys_length for efficient unmap traversal
  - drm/panfrost and drm/panthor switch to the new interface

Cleanup (patches 26-30):
  - io-pgtable selftests switch to iova_to_phys_length
  - Remove deprecated iova_to_phys wrappers from io-pgtable backends
  - Remove iova_to_phys from iommu_domain_ops and io_pgtable_ops

Changes in v2:
  - Use PHYS_ADDR_MAX (~(phys_addr_t)0) as error return instead of 0
    throughout the entire call chain, per review feedback (Jason)
  - Detect invalid domain states by checking ops->iova_to_phys_length
    rather than domain->type == IOMMU_DOMAIN_BLOCKED (Jason)
  - iommu_iova_to_phys() wrapper converts PHYS_ADDR_MAX -> 0 to
    maintain historical semantic for existing callers
  - generic_pt: use pt_entry_oa_lg2sz() which already handles
    contiguous PTE hints natively (Jason)
  - All drivers updated to return PHYS_ADDR_MAX for error/fault paths
    instead of 0

Guanghui Feng (30):
  iommu: introduce iova_to_phys_length in iommu_domain_ops
  iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops
  iommu/io-pgtable-arm-v7s: introduce iova_to_phys_length in
    io_pgtable_ops
  iommu/io-pgtable-dart: introduce iova_to_phys_length in io_pgtable_ops
  iommu/generic_pt: implement iova_to_phys_length
  iommu/arm-smmu-v3: implement iova_to_phys_length
  iommu/arm-smmu: implement iova_to_phys_length
  iommu/qcom_iommu: implement iova_to_phys_length
  iommu/apple-dart: implement iova_to_phys_length
  iommu/ipmmu-vmsa: implement iova_to_phys_length
  iommu/mtk_iommu: implement iova_to_phys_length
  iommu/exynos: implement iova_to_phys_length
  iommu/fsl_pamu: implement iova_to_phys_length
  iommu/msm: implement iova_to_phys_length
  iommu/mtk_v1: implement iova_to_phys_length
  iommu/omap: implement iova_to_phys_length
  iommu/rockchip: implement iova_to_phys_length
  iommu/s390: implement iova_to_phys_length
  iommu/sprd: implement iova_to_phys_length
  iommu/sun50i: implement iova_to_phys_length
  iommu/tegra-smmu: implement iova_to_phys_length
  iommu/virtio: implement iova_to_phys_length
  vfio/iommufd: use iova_to_phys_length for efficient unmap
  drm/panfrost: switch to iova_to_phys_length
  drm/panthor: switch to iova_to_phys_length
  iommu/io-pgtable: selftests switch to iova_to_phys_length
  iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper
  iommu/io-pgtable-arm-v7s: remove deprecated iova_to_phys wrapper
  iommu/io-pgtable-dart: remove deprecated iova_to_phys wrapper
  iommu: remove iova_to_phys from domain_ops and io_pgtable_ops

 drivers/gpu/drm/panfrost/panfrost_mmu.c       |  2 +-
 drivers/gpu/drm/panthor/panthor_mmu.c         |  2 +-
 drivers/iommu/apple-dart.c                    | 13 ++--
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c   | 12 ++-
 .../iommu/arm/arm-smmu/arm-smmu-qcom-debug.c  |  2 +-
 drivers/iommu/arm/arm-smmu/arm-smmu.c         | 15 ++--
 drivers/iommu/arm/arm-smmu/qcom_iommu.c       | 13 ++--
 drivers/iommu/exynos-iommu.c                  | 23 ++++--
 drivers/iommu/fsl_pamu_domain.c               | 28 ++++++-
 drivers/iommu/generic_pt/iommu_pt.h           | 57 ++++++++------
 drivers/iommu/io-pgtable-arm-selftests.c      | 12 +--
 drivers/iommu/io-pgtable-arm-v7s.c            | 27 ++++---
 drivers/iommu/io-pgtable-arm.c                | 16 ++--
 drivers/iommu/io-pgtable-dart.c               | 21 ++++--
 drivers/iommu/iommu.c                         | 32 +++++++-
 drivers/iommu/iommufd/pages.c                 | 75 ++++++++++++++++---
 drivers/iommu/iommufd/selftest.c              |  2 +-
 drivers/iommu/ipmmu-vmsa.c                    | 12 ++-
 drivers/iommu/msm_iommu.c                     | 29 +++++--
 drivers/iommu/mtk_iommu.c                     | 14 +++-
 drivers/iommu/mtk_iommu_v1.c                  | 16 +++-
 drivers/iommu/omap-iommu.c                    | 34 ++++++---
 drivers/iommu/rockchip-iommu.c                | 13 +++-
 drivers/iommu/s390-iommu.c                    | 21 ++++--
 drivers/iommu/sprd-iommu.c                    | 20 +++--
 drivers/iommu/sun50i-iommu.c                  | 17 +++--
 drivers/iommu/tegra-smmu.c                    | 14 +++-
 drivers/iommu/virtio-iommu.c                  | 15 +++-
 drivers/vfio/vfio_iommu_type1.c               | 26 +++++--
 include/linux/generic_pt/iommu.h              | 13 ++--
 include/linux/io-pgtable.h                    | 10 ++-
 include/linux/iommu.h                         | 12 ++-
 32 files changed, 443 insertions(+), 175 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 144+ messages in thread

* [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:05         ` sashiko-bot
  2026-06-03  1:08         ` Jason Gunthorpe
  2026-06-02 10:46       ` [PATCH v2 02/30] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
                         ` (29 subsequent siblings)
  30 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Add iova_to_phys_length callback to struct iommu_domain_ops alongside
the existing iova_to_phys. The new callback returns both the physical
address and the PTE mapping page size in a single page table walk.

Add iommu_iova_to_phys_length() core function that:
- Checks ops->iova_to_phys_length first (preferred path)
- Falls back to ops->iova_to_phys for unmigrated drivers

This enables callers like VFIO to efficiently traverse IOVA space
by actual mapping granularity instead of fixed PAGE_SIZE steps.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/iommu.c | 40 ++++++++++++++++++++++++++++++++++++----
 include/linux/iommu.h |  9 +++++++++
 2 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index d1a9e713d3a0..1b1aaa53dd16 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2545,15 +2545,47 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
 }
 EXPORT_SYMBOL_GPL(iommu_detach_group);
 
-phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+/**
+ * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
+ * @domain: IOMMU domain to query
+ * @iova: IO virtual address to translate
+ * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
+ *
+ * Like iommu_iova_to_phys() but additionally returns the page size of the
+ * PTE mapping at @iova through @mapped_length.
+ *
+ * Return: The physical address for the given IOVA, or PHYS_ADDR_MAX if no translation.
+ */
+phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
+				       dma_addr_t iova,
+				       size_t *mapped_length)
 {
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (domain->type == IOMMU_DOMAIN_IDENTITY)
 		return iova;
 
-	if (domain->type == IOMMU_DOMAIN_BLOCKED)
-		return 0;
+	if (!domain->ops->iova_to_phys_length) {
+		/* Fallback to legacy iova_to_phys without length info */
+		if (domain->ops->iova_to_phys) {
+			phys_addr_t phys = domain->ops->iova_to_phys(domain, iova);
+			if (phys && mapped_length)
+				*mapped_length = PAGE_SIZE;
+			return phys ? phys : PHYS_ADDR_MAX;
+		}
+		return PHYS_ADDR_MAX;
+	}
+
+	return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
+}
+EXPORT_SYMBOL_GPL(iommu_iova_to_phys_length);
+
+phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+{
+	phys_addr_t phys = iommu_iova_to_phys_length(domain, iova, NULL);
 
-	return domain->ops->iova_to_phys(domain, iova);
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
 }
 EXPORT_SYMBOL_GPL(iommu_iova_to_phys);
 
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e587d4ac4d33..19da84c2922c 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -747,6 +747,9 @@ struct iommu_ops {
  *                         invalidation requests. The driver data structure
  *                         must be defined in include/uapi/linux/iommufd.h
  * @iova_to_phys: translate iova to physical address
+ * @iova_to_phys_length: translate iova to physical address and additionally
+ *                       return the page size of the PTE mapping at @iova
+ *                       through @mapped_length.
  * @enforce_cache_coherency: Prevent any kind of DMA from bypassing IOMMU_CACHE,
  *                           including no-snoop TLPs on PCIe or other platform
  *                           specific mechanisms.
@@ -776,6 +779,9 @@ struct iommu_domain_ops {
 
 	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
 				    dma_addr_t iova);
+	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
+					    dma_addr_t iova,
+					    size_t *mapped_length);
 
 	bool (*enforce_cache_coherency)(struct iommu_domain *domain);
 	int (*set_pgtable_quirks)(struct iommu_domain *domain,
@@ -930,6 +936,9 @@ extern ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
 			    struct scatterlist *sg, unsigned int nents,
 			    int prot, gfp_t gfp);
 extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova);
+extern phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
+					      dma_addr_t iova,
+					      size_t *mapped_length);
 extern void iommu_set_fault_handler(struct iommu_domain *domain,
 			iommu_fault_handler_t handler, void *token);
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 02/30] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:09         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 03/30] iommu/io-pgtable-arm-v7s: " Guanghui Feng
                         ` (28 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Add iova_to_phys_length to struct io_pgtable_ops alongside iova_to_phys.
Implement in ARM LPAE backend: returns ARM_LPAE_BLOCK_SIZE at the resolved level.
The old iova_to_phys is kept as a thin wrapper for backward compat.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm.c | 23 +++++++++++++++++++++--
 include/linux/io-pgtable.h     |  7 +++++++
 2 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 0208e5897c29..f33a86fa0f6c 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -731,8 +731,21 @@ static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
 	return 0;
 }
 
+static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
+						 unsigned long iova,
+						 size_t *mapped_length);
+
 static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
 					 unsigned long iova)
+{
+	phys_addr_t phys = arm_lpae_iova_to_phys_length(ops, iova, NULL);
+
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
+}
+
+static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
+						 unsigned long iova,
+						 size_t *mapped_length)
 {
 	struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	struct iova_to_phys_data d;
@@ -742,13 +755,18 @@ static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
 		.addr = iova,
 		.end = iova + 1,
 	};
+	size_t block_size;
 	int ret;
 
 	ret = __arm_lpae_iopte_walk(data, &walk_data, data->pgd, data->start_level);
 	if (ret)
-		return 0;
+		return PHYS_ADDR_MAX;
+
+	block_size = ARM_LPAE_BLOCK_SIZE(d.lvl, data);
+	if (mapped_length)
+		*mapped_length = block_size;
 
-	iova &= (ARM_LPAE_BLOCK_SIZE(d.lvl, data) - 1);
+	iova &= (block_size - 1);
 	return iopte_to_paddr(d.pte, data) | iova;
 }
 
@@ -948,6 +966,7 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
 		.map_pages	= arm_lpae_map_pages,
 		.unmap_pages	= arm_lpae_unmap_pages,
 		.iova_to_phys	= arm_lpae_iova_to_phys,
+		.iova_to_phys_length	= arm_lpae_iova_to_phys_length,
 		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
 		.pgtable_walk	= arm_lpae_pgtable_walk,
 	};
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index e19872e37e06..42bcdd309b88 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -203,6 +203,10 @@ struct arm_lpae_io_pgtable_walk_data {
  * @map_pages:    Map a physically contiguous range of pages of the same size.
  * @unmap_pages:  Unmap a range of virtually contiguous pages of the same size.
  * @iova_to_phys: Translate iova to physical address.
+ * @iova_to_phys_length: Translate iova to physical address and return the
+ *			  remaining mapped length from iova to the end of the
+ *			  mapping entry via @mapped_length. If @mapped_length is
+ *			  NULL, only the physical address is returned.
  * @pgtable_walk: (optional) Perform a page table walk for a given iova.
  * @read_and_clear_dirty: Record dirty info per IOVA. If an IOVA is dirty,
  *			  clear its dirty state from the PTE unless the
@@ -220,6 +224,9 @@ struct io_pgtable_ops {
 			      struct iommu_iotlb_gather *gather);
 	phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,
 				    unsigned long iova);
+	phys_addr_t (*iova_to_phys_length)(struct io_pgtable_ops *ops,
+					   unsigned long iova,
+					   size_t *mapped_length);
 	int (*pgtable_walk)(struct io_pgtable_ops *ops, unsigned long iova, void *wd);
 	int (*read_and_clear_dirty)(struct io_pgtable_ops *ops,
 				    unsigned long iova, size_t size,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 03/30] iommu/io-pgtable-arm-v7s: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 02/30] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:02         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 04/30] iommu/io-pgtable-dart: " Guanghui Feng
                         ` (27 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length in ARM v7s backend: returns block size
derived from level mask. The old iova_to_phys is kept as a thin wrapper.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-v7s.c | 34 ++++++++++++++++++++++++------
 1 file changed, 27 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
index 1dbef8c55007..5868d68e8a85 100644
--- a/drivers/iommu/io-pgtable-arm-v7s.c
+++ b/drivers/iommu/io-pgtable-arm-v7s.c
@@ -641,13 +641,27 @@ static size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova
 	return unmapped;
 }
 
+static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
+						unsigned long iova,
+						size_t *mapped_length);
+
 static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
 					unsigned long iova)
+{
+	phys_addr_t phys = arm_v7s_iova_to_phys_length(ops, iova, NULL);
+
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
+}
+
+static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
+						unsigned long iova,
+						size_t *mapped_length)
 {
 	struct arm_v7s_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	arm_v7s_iopte *ptep = data->pgd, pte;
 	int lvl = 0;
 	u32 mask;
+	size_t blk_size;
 
 	do {
 		ptep += ARM_V7S_LVL_IDX(iova, ++lvl, &data->iop.cfg);
@@ -656,11 +670,16 @@ static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
 	} while (ARM_V7S_PTE_IS_TABLE(pte, lvl));
 
 	if (!ARM_V7S_PTE_IS_VALID(pte))
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	mask = ARM_V7S_LVL_MASK(lvl);
 	if (arm_v7s_pte_is_cont(pte, lvl))
 		mask *= ARM_V7S_CONT_PAGES;
+
+	blk_size = ~mask + 1U;
+	if (mapped_length)
+		*mapped_length = blk_size;
+
 	return iopte_to_paddr(pte, lvl, &data->iop.cfg) | (iova & ~mask);
 }
 
@@ -714,6 +733,7 @@ static struct io_pgtable *arm_v7s_alloc_pgtable(struct io_pgtable_cfg *cfg,
 		.map_pages	= arm_v7s_map_pages,
 		.unmap_pages	= arm_v7s_unmap_pages,
 		.iova_to_phys	= arm_v7s_iova_to_phys,
+		.iova_to_phys_length	= arm_v7s_iova_to_phys_length,
 	};
 
 	/* We have to do this early for __arm_v7s_alloc_table to work... */
@@ -843,13 +863,13 @@ static int __init arm_v7s_do_selftests(void)
 	 * Initial sanity checks.
 	 * Empty page tables shouldn't provide any translations.
 	 */
-	if (ops->iova_to_phys(ops, 42))
+	if (ops->iova_to_phys_length(ops, 42, NULL) != PHYS_ADDR_MAX)
 		return __FAIL(ops);
 
-	if (ops->iova_to_phys(ops, SZ_1G + 42))
+	if (ops->iova_to_phys_length(ops, SZ_1G + 42, NULL) != PHYS_ADDR_MAX)
 		return __FAIL(ops);
 
-	if (ops->iova_to_phys(ops, SZ_2G + 42))
+	if (ops->iova_to_phys_length(ops, SZ_2G + 42, NULL) != PHYS_ADDR_MAX)
 		return __FAIL(ops);
 
 	/*
@@ -870,7 +890,7 @@ static int __init arm_v7s_do_selftests(void)
 				    &mapped))
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 			return __FAIL(ops);
 
 		iova += SZ_16M;
@@ -884,7 +904,7 @@ static int __init arm_v7s_do_selftests(void)
 		if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(ops);
 
 		/* Remap full block */
@@ -892,7 +912,7 @@ static int __init arm_v7s_do_selftests(void)
 				   GFP_KERNEL, &mapped))
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 			return __FAIL(ops);
 
 		iova += SZ_16M;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 04/30] iommu/io-pgtable-dart: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (2 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 03/30] iommu/io-pgtable-arm-v7s: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
                         ` (26 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length in DART backend: returns pgsize from
cfg.pgsize_bitmap (single fixed page size). The old iova_to_phys is kept
as a thin wrapper.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-dart.c | 32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index cbc5d6aa2daa..2dac21a578a7 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -333,29 +333,46 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	return i * pgsize;
 }
 
+static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
+					    unsigned long iova,
+					    size_t *mapped_length);
+
 static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
-					 unsigned long iova)
+				     unsigned long iova)
+{
+	phys_addr_t phys = dart_iova_to_phys_length(ops, iova, NULL);
+
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
+}
+
+static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
+					    unsigned long iova,
+					    size_t *mapped_length)
 {
 	struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	dart_iopte pte, *ptep;
+	size_t pgsize;
 
 	ptep = dart_get_last(data, iova);
 
 	/* Valid L2 IOPTE pointer? */
 	if (!ptep)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	ptep += dart_get_last_index(data, iova);
 
 	pte = READ_ONCE(*ptep);
 	/* Found translation */
 	if (pte) {
-		iova &= (data->iop.cfg.pgsize_bitmap - 1);
+		pgsize = data->iop.cfg.pgsize_bitmap;
+		if (mapped_length)
+			*mapped_length = pgsize;
+		iova &= (pgsize - 1);
 		return iopte_to_paddr(pte, data) | iova;
 	}
 
 	/* Ran out of page tables to walk */
-	return 0;
+	return PHYS_ADDR_MAX;
 }
 
 static struct dart_io_pgtable *
@@ -397,9 +414,10 @@ dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->bits_per_level = bits_per_level;
 
 	data->iop.ops = (struct io_pgtable_ops) {
-		.map_pages	= dart_map_pages,
-		.unmap_pages	= dart_unmap_pages,
-		.iova_to_phys	= dart_iova_to_phys,
+		.map_pages		= dart_map_pages,
+		.unmap_pages		= dart_unmap_pages,
+		.iova_to_phys		= dart_iova_to_phys,
+		.iova_to_phys_length	= dart_iova_to_phys_length,
 	};
 
 	return data;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (3 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 04/30] iommu/io-pgtable-dart: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:06         ` sashiko-bot
  2026-06-03  1:11         ` Jason Gunthorpe
  2026-06-02 10:46       ` [PATCH v2 06/30] iommu/arm-smmu-v3: " Guanghui Feng
                         ` (25 subsequent siblings)
  30 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Extend the Generic Page Table framework to implement iova_to_phys_length.
Use pt_entry_oa_lg2sz() to determine PTE block size. Update
IOMMU_PT_DOMAIN_OPS macro to set .iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/generic_pt/iommu_pt.h | 57 ++++++++++++++++++-----------
 include/linux/generic_pt/iommu.h    | 13 ++++---
 2 files changed, 42 insertions(+), 28 deletions(-)

diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
index dc91fb4e2f61..7563ca83061d 100644
--- a/drivers/iommu/generic_pt/iommu_pt.h
+++ b/drivers/iommu/generic_pt/iommu_pt.h
@@ -145,13 +145,18 @@ static inline unsigned int compute_best_pgsize(struct pt_state *pts,
 				      pts->range->va, pts->range->last_va, oa);
 }
 
-static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
-					     unsigned int level,
-					     struct pt_table_p *table,
-					     pt_level_fn_t descend_fn)
+struct iova_to_phys_length_data {
+	pt_oaddr_t phys;
+	size_t length;
+};
+
+static __always_inline int __do_iova_to_phys_length(struct pt_range *range,
+					       void *arg, unsigned int level,
+					       struct pt_table_p *table,
+					       pt_level_fn_t descend_fn)
 {
 	struct pt_state pts = pt_init(range, level, table);
-	pt_oaddr_t *res = arg;
+	struct iova_to_phys_length_data *data = arg;
 
 	switch (pt_load_single_entry(&pts)) {
 	case PT_ENTRY_EMPTY:
@@ -159,45 +164,53 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
 	case PT_ENTRY_TABLE:
 		return pt_descend(&pts, arg, descend_fn);
 	case PT_ENTRY_OA:
-		*res = pt_entry_oa_exact(&pts);
-		return 0;
+		break;
 	}
-	return -ENOENT;
+
+	data->phys = pt_entry_oa_exact(&pts);
+	data->length = log2_to_int(pt_entry_oa_lg2sz(&pts));
+
+	return 0;
 }
-PT_MAKE_LEVELS(__iova_to_phys, __do_iova_to_phys);
+PT_MAKE_LEVELS(__iova_to_phys_length, __do_iova_to_phys_length);
 
 /**
- * iova_to_phys() - Return the output address for the given IOVA
+ * iova_to_phys_length() - Translate IOVA returning both phys and page size
  * @domain: Table to query
  * @iova: IO virtual address to query
+ * @mapped_length: Output for the PTE page size in bytes
  *
- * Determine the output address from the given IOVA. @iova may have any
- * alignment, the returned physical will be adjusted with any sub page offset.
+ * Walk the IOMMU page table to translate @iova to a physical address while
+ * also returning the page size of the PTE entry through @mapped_length.
+ * This combines iova_to_phys and page size query into a single page table walk.
  *
  * Context: The caller must hold a read range lock that includes @iova.
  *
- * Return: 0 if there is no translation for the given iova.
+ * Return: The physical address, or PHYS_ADDR_MAX if there is no translation.
  */
-phys_addr_t DOMAIN_NS(iova_to_phys)(struct iommu_domain *domain,
-				    dma_addr_t iova)
+phys_addr_t DOMAIN_NS(iova_to_phys_length)(struct iommu_domain *domain,
+					    dma_addr_t iova,
+					    size_t *mapped_length)
 {
 	struct pt_iommu *iommu_table =
 		container_of(domain, struct pt_iommu, domain);
 	struct pt_range range;
-	pt_oaddr_t res;
+	struct iova_to_phys_length_data data;
 	int ret;
 
 	ret = make_range(common_from_iommu(iommu_table), &range, iova, 1);
 	if (ret)
-		return ret;
+		return PHYS_ADDR_MAX;
 
-	ret = pt_walk_range(&range, __iova_to_phys, &res);
-	/* PHYS_ADDR_MAX would be a better error code */
+	ret = pt_walk_range(&range, __iova_to_phys_length, &data);
 	if (ret)
-		return 0;
-	return res;
+		return PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = data.length;
+	return data.phys;
 }
-EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys), "GENERIC_PT_IOMMU");
+EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys_length), "GENERIC_PT_IOMMU");
 
 struct pt_iommu_dirty_args {
 	struct iommu_dirty_bitmap *dirty;
diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
index dd0edd02a48a..859b853e9dc7 100644
--- a/include/linux/generic_pt/iommu.h
+++ b/include/linux/generic_pt/iommu.h
@@ -249,8 +249,9 @@ struct pt_iommu_cfg {
 
 /* Generate the exported function signatures from iommu_pt.h */
 #define IOMMU_PROTOTYPES(fmt)                                                  \
-	phys_addr_t pt_iommu_##fmt##_iova_to_phys(struct iommu_domain *domain, \
-						  dma_addr_t iova);            \
+	phys_addr_t pt_iommu_##fmt##_iova_to_phys_length(			\
+		struct iommu_domain *domain, dma_addr_t iova,			\
+		size_t *mapped_length);						\
 	int pt_iommu_##fmt##_read_and_clear_dirty(                             \
 		struct iommu_domain *domain, unsigned long iova, size_t size,  \
 		unsigned long flags, struct iommu_dirty_bitmap *dirty);        \
@@ -267,11 +268,11 @@ struct pt_iommu_cfg {
 	IOMMU_PROTOTYPES(fmt)
 
 /*
- * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the
- * iommu_pt
+ * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for
+ * the iommu_pt
  */
-#define IOMMU_PT_DOMAIN_OPS(fmt)                        \
-	.iova_to_phys = &pt_iommu_##fmt##_iova_to_phys
+#define IOMMU_PT_DOMAIN_OPS(fmt)					\
+	.iova_to_phys_length = &pt_iommu_##fmt##_iova_to_phys_length
 #define IOMMU_PT_DIRTY_OPS(fmt) \
 	.read_and_clear_dirty = &pt_iommu_##fmt##_read_and_clear_dirty
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 06/30] iommu/arm-smmu-v3: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (4 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 07/30] iommu/arm-smmu: " Guanghui Feng
                         ` (24 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate ARM SMMUv3 to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index e8d7dbe495f0..69fb7ce74681 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4069,14 +4069,18 @@ static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
 }
 
 static phys_addr_t
-arm_smmu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+arm_smmu_iova_to_phys_length(struct iommu_domain *domain, dma_addr_t iova,
+			     size_t *mapped_length)
 {
 	struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static struct platform_driver arm_smmu_driver;
@@ -4396,7 +4400,7 @@ static const struct iommu_ops arm_smmu_ops = {
 		.unmap_pages		= arm_smmu_unmap_pages,
 		.flush_iotlb_all	= arm_smmu_flush_iotlb_all,
 		.iotlb_sync		= arm_smmu_iotlb_sync,
-		.iova_to_phys		= arm_smmu_iova_to_phys,
+		.iova_to_phys_length	= arm_smmu_iova_to_phys_length,
 		.free			= arm_smmu_domain_free_paging,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 07/30] iommu/arm-smmu: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (5 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 06/30] iommu/arm-smmu-v3: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:04         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 08/30] iommu/qcom_iommu: " Guanghui Feng
                         ` (23 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate ARM SMMU to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer. Update qcom-debug
caller accordingly.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c |  2 +-
 drivers/iommu/arm/arm-smmu/arm-smmu.c            | 15 +++++++++------
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
index 65e0ef6539fe..4fd01341157f 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
@@ -415,7 +415,7 @@ irqreturn_t qcom_smmu_context_fault(int irq, void *dev)
 		return IRQ_HANDLED;
 	}
 
-	phys_soft = ops->iova_to_phys(ops, cfi.iova);
+	phys_soft = ops->iova_to_phys_length(ops, cfi.iova, NULL);
 
 	tmp = report_iommu_fault(&smmu_domain->domain, NULL, cfi.iova,
 				 cfi.fsynr & ARM_SMMU_CB_FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ);
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
index 0bd21d206eb3..dfbd541f9e3e 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
@@ -1366,7 +1366,7 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
 			"iova to phys timed out on %pad. Falling back to software table walk.\n",
 			&iova);
 		arm_smmu_rpm_put(smmu);
-		return ops->iova_to_phys(ops, iova);
+		return ops->iova_to_phys_length(ops, iova, NULL);
 	}
 
 	phys = arm_smmu_cb_readq(smmu, idx, ARM_SMMU_CB_PAR);
@@ -1384,20 +1384,23 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
 	return addr;
 }
 
-static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
-					dma_addr_t iova)
+static phys_addr_t arm_smmu_iova_to_phys_length(struct iommu_domain *domain,
+					dma_addr_t iova, size_t *mapped_length)
 {
 	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
 	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS &&
 			smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
 		return arm_smmu_iova_to_phys_hard(domain, iova);
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static bool arm_smmu_capable(struct device *dev, enum iommu_cap cap)
@@ -1652,7 +1655,7 @@ static const struct iommu_ops arm_smmu_ops = {
 		.unmap_pages		= arm_smmu_unmap_pages,
 		.flush_iotlb_all	= arm_smmu_flush_iotlb_all,
 		.iotlb_sync		= arm_smmu_iotlb_sync,
-		.iova_to_phys		= arm_smmu_iova_to_phys,
+		.iova_to_phys_length	= arm_smmu_iova_to_phys_length,
 		.set_pgtable_quirks	= arm_smmu_set_pgtable_quirks,
 		.free			= arm_smmu_domain_free,
 	}
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 08/30] iommu/qcom_iommu: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (6 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 07/30] iommu/arm-smmu: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 09/30] iommu/apple-dart: " Guanghui Feng
                         ` (22 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate Qualcomm IOMMU to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/arm/arm-smmu/qcom_iommu.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu/qcom_iommu.c b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
index a1e8cf29f594..b36e31509c91 100644
--- a/drivers/iommu/arm/arm-smmu/qcom_iommu.c
+++ b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
@@ -489,19 +489,22 @@ static void qcom_iommu_iotlb_sync(struct iommu_domain *domain,
 	qcom_iommu_flush_iotlb_all(domain);
 }
 
-static phys_addr_t qcom_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t qcom_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	phys_addr_t ret;
 	unsigned long flags;
 	struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain);
 	struct io_pgtable_ops *ops = qcom_domain->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	spin_lock_irqsave(&qcom_domain->pgtbl_lock, flags);
-	ret = ops->iova_to_phys(ops, iova);
+	ret = ops->iova_to_phys_length(ops, iova, mapped_length);
 	spin_unlock_irqrestore(&qcom_domain->pgtbl_lock, flags);
 
 	return ret;
@@ -602,7 +605,7 @@ static const struct iommu_ops qcom_iommu_ops = {
 		.unmap_pages	= qcom_iommu_unmap,
 		.flush_iotlb_all = qcom_iommu_flush_iotlb_all,
 		.iotlb_sync	= qcom_iommu_iotlb_sync,
-		.iova_to_phys	= qcom_iommu_iova_to_phys,
+		.iova_to_phys_length = qcom_iommu_iova_to_phys_length,
 		.free		= qcom_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 09/30] iommu/apple-dart: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (7 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 08/30] iommu/qcom_iommu: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 10/30] iommu/ipmmu-vmsa: " Guanghui Feng
                         ` (21 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate Apple DART to implement iova_to_phys_length, passing through
mapped_length from io-pgtable.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/apple-dart.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 17bdadb6b504..2d753ce96cd9 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -528,16 +528,19 @@ static int apple_dart_iotlb_sync_map(struct iommu_domain *domain,
 	return 0;
 }
 
-static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t apple_dart_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	struct apple_dart_domain *dart_domain = to_dart_domain(domain);
 	struct io_pgtable_ops *ops = dart_domain->pgtbl_ops;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
@@ -1018,7 +1021,7 @@ static const struct iommu_ops apple_dart_iommu_ops = {
 		.flush_iotlb_all = apple_dart_flush_iotlb_all,
 		.iotlb_sync	= apple_dart_iotlb_sync,
 		.iotlb_sync_map	= apple_dart_iotlb_sync_map,
-		.iova_to_phys	= apple_dart_iova_to_phys,
+		.iova_to_phys_length = apple_dart_iova_to_phys_length,
 		.free		= apple_dart_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 10/30] iommu/ipmmu-vmsa: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (8 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 09/30] iommu/apple-dart: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-03  1:13         ` Jason Gunthorpe
  2026-06-02 10:46       ` [PATCH v2 11/30] iommu/mtk_iommu: " Guanghui Feng
                         ` (20 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate IPMMU-VMSA to implement iova_to_phys_length, passing through
mapped_length from io-pgtable.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/ipmmu-vmsa.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c
index 9386b752dea2..a1b659ddbdb5 100644
--- a/drivers/iommu/ipmmu-vmsa.c
+++ b/drivers/iommu/ipmmu-vmsa.c
@@ -699,14 +699,18 @@ static void ipmmu_iotlb_sync(struct iommu_domain *io_domain,
 	ipmmu_flush_iotlb_all(io_domain);
 }
 
-static phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain,
-				      dma_addr_t iova)
+static phys_addr_t ipmmu_iova_to_phys_length(struct iommu_domain *io_domain,
+				      dma_addr_t iova, size_t *mapped_length)
 {
 	struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	/* TODO: Is locking needed ? */
 
-	return domain->iop->iova_to_phys(domain->iop, iova);
+	return domain->iop->iova_to_phys_length(domain->iop, iova,
+						mapped_length);
 }
 
 static int ipmmu_init_platform_device(struct device *dev,
@@ -892,7 +896,7 @@ static const struct iommu_ops ipmmu_ops = {
 		.unmap_pages	= ipmmu_unmap,
 		.flush_iotlb_all = ipmmu_flush_iotlb_all,
 		.iotlb_sync	= ipmmu_iotlb_sync,
-		.iova_to_phys	= ipmmu_iova_to_phys,
+		.iova_to_phys_length = ipmmu_iova_to_phys_length,
 		.free		= ipmmu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 11/30] iommu/mtk_iommu: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (9 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 10/30] iommu/ipmmu-vmsa: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-03  1:17         ` Jason Gunthorpe
  2026-06-02 10:46       ` [PATCH v2 12/30] iommu/exynos: " Guanghui Feng
                         ` (19 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate MediaTek IOMMU to implement iova_to_phys_length, passing through
mapped_length from io-pgtable.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/mtk_iommu.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
index 2be990c108de..58125f029214 100644
--- a/drivers/iommu/mtk_iommu.c
+++ b/drivers/iommu/mtk_iommu.c
@@ -858,13 +858,19 @@ static int mtk_iommu_sync_map(struct iommu_domain *domain, unsigned long iova,
 	return 0;
 }
 
-static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain,
-					  dma_addr_t iova)
+static phys_addr_t mtk_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					  dma_addr_t iova, size_t *mapped_length)
 {
 	struct mtk_iommu_domain *dom = to_mtk_domain(domain);
 	phys_addr_t pa;
 
-	pa = dom->iop->iova_to_phys(dom->iop, iova);
+	if (mapped_length)
+		*mapped_length = 0;
+
+	pa = dom->iop->iova_to_phys_length(dom->iop, iova, mapped_length);
+	if (pa == PHYS_ADDR_MAX)
+		return PHYS_ADDR_MAX;
+
 	if (IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT) &&
 	    dom->bank->parent_data->enable_4GB &&
 	    pa >= MTK_IOMMU_4GB_MODE_REMAP_BASE)
@@ -1070,7 +1076,7 @@ static const struct iommu_ops mtk_iommu_ops = {
 		.flush_iotlb_all = mtk_iommu_flush_iotlb_all,
 		.iotlb_sync	= mtk_iommu_iotlb_sync,
 		.iotlb_sync_map	= mtk_iommu_sync_map,
-		.iova_to_phys	= mtk_iommu_iova_to_phys,
+		.iova_to_phys_length = mtk_iommu_iova_to_phys_length,
 		.free		= mtk_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 12/30] iommu/exynos: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (10 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 11/30] iommu/mtk_iommu: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 13/30] iommu/fsl_pamu: " Guanghui Feng
                         ` (18 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for Exynos IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/exynos-iommu.c | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c
index 874d05f4b396..a9da28606cff 100644
--- a/drivers/iommu/exynos-iommu.c
+++ b/drivers/iommu/exynos-iommu.c
@@ -1372,13 +1372,17 @@ static size_t exynos_iommu_unmap(struct iommu_domain *iommu_domain,
 	return 0;
 }
 
-static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain,
-					  dma_addr_t iova)
+static phys_addr_t exynos_iommu_iova_to_phys_length(struct iommu_domain *iommu_domain,
+						 dma_addr_t iova,
+						 size_t *mapped_length)
 {
 	struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain);
 	sysmmu_pte_t *entry;
 	unsigned long flags;
-	phys_addr_t phys = 0;
+	phys_addr_t phys = PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = 0;
 
 	spin_lock_irqsave(&domain->pgtablelock, flags);
 
@@ -1386,13 +1390,20 @@ static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain,
 
 	if (lv1ent_section(entry)) {
 		phys = section_phys(entry) + section_offs(iova);
+		if (mapped_length)
+			*mapped_length = SECT_SIZE;
 	} else if (lv1ent_page(entry)) {
 		entry = page_entry(entry, iova);
 
-		if (lv2ent_large(entry))
+		if (lv2ent_large(entry)) {
 			phys = lpage_phys(entry) + lpage_offs(iova);
-		else if (lv2ent_small(entry))
+			if (mapped_length)
+				*mapped_length = LPAGE_SIZE;
+		} else if (lv2ent_small(entry)) {
 			phys = spage_phys(entry) + spage_offs(iova);
+			if (mapped_length)
+				*mapped_length = SPAGE_SIZE;
+		}
 	}
 
 	spin_unlock_irqrestore(&domain->pgtablelock, flags);
@@ -1484,7 +1495,7 @@ static const struct iommu_ops exynos_iommu_ops = {
 		.attach_dev	= exynos_iommu_attach_device,
 		.map_pages	= exynos_iommu_map,
 		.unmap_pages	= exynos_iommu_unmap,
-		.iova_to_phys	= exynos_iommu_iova_to_phys,
+		.iova_to_phys_length	= exynos_iommu_iova_to_phys_length,
 		.free		= exynos_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 13/30] iommu/fsl_pamu: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (11 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 12/30] iommu/exynos: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:02         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 14/30] iommu/msm: " Guanghui Feng
                         ` (17 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for FSL PAMU IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/fsl_pamu_domain.c | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c
index 9664ef9840d2..643984ac6ae1 100644
--- a/drivers/iommu/fsl_pamu_domain.c
+++ b/drivers/iommu/fsl_pamu_domain.c
@@ -169,12 +169,32 @@ static void attach_device(struct fsl_dma_domain *dma_domain, int liodn, struct d
 	spin_unlock_irqrestore(&device_domain_lock, flags);
 }
 
-static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain,
-					 dma_addr_t iova)
+static phys_addr_t fsl_pamu_iova_to_phys_length(struct iommu_domain *domain,
+						dma_addr_t iova,
+						size_t *mapped_length)
 {
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (iova < domain->geometry.aperture_start ||
 	    iova > domain->geometry.aperture_end)
-		return 0;
+		return PHYS_ADDR_MAX;
+
+	/*
+	 * PAMU configures exactly one Primary PAACE entry per LIODN with the
+	 * Multi-Window (MW) bit cleared and ATM = PAACE_ATM_WINDOW_XLATE,
+	 * WBAL/TWBAL = 0. That is, every LIODN is backed by a single hardware
+	 * mapping window of fixed size (1ULL << 36, i.e. 64GB, see
+	 * pamu_config_ppaace()) performing an identity translation. The driver
+	 * does not split this window into SPAACE sub-windows, so the entire
+	 * aperture is one PAACE "PTE" entry. Return that single entry's size
+	 * as mapped_length, matching the iova_to_phys_length contract that
+	 * mapped_length reports the full size of the mapping entry which
+	 * covers iova (not the remaining bytes from iova to its end).
+	 */
+	if (mapped_length)
+		*mapped_length = 1ULL << 36;
+
 	return iova;
 }
 
@@ -435,7 +455,7 @@ static const struct iommu_ops fsl_pamu_ops = {
 	.device_group   = fsl_pamu_device_group,
 	.default_domain_ops = &(const struct iommu_domain_ops) {
 		.attach_dev	= fsl_pamu_attach_device,
-		.iova_to_phys	= fsl_pamu_iova_to_phys,
+		.iova_to_phys_length	= fsl_pamu_iova_to_phys_length,
 		.free		= fsl_pamu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 14/30] iommu/msm: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (12 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 13/30] iommu/fsl_pamu: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:04         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 15/30] iommu/mtk_v1: " Guanghui Feng
                         ` (16 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for MSM IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/msm_iommu.c | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c
index 0ad5ff431d5b..af4020ed9987 100644
--- a/drivers/iommu/msm_iommu.c
+++ b/drivers/iommu/msm_iommu.c
@@ -523,15 +523,19 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
 	return ret;
 }
 
-static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
-					  dma_addr_t va)
+static phys_addr_t msm_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						 dma_addr_t va,
+						 size_t *mapped_length)
 {
 	struct msm_priv *priv;
 	struct msm_iommu_dev *iommu;
 	struct msm_iommu_ctx_dev *master;
 	unsigned int par;
 	unsigned long flags;
-	phys_addr_t ret = 0;
+	phys_addr_t ret = PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = 0;
 
 	spin_lock_irqsave(&msm_iommu_lock, flags);
 
@@ -558,13 +562,22 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
 	par = GET_PAR(iommu->base, master->num);
 
 	/* We are dealing with a supersection */
-	if (GET_NOFAULT_SS(iommu->base, master->num))
+	if (GET_NOFAULT_SS(iommu->base, master->num)) {
 		ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
-	else	/* Upper 20 bits from PAR, lower 12 from VA */
+		if (mapped_length)
+			*mapped_length = SZ_16M;
+	} else {
+		/* Upper 20 bits from PAR, lower 12 from VA */
 		ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
+		if (mapped_length)
+			*mapped_length = SZ_4K;
+	}
 
-	if (GET_FAULT(iommu->base, master->num))
-		ret = 0;
+	if (GET_FAULT(iommu->base, master->num)) {
+		ret = PHYS_ADDR_MAX;
+		if (mapped_length)
+			*mapped_length = 0;
+	}
 
 	__disable_clocks(iommu);
 fail:
@@ -706,7 +719,7 @@ static struct iommu_ops msm_iommu_ops = {
 		 */
 		.iotlb_sync	= NULL,
 		.iotlb_sync_map	= msm_iommu_sync_map,
-		.iova_to_phys	= msm_iommu_iova_to_phys,
+		.iova_to_phys_length	= msm_iommu_iova_to_phys_length,
 		.free		= msm_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 15/30] iommu/mtk_v1: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (13 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 14/30] iommu/msm: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:12         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 16/30] iommu/omap: " Guanghui Feng
                         ` (15 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for MediaTek v1 IOMMU driver,
returning the actual PTE mapping size.
Also fix pre-existing bug: add page offset to physical address.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/mtk_iommu_v1.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c
index ac97dd2868d4..8733b87c9b94 100644
--- a/drivers/iommu/mtk_iommu_v1.c
+++ b/drivers/iommu/mtk_iommu_v1.c
@@ -393,17 +393,29 @@ static size_t mtk_iommu_v1_unmap(struct iommu_domain *domain, unsigned long iova
 	return size;
 }
 
-static phys_addr_t mtk_iommu_v1_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+static phys_addr_t mtk_iommu_v1_iova_to_phys_length(struct iommu_domain *domain,
+						    dma_addr_t iova,
+						    size_t *mapped_length)
 {
 	struct mtk_iommu_v1_domain *dom = to_mtk_domain(domain);
 	unsigned long flags;
 	phys_addr_t pa;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&dom->pgtlock, flags);
 	pa = *(dom->pgt_va + (iova >> MT2701_IOMMU_PAGE_SHIFT));
 	pa = pa & (~(MT2701_IOMMU_PAGE_SIZE - 1));
 	spin_unlock_irqrestore(&dom->pgtlock, flags);
 
+	if (!pa)
+		return PHYS_ADDR_MAX;
+
+	pa |= (iova & (MT2701_IOMMU_PAGE_SIZE - 1));
+	if (mapped_length)
+		*mapped_length = MT2701_IOMMU_PAGE_SIZE;
+
 	return pa;
 }
 
@@ -590,7 +602,7 @@ static const struct iommu_ops mtk_iommu_v1_ops = {
 		.attach_dev	= mtk_iommu_v1_attach_device,
 		.map_pages	= mtk_iommu_v1_map,
 		.unmap_pages	= mtk_iommu_v1_unmap,
-		.iova_to_phys	= mtk_iommu_v1_iova_to_phys,
+		.iova_to_phys_length = mtk_iommu_v1_iova_to_phys_length,
 		.free		= mtk_iommu_v1_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 16/30] iommu/omap: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (14 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 15/30] iommu/mtk_v1: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:09         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 17/30] iommu/rockchip: " Guanghui Feng
                         ` (14 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for OMAP IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/omap-iommu.c | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c
index 8231d7d6bb6a..883f9f61d4d9 100644
--- a/drivers/iommu/omap-iommu.c
+++ b/drivers/iommu/omap-iommu.c
@@ -1592,15 +1592,19 @@ static void omap_iommu_domain_free(struct iommu_domain *domain)
 	kfree(omap_domain);
 }
 
-static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t da)
+static phys_addr_t omap_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t da,
+						  size_t *mapped_length)
 {
 	struct omap_iommu_domain *omap_domain = to_omap_domain(domain);
 	struct omap_iommu_device *iommu = omap_domain->iommus;
 	struct omap_iommu *oiommu = iommu->iommu_dev;
 	struct device *dev = oiommu->dev;
 	u32 *pgd, *pte;
-	phys_addr_t ret = 0;
+	phys_addr_t ret = PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = 0;
 
 	/*
 	 * all the iommus within the domain will have identical programming,
@@ -1609,21 +1613,31 @@ static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
 	iopgtable_lookup_entry(oiommu, da, &pgd, &pte);
 
 	if (pte) {
-		if (iopte_is_small(*pte))
+		if (iopte_is_small(*pte)) {
 			ret = omap_iommu_translate(*pte, da, IOPTE_MASK);
-		else if (iopte_is_large(*pte))
+			if (mapped_length)
+				*mapped_length = IOPTE_SIZE;
+		} else if (iopte_is_large(*pte)) {
 			ret = omap_iommu_translate(*pte, da, IOLARGE_MASK);
-		else
+			if (mapped_length)
+				*mapped_length = IOLARGE_SIZE;
+		} else {
 			dev_err(dev, "bogus pte 0x%x, da 0x%llx", *pte,
 				(unsigned long long)da);
+		}
 	} else {
-		if (iopgd_is_section(*pgd))
+		if (iopgd_is_section(*pgd)) {
 			ret = omap_iommu_translate(*pgd, da, IOSECTION_MASK);
-		else if (iopgd_is_super(*pgd))
+			if (mapped_length)
+				*mapped_length = IOSECTION_SIZE;
+		} else if (iopgd_is_super(*pgd)) {
 			ret = omap_iommu_translate(*pgd, da, IOSUPER_MASK);
-		else
+			if (mapped_length)
+				*mapped_length = IOSUPER_SIZE;
+		} else {
 			dev_err(dev, "bogus pgd 0x%x, da 0x%llx", *pgd,
 				(unsigned long long)da);
+		}
 	}
 
 	return ret;
@@ -1723,7 +1737,7 @@ static const struct iommu_ops omap_iommu_ops = {
 		.attach_dev	= omap_iommu_attach_dev,
 		.map_pages	= omap_iommu_map,
 		.unmap_pages	= omap_iommu_unmap,
-		.iova_to_phys	= omap_iommu_iova_to_phys,
+		.iova_to_phys_length	= omap_iommu_iova_to_phys_length,
 		.free		= omap_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 17/30] iommu/rockchip: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (15 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 16/30] iommu/omap: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:03         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 18/30] iommu/s390: " Guanghui Feng
                         ` (13 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for Rockchip IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/rockchip-iommu.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c
index 0013cf196c57..5bae28a123c1 100644
--- a/drivers/iommu/rockchip-iommu.c
+++ b/drivers/iommu/rockchip-iommu.c
@@ -648,15 +648,18 @@ static irqreturn_t rk_iommu_irq(int irq, void *dev_id)
 	return ret;
 }
 
-static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
-					 dma_addr_t iova)
+static phys_addr_t rk_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					 dma_addr_t iova, size_t *mapped_length)
 {
 	struct rk_iommu_domain *rk_domain = to_rk_domain(domain);
 	unsigned long flags;
-	phys_addr_t pt_phys, phys = 0;
+	phys_addr_t pt_phys, phys = PHYS_ADDR_MAX;
 	u32 dte, pte;
 	u32 *page_table;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&rk_domain->dt_lock, flags);
 
 	dte = rk_domain->dt[rk_iova_dte_index(iova)];
@@ -670,6 +673,8 @@ static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
 		goto out;
 
 	phys = rk_ops->pt_address(pte) + rk_iova_page_offset(iova);
+	if (mapped_length)
+		*mapped_length = SPAGE_SIZE;
 out:
 	spin_unlock_irqrestore(&rk_domain->dt_lock, flags);
 
@@ -1187,7 +1192,7 @@ static const struct iommu_ops rk_iommu_ops = {
 		.attach_dev	= rk_iommu_attach_device,
 		.map_pages	= rk_iommu_map,
 		.unmap_pages	= rk_iommu_unmap,
-		.iova_to_phys	= rk_iommu_iova_to_phys,
+		.iova_to_phys_length = rk_iommu_iova_to_phys_length,
 		.free		= rk_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 18/30] iommu/s390: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (16 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 17/30] iommu/rockchip: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:10         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 19/30] iommu/sprd: " Guanghui Feng
                         ` (12 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for s390 IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/s390-iommu.c | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c
index f148f559ac56..b12ed389e1bd 100644
--- a/drivers/iommu/s390-iommu.c
+++ b/drivers/iommu/s390-iommu.c
@@ -986,22 +986,26 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,
 	}
 }
 
-static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t s390_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t iova,
+						  size_t *mapped_length)
 {
 	struct s390_domain *s390_domain = to_s390_domain(domain);
 	unsigned long *rto, *sto, *pto;
 	unsigned long ste, pte, rte;
 	unsigned int rtx, sx, px;
-	phys_addr_t phys = 0;
+	phys_addr_t phys = PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = 0;
 
 	if (iova < domain->geometry.aperture_start ||
 	    iova > domain->geometry.aperture_end)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	rto = get_rto_from_iova(s390_domain, iova);
 	if (!rto)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	rtx = calc_rtx(iova);
 	sx = calc_sx(iova);
@@ -1014,8 +1018,11 @@ static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
 		if (reg_entry_isvalid(ste)) {
 			pto = get_st_pto(ste);
 			pte = READ_ONCE(pto[px]);
-			if (pt_entry_isvalid(pte))
+			if (pt_entry_isvalid(pte)) {
 				phys = pte & ZPCI_PTE_ADDR_MASK;
+				if (mapped_length)
+					*mapped_length = SZ_4K;
+			}
 		}
 	}
 
@@ -1183,7 +1190,7 @@ static struct iommu_domain blocking_domain = {
 		.flush_iotlb_all = s390_iommu_flush_iotlb_all, \
 		.iotlb_sync      = s390_iommu_iotlb_sync, \
 		.iotlb_sync_map  = s390_iommu_iotlb_sync_map, \
-		.iova_to_phys	= s390_iommu_iova_to_phys, \
+		.iova_to_phys_length	= s390_iommu_iova_to_phys_length, \
 		.free		= s390_domain_free, \
 	}
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 19/30] iommu/sprd: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (17 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 18/30] iommu/s390: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 20/30] iommu/sun50i: " Guanghui Feng
                         ` (11 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for Spreadtrum IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/sprd-iommu.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/sprd-iommu.c b/drivers/iommu/sprd-iommu.c
index c1a34445d244..f8dbe24c332a 100644
--- a/drivers/iommu/sprd-iommu.c
+++ b/drivers/iommu/sprd-iommu.c
@@ -366,8 +366,9 @@ static void sprd_iommu_sync(struct iommu_domain *domain,
 	sprd_iommu_sync_map(domain, 0, 0);
 }
 
-static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t sprd_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t iova,
+						  size_t *mapped_length)
 {
 	struct sprd_iommu_domain *dom = to_sprd_domain(domain);
 	unsigned long flags;
@@ -375,14 +376,23 @@ static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
 	unsigned long start = domain->geometry.aperture_start;
 	unsigned long end = domain->geometry.aperture_end;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	if (WARN_ON(iova < start || iova > end))
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	spin_lock_irqsave(&dom->pgtlock, flags);
 	pa = *(dom->pgt_va + ((iova - start) >> SPRD_IOMMU_PAGE_SHIFT));
-	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
 	spin_unlock_irqrestore(&dom->pgtlock, flags);
 
+	if (!pa)
+		return PHYS_ADDR_MAX;
+
+	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
+	if (mapped_length)
+		*mapped_length = SPRD_IOMMU_PAGE_SIZE;
+
 	return pa;
 }
 
@@ -420,7 +430,7 @@ static const struct iommu_ops sprd_iommu_ops = {
 		.unmap_pages	= sprd_iommu_unmap,
 		.iotlb_sync_map	= sprd_iommu_sync_map,
 		.iotlb_sync	= sprd_iommu_sync,
-		.iova_to_phys	= sprd_iommu_iova_to_phys,
+		.iova_to_phys_length	= sprd_iommu_iova_to_phys_length,
 		.free		= sprd_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 20/30] iommu/sun50i: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (18 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 19/30] iommu/sprd: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 21/30] iommu/tegra-smmu: " Guanghui Feng
                         ` (10 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for sun50i IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/sun50i-iommu.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/sun50i-iommu.c b/drivers/iommu/sun50i-iommu.c
index be3f1ce696ba..dcd7d926886a 100644
--- a/drivers/iommu/sun50i-iommu.c
+++ b/drivers/iommu/sun50i-iommu.c
@@ -659,23 +659,30 @@ static size_t sun50i_iommu_unmap(struct iommu_domain *domain, unsigned long iova
 	return SZ_4K;
 }
 
-static phys_addr_t sun50i_iommu_iova_to_phys(struct iommu_domain *domain,
-					     dma_addr_t iova)
+static phys_addr_t sun50i_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						    dma_addr_t iova,
+						    size_t *mapped_length)
 {
 	struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
 	phys_addr_t pt_phys;
 	u32 *page_table;
 	u32 dte, pte;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	dte = sun50i_domain->dt[sun50i_iova_get_dte_index(iova)];
 	if (!sun50i_dte_is_pt_valid(dte))
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	pt_phys = sun50i_dte_get_pt_address(dte);
 	page_table = (u32 *)phys_to_virt(pt_phys);
 	pte = page_table[sun50i_iova_get_pte_index(iova)];
 	if (!sun50i_pte_is_page_valid(pte))
-		return 0;
+		return PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = SZ_4K;
 
 	return sun50i_pte_get_page_address(pte) +
 		sun50i_iova_get_page_offset(iova);
@@ -857,7 +864,7 @@ static const struct iommu_ops sun50i_iommu_ops = {
 		.flush_iotlb_all = sun50i_iommu_flush_iotlb_all,
 		.iotlb_sync_map = sun50i_iommu_iotlb_sync_map,
 		.iotlb_sync	= sun50i_iommu_iotlb_sync,
-		.iova_to_phys	= sun50i_iommu_iova_to_phys,
+		.iova_to_phys_length	= sun50i_iommu_iova_to_phys_length,
 		.map_pages	= sun50i_iommu_map,
 		.unmap_pages	= sun50i_iommu_unmap,
 		.free		= sun50i_iommu_domain_free,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 21/30] iommu/tegra-smmu: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (19 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 20/30] iommu/sun50i: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:10         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 22/30] iommu/virtio: " Guanghui Feng
                         ` (9 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for Tegra SMMU IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/tegra-smmu.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
index 67e7a7b925f0..fbe28fb9385f 100644
--- a/drivers/iommu/tegra-smmu.c
+++ b/drivers/iommu/tegra-smmu.c
@@ -803,20 +803,26 @@ static size_t tegra_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
 	return size;
 }
 
-static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t tegra_smmu_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	struct tegra_smmu_as *as = to_smmu_as(domain);
 	unsigned long pfn;
 	dma_addr_t pte_dma;
 	u32 *pte;
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	pte = tegra_smmu_pte_lookup(as, iova, &pte_dma);
 	if (!pte || !*pte)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	pfn = *pte & as->smmu->pfn_mask;
 
+	if (mapped_length)
+		*mapped_length = SZ_4K;
+
 	return SMMU_PFN_PHYS(pfn) + SMMU_OFFSET_IN_PAGE(iova);
 }
 
@@ -1007,7 +1013,7 @@ static const struct iommu_ops tegra_smmu_ops = {
 		.attach_dev	= tegra_smmu_attach_dev,
 		.map_pages	= tegra_smmu_map,
 		.unmap_pages	= tegra_smmu_unmap,
-		.iova_to_phys	= tegra_smmu_iova_to_phys,
+		.iova_to_phys_length = tegra_smmu_iova_to_phys_length,
 		.free		= tegra_smmu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 22/30] iommu/virtio: implement iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (20 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 21/30] iommu/tegra-smmu: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:15         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 23/30] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
                         ` (8 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Implement iova_to_phys_length for virtio IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/virtio-iommu.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c
index 587fc13197f1..c90d02cbbfd0 100644
--- a/drivers/iommu/virtio-iommu.c
+++ b/drivers/iommu/virtio-iommu.c
@@ -912,20 +912,27 @@ static size_t viommu_unmap_pages(struct iommu_domain *domain, unsigned long iova
 	return ret ? 0 : unmapped;
 }
 
-static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
-				       dma_addr_t iova)
+static phys_addr_t viommu_iova_to_phys_length(struct iommu_domain *domain,
+					      dma_addr_t iova,
+					      size_t *mapped_length)
 {
-	u64 paddr = 0;
+	u64 paddr = PHYS_ADDR_MAX;
 	unsigned long flags;
 	struct viommu_mapping *mapping;
 	struct interval_tree_node *node;
 	struct viommu_domain *vdomain = to_viommu_domain(domain);
 
+	if (mapped_length)
+		*mapped_length = 0;
+
 	spin_lock_irqsave(&vdomain->mappings_lock, flags);
 	node = interval_tree_iter_first(&vdomain->mappings, iova, iova);
 	if (node) {
 		mapping = container_of(node, struct viommu_mapping, iova);
 		paddr = mapping->paddr + (iova - mapping->iova.start);
+		if (mapped_length)
+			*mapped_length = mapping->iova.last -
+					 mapping->iova.start + 1;
 	}
 	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
 
@@ -1102,7 +1109,7 @@ static const struct iommu_ops viommu_ops = {
 		.attach_dev		= viommu_attach_dev,
 		.map_pages		= viommu_map_pages,
 		.unmap_pages		= viommu_unmap_pages,
-		.iova_to_phys		= viommu_iova_to_phys,
+		.iova_to_phys_length	= viommu_iova_to_phys_length,
 		.flush_iotlb_all	= viommu_flush_iotlb_all,
 		.iotlb_sync		= viommu_iotlb_sync,
 		.iotlb_sync_map		= viommu_iotlb_sync_map,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 23/30] vfio/iommufd: use iova_to_phys_length for efficient unmap
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (21 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 22/30] iommu/virtio: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:16         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 24/30] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
                         ` (7 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Use iommu_iova_to_phys_length() to get PTE page size, allowing
traversal by actual mapping granularity instead of PAGE_SIZE steps.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/iommufd/pages.c    | 75 +++++++++++++++++++++++++++-----
 drivers/iommu/iommufd/selftest.c |  2 +-
 drivers/vfio/vfio_iommu_type1.c  | 26 ++++++++---
 3 files changed, 85 insertions(+), 18 deletions(-)

diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index 9bdb2945afe1..aed05bd0b01c 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -417,17 +417,44 @@ static void batch_from_domain(struct pfn_batch *batch,
 	if (start_index == iopt_area_index(area))
 		page_offset = area->page_offset;
 	while (start_index <= last_index) {
+		size_t pgsize;
+		unsigned long npages;
+		unsigned long i;
+
 		/*
-		 * This is pretty slow, it would be nice to get the page size
-		 * back from the driver, or have the driver directly fill the
-		 * batch.
+		 * Use iova_to_phys_length to get both the physical address
+		 * and the PTE page size in a single page table walk, allowing
+		 * us to skip ahead by the contiguous region size instead of
+		 * walking the page tables for every PAGE_SIZE step.
 		 */
-		phys = iommu_iova_to_phys(domain, iova) - page_offset;
-		if (!batch_add_pfn(batch, PHYS_PFN(phys)))
-			return;
-		iova += PAGE_SIZE - page_offset;
+		phys = iommu_iova_to_phys_length(domain, iova, &pgsize);
+		if (WARN_ON(phys == PHYS_ADDR_MAX))
+			break;
+		phys -= page_offset;
+		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
+			pgsize = PAGE_SIZE;
+
+		/*
+		 * Calculate contiguous pages within this PTE from our
+		 * position. phys points to the page-aligned start (backed
+		 * up by page_offset), so pages available = bytes from phys
+		 * to PTE end divided by PAGE_SIZE.
+		 */
+		npages = (pgsize - (iova & (pgsize - 1)) + page_offset) /
+			 PAGE_SIZE;
+		npages = min_t(unsigned long, npages,
+			       last_index - start_index + 1);
+		if (!npages)
+			npages = 1;
+
+		for (i = 0; i < npages; i++) {
+			if (!batch_add_pfn(batch, PHYS_PFN(phys) + i))
+				return;
+		}
+
+		iova += npages * PAGE_SIZE - page_offset;
 		page_offset = 0;
-		start_index++;
+		start_index += npages;
 	}
 }
 
@@ -445,11 +472,35 @@ static struct page **raw_pages_from_domain(struct iommu_domain *domain,
 	if (start_index == iopt_area_index(area))
 		page_offset = area->page_offset;
 	while (start_index <= last_index) {
-		phys = iommu_iova_to_phys(domain, iova) - page_offset;
-		*(out_pages++) = pfn_to_page(PHYS_PFN(phys));
-		iova += PAGE_SIZE - page_offset;
+		size_t pgsize;
+		unsigned long npages;
+		unsigned long i;
+
+		/*
+		 * Resolve the PTE page size together with the physical
+		 * address so we can fill multiple struct page pointers per
+		 * page table walk when the IOMMU uses large pages.
+		 */
+		phys = iommu_iova_to_phys_length(domain, iova, &pgsize);
+		if (WARN_ON(phys == PHYS_ADDR_MAX))
+			break;
+		phys -= page_offset;
+		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
+			pgsize = PAGE_SIZE;
+
+		npages = (pgsize - (iova & (pgsize - 1)) + page_offset) /
+			 PAGE_SIZE;
+		npages = min_t(unsigned long, npages,
+			       last_index - start_index + 1);
+		if (!npages)
+			npages = 1;
+
+		for (i = 0; i < npages; i++)
+			*(out_pages++) = pfn_to_page(PHYS_PFN(phys) + i);
+
+		iova += npages * PAGE_SIZE - page_offset;
 		page_offset = 0;
-		start_index++;
+		start_index += npages;
 	}
 	return out_pages;
 }
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index af07c642a526..4b9c3ffc9523 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -1214,7 +1214,7 @@ static int iommufd_test_md_check_pa(struct iommufd_ucmd *ucmd,
 		pfn = page_to_pfn(pages[0]);
 		put_page(pages[0]);
 
-		io_phys = mock->domain.ops->iova_to_phys(&mock->domain, iova);
+		io_phys = iommu_iova_to_phys(&mock->domain, iova);
 		if (io_phys !=
 		    pfn * PAGE_SIZE + ((uintptr_t)uptr % PAGE_SIZE)) {
 			rc = -EINVAL;
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
index c8151ba54de3..c86315b1fcda 100644
--- a/drivers/vfio/vfio_iommu_type1.c
+++ b/drivers/vfio/vfio_iommu_type1.c
@@ -1177,25 +1177,41 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
 
 	iommu_iotlb_gather_init(&iotlb_gather);
 	while (pos < dma->size) {
-		size_t unmapped, len;
+		size_t unmapped, len, pgsize;
 		phys_addr_t phys, next;
 		dma_addr_t iova = dma->iova + pos;
 
-		phys = iommu_iova_to_phys(domain->domain, iova);
-		if (WARN_ON(!phys)) {
+		/* Single page table walk returns both phys and PTE size */
+		phys = iommu_iova_to_phys_length(domain->domain, iova,
+						  &pgsize);
+		if (WARN_ON(phys == PHYS_ADDR_MAX)) {
 			pos += PAGE_SIZE;
 			continue;
 		}
+		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
+			pgsize = PAGE_SIZE;
 
 		/*
 		 * To optimize for fewer iommu_unmap() calls, each of which
 		 * may require hardware cache flushing, try to find the
 		 * largest contiguous physical memory chunk to unmap.
+		 *
+		 * Calculate remaining contiguous bytes within this PTE from
+		 * our position, then try to join following physically
+		 * contiguous PTEs.
 		 */
-		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
-			next = iommu_iova_to_phys(domain->domain, iova + len);
+		len = pgsize - (iova & (pgsize - 1));
+		for (; pos + len < dma->size; ) {
+			size_t next_pgsize;
+
+			next = iommu_iova_to_phys_length(domain->domain,
+							  iova + len,
+							  &next_pgsize);
 			if (next != phys + len)
 				break;
+			if (WARN_ON(!next_pgsize || next_pgsize < PAGE_SIZE))
+				next_pgsize = PAGE_SIZE;
+			len += next_pgsize;
 		}
 
 		/*
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 24/30] drm/panfrost: switch to iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (22 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 23/30] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:14         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 25/30] drm/panthor: " Guanghui Feng
                         ` (6 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate panfrost_mmu to use ops->iova_to_phys_length(ops, iova, NULL)
instead of the deprecated ops->iova_to_phys.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/gpu/drm/panfrost/panfrost_mmu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/panfrost/panfrost_mmu.c b/drivers/gpu/drm/panfrost/panfrost_mmu.c
index 4a3162c3b659..aa0bc82deaf6 100644
--- a/drivers/gpu/drm/panfrost/panfrost_mmu.c
+++ b/drivers/gpu/drm/panfrost/panfrost_mmu.c
@@ -514,7 +514,7 @@ void panfrost_mmu_unmap(struct panfrost_gem_mapping *mapping)
 
 		if (bo->is_heap)
 			pgcount = 1;
-		if (!bo->is_heap || ops->iova_to_phys(ops, iova)) {
+		if (!bo->is_heap || ops->iova_to_phys_length(ops, iova, NULL) != PHYS_ADDR_MAX) {
 			unmapped_page = ops->unmap_pages(ops, iova, pgsize, pgcount, NULL);
 			WARN_ON(unmapped_page != pgsize * pgcount);
 		}
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 25/30] drm/panthor: switch to iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (23 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 24/30] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 26/30] iommu/io-pgtable: selftests " Guanghui Feng
                         ` (5 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate panthor_mmu to use ops->iova_to_phys_length(ops, iova, NULL)
instead of the deprecated ops->iova_to_phys.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/gpu/drm/panthor/panthor_mmu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
index 75d98dad7b1d..3b635fc1f651 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.c
+++ b/drivers/gpu/drm/panthor/panthor_mmu.c
@@ -903,7 +903,7 @@ static void panthor_vm_unmap_pages(struct panthor_vm *vm, u64 iova, u64 size)
 			 * are out-of-sync. This is not supposed to happen, hence the
 			 * above WARN_ON().
 			 */
-			while (!ops->iova_to_phys(ops, iova + unmapped_sz) &&
+			while (ops->iova_to_phys_length(ops, iova + unmapped_sz, NULL) == PHYS_ADDR_MAX &&
 			       unmapped_sz < pgsize * pgcount)
 				unmapped_sz += SZ_4K;
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 26/30] iommu/io-pgtable: selftests switch to iova_to_phys_length
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (24 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 25/30] drm/panthor: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 27/30] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
                         ` (4 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Migrate io-pgtable ARM selftests to use ops->iova_to_phys_length
instead of the deprecated ops->iova_to_phys.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-selftests.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-selftests.c b/drivers/iommu/io-pgtable-arm-selftests.c
index 334e70350924..78252344c3d0 100644
--- a/drivers/iommu/io-pgtable-arm-selftests.c
+++ b/drivers/iommu/io-pgtable-arm-selftests.c
@@ -72,13 +72,13 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 		 * Initial sanity checks.
 		 * Empty page tables shouldn't provide any translations.
 		 */
-		if (ops->iova_to_phys(ops, 42))
+		if (ops->iova_to_phys_length(ops, 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(test, i);
 
-		if (ops->iova_to_phys(ops, SZ_1G + 42))
+		if (ops->iova_to_phys_length(ops, SZ_1G + 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(test, i);
 
-		if (ops->iova_to_phys(ops, SZ_2G + 42))
+		if (ops->iova_to_phys_length(ops, SZ_2G + 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(test, i);
 
 		/*
@@ -100,7 +100,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 					    GFP_KERNEL, &mapped))
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 				return __FAIL(test, i);
 
 			iova += SZ_1G;
@@ -114,7 +114,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 			if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != PHYS_ADDR_MAX)
 				return __FAIL(test, i);
 
 			/* Remap full block */
@@ -122,7 +122,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 					   IOMMU_WRITE, GFP_KERNEL, &mapped))
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 				return __FAIL(test, i);
 
 			iova += SZ_1G;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 27/30] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (25 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 26/30] iommu/io-pgtable: selftests " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 13:22         ` sashiko-bot
  2026-06-02 10:46       ` [PATCH v2 28/30] iommu/io-pgtable-arm-v7s: " Guanghui Feng
                         ` (3 subsequent siblings)
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from ARM LPAE io-pgtable, as all callers now use iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm.c | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index f33a86fa0f6c..55a32346b586 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -731,18 +731,6 @@ static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
 	return 0;
 }
 
-static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
-						 unsigned long iova,
-						 size_t *mapped_length);
-
-static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
-					 unsigned long iova)
-{
-	phys_addr_t phys = arm_lpae_iova_to_phys_length(ops, iova, NULL);
-
-	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
-}
-
 static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
 						 unsigned long iova,
 						 size_t *mapped_length)
@@ -965,7 +953,6 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages	= arm_lpae_map_pages,
 		.unmap_pages	= arm_lpae_unmap_pages,
-		.iova_to_phys	= arm_lpae_iova_to_phys,
 		.iova_to_phys_length	= arm_lpae_iova_to_phys_length,
 		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
 		.pgtable_walk	= arm_lpae_pgtable_walk,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 28/30] iommu/io-pgtable-arm-v7s: remove deprecated iova_to_phys wrapper
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (26 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 27/30] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 29/30] iommu/io-pgtable-dart: " Guanghui Feng
                         ` (2 subsequent siblings)
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from ARM v7s io-pgtable, as all callers now use iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-v7s.c | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
index 5868d68e8a85..2f954842b052 100644
--- a/drivers/iommu/io-pgtable-arm-v7s.c
+++ b/drivers/iommu/io-pgtable-arm-v7s.c
@@ -641,18 +641,6 @@ static size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova
 	return unmapped;
 }
 
-static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
-						unsigned long iova,
-						size_t *mapped_length);
-
-static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
-					unsigned long iova)
-{
-	phys_addr_t phys = arm_v7s_iova_to_phys_length(ops, iova, NULL);
-
-	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
-}
-
 static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
 						unsigned long iova,
 						size_t *mapped_length)
@@ -732,7 +720,6 @@ static struct io_pgtable *arm_v7s_alloc_pgtable(struct io_pgtable_cfg *cfg,
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages	= arm_v7s_map_pages,
 		.unmap_pages	= arm_v7s_unmap_pages,
-		.iova_to_phys	= arm_v7s_iova_to_phys,
 		.iova_to_phys_length	= arm_v7s_iova_to_phys_length,
 	};
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 29/30] iommu/io-pgtable-dart: remove deprecated iova_to_phys wrapper
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (27 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 28/30] iommu/io-pgtable-arm-v7s: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 10:46       ` [PATCH v2 30/30] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
  30 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from DART io-pgtable, as all callers now use iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-dart.c | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index 2dac21a578a7..01c4c022830b 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -333,18 +333,6 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	return i * pgsize;
 }
 
-static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
-					    unsigned long iova,
-					    size_t *mapped_length);
-
-static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
-				     unsigned long iova)
-{
-	phys_addr_t phys = dart_iova_to_phys_length(ops, iova, NULL);
-
-	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
-}
-
 static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
 					    unsigned long iova,
 					    size_t *mapped_length)
@@ -416,7 +404,6 @@ dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages		= dart_map_pages,
 		.unmap_pages		= dart_unmap_pages,
-		.iova_to_phys		= dart_iova_to_phys,
 		.iova_to_phys_length	= dart_iova_to_phys_length,
 	};
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v2 30/30] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (28 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 29/30] iommu/io-pgtable-dart: " Guanghui Feng
@ 2026-06-02 10:46       ` Guanghui Feng
  2026-06-02 11:16         ` sashiko-bot
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
  30 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-02 10:46 UTC (permalink / raw)
  To: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, jgg, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang
  Cc: alikernel-developer

Now that all drivers implement iova_to_phys_length and all callers
have migrated, remove the deprecated interfaces:

- Remove .iova_to_phys from struct iommu_domain_ops
- Remove .iova_to_phys from struct io_pgtable_ops
- Remove fallback path in iommu_iova_to_phys_length()
- iommu_iova_to_phys() remains as a thin wrapper calling _length with NULL

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommu.c      | 12 ++----------
 include/linux/io-pgtable.h |  3 ---
 include/linux/iommu.h      |  3 ---
 3 files changed, 2 insertions(+), 16 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 1b1aaa53dd16..84893d4c7c60 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2554,7 +2554,7 @@ EXPORT_SYMBOL_GPL(iommu_detach_group);
  * Like iommu_iova_to_phys() but additionally returns the page size of the
  * PTE mapping at @iova through @mapped_length.
  *
- * Return: The physical address for the given IOVA, or PHYS_ADDR_MAX if no translation.
+ * Return: The physical address for the given IOVA, or 0 if no translation.
  */
 phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
 				       dma_addr_t iova,
@@ -2566,16 +2566,8 @@ phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
 	if (domain->type == IOMMU_DOMAIN_IDENTITY)
 		return iova;
 
-	if (!domain->ops->iova_to_phys_length) {
-		/* Fallback to legacy iova_to_phys without length info */
-		if (domain->ops->iova_to_phys) {
-			phys_addr_t phys = domain->ops->iova_to_phys(domain, iova);
-			if (phys && mapped_length)
-				*mapped_length = PAGE_SIZE;
-			return phys ? phys : PHYS_ADDR_MAX;
-		}
+	if (!domain->ops->iova_to_phys_length)
 		return PHYS_ADDR_MAX;
-	}
 
 	return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
 }
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index 42bcdd309b88..c595f5b51e61 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -202,7 +202,6 @@ struct arm_lpae_io_pgtable_walk_data {
  *
  * @map_pages:    Map a physically contiguous range of pages of the same size.
  * @unmap_pages:  Unmap a range of virtually contiguous pages of the same size.
- * @iova_to_phys: Translate iova to physical address.
  * @iova_to_phys_length: Translate iova to physical address and return the
  *			  remaining mapped length from iova to the end of the
  *			  mapping entry via @mapped_length. If @mapped_length is
@@ -222,8 +221,6 @@ struct io_pgtable_ops {
 	size_t (*unmap_pages)(struct io_pgtable_ops *ops, unsigned long iova,
 			      size_t pgsize, size_t pgcount,
 			      struct iommu_iotlb_gather *gather);
-	phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,
-				    unsigned long iova);
 	phys_addr_t (*iova_to_phys_length)(struct io_pgtable_ops *ops,
 					   unsigned long iova,
 					   size_t *mapped_length);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 19da84c2922c..ca585647180b 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -746,7 +746,6 @@ struct iommu_ops {
  *                         array->entry_num to report the number of handled
  *                         invalidation requests. The driver data structure
  *                         must be defined in include/uapi/linux/iommufd.h
- * @iova_to_phys: translate iova to physical address
  * @iova_to_phys_length: translate iova to physical address and additionally
  *                       return the page size of the PTE mapping at @iova
  *                       through @mapped_length.
@@ -777,8 +776,6 @@ struct iommu_domain_ops {
 	int (*cache_invalidate_user)(struct iommu_domain *domain,
 				     struct iommu_user_data_array *array);
 
-	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
-				    dma_addr_t iova);
 	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
 					    dma_addr_t iova,
 					    size_t *mapped_length);
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 03/30] iommu/io-pgtable-arm-v7s: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-02 10:46       ` [PATCH v2 03/30] iommu/io-pgtable-arm-v7s: " Guanghui Feng
@ 2026-06-02 11:02         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:02 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Using PHYS_ADDR_MAX as an error sentinel breaks valid translations to high physical memory on 32-bit systems.
--

commit 8d5316c44c1d3b102a4d8d7b0ce51537aca692ab
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/io-pgtable-arm-v7s: introduce iova_to_phys_length in io_pgtable_ops

Implement iova_to_phys_length in ARM v7s backend: returns block size
derived from level mask. The old iova_to_phys is kept as a thin wrapper.

> diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
> --- a/drivers/iommu/io-pgtable-arm-v7s.c
> +++ b/drivers/iommu/io-pgtable-arm-v7s.c
[ ... ]
> +static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
> +					unsigned long iova)
> +{
> +	phys_addr_t phys = arm_v7s_iova_to_phys_length(ops, iova, NULL);
> +
> +	return (phys == PHYS_ADDR_MAX) ? 0 : phys;

[Severity: High]
Does this condition inadvertently catch valid translations on 32-bit systems?

If CONFIG_PHYS_ADDR_T_64BIT is disabled, phys_addr_t is 32-bit, making
PHYS_ADDR_MAX exactly 0xFFFFFFFF. Since 0xFFFFFFFF can be a valid physical
address (e.g., in MMIO space), a valid IOVA mapping that resolves to
0xFFFFFFFF would perfectly match the error sentinel and incorrectly return 0.

> +}
> +
> +static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
> +						unsigned long iova,
> +						size_t *mapped_length)
>  {
[ ... ]
>  	if (!ARM_V7S_PTE_IS_VALID(pte))
> -		return 0;
> +		return PHYS_ADDR_MAX;

[Severity: High]
Could this similarly cause direct callers of iova_to_phys_length() to
misinterpret a legitimate translation to 0xFFFFFFFF as a failure?

Since 0xFFFFFFFF is a valid physical address, using it as an error sentinel
might make it impossible to correctly translate IOVAs pointing to the top byte
of memory on 32-bit systems.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=3

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 13/30] iommu/fsl_pamu: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 13/30] iommu/fsl_pamu: " Guanghui Feng
@ 2026-06-02 11:02         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:02 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Assigning 64GB (1ULL << 36) to size_t *mapped_length causes a silent truncation to 0 on 32-bit platforms, leading to severe performance degradation and kernel lockups.
--

commit 0bcd395ee310c27e90762eec37163a95fe19bc3f
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/fsl_pamu: implement iova_to_phys_length

This commit implements the iova_to_phys_length callback for the FSL PAMU
IOMMU driver. It configures the mapping to return the actual PTE mapping
size of the hardware window.

> diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c
> index 9664ef9840d2c..643984ac6ae1c 100644
> --- a/drivers/iommu/fsl_pamu_domain.c
> +++ b/drivers/iommu/fsl_pamu_domain.c

[ ... ]

> -static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain,
> -					 dma_addr_t iova)
> +static phys_addr_t fsl_pamu_iova_to_phys_length(struct iommu_domain *domain,
> +						dma_addr_t iova,
> +						size_t *mapped_length)
>  {
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	if (iova < domain->geometry.aperture_start ||
>  	    iova > domain->geometry.aperture_end)
> -		return 0;
> +		return PHYS_ADDR_MAX;
> +
> +	/*
> +	 * PAMU configures exactly one Primary PAACE entry per LIODN with the
> +	 * Multi-Window (MW) bit cleared and ATM = PAACE_ATM_WINDOW_XLATE,
> +	 * WBAL/TWBAL = 0. That is, every LIODN is backed by a single hardware
> +	 * mapping window of fixed size (1ULL << 36, i.e. 64GB, see
> +	 * pamu_config_ppaace()) performing an identity translation. The driver
> +	 * does not split this window into SPAACE sub-windows, so the entire
> +	 * aperture is one PAACE "PTE" entry. Return that single entry's size
> +	 * as mapped_length, matching the iova_to_phys_length contract that
> +	 * mapped_length reports the full size of the mapping entry which
> +	 * covers iova (not the remaining bytes from iova to its end).
> +	 */
> +	if (mapped_length)
> +		*mapped_length = 1ULL << 36;

[Severity: High]
Will assigning 1ULL << 36 (64GB) to a size_t pointer cause a silent 
truncation to 0 on 32-bit platforms?

The FSL PAMU driver is used on 32-bit PowerPC architectures with 36-bit
physical addressing, where size_t is 32 bits wide. On these systems, 
attempting to store 0x1000000000 into a 32-bit integer truncates to exactly 0.

If mapped_length is reported as 0, IOMMU core callers (like iommufd page
iterators or vfio_unmap_unpin) can receive a 0 mapped length, which triggers
a WARN_ON(!pgsize) and falls back to advancing by PAGE_SIZE (typically 4KB).

Stepping through a 64GB window in 4KB increments requires over 16.7 million
iterations.

Could this massive loop cause extreme log spam and system lockups?

> +
>  	return iova;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=13

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 17/30] iommu/rockchip: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 17/30] iommu/rockchip: " Guanghui Feng
@ 2026-06-02 11:03         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:03 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The driver incorrectly uses `phys_to_virt()` on a DMA address extracted from a page table entry, violating the DMA API.
--

commit d6c5c4bdd4cf407e0f8273e72add45053bb73b63
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/rockchip: implement iova_to_phys_length
    
Implement iova_to_phys_length for Rockchip IOMMU driver, returning the
actual PTE mapping size.

> diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c
> index 0013cf196c573..5bae28a123c1d 100644
> --- a/drivers/iommu/rockchip-iommu.c
> +++ b/drivers/iommu/rockchip-iommu.c
> @@ -648,15 +648,18 @@ static irqreturn_t rk_iommu_irq(int irq, void *dev_id)
>  	return ret;
>  }
>  
> -static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
> -					 dma_addr_t iova)
> +static phys_addr_t rk_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +					 dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct rk_iommu_domain *rk_domain = to_rk_domain(domain);
>  	unsigned long flags;
> -	phys_addr_t pt_phys, phys = 0;
> +	phys_addr_t pt_phys, phys = PHYS_ADDR_MAX;
>  	u32 dte, pte;
>  	u32 *page_table;
>  
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	spin_lock_irqsave(&rk_domain->dt_lock, flags);
>  
>  	dte = rk_domain->dt[rk_iova_dte_index(iova)];

[Severity: High]
This is a pre-existing issue, but does this function pass a dma_addr_t to
phys_to_virt()?

When extracting the level 1 page table address via rk_ops->pt_address(dte)
just below this line, the returned value is a dma_addr_t, not a physical
address.

If the code continues with:

	pt_phys = rk_ops->pt_address(dte);
	page_table = (u32 *)phys_to_virt(pt_phys);

Could this result in an invalid kernel virtual address if the system uses
bounce buffers or if the chip uses a dma offset?

Dereferencing this virtual address on the next line when accessing the page
table could lead to a panic or silent memory corruption.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=17

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 14/30] iommu/msm: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 14/30] iommu/msm: " Guanghui Feng
@ 2026-06-02 11:04         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:04 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] Incorrect mapping length hardcoded to `SZ_4K` for 64KB and 1MB mappings causes unmap failures and resource leaks.

Pre-existing issues:
- [High] Negative error code from `__enable_clocks()` is returned as a valid physical address, bypassing IOMMU API failure checks.
- [High] Unsafe `list_first_entry()` on a potentially empty list causes invalid pointer dereference.
--

commit e11fea990236734e10ab3d7e27031085045028c3
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/msm: implement iova_to_phys_length

Implement iova_to_phys_length for MSM IOMMU driver, returning the actual
PTE mapping size.

> diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c
> index 0ad5ff431d5b6..af4020ed99876 100644
> --- a/drivers/iommu/msm_iommu.c
> +++ b/drivers/iommu/msm_iommu.c
> @@ -523,15 +523,19 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
>  	return ret;
>  }
>  
> -static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
> -					  dma_addr_t va)
> +static phys_addr_t msm_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +						 dma_addr_t va,
> +						 size_t *mapped_length)
>  {

[Severity: High]
This is a pre-existing issue, but does list_first_entry() return a valid
pointer if the list_attached list is empty?

If a domain has no devices attached, such as directly after domain
allocation, the list will be empty. Because list_first_entry() does not
check for emptiness, it seems this could result in a bogus pointer being
dereferenced during the subsequent list_empty() check on the ctx_list:

msm_iommu.c:msm_iommu_iova_to_phys_length() {
        ...
        priv = to_msm_priv(domain);
        iommu = list_first_entry(&priv->list_attached,
                                 struct msm_iommu_dev, dom_node);

        if (list_empty(&iommu->ctx_list))
                goto fail;
        ...
}

[Severity: High]
This is also a pre-existing issue, but does the failure path for
__enable_clocks() unintentionally return a negative error code as a valid
physical address?

When __enable_clocks() fails, it returns a negative integer like -EINVAL.
This is assigned to ret, which is of type phys_addr_t. Since the code then
jumps to the fail label and returns ret, the unsigned cast of the negative
error code won't match the PHYS_ADDR_MAX failure sentinel:

msm_iommu.c:msm_iommu_iova_to_phys_length() {
        ...
        ret = __enable_clocks(iommu);
        if (ret)
                goto fail;
        ...
fail:
        spin_unlock_irqrestore(&msm_iommu_lock, flags);
        return ret;
}

Could this cause the IOMMU API to interpret the failure as a valid mapping?

>  	struct msm_priv *priv;
>  	struct msm_iommu_dev *iommu;
>  	struct msm_iommu_ctx_dev *master;

[ ... ]

> @@ -558,13 +562,22 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
>  	par = GET_PAR(iommu->base, master->num);
>  
>  	/* We are dealing with a supersection */
> -	if (GET_NOFAULT_SS(iommu->base, master->num))
> +	if (GET_NOFAULT_SS(iommu->base, master->num)) {
>  		ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
> -	else	/* Upper 20 bits from PAR, lower 12 from VA */
> +		if (mapped_length)
> +			*mapped_length = SZ_16M;
> +	} else {
> +		/* Upper 20 bits from PAR, lower 12 from VA */
>  		ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
> +		if (mapped_length)
> +			*mapped_length = SZ_4K;
> +	}

[Severity: High]
Does setting mapped_length to SZ_4K for all non-supersection mappings cause
issues with 64KB and 1MB mappings?

The hardware uses the ARM_V7S short descriptor format where the PAR register
doesn't explicitly differentiate between 1MB section, 64KB large page, and
4KB page mappings. If mapped_length is hardcoded to SZ_4K here, unmap
requests for larger regions might fail.

The underlying arm_v7s_unmap() pagetable code rejects partial unmaps of
larger sections. Could this lead to leaked memory and IOVA space?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=14

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 07/30] iommu/arm-smmu: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 07/30] iommu/arm-smmu: " Guanghui Feng
@ 2026-06-02 11:04         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:04 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] The `iova_to_phys_length` API contract is violated because `mapped_length` is not populated during hardware translation or its software fallback.
- [High] `arm_smmu_iova_to_phys_hard` returns `0` instead of `PHYS_ADDR_MAX` on translation failure, masquerading as a successful translation.
- [Low] The error check for software table walk failures in `qcom_smmu_context_fault` is broken, failing to detect `PHYS_ADDR_MAX`.

Pre-existing issues:
- [High] `arm_smmu_rpm_get()` can sleep, causing deadlocks if `iova_to_phys` is called from atomic contexts.
--

commit caa5d047d0fcd8b121e03e1968cc870e79540cdb
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/arm-smmu: implement iova_to_phys_length

Migrate ARM SMMU to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer. Update qcom-debug
caller accordingly.

> diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> index 65e0ef6539fe7..4fd01341157f3 100644
> --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> @@ -415,7 +415,7 @@ irqreturn_t qcom_smmu_context_fault(int irq, void *dev)
>  		return IRQ_HANDLED;
>  	}
>  
> -	phys_soft = ops->iova_to_phys(ops, cfi.iova);
> +	phys_soft = ops->iova_to_phys_length(ops, cfi.iova, NULL);

[Severity: Low]
With the new iova_to_phys_length API returning PHYS_ADDR_MAX on failure,
should the error check further down in qcom_smmu_context_fault() be
updated?

It currently checks for zero:

qcom_smmu_context_fault() {
    ...
    phys_soft = ops->iova_to_phys_length(ops, cfi.iova, NULL);
    ...
    if (!phys_soft)
        dev_err(smmu->dev,
            "SOFTWARE TABLE WALK FAILED! ...");
    ...
}

If the software walk fails and returns PHYS_ADDR_MAX, will this check
silently skip the diagnostic error message?

>  
>  	tmp = report_iommu_fault(&smmu_domain->domain, NULL, cfi.iova,
>  				 cfi.fsynr & ARM_SMMU_CB_FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ);

[ ... ]

> diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> index 0bd21d206eb3e..dfbd541f9e3e0 100644
> --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
> +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> @@ -1366,7 +1366,7 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
>  			"iova to phys timed out on %pad. Falling back to software table walk.\n",
>  			&iova);
>  		arm_smmu_rpm_put(smmu);
> -		return ops->iova_to_phys(ops, iova);
> +		return ops->iova_to_phys_length(ops, iova, NULL);

[Severity: High]
If we fall back to the software table walk here and pass NULL for the
mapped_length, does this violate the iova_to_phys_length API contract?
Callers might expect the mapped_length to be populated.

[Severity: High]
Is it necessary to update arm_smmu_iova_to_phys_hard() to return
PHYS_ADDR_MAX instead of 0 on failures?

The new iova_to_phys_length API contract requires returning PHYS_ADDR_MAX
on mapping failures. If this continues returning 0 on arm_smmu_rpm_get()
errors or translation faults, callers could incorrectly interpret it as a
valid mapping at physical address 0.

[Severity: High]
This is a pre-existing issue, but it looks like arm_smmu_iova_to_phys_hard()
calls arm_smmu_rpm_get() earlier in this function, which can sleep.

Since iommu_iova_to_phys() is frequently called from atomic contexts like
NAPI fast paths, could this cause a sleeping-in-atomic regression?

>  	}
>  
>  	phys = arm_smmu_cb_readq(smmu, idx, ARM_SMMU_CB_PAR);

[ ... ]

> @@ -1384,20 +1384,23 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
>  	return addr;
>  }
>  
> -static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
> -					dma_addr_t iova)
> +static phys_addr_t arm_smmu_iova_to_phys_length(struct iommu_domain *domain,
> +					dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
>  	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
>  
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	if (!ops)
> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS &&
>  			smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
>  		return arm_smmu_iova_to_phys_hard(domain, iova);

[Severity: High]
When delegating to arm_smmu_iova_to_phys_hard(), the mapped_length pointer
is not passed and remains 0. Will callers that use this length to advance
their IOVA iterators enter an infinite loop when the size remains 0 after
a successful hardware translation?

>  
> -	return ops->iova_to_phys(ops, iova);
> +	return ops->iova_to_phys_length(ops, iova, mapped_length);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=7

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-02 10:46       ` [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
@ 2026-06-02 11:05         ` sashiko-bot
  2026-06-03  1:08         ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:05 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] For identity domains (`IOMMU_DOMAIN_IDENTITY`), `iommu_iova_to_phys_length()` returns a valid physical address but leaves `*mapped_length` as `0`, leading to an infinite loop in callers traversing IOVA space.
- [High] The core `iommu_iova_to_phys()` wrapper passes a `NULL` `mapped_length` to `iommu_iova_to_phys_length()`, which is then passed directly to the driver's `iova_to_phys_length` callback, creating a severe NULL pointer dereference trap.
--

commit 8927aadcb32f5c1eb69647a6ec1ef7df78d3d8a6
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu: introduce iova_to_phys_length in iommu_domain_ops

This commit adds an iova_to_phys_length callback to struct iommu_domain_ops
alongside the existing iova_to_phys. The new callback returns both the
physical address and the PTE mapping page size in a single page table walk.
It also adds an iommu_iova_to_phys_length core function to utilize this.

> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index d1a9e713d3a05..1b1aaa53dd16a 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
> @@ -2545,15 +2545,47 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
[ ... ]
> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> +				       dma_addr_t iova,
> +				       size_t *mapped_length)
>  {
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	if (domain->type == IOMMU_DOMAIN_IDENTITY)
>  		return iova;

[Severity: High]
If we return here for an identity domain, mapped_length remains 0. Since the
API is intended for callers to traverse IOVA space by advancing by the
mapped_length, will returning 0 cause those callers to enter an infinite loop?

Should we set mapped_length to a valid non-zero value, like PAGE_SIZE or the
remaining address space, before returning?

> -	if (domain->type == IOMMU_DOMAIN_BLOCKED)
> -		return 0;
> +	if (!domain->ops->iova_to_phys_length) {
> +		/* Fallback to legacy iova_to_phys without length info */
> +		if (domain->ops->iova_to_phys) {
> +			phys_addr_t phys = domain->ops->iova_to_phys(domain, iova);
> +			if (phys && mapped_length)
> +				*mapped_length = PAGE_SIZE;
> +			return phys ? phys : PHYS_ADDR_MAX;
> +		}
> +		return PHYS_ADDR_MAX;
> +	}
> +
> +	return domain->ops->iova_to_phys_length(domain, iova, mapped_length);

[Severity: High]
When iommu_iova_to_phys calls iommu_iova_to_phys_length with NULL for
mapped_length, it gets passed directly into the driver's iova_to_phys_length
callback here.

Because the driver callback documentation doesn't specify that mapped_length
can be NULL, does this place an undocumented burden on driver authors to check
for NULL on every invocation? If a driver blindly writes to mapped_length, it
would cause a NULL pointer dereference.

Could we provide a dummy variable in the core function when mapped_length is
NULL to guarantee safety?

> +}
> +EXPORT_SYMBOL_GPL(iommu_iova_to_phys_length);
> +
> +phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
> +{
> +	phys_addr_t phys = iommu_iova_to_phys_length(domain, iova, NULL);
>  
> -	return domain->ops->iova_to_phys(domain, iova);
> +	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
>  }
>  EXPORT_SYMBOL_GPL(iommu_iova_to_phys);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=1

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
@ 2026-06-02 11:06         ` sashiko-bot
  2026-06-03  1:11         ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:06 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Missing .iova_to_phys in IOMMU_PT_DOMAIN_OPS leads to NULL pointer dereference in iommufd selftests.
--

commit e2c842a298a80551337d0eb21cf64d10984cc30d
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/generic_pt: implement iova_to_phys_length

Extend the Generic Page Table framework to implement iova_to_phys_length.
Update the IOMMU_PT_DOMAIN_OPS macro to set .iova_to_phys_length instead
of .iova_to_phys.

> diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
> index dd0edd02a48a2..859b853e9dc7e 100644
> --- a/include/linux/generic_pt/iommu.h
> +++ b/include/linux/generic_pt/iommu.h
[ ... ]
> @@ -267,11 +268,11 @@ struct pt_iommu_cfg {
>  	IOMMU_PROTOTYPES(fmt)
>  
>  /*
> - * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the
> - * iommu_pt
> + * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for
> + * the iommu_pt
>   */
> -#define IOMMU_PT_DOMAIN_OPS(fmt)                        \
> -	.iova_to_phys = &pt_iommu_##fmt##_iova_to_phys
> +#define IOMMU_PT_DOMAIN_OPS(fmt)					\
> +	.iova_to_phys_length = &pt_iommu_##fmt##_iova_to_phys_length

[Severity: Medium]
Will this macro change cause a NULL pointer dereference in the iommufd
selftests?

Looking at iommufd_test_md_check_pa() in drivers/iommu/iommufd/selftest.c,
it bypasses the safe iommu_iova_to_phys() wrapper and calls the op directly
without checking for NULL:

drivers/iommu/iommufd/selftest.c:iommufd_test_md_check_pa() {
    ...
    io_phys = mock->domain.ops->iova_to_phys(&mock->domain, iova);
    ...
}

Because mock domains in the selftests are initialized using this macro,
their .iova_to_phys function pointer will now be NULL, which causes a crash
when the selftest executes.

I see this issue is fixed later in the series by commit cb17e198ab7d
"vfio/iommufd: use iova_to_phys_length for efficient unmap", but does this
intermediate commit break the build's selftests during bisection?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=5

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 16/30] iommu/omap: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 16/30] iommu/omap: " Guanghui Feng
@ 2026-06-02 11:09         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:09 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Calling `iova_to_phys_length` on an unattached domain causes a NULL pointer dereference.
--

commit 1684c564b3ca8b0ede767819c248c3b62e1635dd
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/omap: implement iova_to_phys_length

This commit implements the iova_to_phys_length callback for the OMAP IOMMU
driver. It modifies the existing translation function to also return the
mapped page size.

> diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c
> index 8231d7d6bb6a9..883f9f61d4d95 100644
> --- a/drivers/iommu/omap-iommu.c
> +++ b/drivers/iommu/omap-iommu.c
> @@ -1592,15 +1592,19 @@ static void omap_iommu_domain_free(struct iommu_domain *domain)
>  	kfree(omap_domain);
>  }
>  
> -static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
> -					   dma_addr_t da)
> +static phys_addr_t omap_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +						  dma_addr_t da,
> +						  size_t *mapped_length)
>  {
>  	struct omap_iommu_domain *omap_domain = to_omap_domain(domain);
>  	struct omap_iommu_device *iommu = omap_domain->iommus;
>  	struct omap_iommu *oiommu = iommu->iommu_dev;

[Severity: High]
This isn't a bug introduced by this patch, but can this lead to a NULL pointer
dereference if omap_iommu_iova_to_phys_length() is called on an unattached
domain?

When a domain is unattached, omap_domain->iommus is NULL, so setting
iommu = omap_domain->iommus and unconditionally accessing iommu->iommu_dev
would crash the kernel.

It seems possible to trigger this from userspace via iommufd by calling
IOMMU_HWPT_ALLOC with immediate_attach = false. This creates an unattached
domain and adds it to the IOAS.

If subsequent memory access operations trigger pfn_reader_first(), this
could query iova_to_phys_length on all domains within the IOAS, leading
to a local Denial of Service by unprivileged users.

>  	struct device *dev = oiommu->dev;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=16

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 02/30] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-02 10:46       ` [PATCH v2 02/30] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
@ 2026-06-02 11:09         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:09 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] The `PHYS_ADDR_MAX` error sentinel collides with a legitimate 32-bit physical address, breaking translations for PA 0xFFFFFFFF.
- [Medium] The documentation for `iova_to_phys_length` contradicts its implementation and callers' expectations, creating a dangerous API contract mismatch.

Pre-existing issues:
- [High] If `iova` is `ULONG_MAX`, the page table walk loop silently skips execution and returns success, leading to undefined behavior and uninitialized stack variable usage.
- [High] Time-of-Check to Time-of-Use (TOCTOU) race and potential torn read in lockless page table walk.
--

commit 8479ee64fd320d9e1e651dd5c19fe22d7dbeb287
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops

This commit adds iova_to_phys_length to struct io_pgtable_ops and implements
it for the ARM LPAE backend. The new function returns the physical address and
the mapped length at the resolved level.

> diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
> index 476c0e25631af..2b94e0563e214 100644
> --- a/drivers/iommu/io-pgtable-arm.c
> +++ b/drivers/iommu/io-pgtable-arm.c
> @@ -765,8 +765,21 @@ static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
>  	return 0;
>  }

[Severity: High]
This is a pre-existing issue, but is there a risk of a torn read or
time-of-check to time-of-use (TOCTOU) race in the visitor callback?

While io_pgtable_visit() safely reads the entry via READ_ONCE(*ptep) to
protect against concurrent modification, it passes the raw ptep pointer to
visit_iova_to_phys(). The callback then performs a second unprotected read
via data->pte = *ptep.

If a concurrent page table modification occurs, the callback could capture a
new value while io_pgtable_visit() validated the old one. Additionally, since
arm_lpae_iopte is 64-bit, reading it without READ_ONCE() on 32-bit ARM might
allow the compiler to emit multiple 32-bit loads, potentially resulting in a
torn read.

>  
> +static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
> +						 unsigned long iova,
> +						 size_t *mapped_length);
> +
> +static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
> +					 unsigned long iova)
> +{
> +	phys_addr_t phys = arm_lpae_iova_to_phys_length(ops, iova, NULL);
> +
> +	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
> +}

[Severity: High]
Could this return logic incorrectly mask a valid physical address?

On 32-bit platforms where phys_addr_t is 32 bits, PHYS_ADDR_MAX evaluates to
0xFFFFFFFF. If a valid MMIO or RAM region is mapped exactly at the top of the
physical address space, returning 0xFFFFFFFF from
arm_lpae_iova_to_phys_length() would be treated as an error here, and
incorrectly return 0 instead.

> +
> +static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
> +						 unsigned long iova,
> +						 size_t *mapped_length)
>  {
>  	struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
>  	struct iova_to_phys_data d;
> @@ -776,13 +789,18 @@ static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
>  		.addr = iova,
>  		.end = iova + 1,
>  	};

[Severity: High]
This is a pre-existing issue, but what happens to the walk_data.end
calculation if iova is ULONG_MAX?

If iova is exactly ULONG_MAX, walk_data.end = iova + 1 will overflow to 0.
During the __arm_lpae_iopte_walk() loop, the condition
(walk_data->addr < walk_data->end) will evaluate to (ULONG_MAX < 0), which
is immediately false.

Because the loop exits without calling the visit_iova_to_phys() callback, the
function would assume success and proceed to calculate the block_size using an
uninitialized d.lvl variable from the stack.

[ ... ]

> diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
> index e19872e37e067..42bcdd309b889 100644
> --- a/include/linux/io-pgtable.h
> +++ b/include/linux/io-pgtable.h
> @@ -203,6 +203,10 @@ struct arm_lpae_io_pgtable_walk_data {
>   * @map_pages:    Map a physically contiguous range of pages of the same size.
>   * @unmap_pages:  Unmap a range of virtually contiguous pages of the same size.
>   * @iova_to_phys: Translate iova to physical address.
> + * @iova_to_phys_length: Translate iova to physical address and return the
> + *			  remaining mapped length from iova to the end of the
> + *			  mapping entry via @mapped_length. If @mapped_length is
> + *			  NULL, only the physical address is returned.

[Severity: Medium]
Does this documentation accurately describe the implementation?

The comment specifies that mapped_length outputs the remaining mapped length
from the IOVA to the end of the entry. However, the
arm_lpae_iova_to_phys_length() implementation returns the full block size of
the PTE mapping without subtracting the IOVA offset.

Could this mismatch mislead developers implementing future backends?

>   * @pgtable_walk: (optional) Perform a page table walk for a given iova.
>   * @read_and_clear_dirty: Record dirty info per IOVA. If an IOVA is dirty,

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=2

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 18/30] iommu/s390: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 18/30] iommu/s390: " Guanghui Feng
@ 2026-06-02 11:10         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:10 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Missing Intra-Page Offset in Physical Address Translation
- [High] Null Pointer Dereference in get_rto_from_iova()
--

commit f8957d220cfee6484b5b88e6614c133bcac327ca
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/s390: implement iova_to_phys_length
    
Implement iova_to_phys_length for s390 IOMMU driver,
returning the actual PTE mapping size.

> diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c
> index f148f559ac564..b12ed389e1bd9 100644
> --- a/drivers/iommu/s390-iommu.c
> +++ b/drivers/iommu/s390-iommu.c
> @@ -986,22 +986,26 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,
[ ... ]
> -static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
> -					   dma_addr_t iova)
> +static phys_addr_t s390_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +						  dma_addr_t iova,
> +						  size_t *mapped_length)
>  {
[ ... ]
>  	rto = get_rto_from_iova(s390_domain, iova);

[Severity: High]
This is a pre-existing issue, but does get_rto_from_iova() safely handle
unmapped IOVAs?

Looking at get_rto_from_iova() in drivers/iommu/s390-iommu.c, it calls
get_rso_from_iova() which can return NULL if the top-level Region First entry
is invalid. However, the pointer is dereferenced unconditionally:

drivers/iommu/s390-iommu.c:get_rto_from_iova() {
	...
	case ZPCI_TABLE_TYPE_RFX:
	case ZPCI_TABLE_TYPE_RSX:
		rso = get_rso_from_iova(domain, iova);
		rsx = calc_rsx(iova);
		rse = READ_ONCE(rso[rsx]);
	...
}

Would calling iommu_iova_to_phys() on an unmapped IOVA trigger a NULL pointer
dereference here?

>  	if (!rto)
> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	rtx = calc_rtx(iova);
>  	sx = calc_sx(iova);
> @@ -1014,8 +1018,11 @@ static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
>  		if (reg_entry_isvalid(ste)) {
>  			pto = get_st_pto(ste);
>  			pte = READ_ONCE(pto[px]);
> -			if (pt_entry_isvalid(pte))
> +			if (pt_entry_isvalid(pte)) {
>  				phys = pte & ZPCI_PTE_ADDR_MASK;

[Severity: High]
This isn't a bug introduced by this patch, but does this physical address
translation truncate the intra-page offset?

The returned physical address is constructed by masking the page table entry
with ZPCI_PTE_ADDR_MASK, but it doesn't incorporate the sub-page offset from
the original IOVA. Since the IOMMU core API iommu_iova_to_phys() expects the
exact physical address corresponding to the byte-level IOVA, could truncating
the offset cause callers querying unaligned IOVAs to receive incorrect
addresses?

> +				if (mapped_length)
> +					*mapped_length = SZ_4K;
> +			}
>  		}
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=18

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 21/30] iommu/tegra-smmu: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 21/30] iommu/tegra-smmu: " Guanghui Feng
@ 2026-06-02 11:10         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:10 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Lockless traversal in `tegra_smmu_iova_to_phys_length` can cause a use-after-free on page table memory and a TOCTOU race on the PTE value.
--

commit 681b9a6734c92cb4ecc9cde9011c68a58a0d83fd
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/tegra-smmu: implement iova_to_phys_length

This commit implements iova_to_phys_length for the Tegra SMMU IOMMU driver,
returning the actual PTE mapping size.

> diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
> --- a/drivers/iommu/tegra-smmu.c
> +++ b/drivers/iommu/tegra-smmu.c

[ ... ]

> @@ -803,20 +803,26 @@ static size_t tegra_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
> -static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain,
> -					   dma_addr_t iova)
> +static phys_addr_t tegra_smmu_iova_to_phys_length(struct iommu_domain *domain,
> +					   dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct tegra_smmu_as *as = to_smmu_as(domain);
>  	unsigned long pfn;
>  	dma_addr_t pte_dma;
>  	u32 *pte;
>  
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	pte = tegra_smmu_pte_lookup(as, iova, &pte_dma);
>  	if (!pte || !*pte)
> -		return 0;
> +		return PHYS_ADDR_MAX;

[Severity: High]
This is a pre-existing issue, but does this lockless traversal risk a
use-after-free on the page table memory?

Unlike tegra_smmu_map() and tegra_smmu_unmap(), this function looks up the
page table and reads the PTE locklessly. If a concurrent unmap operation
removes the last IOVA in a page directory, tegra_smmu_pte_put_use() will
synchronously free the page table:

tegra_smmu_unmap()
  __tegra_smmu_unmap()
    tegra_smmu_set_pte()
      tegra_smmu_pte_put_use() {
        ...
        iommu_free_pages(pt);
        as->pts[pde] = NULL;
      }

If this runs concurrently on an unmapped IOVA within the same directory,
could tegra_smmu_pte_lookup() return a pointer to freed memory, leading
to a crash when evaluating !*pte?

Additionally, is there a time-of-check to time-of-use race here? The pointer
*pte is evaluated twice without READ_ONCE(). If an unmap happens between
the check (!*pte) and the assignment:

>  	pfn = *pte & as->smmu->pfn_mask;

Could *pte become 0 during the second read, causing the function to silently
return a physical address of 0 instead of PHYS_ADDR_MAX?

> +	if (mapped_length)
> +		*mapped_length = SZ_4K;
> +
>  	return SMMU_PFN_PHYS(pfn) + SMMU_OFFSET_IN_PAGE(iova);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=21

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 15/30] iommu/mtk_v1: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 15/30] iommu/mtk_v1: " Guanghui Feng
@ 2026-06-02 11:12         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:12 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Valid PTE mappings to physical address 0 are incorrectly treated as unmapped.
--

commit 8a65585ea3e0470504b1a4dc830dd977b11466cf
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/mtk_v1: implement iova_to_phys_length
    
This commit implements the iova_to_phys_length callback for the MediaTek
v1 IOMMU driver to return the actual PTE mapping size. It also fixes a
bug by adding the page offset to the physical address.

> diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c
> --- a/drivers/iommu/mtk_iommu_v1.c
> +++ b/drivers/iommu/mtk_iommu_v1.c
[ ... ]
> -static phys_addr_t mtk_iommu_v1_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
> +static phys_addr_t mtk_iommu_v1_iova_to_phys_length(struct iommu_domain *domain,
> +						    dma_addr_t iova,
> +						    size_t *mapped_length)
>  {
>  	struct mtk_iommu_v1_domain *dom = to_mtk_domain(domain);
>  	unsigned long flags;
>  	phys_addr_t pa;
>  
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	spin_lock_irqsave(&dom->pgtlock, flags);
>  	pa = *(dom->pgt_va + (iova >> MT2701_IOMMU_PAGE_SHIFT));
>  	pa = pa & (~(MT2701_IOMMU_PAGE_SIZE - 1));
>  	spin_unlock_irqrestore(&dom->pgtlock, flags);
>  
> +	if (!pa)
> +		return PHYS_ADDR_MAX;

[Severity: High]
Does this incorrectly treat a valid mapping at physical address 0 as
unmapped?

If the hardware PTE points to physical address 0x0, the raw PTE contains
valid flags, but the masking operation isolates just the physical address:

    pa = pa & (~(MT2701_IOMMU_PAGE_SIZE - 1));

This results in pa evaluating to 0. The subsequent if (!pa) check will
then evaluate to true, returning PHYS_ADDR_MAX and leaving mapped_length
as 0.

Should this code check the valid bit on the raw PTE before applying the
physical address mask to determine if the page is mapped?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=15

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 24/30] drm/panfrost: switch to iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 24/30] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
@ 2026-06-02 11:14         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:14 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Use-after-free of panfrost_gem_mapping caused by missing kref_get_unless_zero() during page fault handling.
- [High] IOVA aliasing and page table corruption due to the page fault handler resurrecting purged heap BOs.
--

commit 2c49ad58ab72225d5654336ec80f9da1ce85ee4e
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

drm/panfrost: switch to iova_to_phys_length

Migrate panfrost_mmu to use ops->iova_to_phys_length(ops, iova, NULL)
instead of the deprecated ops->iova_to_phys.

> diff --git a/drivers/gpu/drm/panfrost/panfrost_mmu.c b/drivers/gpu/drm/panfrost/panfrost_mmu.c
> index 4a3162c3b659f..aa0bc82deaf68 100644
> --- a/drivers/gpu/drm/panfrost/panfrost_mmu.c
> +++ b/drivers/gpu/drm/panfrost/panfrost_mmu.c
> @@ -514,7 +514,7 @@ void panfrost_mmu_unmap(struct panfrost_gem_mapping *mapping)
>  
>  		if (bo->is_heap)
>  			pgcount = 1;
> -		if (!bo->is_heap || ops->iova_to_phys(ops, iova)) {
> +		if (!bo->is_heap || ops->iova_to_phys_length(ops, iova, NULL) != PHYS_ADDR_MAX) {
>  			unmapped_page = ops->unmap_pages(ops, iova, pgsize, pgcount, NULL);
>  			WARN_ON(unmapped_page != pgsize * pgcount);
>  		}

[Severity: High]
This isn't a bug introduced by this patch, but could this long-running
hardware unmap operation race with page fault handling and cause a
use-after-free?

When a GEM mapping's refcount drops to 0, panfrost_gem_mapping_release()
calls panfrost_gem_teardown_mapping(), which drops the mm_lock during hardware
unmapping:

drivers/gpu/drm/panfrost/panfrost_gem.c:panfrost_gem_teardown_mapping() {
    ...
    if (mapping->active)
        panfrost_mmu_unmap(mapping);

    spin_lock(&mapping->mmu->mm_lock);
    if (drm_mm_node_allocated(&mapping->mmnode))
        drm_mm_remove_node(&mapping->mmnode);
    spin_unlock(&mapping->mmu->mm_lock);
    ...
}

While the mm_lock is dropped, the mmnode remains discoverable in the drm_mm
tree. A concurrent page fault can then find this soon-to-be-freed mapping
and increment its refcount from 0 without checking:

drivers/gpu/drm/panfrost/panfrost_mmu.c:addr_to_mapping() {
    ...
    mapping = drm_mm_node_to_panfrost_mapping(node);

    kref_get(&mapping->refcount);
    break;
    ...
}

Once the release function finishes and frees the mapping, the page fault handler
would dereference the freed mapping pointer. Does addr_to_mapping() need to
use kref_get_unless_zero() to prevent this?

[Severity: High]
This is a pre-existing issue, but can the page fault handler resurrect a
purged heap BO and cause IOVA aliasing?

When the shrinker purges a heap BO, it frees the IOVA range and drops the
pages before releasing the BO's dma_resv lock:

drivers/gpu/drm/panfrost/panfrost_gem_shrinker.c:panfrost_gem_purge() {
    ...
    panfrost_gem_teardown_mappings_locked(bo);
    drm_gem_shmem_purge_locked(&bo->base);
    ...
}

Concurrently, a page fault handler might have found the mapping via
addr_to_mapping() and blocked waiting for the same dma_resv lock.

Once the shrinker completes and drops the lock, the fault handler wakes up,
acquires the lock, and sees !bo->base.pages. It then blindly allocates new
pages and maps them:

drivers/gpu/drm/panfrost/panfrost_mmu.c:panfrost_mmu_map_fault_addr() {
    ...
    if (!bo->base.pages) {
        bo->sgts = kvmalloc_objs(struct sg_table,
                                 bo->base.base.size / SZ_2M,
                                 GFP_KERNEL | __GFP_ZERO);
    ...
}

Since the IOVA range was already freed in the drm_mm allocator by the shrinker,
it could have been assigned to a different BO, leading to hardware page table
corruption. Should panfrost_mmu_map_fault_addr() verify if the BO was purged
(e.g., by checking if the mmnode is still allocated) before allocating new
pages?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=24

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 22/30] iommu/virtio: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 22/30] iommu/virtio: " Guanghui Feng
@ 2026-06-02 11:15         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:15 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [Critical] viommu_iova_to_phys_length returns the total arbitrary size of the interval tree mapping instead of a valid power-of-two PTE page size, leading to bitwise math errors in callers and out-of-bounds physical memory access.
- [High] The mapped_length calculation overflows to 0 for mappings covering the maximum address space.

Pre-existing issues:
- [Critical] Virtio-IOMMU uses standard `interval_tree` which silently truncates 64-bit `dma_addr_t` IOVAs to 32-bit `unsigned long` on 32-bit systems with LPAE.
--

commit aeb0a59bd9dc88bffe77f19f211f3b107da4e089
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/virtio: implement iova_to_phys_length

Implement iova_to_phys_length for virtio IOMMU driver,
returning the actual PTE mapping size.

> diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c
> index 587fc13197f12..c90d02cbbfd0d 100644
> --- a/drivers/iommu/virtio-iommu.c
> +++ b/drivers/iommu/virtio-iommu.c
[ ... ]
> -static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
> -				       dma_addr_t iova)
> +static phys_addr_t viommu_iova_to_phys_length(struct iommu_domain *domain,
> +					      dma_addr_t iova,
> +					      size_t *mapped_length)
>  {
> -	u64 paddr = 0;
> +	u64 paddr = PHYS_ADDR_MAX;
>  	unsigned long flags;
>  	struct viommu_mapping *mapping;
>  	struct interval_tree_node *node;
>  	struct viommu_domain *vdomain = to_viommu_domain(domain);
>  
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	spin_lock_irqsave(&vdomain->mappings_lock, flags);
>  	node = interval_tree_iter_first(&vdomain->mappings, iova, iova);
>  	if (node) {
>  		mapping = container_of(node, struct viommu_mapping, iova);
>  		paddr = mapping->paddr + (iova - mapping->iova.start);

[Severity: Critical]
This is a pre-existing issue, but does this calculation in
viommu_iova_to_phys_length() suffer from truncation on 32-bit systems
with LPAE?

The standard interval tree node uses unsigned long for its boundaries.
When assigning a 64-bit dma_addr_t to mapping->iova.start on these
architectures, it is silently truncated to 32 bits.

Mixing the 64-bit iova with the truncated 32-bit start in this subtraction
could produce incorrect physical address offsets and corrupt memory.

> +		if (mapped_length)
> +			*mapped_length = mapping->iova.last -
> +					 mapping->iova.start + 1;

[Severity: Critical]
Does this return an arbitrary block size instead of a valid power-of-two PTE
page size?

The IOMMU API expects mapped_length to output the PTE page size, which is a
power of two. Since virtio-iommu tracks mappings as coalesced interval tree
nodes, this calculation returns the full block size which is not guaranteed
to be a power of two.

Callers like batch_from_domain() in drivers/iommu/iommufd/pages.c rely on
bitwise arithmetic that assumes a power-of-two size:

	npages = (pgsize - (iova & (pgsize - 1)) + page_offset) / PAGE_SIZE;

Passing a non-power-of-two size here calculates an incorrect, larger
remaining length, causing out-of-bounds physical memory accesses.

[Severity: High]
Can this mapped_length calculation overflow and evaluate to zero for mappings
covering the maximum address space?

If an identity mapping spans the entire available address space,
mapping->iova.last will be ULONG_MAX and mapping->iova.start will be 0.
Adding 1 to this difference causes an integer overflow resulting in 0.

While callers defensively handle a 0 return by falling back to PAGE_SIZE, it
triggers WARN_ON(!pgsize) in downstream callers like vfio_iommu_type1.c and
iommufd, which can panic the kernel if panic_on_warn is set.

>  	}
>  	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=22

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 23/30] vfio/iommufd: use iova_to_phys_length for efficient unmap
  2026-06-02 10:46       ` [PATCH v2 23/30] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
@ 2026-06-02 11:16         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:16 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Out-of-bounds memory unpinning and unmapping in vfio_unmap_unpin() due to missing length cap.
--

commit cb17e198ab7d19ccbb139738e16d729d0fafa5dc
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

vfio/iommufd: use iova_to_phys_length for efficient unmap

This commit modifies the IOMMU unmap paths to use
iommu_iova_to_phys_length(), allowing the code to skip ahead by the
mapping granularity rather than walking the page tables in PAGE_SIZE
steps.

> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index c8151ba54de33..c86315b1fcdad 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -1177,25 +1177,41 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
[ ... ]
>  		/*
>  		 * To optimize for fewer iommu_unmap() calls, each of which
>  		 * may require hardware cache flushing, try to find the
>  		 * largest contiguous physical memory chunk to unmap.
> +		 *
> +		 * Calculate remaining contiguous bytes within this PTE from
> +		 * our position, then try to join following physically
> +		 * contiguous PTEs.
>  		 */
> -		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
> -			next = iommu_iova_to_phys(domain->domain, iova + len);
> +		len = pgsize - (iova & (pgsize - 1));
> +		for (; pos + len < dma->size; ) {
> +			size_t next_pgsize;
> +
> +			next = iommu_iova_to_phys_length(domain->domain,
> +							  iova + len,
> +							  &next_pgsize);
>  			if (next != phys + len)
>  				break;
> +			if (WARN_ON(!next_pgsize || next_pgsize < PAGE_SIZE))
> +				next_pgsize = PAGE_SIZE;
> +			len += next_pgsize;
>  		}

[Severity: Critical]
Does this code introduce an out-of-bounds regression when an IOMMU PTE covers
space beyond the current vfio_dma region?

If the IOMMU driver dynamically coalesces adjacent mappings into a larger PTE
(such as a 2M hugepage covering two contiguous vfio_dma regions), pgsize
can exceed the remaining dma->size.

Because len is initialized to pgsize - (iova & (pgsize - 1)) and is not
clamped to dma->size - pos, it appears this larger length will be passed down
to unmap_unpin_fast().

Could this result in unpinning physical pages that belong to adjacent DMA
regions, which might later be unpinned a second time when those adjacent
regions are processed?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=23

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 30/30] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops
  2026-06-02 10:46       ` [PATCH v2 30/30] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
@ 2026-06-02 11:16         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 11:16 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The kerneldoc for `iommu_iova_to_phys_length()` is incorrectly updated to state it returns `0` on failure, contradicting its implementation and API contract.
- [Low] The commit breaks compilation of `drivers/iommu/vsi-iommu.c` because it removes the `.iova_to_phys` field from `struct iommu_domain_ops` but leaves its initialization in `vsi_iommu_ops`.
--

commit e371c2338468e326f04a98ffb8dd1fb18a2872a0
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>
iommu: remove iova_to_phys from domain_ops and io_pgtable_ops

This commit removes the deprecated iova_to_phys interfaces from both
iommu_domain_ops and io_pgtable_ops now that callers are expected to have
migrated to iova_to_phys_length. It drops the fallback path in
iommu_iova_to_phys_length() and updates its kerneldoc.

> Now that all drivers implement iova_to_phys_length and all callers
> have migrated, remove the deprecated interfaces:

[Severity: Low]
Does drivers/iommu/vsi-iommu.c still need to be migrated? It appears to still
initialize the removed .iova_to_phys field in vsi_iommu_ops, which would cause
a compilation error:

drivers/iommu/vsi-iommu.c:
static const struct iommu_domain_ops vsi_iommu_ops = {
	...
	.iova_to_phys		= vsi_iommu_iova_to_phys,
};

> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index 1b1aaa53dd16a..84893d4c7c607 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
> @@ -2554,7 +2554,7 @@ EXPORT_SYMBOL_GPL(iommu_detach_group);
>   * Like iommu_iova_to_phys() but additionally returns the page size of the
>   * PTE mapping at @iova through @mapped_length.
>   *
> - * Return: The physical address for the given IOVA, or PHYS_ADDR_MAX if no translation.
> + * Return: The physical address for the given IOVA, or 0 if no translation.

[Severity: Medium]
Is this documentation update accurate?

The implementation of iommu_iova_to_phys_length() still returns
PHYS_ADDR_MAX when the domain ops lack the iova_to_phys_length callback:

	if (!domain->ops->iova_to_phys_length)
		return PHYS_ADDR_MAX;

Could misdocumenting this cause future driver authors to return 0 on
failure instead of PHYS_ADDR_MAX, potentially bypassing safety checks and
treating physical address 0 as a valid mapping?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=30

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length
  2026-06-02  7:20         ` guanghuifeng
@ 2026-06-02 12:32           ` Jason Gunthorpe
  0 siblings, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-02 12:32 UTC (permalink / raw)
  To: guanghuifeng@linux.alibaba.com
  Cc: boris.brezillon, robh, steven.price, adrian.larumbe,
	maarten.lankhorst, mripard, tzimmermann, airlied, liviu.dudau,
	joro, will, robin.murphy, alex, dri-devel, linux-kernel, iommu,
	kvm, linux-arm-kernel, kevin.tian, baolu.lu,
	suravee.suthikulpanit, dwmw2, xlpang, oliver.yang, shiyu.zsq,
	wei.guo.simon, alikernel-developer

On Tue, Jun 02, 2026 at 03:20:05PM +0800, guanghuifeng@linux.alibaba.com wrote:
> 
> 在 2026/6/1 7:54, Jason Gunthorpe 写道:
> > On Sun, May 31, 2026 at 05:36:31PM +0800, Guanghui Feng wrote:
> > > @@ -159,45 +164,51 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
> > >   	case PT_ENTRY_TABLE:
> > >   		return pt_descend(&pts, arg, descend_fn);
> > >   	case PT_ENTRY_OA:
> > > -		*res = pt_entry_oa_exact(&pts);
> > > +		data->phys = pt_entry_oa_exact(&pts);
> > > +		data->length = BIT(pt_entry_oa_lg2sz(&pts));
> > BIT is the wrong function, it uses the wrong type. log2_to_int() is
> > type'd properly
> > 
> > This also needs to keep walking and accumulating length for
> > consecutive PTEs until it reaches a non-contiguity.
> > 
> > The other drivers don't need to have that complexity.
> > 
> > Jason
> 
> pt_entry_oa_lg2sz has already considered continuous PTEs.
> 
> Does this mean that multiple PTEs need to be traversed additionally for
> consecutive PA address mappings?

yes

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 27/30] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper
  2026-06-02 10:46       ` [PATCH v2 27/30] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
@ 2026-06-02 13:22         ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-02 13:22 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] In `arm_smmu_iova_to_phys_hard`, a hardware translation failure returns `0` instead of `PHYS_ADDR_MAX`. This breaks callers of `iommu_iova_to_phys_length` which expect `PHYS_ADDR_MAX` to detect failures, causing them to treat physical address `0` as a valid translation result.
--

commit 17d24f53e1c566320f87a73674b736d336e36024
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from ARM LPAE io-pgtable, as all callers now use iova_to_phys_length.

> diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
> index 2b94e0563e214..ceea8da8d1fd7 100644
> --- a/drivers/iommu/io-pgtable-arm.c
> +++ b/drivers/iommu/io-pgtable-arm.c
[ ... ]
> @@ -999,7 +987,6 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
>  	data->iop.ops = (struct io_pgtable_ops) {
>  		.map_pages	= arm_lpae_map_pages,
>  		.unmap_pages	= arm_lpae_unmap_pages,
> -		.iova_to_phys	= arm_lpae_iova_to_phys,
>  		.iova_to_phys_length	= arm_lpae_iova_to_phys_length,

[Severity: Critical]
With the API migration to iova_to_phys_length now complete, does this introduce
a regression in error handling for drivers like arm-smmu?

The old iova_to_phys interface expected 0 on failure, but iova_to_phys_length
expects PHYS_ADDR_MAX. Looking at drivers/iommu/arm/arm-smmu/arm-smmu.c,
arm_smmu_iova_to_phys_length() can call arm_smmu_iova_to_phys_hard() which
still returns 0 instead of PHYS_ADDR_MAX on runtime PM get failure:

	ret = arm_smmu_rpm_get(smmu);
	if (ret < 0)
		return 0;

It also returns 0 on a hardware translation fault:

	if (phys & ARM_SMMU_CB_PAR_F) {
		dev_err(dev, "translation fault!\n");
		dev_err(dev, "PAR = 0x%llx\n", phys);
		goto out;
	}

	addr = (phys & GENMASK_ULL(39, 12)) | (iova & 0xfff);
out:
	arm_smmu_rpm_put(smmu);

	return addr;

Could callers like vfio_iommu_type1 now treat 0 as a valid physical address,
bypassing the WARN_ON(phys == PHYS_ADDR_MAX) check?

If iommu_unmap() unmaps the region, would VFIO use this incorrect physical
address 0 to unpin pages?

This appears to lead to an arbitrary page reference count decrement on PFN 0,
and could leak the reference count of the actual unmapped physical memory.

>  		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
>  		.pgtable_walk	= arm_lpae_pgtable_walk,
>  	};

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260602104637.1219810-1-guanghuifeng@linux.alibaba.com?part=27

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-02 10:46       ` [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
  2026-06-02 11:05         ` sashiko-bot
@ 2026-06-03  1:08         ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-03  1:08 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, joro, kevin.tian, kvm, linux-arm-kernel,
	linux-kernel, liviu.dudau, maarten.lankhorst, mripard,
	oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang,
	alikernel-developer

On Tue, Jun 02, 2026 at 06:46:08PM +0800, Guanghui Feng wrote:
> +/**
> + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
> + * @domain: IOMMU domain to query
> + * @iova: IO virtual address to translate
> + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
> + *
> + * Like iommu_iova_to_phys() but additionally returns the page size of the
> + * PTE mapping at @iova through @mapped_length.
> + *
> + * Return: The physical address for the given IOVA, or PHYS_ADDR_MAX if no translation.
> + */
> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> +				       dma_addr_t iova,
> +				       size_t *mapped_length)
>  {
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
>  	if (domain->type == IOMMU_DOMAIN_IDENTITY)
>  		return iova;

This doesn't seem right. It needs to return a mapped_length of
something that is not zero..

> -	if (domain->type == IOMMU_DOMAIN_BLOCKED)
> -		return 0;
> +	if (!domain->ops->iova_to_phys_length) {
> +		/* Fallback to legacy iova_to_phys without length info */
> +		if (domain->ops->iova_to_phys) {
> +			phys_addr_t phys = domain->ops->iova_to_phys(domain, iova);
> +			if (phys && mapped_length)
> +				*mapped_length = PAGE_SIZE;
> +			return phys ? phys : PHYS_ADDR_MAX;
> +		}

Follow success oriented flow..

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
  2026-06-02 11:06         ` sashiko-bot
@ 2026-06-03  1:11         ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-03  1:11 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, joro, kevin.tian, kvm, linux-arm-kernel,
	linux-kernel, liviu.dudau, maarten.lankhorst, mripard,
	oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang,
	alikernel-developer

On Tue, Jun 02, 2026 at 06:46:12PM +0800, Guanghui Feng wrote:
> +static __always_inline int __do_iova_to_phys_length(struct pt_range *range,
> +					       void *arg, unsigned int level,
> +					       struct pt_table_p *table,
> +					       pt_level_fn_t descend_fn)
>  {
>  	struct pt_state pts = pt_init(range, level, table);
> -	pt_oaddr_t *res = arg;
> +	struct iova_to_phys_length_data *data = arg;
>  
>  	switch (pt_load_single_entry(&pts)) {
>  	case PT_ENTRY_EMPTY:
> @@ -159,45 +164,53 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
>  	case PT_ENTRY_TABLE:
>  		return pt_descend(&pts, arg, descend_fn);
>  	case PT_ENTRY_OA:
> -		*res = pt_entry_oa_exact(&pts);
> -		return 0;
> +		break;
>  	}
> -	return -ENOENT;
> +
> +	data->phys = pt_entry_oa_exact(&pts);
> +	data->length = log2_to_int(pt_entry_oa_lg2sz(&pts));

I really do want this to accumulate the contiguous length if we are
changing things like this.

As I said before only iommufd needs to do this, the other drivers
don't have to, but the wrapper should take care to give a consistent
result so we don't have callers open coding another inefficient loop
on top.

vfio and iommufd should call it exactly once and then move on.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 10/30] iommu/ipmmu-vmsa: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 10/30] iommu/ipmmu-vmsa: " Guanghui Feng
@ 2026-06-03  1:13         ` Jason Gunthorpe
  0 siblings, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-03  1:13 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, joro, kevin.tian, kvm, linux-arm-kernel,
	linux-kernel, liviu.dudau, maarten.lankhorst, mripard,
	oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang,
	alikernel-developer

On Tue, Jun 02, 2026 at 06:46:17PM +0800, Guanghui Feng wrote:
> Migrate IPMMU-VMSA to implement iova_to_phys_length, passing through
> mapped_length from io-pgtable.
> 
> Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
> ---
>  drivers/iommu/ipmmu-vmsa.c | 12 ++++++++----
>  1 file changed, 8 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c
> index 9386b752dea2..a1b659ddbdb5 100644
> --- a/drivers/iommu/ipmmu-vmsa.c
> +++ b/drivers/iommu/ipmmu-vmsa.c
> @@ -699,14 +699,18 @@ static void ipmmu_iotlb_sync(struct iommu_domain *io_domain,
>  	ipmmu_flush_iotlb_all(io_domain);
>  }
>  
> -static phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain,
> -				      dma_addr_t iova)
> +static phys_addr_t ipmmu_iova_to_phys_length(struct iommu_domain *io_domain,
> +				      dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
>  
> +	if (mapped_length)
> +		*mapped_length = 0;

Why?

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v2 11/30] iommu/mtk_iommu: implement iova_to_phys_length
  2026-06-02 10:46       ` [PATCH v2 11/30] iommu/mtk_iommu: " Guanghui Feng
@ 2026-06-03  1:17         ` Jason Gunthorpe
  0 siblings, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-03  1:17 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: adrian.larumbe, airlied, alex, baolu.lu, boris.brezillon,
	dri-devel, dwmw2, iommu, joro, kevin.tian, kvm, linux-arm-kernel,
	linux-kernel, liviu.dudau, maarten.lankhorst, mripard,
	oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang,
	alikernel-developer

On Tue, Jun 02, 2026 at 06:46:18PM +0800, Guanghui Feng wrote:
> -	pa = dom->iop->iova_to_phys(dom->iop, iova);
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
> +	pa = dom->iop->iova_to_phys_length(dom->iop, iova, mapped_length);
> +	if (pa == PHYS_ADDR_MAX)
> +		return PHYS_ADDR_MAX;

???

I guess lots of AI made this right? Please review the AI output for
the little slop things left over..

I saw enough unnecessary hunks, and goofy things like putting the
mapped_len zero in every driver instead of in the core caller.

That said it looks pretty close overall..

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys
  2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
                         ` (29 preceding siblings ...)
  2026-06-02 10:46       ` [PATCH v2 30/30] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
@ 2026-06-03 15:17       ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
                           ` (31 more replies)
  30 siblings, 32 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

This series introduces a new iova_to_phys_length() interface to the IOMMU
subsystem, migrates all drivers and callers to it, and finally removes the
legacy iova_to_phys() interface.

Motivation
==========

The existing iommu_iova_to_phys() returns only the physical address for a
given IOVA. Callers that need to walk a range of IOVA space (most notably
VFIO and IOMMUFD during unmap) have no way to learn the size of the mapping
backing an IOVA, so they are forced to iterate in fixed PAGE_SIZE steps.
When the underlying mapping uses large pages (e.g. 2MB or 1GB), this results
in a separate page table walk for every 4KB, which is highly inefficient for
large regions.

iommu_iova_to_phys_length() returns both the physical address and the page
size of the PTE backing the IOVA in a single page table walk. Callers can
then advance by the actual mapping granularity instead of PAGE_SIZE,
dramatically reducing the number of page table walks during teardown of
large mappings.

The new helper translates a missing mapping to PHYS_ADDR_MAX (rather than 0)
so that a zero physical address can be distinguished from a translation
failure, and the legacy iommu_iova_to_phys() is reimplemented on top of it.

Approach
========

The series is structured to keep the tree bisectable at every step: the new
interface is added first with a fallback to the legacy callback, every
driver and caller is migrated, and only then is the old interface removed.

  - Core and page table layer (patches 1-5):
    Add iova_to_phys_length to iommu_domain_ops and io_pgtable_ops, the
    core iommu_iova_to_phys_length() helper with a fallback to the legacy
    iova_to_phys, and implement it in the io-pgtable arm, arm-v7s and dart
    formats as well as the generic_pt framework.

  - Driver migration (patches 6-22):
    Implement iova_to_phys_length in all remaining IOMMU drivers:
    arm-smmu-v3, arm-smmu, qcom_iommu, apple-dart, ipmmu-vmsa, mtk_iommu,
    exynos, fsl_pamu, msm, mtk_v1, omap, rockchip, s390, sprd, sun50i,
    tegra-smmu and virtio.

  - Caller migration (patches 23-28):
    Switch VFIO and IOMMUFD to iova_to_phys_length for efficient,
    granularity-aware unmap, update the iommufd selftest, migrate the
    DRM panfrost and panthor drivers, and switch the io-pgtable selftests
    to the new interface.

  - Removal (patches 29-32):
    Drop the now-unused iova_to_phys wrappers from the io-pgtable arm,
    arm-v7s and dart formats, and finally remove iova_to_phys from
    iommu_domain_ops and io_pgtable_ops.

No functional change is intended for translation results; the change is
purely about exposing mapping size and using it to make range operations
more efficient.

Guanghui Feng (32):
  iommu: introduce iova_to_phys_length in iommu_domain_ops
  iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops
  iommu/io-pgtable-arm-v7s: introduce iova_to_phys_length in
    io_pgtable_ops
  iommu/io-pgtable-dart: introduce iova_to_phys_length in io_pgtable_ops
  iommu/generic_pt: implement iova_to_phys_length
  iommu/arm-smmu-v3: implement iova_to_phys_length
  iommu/arm-smmu: implement iova_to_phys_length
  iommu/qcom_iommu: implement iova_to_phys_length
  iommu/apple-dart: implement iova_to_phys_length
  iommu/ipmmu-vmsa: implement iova_to_phys_length
  iommu/mtk_iommu: implement iova_to_phys_length
  iommu/exynos: implement iova_to_phys_length
  iommu/fsl_pamu: implement iova_to_phys_length
  iommu/msm: implement iova_to_phys_length
  iommu/mtk_v1: implement iova_to_phys_length
  iommu/omap: implement iova_to_phys_length
  iommu/rockchip: implement iova_to_phys_length
  iommu/s390: implement iova_to_phys_length
  iommu/sprd: implement iova_to_phys_length
  iommu/sun50i: implement iova_to_phys_length
  iommu/tegra-smmu: implement iova_to_phys_length
  iommu/virtio: implement iova_to_phys_length
  vfio: use iova_to_phys_length for efficient unmap
  iommufd: use iova_to_phys_length for efficient unmap
  iommufd/selftest: switch to iommu_iova_to_phys_length
  drm/panfrost: switch to iova_to_phys_length
  drm/panthor: switch to iova_to_phys_length
  iommu/io-pgtable: selftests switch to iova_to_phys_length
  iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper
  iommu/io-pgtable-arm-v7s: remove deprecated iova_to_phys wrapper
  iommu/io-pgtable-dart: remove deprecated iova_to_phys wrapper
  iommu: remove iova_to_phys from domain_ops and io_pgtable_ops

 drivers/gpu/drm/panfrost/panfrost_mmu.c       |  2 +-
 drivers/gpu/drm/panthor/panthor_mmu.c         |  2 +-
 drivers/iommu/apple-dart.c                    | 11 +--
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c   | 10 ++-
 .../iommu/arm/arm-smmu/arm-smmu-qcom-debug.c  |  2 +-
 drivers/iommu/arm/arm-smmu/arm-smmu.c         | 13 +--
 drivers/iommu/arm/arm-smmu/qcom_iommu.c       | 11 +--
 drivers/iommu/exynos-iommu.c                  | 20 +++--
 drivers/iommu/fsl_pamu_domain.c               | 26 +++++-
 drivers/iommu/generic_pt/iommu_pt.h           | 84 ++++++++++++++-----
 drivers/iommu/io-pgtable-arm-selftests.c      | 12 +--
 drivers/iommu/io-pgtable-arm-v7s.c            | 25 +++---
 drivers/iommu/io-pgtable-arm.c                | 16 ++--
 drivers/iommu/io-pgtable-dart.c               | 21 +++--
 drivers/iommu/iommu.c                         | 38 +++++++--
 drivers/iommu/iommufd/pages.c                 | 74 +++++++++++++---
 drivers/iommu/iommufd/selftest.c              |  2 +-
 drivers/iommu/ipmmu-vmsa.c                    | 10 ++-
 drivers/iommu/msm_iommu.c                     | 21 +++--
 drivers/iommu/mtk_iommu.c                     | 11 ++-
 drivers/iommu/mtk_iommu_v1.c                  | 13 ++-
 drivers/iommu/omap-iommu.c                    | 29 ++++---
 drivers/iommu/rockchip-iommu.c                | 10 ++-
 drivers/iommu/s390-iommu.c                    | 18 ++--
 drivers/iommu/sprd-iommu.c                    | 17 ++--
 drivers/iommu/sun50i-iommu.c                  | 14 ++--
 drivers/iommu/tegra-smmu.c                    | 11 ++-
 drivers/iommu/virtio-iommu.c                  | 12 ++-
 drivers/vfio/vfio_iommu_type1.c               | 27 ++++--
 include/linux/generic_pt/iommu.h              | 13 +--
 include/linux/io-pgtable.h                    | 11 ++-
 include/linux/iommu.h                         | 12 ++-
 32 files changed, 423 insertions(+), 175 deletions(-)

-- 
2.43.7


^ permalink raw reply	[flat|nested] 144+ messages in thread

* [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:38           ` sashiko-bot
                             ` (2 more replies)
  2026-06-03 15:17         ` [PATCH v3 02/32] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
                           ` (30 subsequent siblings)
  31 siblings, 3 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Add iova_to_phys_length callback to struct iommu_domain_ops alongside
the existing iova_to_phys. The new callback returns both the physical
address and the PTE mapping page size in a single page table walk.

Add iommu_iova_to_phys_length() core function that:
- Checks ops->iova_to_phys_length first (preferred path)
- Falls back to ops->iova_to_phys for unmigrated drivers

This enables callers like VFIO to efficiently traverse IOVA space
by actual mapping granularity instead of fixed PAGE_SIZE steps.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/iommu.c | 50 ++++++++++++++++++++++++++++++++++++++-----
 include/linux/iommu.h |  9 ++++++++
 2 files changed, 54 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index d1a9e713d3a0..320ea13488e7 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2545,15 +2545,55 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
 }
 EXPORT_SYMBOL_GPL(iommu_detach_group);
 
-phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+/**
+ * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
+ * @domain: IOMMU domain to query
+ * @iova: IO virtual address to translate
+ * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
+ *
+ * Like iommu_iova_to_phys() but additionally returns the page size of the
+ * PTE mapping at @iova through @mapped_length.
+ *
+ * Return: The physical address for the given IOVA, or PHYS_ADDR_MAX if no
+ *         translation exists.
+ */
+phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
+				       dma_addr_t iova,
+				       size_t *mapped_length)
 {
-	if (domain->type == IOMMU_DOMAIN_IDENTITY)
+	phys_addr_t phys;
+
+	if (domain->type == IOMMU_DOMAIN_IDENTITY) {
+		if (mapped_length)
+			*mapped_length = PAGE_SIZE;
 		return iova;
+	}
 
-	if (domain->type == IOMMU_DOMAIN_BLOCKED)
-		return 0;
+	if (mapped_length)
+		*mapped_length = 0;
+
+	if (domain->ops->iova_to_phys_length)
+		return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
+
+	/* Fallback to legacy iova_to_phys without length info */
+	if (!domain->ops->iova_to_phys)
+		return PHYS_ADDR_MAX;
+
+	phys = domain->ops->iova_to_phys(domain, iova);
+	if (!phys)
+		return PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = PAGE_SIZE;
+	return phys;
+}
+EXPORT_SYMBOL_GPL(iommu_iova_to_phys_length);
+
+phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+{
+	phys_addr_t phys = iommu_iova_to_phys_length(domain, iova, NULL);
 
-	return domain->ops->iova_to_phys(domain, iova);
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
 }
 EXPORT_SYMBOL_GPL(iommu_iova_to_phys);
 
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e587d4ac4d33..19da84c2922c 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -747,6 +747,9 @@ struct iommu_ops {
  *                         invalidation requests. The driver data structure
  *                         must be defined in include/uapi/linux/iommufd.h
  * @iova_to_phys: translate iova to physical address
+ * @iova_to_phys_length: translate iova to physical address and additionally
+ *                       return the page size of the PTE mapping at @iova
+ *                       through @mapped_length.
  * @enforce_cache_coherency: Prevent any kind of DMA from bypassing IOMMU_CACHE,
  *                           including no-snoop TLPs on PCIe or other platform
  *                           specific mechanisms.
@@ -776,6 +779,9 @@ struct iommu_domain_ops {
 
 	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
 				    dma_addr_t iova);
+	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
+					    dma_addr_t iova,
+					    size_t *mapped_length);
 
 	bool (*enforce_cache_coherency)(struct iommu_domain *domain);
 	int (*set_pgtable_quirks)(struct iommu_domain *domain,
@@ -930,6 +936,9 @@ extern ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
 			    struct scatterlist *sg, unsigned int nents,
 			    int prot, gfp_t gfp);
 extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova);
+extern phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
+					      dma_addr_t iova,
+					      size_t *mapped_length);
 extern void iommu_set_fault_handler(struct iommu_domain *domain,
 			iommu_fault_handler_t handler, void *token);
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 02/32] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:35           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 03/32] iommu/io-pgtable-arm-v7s: " Guanghui Feng
                           ` (29 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Add iova_to_phys_length to struct io_pgtable_ops alongside iova_to_phys.
Implement in ARM LPAE backend: returns ARM_LPAE_BLOCK_SIZE at the resolved level.
The old iova_to_phys is kept as a thin wrapper for backward compat.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm.c | 23 +++++++++++++++++++++--
 include/linux/io-pgtable.h     |  7 +++++++
 2 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 0208e5897c29..f33a86fa0f6c 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -731,8 +731,21 @@ static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
 	return 0;
 }
 
+static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
+						 unsigned long iova,
+						 size_t *mapped_length);
+
 static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
 					 unsigned long iova)
+{
+	phys_addr_t phys = arm_lpae_iova_to_phys_length(ops, iova, NULL);
+
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
+}
+
+static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
+						 unsigned long iova,
+						 size_t *mapped_length)
 {
 	struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	struct iova_to_phys_data d;
@@ -742,13 +755,18 @@ static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
 		.addr = iova,
 		.end = iova + 1,
 	};
+	size_t block_size;
 	int ret;
 
 	ret = __arm_lpae_iopte_walk(data, &walk_data, data->pgd, data->start_level);
 	if (ret)
-		return 0;
+		return PHYS_ADDR_MAX;
+
+	block_size = ARM_LPAE_BLOCK_SIZE(d.lvl, data);
+	if (mapped_length)
+		*mapped_length = block_size;
 
-	iova &= (ARM_LPAE_BLOCK_SIZE(d.lvl, data) - 1);
+	iova &= (block_size - 1);
 	return iopte_to_paddr(d.pte, data) | iova;
 }
 
@@ -948,6 +966,7 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
 		.map_pages	= arm_lpae_map_pages,
 		.unmap_pages	= arm_lpae_unmap_pages,
 		.iova_to_phys	= arm_lpae_iova_to_phys,
+		.iova_to_phys_length	= arm_lpae_iova_to_phys_length,
 		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
 		.pgtable_walk	= arm_lpae_pgtable_walk,
 	};
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index e19872e37e06..42bcdd309b88 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -203,6 +203,10 @@ struct arm_lpae_io_pgtable_walk_data {
  * @map_pages:    Map a physically contiguous range of pages of the same size.
  * @unmap_pages:  Unmap a range of virtually contiguous pages of the same size.
  * @iova_to_phys: Translate iova to physical address.
+ * @iova_to_phys_length: Translate iova to physical address and return the
+ *			  remaining mapped length from iova to the end of the
+ *			  mapping entry via @mapped_length. If @mapped_length is
+ *			  NULL, only the physical address is returned.
  * @pgtable_walk: (optional) Perform a page table walk for a given iova.
  * @read_and_clear_dirty: Record dirty info per IOVA. If an IOVA is dirty,
  *			  clear its dirty state from the PTE unless the
@@ -220,6 +224,9 @@ struct io_pgtable_ops {
 			      struct iommu_iotlb_gather *gather);
 	phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,
 				    unsigned long iova);
+	phys_addr_t (*iova_to_phys_length)(struct io_pgtable_ops *ops,
+					   unsigned long iova,
+					   size_t *mapped_length);
 	int (*pgtable_walk)(struct io_pgtable_ops *ops, unsigned long iova, void *wd);
 	int (*read_and_clear_dirty)(struct io_pgtable_ops *ops,
 				    unsigned long iova, size_t size,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 03/32] iommu/io-pgtable-arm-v7s: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 02/32] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 04/32] iommu/io-pgtable-dart: " Guanghui Feng
                           ` (28 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length in ARM v7s backend: returns block size
derived from level mask. The old iova_to_phys is kept as a thin wrapper.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-v7s.c | 32 +++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
index 1dbef8c55007..62198e31a393 100644
--- a/drivers/iommu/io-pgtable-arm-v7s.c
+++ b/drivers/iommu/io-pgtable-arm-v7s.c
@@ -641,8 +641,21 @@ static size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova
 	return unmapped;
 }
 
+static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
+						unsigned long iova,
+						size_t *mapped_length);
+
 static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
 					unsigned long iova)
+{
+	phys_addr_t phys = arm_v7s_iova_to_phys_length(ops, iova, NULL);
+
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
+}
+
+static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
+						unsigned long iova,
+						size_t *mapped_length)
 {
 	struct arm_v7s_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	arm_v7s_iopte *ptep = data->pgd, pte;
@@ -656,11 +669,15 @@ static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
 	} while (ARM_V7S_PTE_IS_TABLE(pte, lvl));
 
 	if (!ARM_V7S_PTE_IS_VALID(pte))
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	mask = ARM_V7S_LVL_MASK(lvl);
 	if (arm_v7s_pte_is_cont(pte, lvl))
 		mask *= ARM_V7S_CONT_PAGES;
+
+	if (mapped_length)
+		*mapped_length = ~mask + 1U;
+
 	return iopte_to_paddr(pte, lvl, &data->iop.cfg) | (iova & ~mask);
 }
 
@@ -714,6 +731,7 @@ static struct io_pgtable *arm_v7s_alloc_pgtable(struct io_pgtable_cfg *cfg,
 		.map_pages	= arm_v7s_map_pages,
 		.unmap_pages	= arm_v7s_unmap_pages,
 		.iova_to_phys	= arm_v7s_iova_to_phys,
+		.iova_to_phys_length	= arm_v7s_iova_to_phys_length,
 	};
 
 	/* We have to do this early for __arm_v7s_alloc_table to work... */
@@ -843,13 +861,13 @@ static int __init arm_v7s_do_selftests(void)
 	 * Initial sanity checks.
 	 * Empty page tables shouldn't provide any translations.
 	 */
-	if (ops->iova_to_phys(ops, 42))
+	if (ops->iova_to_phys_length(ops, 42, NULL) != PHYS_ADDR_MAX)
 		return __FAIL(ops);
 
-	if (ops->iova_to_phys(ops, SZ_1G + 42))
+	if (ops->iova_to_phys_length(ops, SZ_1G + 42, NULL) != PHYS_ADDR_MAX)
 		return __FAIL(ops);
 
-	if (ops->iova_to_phys(ops, SZ_2G + 42))
+	if (ops->iova_to_phys_length(ops, SZ_2G + 42, NULL) != PHYS_ADDR_MAX)
 		return __FAIL(ops);
 
 	/*
@@ -870,7 +888,7 @@ static int __init arm_v7s_do_selftests(void)
 				    &mapped))
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 			return __FAIL(ops);
 
 		iova += SZ_16M;
@@ -884,7 +902,7 @@ static int __init arm_v7s_do_selftests(void)
 		if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(ops);
 
 		/* Remap full block */
@@ -892,7 +910,7 @@ static int __init arm_v7s_do_selftests(void)
 				   GFP_KERNEL, &mapped))
 			return __FAIL(ops);
 
-		if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+		if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 			return __FAIL(ops);
 
 		iova += SZ_16M;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 04/32] iommu/io-pgtable-dart: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (2 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 03/32] iommu/io-pgtable-arm-v7s: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
                           ` (27 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length in DART backend: returns pgsize from
cfg.pgsize_bitmap (single fixed page size). The old iova_to_phys is kept
as a thin wrapper.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-dart.c | 32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index cbc5d6aa2daa..2dac21a578a7 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -333,29 +333,46 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	return i * pgsize;
 }
 
+static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
+					    unsigned long iova,
+					    size_t *mapped_length);
+
 static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
-					 unsigned long iova)
+				     unsigned long iova)
+{
+	phys_addr_t phys = dart_iova_to_phys_length(ops, iova, NULL);
+
+	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
+}
+
+static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
+					    unsigned long iova,
+					    size_t *mapped_length)
 {
 	struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	dart_iopte pte, *ptep;
+	size_t pgsize;
 
 	ptep = dart_get_last(data, iova);
 
 	/* Valid L2 IOPTE pointer? */
 	if (!ptep)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	ptep += dart_get_last_index(data, iova);
 
 	pte = READ_ONCE(*ptep);
 	/* Found translation */
 	if (pte) {
-		iova &= (data->iop.cfg.pgsize_bitmap - 1);
+		pgsize = data->iop.cfg.pgsize_bitmap;
+		if (mapped_length)
+			*mapped_length = pgsize;
+		iova &= (pgsize - 1);
 		return iopte_to_paddr(pte, data) | iova;
 	}
 
 	/* Ran out of page tables to walk */
-	return 0;
+	return PHYS_ADDR_MAX;
 }
 
 static struct dart_io_pgtable *
@@ -397,9 +414,10 @@ dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->bits_per_level = bits_per_level;
 
 	data->iop.ops = (struct io_pgtable_ops) {
-		.map_pages	= dart_map_pages,
-		.unmap_pages	= dart_unmap_pages,
-		.iova_to_phys	= dart_iova_to_phys,
+		.map_pages		= dart_map_pages,
+		.unmap_pages		= dart_unmap_pages,
+		.iova_to_phys		= dart_iova_to_phys,
+		.iova_to_phys_length	= dart_iova_to_phys_length,
 	};
 
 	return data;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (3 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 04/32] iommu/io-pgtable-dart: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:39           ` sashiko-bot
  2026-06-04  3:30           ` Baolu Lu
  2026-06-03 15:17         ` [PATCH v3 06/32] iommu/arm-smmu-v3: " Guanghui Feng
                           ` (26 subsequent siblings)
  31 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Extend the Generic Page Table framework to implement iova_to_phys_length.
Use pt_entry_oa_lg2sz() to determine PTE block size. Update
IOMMU_PT_DOMAIN_OPS macro to set .iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/generic_pt/iommu_pt.h | 84 +++++++++++++++++++++--------
 include/linux/generic_pt/iommu.h    | 13 ++---
 2 files changed, 69 insertions(+), 28 deletions(-)

diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
index dc91fb4e2f61..e362e819ef9c 100644
--- a/drivers/iommu/generic_pt/iommu_pt.h
+++ b/drivers/iommu/generic_pt/iommu_pt.h
@@ -145,13 +145,21 @@ static inline unsigned int compute_best_pgsize(struct pt_state *pts,
 				      pts->range->va, pts->range->last_va, oa);
 }
 
-static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
-					     unsigned int level,
-					     struct pt_table_p *table,
-					     pt_level_fn_t descend_fn)
+struct iova_to_phys_length_data {
+	pt_oaddr_t phys;
+	size_t length;
+};
+
+static __always_inline int __do_iova_to_phys_length(struct pt_range *range,
+					       void *arg, unsigned int level,
+					       struct pt_table_p *table,
+					       pt_level_fn_t descend_fn)
 {
 	struct pt_state pts = pt_init(range, level, table);
-	pt_oaddr_t *res = arg;
+	struct iova_to_phys_length_data *data = arg;
+	unsigned int entry_lg2sz;
+	size_t entry_sz;
+	pt_oaddr_t expected_oa;
 
 	switch (pt_load_single_entry(&pts)) {
 	case PT_ENTRY_EMPTY:
@@ -159,45 +167,77 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
 	case PT_ENTRY_TABLE:
 		return pt_descend(&pts, arg, descend_fn);
 	case PT_ENTRY_OA:
-		*res = pt_entry_oa_exact(&pts);
-		return 0;
+		break;
 	}
-	return -ENOENT;
+
+	data->phys = pt_entry_oa_exact(&pts);
+	entry_lg2sz = pt_entry_oa_lg2sz(&pts);
+	entry_sz = log2_to_int(entry_lg2sz);
+
+	/* Start with the full mapping size of the first entry */
+	data->length = entry_sz;
+
+	/* Accumulate subsequent physically contiguous entries */
+	expected_oa = pt_entry_oa(&pts) + entry_sz;
+	pts.end_index = log2_to_int(pt_num_items_lg2(&pts));
+	pt_next_entry(&pts);
+
+	while (pts.index < pts.end_index) {
+		pt_load_entry(&pts);
+		if (pts.type != PT_ENTRY_OA)
+			break;
+		if (pt_entry_oa_lg2sz(&pts) != entry_lg2sz)
+			break;
+		if (pt_entry_oa(&pts) != expected_oa)
+			break;
+		data->length += entry_sz;
+		expected_oa += entry_sz;
+		pt_next_entry(&pts);
+	}
+
+	return 0;
 }
-PT_MAKE_LEVELS(__iova_to_phys, __do_iova_to_phys);
+PT_MAKE_LEVELS(__iova_to_phys_length, __do_iova_to_phys_length);
 
 /**
- * iova_to_phys() - Return the output address for the given IOVA
+ * iova_to_phys_length() - Translate IOVA returning phys and contiguous length
  * @domain: Table to query
  * @iova: IO virtual address to query
+ * @mapped_length: Output for the total contiguous mapped length in bytes
  *
- * Determine the output address from the given IOVA. @iova may have any
- * alignment, the returned physical will be adjusted with any sub page offset.
+ * Walk the IOMMU page table to translate @iova to a physical address while
+ * also returning the total contiguous physically mapped length through
+ * @mapped_length. The function accumulates consecutive page table entries that
+ * are physically contiguous, so callers can determine the full contiguous
+ * mapping extent with a single call.
  *
  * Context: The caller must hold a read range lock that includes @iova.
  *
- * Return: 0 if there is no translation for the given iova.
+ * Return: The physical address, or PHYS_ADDR_MAX if there is no translation.
  */
-phys_addr_t DOMAIN_NS(iova_to_phys)(struct iommu_domain *domain,
-				    dma_addr_t iova)
+phys_addr_t DOMAIN_NS(iova_to_phys_length)(struct iommu_domain *domain,
+					    dma_addr_t iova,
+					    size_t *mapped_length)
 {
 	struct pt_iommu *iommu_table =
 		container_of(domain, struct pt_iommu, domain);
 	struct pt_range range;
-	pt_oaddr_t res;
+	struct iova_to_phys_length_data data;
 	int ret;
 
 	ret = make_range(common_from_iommu(iommu_table), &range, iova, 1);
 	if (ret)
-		return ret;
+		return PHYS_ADDR_MAX;
 
-	ret = pt_walk_range(&range, __iova_to_phys, &res);
-	/* PHYS_ADDR_MAX would be a better error code */
+	ret = pt_walk_range(&range, __iova_to_phys_length, &data);
 	if (ret)
-		return 0;
-	return res;
+		return PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = data.length;
+	return data.phys;
 }
-EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys), "GENERIC_PT_IOMMU");
+EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys_length), "GENERIC_PT_IOMMU");
 
 struct pt_iommu_dirty_args {
 	struct iommu_dirty_bitmap *dirty;
diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
index dd0edd02a48a..859b853e9dc7 100644
--- a/include/linux/generic_pt/iommu.h
+++ b/include/linux/generic_pt/iommu.h
@@ -249,8 +249,9 @@ struct pt_iommu_cfg {
 
 /* Generate the exported function signatures from iommu_pt.h */
 #define IOMMU_PROTOTYPES(fmt)                                                  \
-	phys_addr_t pt_iommu_##fmt##_iova_to_phys(struct iommu_domain *domain, \
-						  dma_addr_t iova);            \
+	phys_addr_t pt_iommu_##fmt##_iova_to_phys_length(			\
+		struct iommu_domain *domain, dma_addr_t iova,			\
+		size_t *mapped_length);						\
 	int pt_iommu_##fmt##_read_and_clear_dirty(                             \
 		struct iommu_domain *domain, unsigned long iova, size_t size,  \
 		unsigned long flags, struct iommu_dirty_bitmap *dirty);        \
@@ -267,11 +268,11 @@ struct pt_iommu_cfg {
 	IOMMU_PROTOTYPES(fmt)
 
 /*
- * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the
- * iommu_pt
+ * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for
+ * the iommu_pt
  */
-#define IOMMU_PT_DOMAIN_OPS(fmt)                        \
-	.iova_to_phys = &pt_iommu_##fmt##_iova_to_phys
+#define IOMMU_PT_DOMAIN_OPS(fmt)					\
+	.iova_to_phys_length = &pt_iommu_##fmt##_iova_to_phys_length
 #define IOMMU_PT_DIRTY_OPS(fmt) \
 	.read_and_clear_dirty = &pt_iommu_##fmt##_read_and_clear_dirty
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 06/32] iommu/arm-smmu-v3: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (4 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 07/32] iommu/arm-smmu: " Guanghui Feng
                           ` (25 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate ARM SMMUv3 to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index e8d7dbe495f0..616e7057ec7f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4069,14 +4069,16 @@ static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
 }
 
 static phys_addr_t
-arm_smmu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+arm_smmu_iova_to_phys_length(struct iommu_domain *domain, dma_addr_t iova,
+			     size_t *mapped_length)
 {
 	struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;
 
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static struct platform_driver arm_smmu_driver;
@@ -4396,7 +4398,7 @@ static const struct iommu_ops arm_smmu_ops = {
 		.unmap_pages		= arm_smmu_unmap_pages,
 		.flush_iotlb_all	= arm_smmu_flush_iotlb_all,
 		.iotlb_sync		= arm_smmu_iotlb_sync,
-		.iova_to_phys		= arm_smmu_iova_to_phys,
+		.iova_to_phys_length	= arm_smmu_iova_to_phys_length,
 		.free			= arm_smmu_domain_free_paging,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 07/32] iommu/arm-smmu: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (5 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 06/32] iommu/arm-smmu-v3: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:42           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 08/32] iommu/qcom_iommu: " Guanghui Feng
                           ` (24 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate ARM SMMU to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer. Update qcom-debug
caller accordingly.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c |  2 +-
 drivers/iommu/arm/arm-smmu/arm-smmu.c            | 13 +++++++------
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
index 65e0ef6539fe..4fd01341157f 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
@@ -415,7 +415,7 @@ irqreturn_t qcom_smmu_context_fault(int irq, void *dev)
 		return IRQ_HANDLED;
 	}
 
-	phys_soft = ops->iova_to_phys(ops, cfi.iova);
+	phys_soft = ops->iova_to_phys_length(ops, cfi.iova, NULL);
 
 	tmp = report_iommu_fault(&smmu_domain->domain, NULL, cfi.iova,
 				 cfi.fsynr & ARM_SMMU_CB_FSYNR0_WNR ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ);
diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
index 0bd21d206eb3..5c9ec7c93763 100644
--- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
+++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
@@ -1366,7 +1366,7 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
 			"iova to phys timed out on %pad. Falling back to software table walk.\n",
 			&iova);
 		arm_smmu_rpm_put(smmu);
-		return ops->iova_to_phys(ops, iova);
+		return ops->iova_to_phys_length(ops, iova, NULL);
 	}
 
 	phys = arm_smmu_cb_readq(smmu, idx, ARM_SMMU_CB_PAR);
@@ -1384,20 +1384,21 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
 	return addr;
 }
 
-static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
-					dma_addr_t iova)
+static phys_addr_t arm_smmu_iova_to_phys_length(struct iommu_domain *domain,
+					dma_addr_t iova, size_t *mapped_length)
 {
 	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
 	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
 
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS &&
 			smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
 		return arm_smmu_iova_to_phys_hard(domain, iova);
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static bool arm_smmu_capable(struct device *dev, enum iommu_cap cap)
@@ -1652,7 +1653,7 @@ static const struct iommu_ops arm_smmu_ops = {
 		.unmap_pages		= arm_smmu_unmap_pages,
 		.flush_iotlb_all	= arm_smmu_flush_iotlb_all,
 		.iotlb_sync		= arm_smmu_iotlb_sync,
-		.iova_to_phys		= arm_smmu_iova_to_phys,
+		.iova_to_phys_length	= arm_smmu_iova_to_phys_length,
 		.set_pgtable_quirks	= arm_smmu_set_pgtable_quirks,
 		.free			= arm_smmu_domain_free,
 	}
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 08/32] iommu/qcom_iommu: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (6 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 07/32] iommu/arm-smmu: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 09/32] iommu/apple-dart: " Guanghui Feng
                           ` (23 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate Qualcomm IOMMU to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/arm/arm-smmu/qcom_iommu.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu/qcom_iommu.c b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
index a1e8cf29f594..9e0e8b5caec1 100644
--- a/drivers/iommu/arm/arm-smmu/qcom_iommu.c
+++ b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
@@ -489,19 +489,20 @@ static void qcom_iommu_iotlb_sync(struct iommu_domain *domain,
 	qcom_iommu_flush_iotlb_all(domain);
 }
 
-static phys_addr_t qcom_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t qcom_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	phys_addr_t ret;
 	unsigned long flags;
 	struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain);
 	struct io_pgtable_ops *ops = qcom_domain->pgtbl_ops;
 
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	spin_lock_irqsave(&qcom_domain->pgtbl_lock, flags);
-	ret = ops->iova_to_phys(ops, iova);
+	ret = ops->iova_to_phys_length(ops, iova, mapped_length);
 	spin_unlock_irqrestore(&qcom_domain->pgtbl_lock, flags);
 
 	return ret;
@@ -602,7 +603,7 @@ static const struct iommu_ops qcom_iommu_ops = {
 		.unmap_pages	= qcom_iommu_unmap,
 		.flush_iotlb_all = qcom_iommu_flush_iotlb_all,
 		.iotlb_sync	= qcom_iommu_iotlb_sync,
-		.iova_to_phys	= qcom_iommu_iova_to_phys,
+		.iova_to_phys_length = qcom_iommu_iova_to_phys_length,
 		.free		= qcom_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 09/32] iommu/apple-dart: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (7 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 08/32] iommu/qcom_iommu: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 10/32] iommu/ipmmu-vmsa: " Guanghui Feng
                           ` (22 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate Apple DART to implement iova_to_phys_length, passing through
mapped_length from io-pgtable.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/apple-dart.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 17bdadb6b504..fdc533ba72da 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -528,16 +528,17 @@ static int apple_dart_iotlb_sync_map(struct iommu_domain *domain,
 	return 0;
 }
 
-static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t apple_dart_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	struct apple_dart_domain *dart_domain = to_dart_domain(domain);
 	struct io_pgtable_ops *ops = dart_domain->pgtbl_ops;
 
+
 	if (!ops)
-		return 0;
+		return PHYS_ADDR_MAX;
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys_length(ops, iova, mapped_length);
 }
 
 static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
@@ -1018,7 +1019,7 @@ static const struct iommu_ops apple_dart_iommu_ops = {
 		.flush_iotlb_all = apple_dart_flush_iotlb_all,
 		.iotlb_sync	= apple_dart_iotlb_sync,
 		.iotlb_sync_map	= apple_dart_iotlb_sync_map,
-		.iova_to_phys	= apple_dart_iova_to_phys,
+		.iova_to_phys_length = apple_dart_iova_to_phys_length,
 		.free		= apple_dart_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 10/32] iommu/ipmmu-vmsa: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (8 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 09/32] iommu/apple-dart: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 11/32] iommu/mtk_iommu: " Guanghui Feng
                           ` (21 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate IPMMU-VMSA to implement iova_to_phys_length, passing through
mapped_length from io-pgtable.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/ipmmu-vmsa.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c
index 9386b752dea2..6e2e5922ab1b 100644
--- a/drivers/iommu/ipmmu-vmsa.c
+++ b/drivers/iommu/ipmmu-vmsa.c
@@ -699,14 +699,16 @@ static void ipmmu_iotlb_sync(struct iommu_domain *io_domain,
 	ipmmu_flush_iotlb_all(io_domain);
 }
 
-static phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain,
-				      dma_addr_t iova)
+static phys_addr_t ipmmu_iova_to_phys_length(struct iommu_domain *io_domain,
+				      dma_addr_t iova, size_t *mapped_length)
 {
 	struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
 
+
 	/* TODO: Is locking needed ? */
 
-	return domain->iop->iova_to_phys(domain->iop, iova);
+	return domain->iop->iova_to_phys_length(domain->iop, iova,
+						mapped_length);
 }
 
 static int ipmmu_init_platform_device(struct device *dev,
@@ -892,7 +894,7 @@ static const struct iommu_ops ipmmu_ops = {
 		.unmap_pages	= ipmmu_unmap,
 		.flush_iotlb_all = ipmmu_flush_iotlb_all,
 		.iotlb_sync	= ipmmu_iotlb_sync,
-		.iova_to_phys	= ipmmu_iova_to_phys,
+		.iova_to_phys_length = ipmmu_iova_to_phys_length,
 		.free		= ipmmu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 11/32] iommu/mtk_iommu: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (9 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 10/32] iommu/ipmmu-vmsa: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 12/32] iommu/exynos: " Guanghui Feng
                           ` (20 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate MediaTek IOMMU to implement iova_to_phys_length, passing through
mapped_length from io-pgtable.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/mtk_iommu.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
index 2be990c108de..6ca31f8d4d96 100644
--- a/drivers/iommu/mtk_iommu.c
+++ b/drivers/iommu/mtk_iommu.c
@@ -858,13 +858,16 @@ static int mtk_iommu_sync_map(struct iommu_domain *domain, unsigned long iova,
 	return 0;
 }
 
-static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain,
-					  dma_addr_t iova)
+static phys_addr_t mtk_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					  dma_addr_t iova, size_t *mapped_length)
 {
 	struct mtk_iommu_domain *dom = to_mtk_domain(domain);
 	phys_addr_t pa;
 
-	pa = dom->iop->iova_to_phys(dom->iop, iova);
+	pa = dom->iop->iova_to_phys_length(dom->iop, iova, mapped_length);
+	if (pa == PHYS_ADDR_MAX)
+		return PHYS_ADDR_MAX;
+
 	if (IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT) &&
 	    dom->bank->parent_data->enable_4GB &&
 	    pa >= MTK_IOMMU_4GB_MODE_REMAP_BASE)
@@ -1070,7 +1073,7 @@ static const struct iommu_ops mtk_iommu_ops = {
 		.flush_iotlb_all = mtk_iommu_flush_iotlb_all,
 		.iotlb_sync	= mtk_iommu_iotlb_sync,
 		.iotlb_sync_map	= mtk_iommu_sync_map,
-		.iova_to_phys	= mtk_iommu_iova_to_phys,
+		.iova_to_phys_length = mtk_iommu_iova_to_phys_length,
 		.free		= mtk_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 12/32] iommu/exynos: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (10 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 11/32] iommu/mtk_iommu: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:46           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 13/32] iommu/fsl_pamu: " Guanghui Feng
                           ` (19 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for Exynos IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/exynos-iommu.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c
index 874d05f4b396..17d77b9114c4 100644
--- a/drivers/iommu/exynos-iommu.c
+++ b/drivers/iommu/exynos-iommu.c
@@ -1372,13 +1372,14 @@ static size_t exynos_iommu_unmap(struct iommu_domain *iommu_domain,
 	return 0;
 }
 
-static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain,
-					  dma_addr_t iova)
+static phys_addr_t exynos_iommu_iova_to_phys_length(struct iommu_domain *iommu_domain,
+						 dma_addr_t iova,
+						 size_t *mapped_length)
 {
 	struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain);
 	sysmmu_pte_t *entry;
 	unsigned long flags;
-	phys_addr_t phys = 0;
+	phys_addr_t phys = PHYS_ADDR_MAX;
 
 	spin_lock_irqsave(&domain->pgtablelock, flags);
 
@@ -1386,13 +1387,20 @@ static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain,
 
 	if (lv1ent_section(entry)) {
 		phys = section_phys(entry) + section_offs(iova);
+		if (mapped_length)
+			*mapped_length = SECT_SIZE;
 	} else if (lv1ent_page(entry)) {
 		entry = page_entry(entry, iova);
 
-		if (lv2ent_large(entry))
+		if (lv2ent_large(entry)) {
 			phys = lpage_phys(entry) + lpage_offs(iova);
-		else if (lv2ent_small(entry))
+			if (mapped_length)
+				*mapped_length = LPAGE_SIZE;
+		} else if (lv2ent_small(entry)) {
 			phys = spage_phys(entry) + spage_offs(iova);
+			if (mapped_length)
+				*mapped_length = SPAGE_SIZE;
+		}
 	}
 
 	spin_unlock_irqrestore(&domain->pgtablelock, flags);
@@ -1484,7 +1492,7 @@ static const struct iommu_ops exynos_iommu_ops = {
 		.attach_dev	= exynos_iommu_attach_device,
 		.map_pages	= exynos_iommu_map,
 		.unmap_pages	= exynos_iommu_unmap,
-		.iova_to_phys	= exynos_iommu_iova_to_phys,
+		.iova_to_phys_length	= exynos_iommu_iova_to_phys_length,
 		.free		= exynos_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 13/32] iommu/fsl_pamu: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (11 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 12/32] iommu/exynos: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:48           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 14/32] iommu/msm: " Guanghui Feng
                           ` (18 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for FSL PAMU IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/fsl_pamu_domain.c | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c
index 9664ef9840d2..60abd497dc63 100644
--- a/drivers/iommu/fsl_pamu_domain.c
+++ b/drivers/iommu/fsl_pamu_domain.c
@@ -169,12 +169,30 @@ static void attach_device(struct fsl_dma_domain *dma_domain, int liodn, struct d
 	spin_unlock_irqrestore(&device_domain_lock, flags);
 }
 
-static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain,
-					 dma_addr_t iova)
+static phys_addr_t fsl_pamu_iova_to_phys_length(struct iommu_domain *domain,
+						dma_addr_t iova,
+						size_t *mapped_length)
 {
+
 	if (iova < domain->geometry.aperture_start ||
 	    iova > domain->geometry.aperture_end)
-		return 0;
+		return PHYS_ADDR_MAX;
+
+	/*
+	 * PAMU configures exactly one Primary PAACE entry per LIODN with the
+	 * Multi-Window (MW) bit cleared and ATM = PAACE_ATM_WINDOW_XLATE,
+	 * WBAL/TWBAL = 0. That is, every LIODN is backed by a single hardware
+	 * mapping window of fixed size (1ULL << 36, i.e. 64GB, see
+	 * pamu_config_ppaace()) performing an identity translation. The driver
+	 * does not split this window into SPAACE sub-windows, so the entire
+	 * aperture is one PAACE "PTE" entry. Return that single entry's size
+	 * as mapped_length, matching the iova_to_phys_length contract that
+	 * mapped_length reports the full size of the mapping entry which
+	 * covers iova (not the remaining bytes from iova to its end).
+	 */
+	if (mapped_length)
+		*mapped_length = 1ULL << 36;
+
 	return iova;
 }
 
@@ -435,7 +453,7 @@ static const struct iommu_ops fsl_pamu_ops = {
 	.device_group   = fsl_pamu_device_group,
 	.default_domain_ops = &(const struct iommu_domain_ops) {
 		.attach_dev	= fsl_pamu_attach_device,
-		.iova_to_phys	= fsl_pamu_iova_to_phys,
+		.iova_to_phys_length	= fsl_pamu_iova_to_phys_length,
 		.free		= fsl_pamu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 14/32] iommu/msm: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (12 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 13/32] iommu/fsl_pamu: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:51           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 15/32] iommu/mtk_v1: " Guanghui Feng
                           ` (17 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for MSM IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/msm_iommu.c | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c
index 0ad5ff431d5b..1038e8141223 100644
--- a/drivers/iommu/msm_iommu.c
+++ b/drivers/iommu/msm_iommu.c
@@ -523,15 +523,16 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
 	return ret;
 }
 
-static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
-					  dma_addr_t va)
+static phys_addr_t msm_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						 dma_addr_t va,
+						 size_t *mapped_length)
 {
 	struct msm_priv *priv;
 	struct msm_iommu_dev *iommu;
 	struct msm_iommu_ctx_dev *master;
 	unsigned int par;
 	unsigned long flags;
-	phys_addr_t ret = 0;
+	phys_addr_t ret = PHYS_ADDR_MAX;
 
 	spin_lock_irqsave(&msm_iommu_lock, flags);
 
@@ -558,13 +559,19 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
 	par = GET_PAR(iommu->base, master->num);
 
 	/* We are dealing with a supersection */
-	if (GET_NOFAULT_SS(iommu->base, master->num))
+	if (GET_NOFAULT_SS(iommu->base, master->num)) {
 		ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
-	else	/* Upper 20 bits from PAR, lower 12 from VA */
+		if (mapped_length)
+			*mapped_length = SZ_16M;
+	} else {
+		/* Upper 20 bits from PAR, lower 12 from VA */
 		ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
+		if (mapped_length)
+			*mapped_length = SZ_4K;
+	}
 
 	if (GET_FAULT(iommu->base, master->num))
-		ret = 0;
+		ret = PHYS_ADDR_MAX;
 
 	__disable_clocks(iommu);
 fail:
@@ -706,7 +713,7 @@ static struct iommu_ops msm_iommu_ops = {
 		 */
 		.iotlb_sync	= NULL,
 		.iotlb_sync_map	= msm_iommu_sync_map,
-		.iova_to_phys	= msm_iommu_iova_to_phys,
+		.iova_to_phys_length	= msm_iommu_iova_to_phys_length,
 		.free		= msm_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 15/32] iommu/mtk_v1: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (13 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 14/32] iommu/msm: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:58           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 16/32] iommu/omap: " Guanghui Feng
                           ` (16 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for MediaTek v1 IOMMU driver,
returning the actual PTE mapping size.
Also fix pre-existing bug: add page offset to physical address.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/mtk_iommu_v1.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c
index ac97dd2868d4..da41dda7620b 100644
--- a/drivers/iommu/mtk_iommu_v1.c
+++ b/drivers/iommu/mtk_iommu_v1.c
@@ -393,7 +393,9 @@ static size_t mtk_iommu_v1_unmap(struct iommu_domain *domain, unsigned long iova
 	return size;
 }
 
-static phys_addr_t mtk_iommu_v1_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+static phys_addr_t mtk_iommu_v1_iova_to_phys_length(struct iommu_domain *domain,
+						    dma_addr_t iova,
+						    size_t *mapped_length)
 {
 	struct mtk_iommu_v1_domain *dom = to_mtk_domain(domain);
 	unsigned long flags;
@@ -404,6 +406,13 @@ static phys_addr_t mtk_iommu_v1_iova_to_phys(struct iommu_domain *domain, dma_ad
 	pa = pa & (~(MT2701_IOMMU_PAGE_SIZE - 1));
 	spin_unlock_irqrestore(&dom->pgtlock, flags);
 
+	if (!pa)
+		return PHYS_ADDR_MAX;
+
+	pa |= (iova & (MT2701_IOMMU_PAGE_SIZE - 1));
+	if (mapped_length)
+		*mapped_length = MT2701_IOMMU_PAGE_SIZE;
+
 	return pa;
 }
 
@@ -590,7 +599,7 @@ static const struct iommu_ops mtk_iommu_v1_ops = {
 		.attach_dev	= mtk_iommu_v1_attach_device,
 		.map_pages	= mtk_iommu_v1_map,
 		.unmap_pages	= mtk_iommu_v1_unmap,
-		.iova_to_phys	= mtk_iommu_v1_iova_to_phys,
+		.iova_to_phys_length = mtk_iommu_v1_iova_to_phys_length,
 		.free		= mtk_iommu_v1_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 16/32] iommu/omap: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (14 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 15/32] iommu/mtk_v1: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 17/32] iommu/rockchip: " Guanghui Feng
                           ` (15 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for OMAP IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/omap-iommu.c | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c
index 8231d7d6bb6a..f4a416326f7c 100644
--- a/drivers/iommu/omap-iommu.c
+++ b/drivers/iommu/omap-iommu.c
@@ -1592,15 +1592,16 @@ static void omap_iommu_domain_free(struct iommu_domain *domain)
 	kfree(omap_domain);
 }
 
-static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t da)
+static phys_addr_t omap_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t da,
+						  size_t *mapped_length)
 {
 	struct omap_iommu_domain *omap_domain = to_omap_domain(domain);
 	struct omap_iommu_device *iommu = omap_domain->iommus;
 	struct omap_iommu *oiommu = iommu->iommu_dev;
 	struct device *dev = oiommu->dev;
 	u32 *pgd, *pte;
-	phys_addr_t ret = 0;
+	phys_addr_t ret = PHYS_ADDR_MAX;
 
 	/*
 	 * all the iommus within the domain will have identical programming,
@@ -1609,19 +1610,27 @@ static phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain,
 	iopgtable_lookup_entry(oiommu, da, &pgd, &pte);
 
 	if (pte) {
-		if (iopte_is_small(*pte))
+		if (iopte_is_small(*pte)) {
 			ret = omap_iommu_translate(*pte, da, IOPTE_MASK);
-		else if (iopte_is_large(*pte))
+			if (mapped_length)
+				*mapped_length = IOPTE_SIZE;
+		} else if (iopte_is_large(*pte)) {
 			ret = omap_iommu_translate(*pte, da, IOLARGE_MASK);
-		else
+			if (mapped_length)
+				*mapped_length = IOLARGE_SIZE;
+		} else
 			dev_err(dev, "bogus pte 0x%x, da 0x%llx", *pte,
 				(unsigned long long)da);
 	} else {
-		if (iopgd_is_section(*pgd))
+		if (iopgd_is_section(*pgd)) {
 			ret = omap_iommu_translate(*pgd, da, IOSECTION_MASK);
-		else if (iopgd_is_super(*pgd))
+			if (mapped_length)
+				*mapped_length = IOSECTION_SIZE;
+		} else if (iopgd_is_super(*pgd)) {
 			ret = omap_iommu_translate(*pgd, da, IOSUPER_MASK);
-		else
+			if (mapped_length)
+				*mapped_length = IOSUPER_SIZE;
+		} else
 			dev_err(dev, "bogus pgd 0x%x, da 0x%llx", *pgd,
 				(unsigned long long)da);
 	}
@@ -1723,7 +1732,7 @@ static const struct iommu_ops omap_iommu_ops = {
 		.attach_dev	= omap_iommu_attach_dev,
 		.map_pages	= omap_iommu_map,
 		.unmap_pages	= omap_iommu_unmap,
-		.iova_to_phys	= omap_iommu_iova_to_phys,
+		.iova_to_phys_length	= omap_iommu_iova_to_phys_length,
 		.free		= omap_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 17/32] iommu/rockchip: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (15 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 16/32] iommu/omap: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:53           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 18/32] iommu/s390: " Guanghui Feng
                           ` (14 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for Rockchip IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/rockchip-iommu.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c
index 0013cf196c57..a51c29340b98 100644
--- a/drivers/iommu/rockchip-iommu.c
+++ b/drivers/iommu/rockchip-iommu.c
@@ -648,12 +648,12 @@ static irqreturn_t rk_iommu_irq(int irq, void *dev_id)
 	return ret;
 }
 
-static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
-					 dma_addr_t iova)
+static phys_addr_t rk_iommu_iova_to_phys_length(struct iommu_domain *domain,
+					 dma_addr_t iova, size_t *mapped_length)
 {
 	struct rk_iommu_domain *rk_domain = to_rk_domain(domain);
 	unsigned long flags;
-	phys_addr_t pt_phys, phys = 0;
+	phys_addr_t pt_phys, phys = PHYS_ADDR_MAX;
 	u32 dte, pte;
 	u32 *page_table;
 
@@ -670,6 +670,8 @@ static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
 		goto out;
 
 	phys = rk_ops->pt_address(pte) + rk_iova_page_offset(iova);
+	if (mapped_length)
+		*mapped_length = SPAGE_SIZE;
 out:
 	spin_unlock_irqrestore(&rk_domain->dt_lock, flags);
 
@@ -1187,7 +1189,7 @@ static const struct iommu_ops rk_iommu_ops = {
 		.attach_dev	= rk_iommu_attach_device,
 		.map_pages	= rk_iommu_map,
 		.unmap_pages	= rk_iommu_unmap,
-		.iova_to_phys	= rk_iommu_iova_to_phys,
+		.iova_to_phys_length = rk_iommu_iova_to_phys_length,
 		.free		= rk_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 18/32] iommu/s390: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (16 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 17/32] iommu/rockchip: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:03           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 19/32] iommu/sprd: " Guanghui Feng
                           ` (13 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for s390 IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/s390-iommu.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c
index f148f559ac56..6cfcc55ef59f 100644
--- a/drivers/iommu/s390-iommu.c
+++ b/drivers/iommu/s390-iommu.c
@@ -986,22 +986,23 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,
 	}
 }
 
-static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t s390_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t iova,
+						  size_t *mapped_length)
 {
 	struct s390_domain *s390_domain = to_s390_domain(domain);
 	unsigned long *rto, *sto, *pto;
 	unsigned long ste, pte, rte;
 	unsigned int rtx, sx, px;
-	phys_addr_t phys = 0;
+	phys_addr_t phys = PHYS_ADDR_MAX;
 
 	if (iova < domain->geometry.aperture_start ||
 	    iova > domain->geometry.aperture_end)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	rto = get_rto_from_iova(s390_domain, iova);
 	if (!rto)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	rtx = calc_rtx(iova);
 	sx = calc_sx(iova);
@@ -1014,8 +1015,11 @@ static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
 		if (reg_entry_isvalid(ste)) {
 			pto = get_st_pto(ste);
 			pte = READ_ONCE(pto[px]);
-			if (pt_entry_isvalid(pte))
+			if (pt_entry_isvalid(pte)) {
 				phys = pte & ZPCI_PTE_ADDR_MASK;
+				if (mapped_length)
+					*mapped_length = SZ_4K;
+			}
 		}
 	}
 
@@ -1183,7 +1187,7 @@ static struct iommu_domain blocking_domain = {
 		.flush_iotlb_all = s390_iommu_flush_iotlb_all, \
 		.iotlb_sync      = s390_iommu_iotlb_sync, \
 		.iotlb_sync_map  = s390_iommu_iotlb_sync_map, \
-		.iova_to_phys	= s390_iommu_iova_to_phys, \
+		.iova_to_phys_length	= s390_iommu_iova_to_phys_length, \
 		.free		= s390_domain_free, \
 	}
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 19/32] iommu/sprd: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (17 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 18/32] iommu/s390: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:57           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 20/32] iommu/sun50i: " Guanghui Feng
                           ` (12 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for Spreadtrum IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/sprd-iommu.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/sprd-iommu.c b/drivers/iommu/sprd-iommu.c
index c1a34445d244..3c04ec040d96 100644
--- a/drivers/iommu/sprd-iommu.c
+++ b/drivers/iommu/sprd-iommu.c
@@ -366,8 +366,9 @@ static void sprd_iommu_sync(struct iommu_domain *domain,
 	sprd_iommu_sync_map(domain, 0, 0);
 }
 
-static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t sprd_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						  dma_addr_t iova,
+						  size_t *mapped_length)
 {
 	struct sprd_iommu_domain *dom = to_sprd_domain(domain);
 	unsigned long flags;
@@ -376,13 +377,19 @@ static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
 	unsigned long end = domain->geometry.aperture_end;
 
 	if (WARN_ON(iova < start || iova > end))
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	spin_lock_irqsave(&dom->pgtlock, flags);
 	pa = *(dom->pgt_va + ((iova - start) >> SPRD_IOMMU_PAGE_SHIFT));
-	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
 	spin_unlock_irqrestore(&dom->pgtlock, flags);
 
+	if (!pa)
+		return PHYS_ADDR_MAX;
+
+	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
+	if (mapped_length)
+		*mapped_length = SPRD_IOMMU_PAGE_SIZE;
+
 	return pa;
 }
 
@@ -420,7 +427,7 @@ static const struct iommu_ops sprd_iommu_ops = {
 		.unmap_pages	= sprd_iommu_unmap,
 		.iotlb_sync_map	= sprd_iommu_sync_map,
 		.iotlb_sync	= sprd_iommu_sync,
-		.iova_to_phys	= sprd_iommu_iova_to_phys,
+		.iova_to_phys_length	= sprd_iommu_iova_to_phys_length,
 		.free		= sprd_iommu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 20/32] iommu/sun50i: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (18 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 19/32] iommu/sprd: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 15:17         ` [PATCH v3 21/32] iommu/tegra-smmu: " Guanghui Feng
                           ` (11 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for sun50i IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/sun50i-iommu.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/sun50i-iommu.c b/drivers/iommu/sun50i-iommu.c
index be3f1ce696ba..9f39fe4a9d4f 100644
--- a/drivers/iommu/sun50i-iommu.c
+++ b/drivers/iommu/sun50i-iommu.c
@@ -659,8 +659,9 @@ static size_t sun50i_iommu_unmap(struct iommu_domain *domain, unsigned long iova
 	return SZ_4K;
 }
 
-static phys_addr_t sun50i_iommu_iova_to_phys(struct iommu_domain *domain,
-					     dma_addr_t iova)
+static phys_addr_t sun50i_iommu_iova_to_phys_length(struct iommu_domain *domain,
+						    dma_addr_t iova,
+						    size_t *mapped_length)
 {
 	struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
 	phys_addr_t pt_phys;
@@ -669,13 +670,16 @@ static phys_addr_t sun50i_iommu_iova_to_phys(struct iommu_domain *domain,
 
 	dte = sun50i_domain->dt[sun50i_iova_get_dte_index(iova)];
 	if (!sun50i_dte_is_pt_valid(dte))
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	pt_phys = sun50i_dte_get_pt_address(dte);
 	page_table = (u32 *)phys_to_virt(pt_phys);
 	pte = page_table[sun50i_iova_get_pte_index(iova)];
 	if (!sun50i_pte_is_page_valid(pte))
-		return 0;
+		return PHYS_ADDR_MAX;
+
+	if (mapped_length)
+		*mapped_length = SZ_4K;
 
 	return sun50i_pte_get_page_address(pte) +
 		sun50i_iova_get_page_offset(iova);
@@ -857,7 +861,7 @@ static const struct iommu_ops sun50i_iommu_ops = {
 		.flush_iotlb_all = sun50i_iommu_flush_iotlb_all,
 		.iotlb_sync_map = sun50i_iommu_iotlb_sync_map,
 		.iotlb_sync	= sun50i_iommu_iotlb_sync,
-		.iova_to_phys	= sun50i_iommu_iova_to_phys,
+		.iova_to_phys_length	= sun50i_iommu_iova_to_phys_length,
 		.map_pages	= sun50i_iommu_map,
 		.unmap_pages	= sun50i_iommu_unmap,
 		.free		= sun50i_iommu_domain_free,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 21/32] iommu/tegra-smmu: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (19 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 20/32] iommu/sun50i: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:04           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 22/32] iommu/virtio: " Guanghui Feng
                           ` (10 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for Tegra SMMU IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/tegra-smmu.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
index 67e7a7b925f0..12f9bb623d87 100644
--- a/drivers/iommu/tegra-smmu.c
+++ b/drivers/iommu/tegra-smmu.c
@@ -803,8 +803,8 @@ static size_t tegra_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
 	return size;
 }
 
-static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain,
-					   dma_addr_t iova)
+static phys_addr_t tegra_smmu_iova_to_phys_length(struct iommu_domain *domain,
+					   dma_addr_t iova, size_t *mapped_length)
 {
 	struct tegra_smmu_as *as = to_smmu_as(domain);
 	unsigned long pfn;
@@ -813,10 +813,13 @@ static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain,
 
 	pte = tegra_smmu_pte_lookup(as, iova, &pte_dma);
 	if (!pte || !*pte)
-		return 0;
+		return PHYS_ADDR_MAX;
 
 	pfn = *pte & as->smmu->pfn_mask;
 
+	if (mapped_length)
+		*mapped_length = SZ_4K;
+
 	return SMMU_PFN_PHYS(pfn) + SMMU_OFFSET_IN_PAGE(iova);
 }
 
@@ -1007,7 +1010,7 @@ static const struct iommu_ops tegra_smmu_ops = {
 		.attach_dev	= tegra_smmu_attach_dev,
 		.map_pages	= tegra_smmu_map,
 		.unmap_pages	= tegra_smmu_unmap,
-		.iova_to_phys	= tegra_smmu_iova_to_phys,
+		.iova_to_phys_length = tegra_smmu_iova_to_phys_length,
 		.free		= tegra_smmu_domain_free,
 	}
 };
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 22/32] iommu/virtio: implement iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (20 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 21/32] iommu/tegra-smmu: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:10           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap Guanghui Feng
                           ` (9 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Implement iova_to_phys_length for virtio IOMMU driver,
returning the actual PTE mapping size.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/virtio-iommu.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c
index 587fc13197f1..b92316257e42 100644
--- a/drivers/iommu/virtio-iommu.c
+++ b/drivers/iommu/virtio-iommu.c
@@ -912,10 +912,11 @@ static size_t viommu_unmap_pages(struct iommu_domain *domain, unsigned long iova
 	return ret ? 0 : unmapped;
 }
 
-static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
-				       dma_addr_t iova)
+static phys_addr_t viommu_iova_to_phys_length(struct iommu_domain *domain,
+					      dma_addr_t iova,
+					      size_t *mapped_length)
 {
-	u64 paddr = 0;
+	u64 paddr = PHYS_ADDR_MAX;
 	unsigned long flags;
 	struct viommu_mapping *mapping;
 	struct interval_tree_node *node;
@@ -926,6 +927,9 @@ static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
 	if (node) {
 		mapping = container_of(node, struct viommu_mapping, iova);
 		paddr = mapping->paddr + (iova - mapping->iova.start);
+		if (mapped_length)
+			*mapped_length = mapping->iova.last -
+					 mapping->iova.start + 1;
 	}
 	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
 
@@ -1102,7 +1106,7 @@ static const struct iommu_ops viommu_ops = {
 		.attach_dev		= viommu_attach_dev,
 		.map_pages		= viommu_map_pages,
 		.unmap_pages		= viommu_unmap_pages,
-		.iova_to_phys		= viommu_iova_to_phys,
+		.iova_to_phys_length	= viommu_iova_to_phys_length,
 		.flush_iotlb_all	= viommu_flush_iotlb_all,
 		.iotlb_sync		= viommu_iotlb_sync,
 		.iotlb_sync_map		= viommu_iotlb_sync_map,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (21 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 22/32] iommu/virtio: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:14           ` sashiko-bot
  2026-06-04 14:27           ` Jason Gunthorpe
  2026-06-03 15:17         ` [PATCH v3 24/32] iommufd: " Guanghui Feng
                           ` (8 subsequent siblings)
  31 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Use iommu_iova_to_phys_length() to get PTE page size, allowing
traversal by actual mapping granularity instead of PAGE_SIZE steps.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/vfio/vfio_iommu_type1.c | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
index c8151ba54de3..115d88d7003e 100644
--- a/drivers/vfio/vfio_iommu_type1.c
+++ b/drivers/vfio/vfio_iommu_type1.c
@@ -1177,25 +1177,42 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
 
 	iommu_iotlb_gather_init(&iotlb_gather);
 	while (pos < dma->size) {
-		size_t unmapped, len;
+		size_t unmapped, len, pgsize;
 		phys_addr_t phys, next;
 		dma_addr_t iova = dma->iova + pos;
 
-		phys = iommu_iova_to_phys(domain->domain, iova);
-		if (WARN_ON(!phys)) {
+		/* Single page table walk returns both phys and PTE size */
+		phys = iommu_iova_to_phys_length(domain->domain, iova,
+						  &pgsize);
+		if (WARN_ON(phys == PHYS_ADDR_MAX)) {
 			pos += PAGE_SIZE;
 			continue;
 		}
+		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
+			pgsize = PAGE_SIZE;
 
 		/*
 		 * To optimize for fewer iommu_unmap() calls, each of which
 		 * may require hardware cache flushing, try to find the
 		 * largest contiguous physical memory chunk to unmap.
+		 *
+		 * mapped_length already accounts for contiguous entries
+		 * from iova, then try to join following physically
+		 * contiguous PTEs.
 		 */
-		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
-			next = iommu_iova_to_phys(domain->domain, iova + len);
+		len = min_t(size_t, pgsize, dma->size - pos);
+		for (; pos + len < dma->size; ) {
+			size_t next_pgsize;
+
+			next = iommu_iova_to_phys_length(domain->domain,
+							  iova + len,
+							  &next_pgsize);
 			if (next != phys + len)
 				break;
+			if (WARN_ON(!next_pgsize || next_pgsize < PAGE_SIZE))
+				next_pgsize = PAGE_SIZE;
+			len += min_t(size_t, next_pgsize,
+				     dma->size - pos - len);
 		}
 
 		/*
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 24/32] iommufd: use iova_to_phys_length for efficient unmap
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (22 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:14           ` sashiko-bot
  2026-06-04 14:26           ` Jason Gunthorpe
  2026-06-03 15:17         ` [PATCH v3 25/32] iommufd/selftest: switch to iommu_iova_to_phys_length Guanghui Feng
                           ` (7 subsequent siblings)
  31 siblings, 2 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Use iommu_iova_to_phys_length() to get PTE page size in
batch_from_domain and raw_pages_from_domain, allowing traversal
by actual mapping granularity instead of PAGE_SIZE steps.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/iommufd/pages.c | 74 +++++++++++++++++++++++++++++------
 1 file changed, 62 insertions(+), 12 deletions(-)

diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index 9bdb2945afe1..40a2fe9adf9c 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -417,17 +417,42 @@ static void batch_from_domain(struct pfn_batch *batch,
 	if (start_index == iopt_area_index(area))
 		page_offset = area->page_offset;
 	while (start_index <= last_index) {
+		size_t pgsize;
+		unsigned long npages;
+		unsigned long i;
+
 		/*
-		 * This is pretty slow, it would be nice to get the page size
-		 * back from the driver, or have the driver directly fill the
-		 * batch.
+		 * Use iova_to_phys_length to get both the physical address
+		 * and the contiguous mapped length in a single page table
+		 * walk, allowing us to skip ahead by the contiguous region
+		 * size instead of walking page tables for every PAGE_SIZE.
+		 * Query at page-aligned iova so pgsize covers from page start.
 		 */
-		phys = iommu_iova_to_phys(domain, iova) - page_offset;
-		if (!batch_add_pfn(batch, PHYS_PFN(phys)))
-			return;
-		iova += PAGE_SIZE - page_offset;
+		phys = iommu_iova_to_phys_length(domain, iova - page_offset,
+						  &pgsize);
+		if (WARN_ON(phys == PHYS_ADDR_MAX))
+			break;
+		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
+			pgsize = PAGE_SIZE;
+
+		/*
+		 * pgsize is the contiguous length from the page-aligned
+		 * iova, so npages is simply pgsize / PAGE_SIZE.
+		 */
+		npages = pgsize / PAGE_SIZE;
+		npages = min_t(unsigned long, npages,
+			       last_index - start_index + 1);
+		if (!npages)
+			npages = 1;
+
+		for (i = 0; i < npages; i++) {
+			if (!batch_add_pfn(batch, PHYS_PFN(phys) + i))
+				return;
+		}
+
+		iova += npages * PAGE_SIZE - page_offset;
 		page_offset = 0;
-		start_index++;
+		start_index += npages;
 	}
 }
 
@@ -445,11 +470,36 @@ static struct page **raw_pages_from_domain(struct iommu_domain *domain,
 	if (start_index == iopt_area_index(area))
 		page_offset = area->page_offset;
 	while (start_index <= last_index) {
-		phys = iommu_iova_to_phys(domain, iova) - page_offset;
-		*(out_pages++) = pfn_to_page(PHYS_PFN(phys));
-		iova += PAGE_SIZE - page_offset;
+		size_t pgsize;
+		unsigned long npages;
+		unsigned long i;
+
+		/*
+		 * Resolve the contiguous mapped length together with the
+		 * physical address so we can fill multiple struct page
+		 * pointers per page table walk when the IOMMU uses large
+		 * pages. Query at page-aligned iova so pgsize covers from
+		 * page start.
+		 */
+		phys = iommu_iova_to_phys_length(domain, iova - page_offset,
+						  &pgsize);
+		if (WARN_ON(phys == PHYS_ADDR_MAX))
+			break;
+		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
+			pgsize = PAGE_SIZE;
+
+		npages = pgsize / PAGE_SIZE;
+		npages = min_t(unsigned long, npages,
+			       last_index - start_index + 1);
+		if (!npages)
+			npages = 1;
+
+		for (i = 0; i < npages; i++)
+			*(out_pages++) = pfn_to_page(PHYS_PFN(phys) + i);
+
+		iova += npages * PAGE_SIZE - page_offset;
 		page_offset = 0;
-		start_index++;
+		start_index += npages;
 	}
 	return out_pages;
 }
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 25/32] iommufd/selftest: switch to iommu_iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (23 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 24/32] iommufd: " Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:17           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 26/32] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
                           ` (6 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Replace direct domain->ops->iova_to_phys() call with the new
iommu_iova_to_phys_length() interface in selftest.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
---
 drivers/iommu/iommufd/selftest.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index af07c642a526..d4dd39930224 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -1214,7 +1214,7 @@ static int iommufd_test_md_check_pa(struct iommufd_ucmd *ucmd,
 		pfn = page_to_pfn(pages[0]);
 		put_page(pages[0]);
 
-		io_phys = mock->domain.ops->iova_to_phys(&mock->domain, iova);
+		io_phys = iommu_iova_to_phys_length(&mock->domain, iova, NULL);
 		if (io_phys !=
 		    pfn * PAGE_SIZE + ((uintptr_t)uptr % PAGE_SIZE)) {
 			rc = -EINVAL;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 26/32] drm/panfrost: switch to iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (24 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 25/32] iommufd/selftest: switch to iommu_iova_to_phys_length Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:13           ` sashiko-bot
  2026-06-03 15:17         ` [PATCH v3 27/32] drm/panthor: " Guanghui Feng
                           ` (5 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate panfrost_mmu to use ops->iova_to_phys_length(ops, iova, NULL)
instead of the deprecated ops->iova_to_phys.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/gpu/drm/panfrost/panfrost_mmu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/panfrost/panfrost_mmu.c b/drivers/gpu/drm/panfrost/panfrost_mmu.c
index 4a3162c3b659..aa0bc82deaf6 100644
--- a/drivers/gpu/drm/panfrost/panfrost_mmu.c
+++ b/drivers/gpu/drm/panfrost/panfrost_mmu.c
@@ -514,7 +514,7 @@ void panfrost_mmu_unmap(struct panfrost_gem_mapping *mapping)
 
 		if (bo->is_heap)
 			pgcount = 1;
-		if (!bo->is_heap || ops->iova_to_phys(ops, iova)) {
+		if (!bo->is_heap || ops->iova_to_phys_length(ops, iova, NULL) != PHYS_ADDR_MAX) {
 			unmapped_page = ops->unmap_pages(ops, iova, pgsize, pgcount, NULL);
 			WARN_ON(unmapped_page != pgsize * pgcount);
 		}
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 27/32] drm/panthor: switch to iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (25 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 26/32] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
@ 2026-06-03 15:17         ` Guanghui Feng
  2026-06-03 16:16           ` sashiko-bot
  2026-06-03 15:18         ` [PATCH v3 28/32] iommu/io-pgtable: selftests " Guanghui Feng
                           ` (4 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:17 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate panthor_mmu to use ops->iova_to_phys_length(ops, iova, NULL)
instead of the deprecated ops->iova_to_phys.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/gpu/drm/panthor/panthor_mmu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
index 75d98dad7b1d..3b635fc1f651 100644
--- a/drivers/gpu/drm/panthor/panthor_mmu.c
+++ b/drivers/gpu/drm/panthor/panthor_mmu.c
@@ -903,7 +903,7 @@ static void panthor_vm_unmap_pages(struct panthor_vm *vm, u64 iova, u64 size)
 			 * are out-of-sync. This is not supposed to happen, hence the
 			 * above WARN_ON().
 			 */
-			while (!ops->iova_to_phys(ops, iova + unmapped_sz) &&
+			while (ops->iova_to_phys_length(ops, iova + unmapped_sz, NULL) == PHYS_ADDR_MAX &&
 			       unmapped_sz < pgsize * pgcount)
 				unmapped_sz += SZ_4K;
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 28/32] iommu/io-pgtable: selftests switch to iova_to_phys_length
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (26 preceding siblings ...)
  2026-06-03 15:17         ` [PATCH v3 27/32] drm/panthor: " Guanghui Feng
@ 2026-06-03 15:18         ` Guanghui Feng
  2026-06-03 15:18         ` [PATCH v3 29/32] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
                           ` (3 subsequent siblings)
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:18 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Migrate io-pgtable ARM selftests to use ops->iova_to_phys_length
instead of the deprecated ops->iova_to_phys.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-selftests.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-selftests.c b/drivers/iommu/io-pgtable-arm-selftests.c
index 334e70350924..78252344c3d0 100644
--- a/drivers/iommu/io-pgtable-arm-selftests.c
+++ b/drivers/iommu/io-pgtable-arm-selftests.c
@@ -72,13 +72,13 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 		 * Initial sanity checks.
 		 * Empty page tables shouldn't provide any translations.
 		 */
-		if (ops->iova_to_phys(ops, 42))
+		if (ops->iova_to_phys_length(ops, 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(test, i);
 
-		if (ops->iova_to_phys(ops, SZ_1G + 42))
+		if (ops->iova_to_phys_length(ops, SZ_1G + 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(test, i);
 
-		if (ops->iova_to_phys(ops, SZ_2G + 42))
+		if (ops->iova_to_phys_length(ops, SZ_2G + 42, NULL) != PHYS_ADDR_MAX)
 			return __FAIL(test, i);
 
 		/*
@@ -100,7 +100,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 					    GFP_KERNEL, &mapped))
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 				return __FAIL(test, i);
 
 			iova += SZ_1G;
@@ -114,7 +114,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 			if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != PHYS_ADDR_MAX)
 				return __FAIL(test, i);
 
 			/* Remap full block */
@@ -122,7 +122,7 @@ static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg)
 					   IOMMU_WRITE, GFP_KERNEL, &mapped))
 				return __FAIL(test, i);
 
-			if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+			if (ops->iova_to_phys_length(ops, iova + 42, NULL) != (iova + 42))
 				return __FAIL(test, i);
 
 			iova += SZ_1G;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 29/32] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (27 preceding siblings ...)
  2026-06-03 15:18         ` [PATCH v3 28/32] iommu/io-pgtable: selftests " Guanghui Feng
@ 2026-06-03 15:18         ` Guanghui Feng
  2026-06-03 16:32           ` sashiko-bot
  2026-06-03 15:18         ` [PATCH v3 30/32] iommu/io-pgtable-arm-v7s: " Guanghui Feng
                           ` (2 subsequent siblings)
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:18 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from ARM LPAE io-pgtable, as all callers now use iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm.c | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index f33a86fa0f6c..55a32346b586 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -731,18 +731,6 @@ static int visit_iova_to_phys(struct io_pgtable_walk_data *walk_data, int lvl,
 	return 0;
 }
 
-static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
-						 unsigned long iova,
-						 size_t *mapped_length);
-
-static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
-					 unsigned long iova)
-{
-	phys_addr_t phys = arm_lpae_iova_to_phys_length(ops, iova, NULL);
-
-	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
-}
-
 static phys_addr_t arm_lpae_iova_to_phys_length(struct io_pgtable_ops *ops,
 						 unsigned long iova,
 						 size_t *mapped_length)
@@ -965,7 +953,6 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages	= arm_lpae_map_pages,
 		.unmap_pages	= arm_lpae_unmap_pages,
-		.iova_to_phys	= arm_lpae_iova_to_phys,
 		.iova_to_phys_length	= arm_lpae_iova_to_phys_length,
 		.read_and_clear_dirty = arm_lpae_read_and_clear_dirty,
 		.pgtable_walk	= arm_lpae_pgtable_walk,
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 30/32] iommu/io-pgtable-arm-v7s: remove deprecated iova_to_phys wrapper
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (28 preceding siblings ...)
  2026-06-03 15:18         ` [PATCH v3 29/32] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
@ 2026-06-03 15:18         ` Guanghui Feng
  2026-06-03 16:31           ` sashiko-bot
  2026-06-03 15:18         ` [PATCH v3 31/32] iommu/io-pgtable-dart: " Guanghui Feng
  2026-06-03 15:18         ` [PATCH v3 32/32] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:18 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from ARM v7s io-pgtable, as all callers now use iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-arm-v7s.c | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
index 62198e31a393..da065747e37c 100644
--- a/drivers/iommu/io-pgtable-arm-v7s.c
+++ b/drivers/iommu/io-pgtable-arm-v7s.c
@@ -641,18 +641,6 @@ static size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova
 	return unmapped;
 }
 
-static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
-						unsigned long iova,
-						size_t *mapped_length);
-
-static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
-					unsigned long iova)
-{
-	phys_addr_t phys = arm_v7s_iova_to_phys_length(ops, iova, NULL);
-
-	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
-}
-
 static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
 						unsigned long iova,
 						size_t *mapped_length)
@@ -730,7 +718,6 @@ static struct io_pgtable *arm_v7s_alloc_pgtable(struct io_pgtable_cfg *cfg,
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages	= arm_v7s_map_pages,
 		.unmap_pages	= arm_v7s_unmap_pages,
-		.iova_to_phys	= arm_v7s_iova_to_phys,
 		.iova_to_phys_length	= arm_v7s_iova_to_phys_length,
 	};
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 31/32] iommu/io-pgtable-dart: remove deprecated iova_to_phys wrapper
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (29 preceding siblings ...)
  2026-06-03 15:18         ` [PATCH v3 30/32] iommu/io-pgtable-arm-v7s: " Guanghui Feng
@ 2026-06-03 15:18         ` Guanghui Feng
  2026-06-03 15:18         ` [PATCH v3 32/32] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
  31 siblings, 0 replies; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:18 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from DART io-pgtable, as all callers now use iova_to_phys_length.

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/io-pgtable-dart.c | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index 2dac21a578a7..01c4c022830b 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -333,18 +333,6 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	return i * pgsize;
 }
 
-static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
-					    unsigned long iova,
-					    size_t *mapped_length);
-
-static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
-				     unsigned long iova)
-{
-	phys_addr_t phys = dart_iova_to_phys_length(ops, iova, NULL);
-
-	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
-}
-
 static phys_addr_t dart_iova_to_phys_length(struct io_pgtable_ops *ops,
 					    unsigned long iova,
 					    size_t *mapped_length)
@@ -416,7 +404,6 @@ dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
 	data->iop.ops = (struct io_pgtable_ops) {
 		.map_pages		= dart_map_pages,
 		.unmap_pages		= dart_unmap_pages,
-		.iova_to_phys		= dart_iova_to_phys,
 		.iova_to_phys_length	= dart_iova_to_phys_length,
 	};
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* [PATCH v3 32/32] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops
  2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
                           ` (30 preceding siblings ...)
  2026-06-03 15:18         ` [PATCH v3 31/32] iommu/io-pgtable-dart: " Guanghui Feng
@ 2026-06-03 15:18         ` Guanghui Feng
  2026-06-03 16:26           ` sashiko-bot
  31 siblings, 1 reply; 144+ messages in thread
From: Guanghui Feng @ 2026-06-03 15:18 UTC (permalink / raw)
  To: jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

Now that all drivers implement iova_to_phys_length and all callers
have migrated, remove the deprecated interfaces:

- Remove .iova_to_phys from struct iommu_domain_ops
- Remove .iova_to_phys from struct io_pgtable_ops
- Remove fallback path in iommu_iova_to_phys_length()
- iommu_iova_to_phys() remains as a thin wrapper calling _length with NULL

Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 drivers/iommu/iommu.c      | 16 ++--------------
 include/linux/io-pgtable.h | 12 +++++-------
 include/linux/iommu.h      |  3 ---
 3 files changed, 7 insertions(+), 24 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 320ea13488e7..1ad4787925cd 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2561,8 +2561,6 @@ phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
 				       dma_addr_t iova,
 				       size_t *mapped_length)
 {
-	phys_addr_t phys;
-
 	if (domain->type == IOMMU_DOMAIN_IDENTITY) {
 		if (mapped_length)
 			*mapped_length = PAGE_SIZE;
@@ -2572,20 +2570,10 @@ phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
 	if (mapped_length)
 		*mapped_length = 0;
 
-	if (domain->ops->iova_to_phys_length)
-		return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
-
-	/* Fallback to legacy iova_to_phys without length info */
-	if (!domain->ops->iova_to_phys)
+	if (!domain->ops->iova_to_phys_length)
 		return PHYS_ADDR_MAX;
 
-	phys = domain->ops->iova_to_phys(domain, iova);
-	if (!phys)
-		return PHYS_ADDR_MAX;
-
-	if (mapped_length)
-		*mapped_length = PAGE_SIZE;
-	return phys;
+	return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
 }
 EXPORT_SYMBOL_GPL(iommu_iova_to_phys_length);
 
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index 42bcdd309b88..ea7e473146e4 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -202,11 +202,11 @@ struct arm_lpae_io_pgtable_walk_data {
  *
  * @map_pages:    Map a physically contiguous range of pages of the same size.
  * @unmap_pages:  Unmap a range of virtually contiguous pages of the same size.
- * @iova_to_phys: Translate iova to physical address.
- * @iova_to_phys_length: Translate iova to physical address and return the
- *			  remaining mapped length from iova to the end of the
- *			  mapping entry via @mapped_length. If @mapped_length is
- *			  NULL, only the physical address is returned.
+ * @iova_to_phys_length: Translate iova to physical address and return, via
+ *			  @mapped_length, the full size of the mapping entry
+ *			  that covers @iova (e.g. 4KB/2MB/1GB). If
+ *			  @mapped_length is NULL, only the physical address
+ *			  is returned.
  * @pgtable_walk: (optional) Perform a page table walk for a given iova.
  * @read_and_clear_dirty: Record dirty info per IOVA. If an IOVA is dirty,
  *			  clear its dirty state from the PTE unless the
@@ -222,8 +222,6 @@ struct io_pgtable_ops {
 	size_t (*unmap_pages)(struct io_pgtable_ops *ops, unsigned long iova,
 			      size_t pgsize, size_t pgcount,
 			      struct iommu_iotlb_gather *gather);
-	phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,
-				    unsigned long iova);
 	phys_addr_t (*iova_to_phys_length)(struct io_pgtable_ops *ops,
 					   unsigned long iova,
 					   size_t *mapped_length);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 19da84c2922c..ca585647180b 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -746,7 +746,6 @@ struct iommu_ops {
  *                         array->entry_num to report the number of handled
  *                         invalidation requests. The driver data structure
  *                         must be defined in include/uapi/linux/iommufd.h
- * @iova_to_phys: translate iova to physical address
  * @iova_to_phys_length: translate iova to physical address and additionally
  *                       return the page size of the PTE mapping at @iova
  *                       through @mapped_length.
@@ -777,8 +776,6 @@ struct iommu_domain_ops {
 	int (*cache_invalidate_user)(struct iommu_domain *domain,
 				     struct iommu_user_data_array *array);
 
-	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
-				    dma_addr_t iova);
 	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
 					    dma_addr_t iova,
 					    size_t *mapped_length);
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 02/32] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops
  2026-06-03 15:17         ` [PATCH v3 02/32] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
@ 2026-06-03 15:35           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:35 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] arm_lpae_iova_to_phys_length unconditionally returns the full block size instead of the remaining mapped length from the given IOVA.
--

commit c1536d0ff7a2be05195b8dfb8b49fd2e59d5c8f3
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops

This commit adds a new iova_to_phys_length callback to struct io_pgtable_ops
and implements it for the ARM LPAE backend to return both the physical address
and the length of the mapping. The existing iova_to_phys is updated to wrap
the new callback.

> diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
> --- a/drivers/iommu/io-pgtable-arm.c
> +++ b/drivers/iommu/io-pgtable-arm.c
[ ... ]
> @@ -776,13 +789,18 @@ static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
[ ... ]
>  	ret = __arm_lpae_iopte_walk(data, &walk_data, data->pgd, data->start_level);
>  	if (ret)
> -		return 0;
> +		return PHYS_ADDR_MAX;
> +
> +	block_size = ARM_LPAE_BLOCK_SIZE(d.lvl, data);
> +	if (mapped_length)
> +		*mapped_length = block_size;

[Severity: Critical]
Does this code return the correct length if the iova is not perfectly aligned
to the block boundary?

If the provided iova has an offset within the block, unconditionally assigning
the full block_size to *mapped_length might cause callers to overestimate the
contiguous physical memory mapped starting from that specific iova.

The actual remaining contiguous memory in the block from the iova would be
block_size - (iova & (block_size - 1)). Could returning the full block size
cause callers to overrun into unmapped physical memory or memory belonging to
other processes?

>  
> -	iova &= (ARM_LPAE_BLOCK_SIZE(d.lvl, data) - 1);
> +	iova &= (block_size - 1);
>  	return iopte_to_paddr(d.pte, data) | iova;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=2

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-03 15:17         ` [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
@ 2026-06-03 15:38           ` sashiko-bot
  2026-06-04  2:44           ` Baolu Lu
  2026-06-04 14:16           ` Jason Gunthorpe
  2 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:38 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] The iova_to_phys_length callback API lacks error-value documentation, leading to implementations returning 0 on error instead of PHYS_ADDR_MAX, causing critical security issues when physical page 0 is wrongly unpinned.
--

commit 9607eab32e3052be526d8972c7af679e0ea36c75
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu: introduce iova_to_phys_length in iommu_domain_ops

This commit introduces a new iova_to_phys_length callback in
iommu_domain_ops and a corresponding iommu_iova_to_phys_length() core
function. The callback allows returning both the physical address and the
PTE mapping page size in a single page table walk. Callers like VFIO can
use this to traverse IOVA space more efficiently.

> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index d1a9e713d3a05..320ea13488e7b 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c

[ ... ]

> @@ -2545,15 +2545,55 @@ void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group)
>  }
>  EXPORT_SYMBOL_GPL(iommu_detach_group);
>  
> -phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
> +/**
> + * iommu_iova_to_phys_length - Translate IOVA and return mapping page size
> + * @domain: IOMMU domain to query
> + * @iova: IO virtual address to translate
> + * @mapped_length: Output parameter for the PTE page size (e.g. 4KB/2MB/1GB)
> + *
> + * Like iommu_iova_to_phys() but additionally returns the page size of the
> + * PTE mapping at @iova through @mapped_length.
> + *
> + * Return: The physical address for the given IOVA, or PHYS_ADDR_MAX if no
> + *         translation exists.
> + */
> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> +				       dma_addr_t iova,
> +				       size_t *mapped_length)
>  {
> -	if (domain->type == IOMMU_DOMAIN_IDENTITY)
> +	phys_addr_t phys;
> +
> +	if (domain->type == IOMMU_DOMAIN_IDENTITY) {
> +		if (mapped_length)
> +			*mapped_length = PAGE_SIZE;
>  		return iova;
> +	}
>  
> -	if (domain->type == IOMMU_DOMAIN_BLOCKED)
> -		return 0;
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
> +	if (domain->ops->iova_to_phys_length)
> +		return domain->ops->iova_to_phys_length(domain, iova, mapped_length);

[Severity: Critical]
Does this code handle failures correctly if the driver returns 0 on error?

The documentation for iommu_iova_to_phys_length() states that it returns
PHYS_ADDR_MAX if no translation exists. However, if the underlying driver
incorrectly returns 0 on failure, this wrapper directly propagates that 0.

If callers like VFIO check for PHYS_ADDR_MAX to detect errors, they will
assume 0 is a valid physical address. This can lead to unmapping and
unpinning physical PFN 0, which corrupts page refcounts and can result in
use-after-free vulnerabilities.

Could we sanitize the return value here?

> diff --git a/include/linux/iommu.h b/include/linux/iommu.h
> index bf8a77a164e4d..29c7c042d400d 100644
> --- a/include/linux/iommu.h
> +++ b/include/linux/iommu.h

[ ... ]

> @@ -804,6 +807,9 @@ struct iommu_domain_ops {
>  
>  	phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
>  				    dma_addr_t iova);
> +	phys_addr_t (*iova_to_phys_length)(struct iommu_domain *domain,
> +					    dma_addr_t iova,
> +					    size_t *mapped_length);

[Severity: Critical]
Could we add documentation specifying the error return value for this new
callback?

Since legacy iova_to_phys implementations often returned 0 on error, drivers
implementing this new callback might also return 0 instead of PHYS_ADDR_MAX
on failure.

For example, arm_smmu_iova_to_phys_hard() defaults its address variable to 0
on translation faults.

Additionally, drivers might fail to set mapped_length on success, which
can cause kernel warnings in callers like VFIO and iommufd.

Explicitly documenting the expectations for mapped_length and the return
value on failure (PHYS_ADDR_MAX) might prevent these driver implementation
errors.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=1

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
@ 2026-06-03 15:39           ` sashiko-bot
  2026-06-04  3:30           ` Baolu Lu
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:39 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] iova_to_phys_length overstates mapped_length for unaligned IOVAs, causing boundary overshoot.
- [Medium] Unnecessary O(N) page table traversal when mapped_length is not requested.
- [Medium] Contiguous physical length accumulation artificially truncates at page table boundaries.
--

commit a86cc79db974a9c9b664e54606ef192f94d2d77f
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/generic_pt: implement iova_to_phys_length
    
Extend the Generic Page Table framework to implement iova_to_phys_length.
Use pt_entry_oa_lg2sz() to determine PTE block size. Update
IOMMU_PT_DOMAIN_OPS macro to set .iova_to_phys_length.

> diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
> index c2752151c80af..914215b467213 100644
> --- a/drivers/iommu/generic_pt/iommu_pt.h
> +++ b/drivers/iommu/generic_pt/iommu_pt.h
[ ... ]
> @@ -180,13 +180,21 @@ static inline unsigned int compute_best_pgsize(struct pt_state *pts,
> +static __always_inline int __do_iova_to_phys_length(struct pt_range *range,
> +					       void *arg, unsigned int level,
> +					       struct pt_table_p *table,
> +					       pt_level_fn_t descend_fn)
>  {
[ ... ]
> +	data->phys = pt_entry_oa_exact(&pts);
> +	entry_lg2sz = pt_entry_oa_lg2sz(&pts);
> +	entry_sz = log2_to_int(entry_lg2sz);
> +
> +	/* Start with the full mapping size of the first entry */
> +	data->length = entry_sz;

[Severity: High]
Does this calculation overstate the mapped length for unaligned IOVAs?

If the IOVA is not aligned to the PTE block size, pt_entry_oa_exact()
includes the intra-page offset in data->phys. However, data->length
is unconditionally initialized to the full entry_sz rather than
entry_sz - offset. Callers relying on mapped_length might operate
on out-of-bounds memory because data->phys + data->length extends
beyond the valid mapped physical memory by the unaligned offset amount.

> +
> +	/* Accumulate subsequent physically contiguous entries */
> +	expected_oa = pt_entry_oa(&pts) + entry_sz;
> +	pts.end_index = log2_to_int(pt_num_items_lg2(&pts));
> +	pt_next_entry(&pts);
> +
> +	while (pts.index < pts.end_index) {

[Severity: Medium]
Could this introduce overhead by performing an unnecessary traversal?

When iova_to_phys_length() is called via API paths that do not request
a length (passing NULL for mapped_length), this loop still
unconditionally iterates over all remaining page table entries in the
current block. This adds extra work for lookups that just need the
physical address.

[Severity: Medium]
Is the contiguous physical length accumulation artificially truncated
at page table boundaries?

The function documentation states that callers can determine the full
contiguous mapping extent with a single call. However, this loop
terminates at pts.end_index, which is the end of the current leaf
page table block. It does not ascend or cross into the next page
table block to verify if the mapping remains physically contiguous.

> +		pt_load_entry(&pts);
> +		if (pts.type != PT_ENTRY_OA)
> +			break;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=5

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 07/32] iommu/arm-smmu: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 07/32] iommu/arm-smmu: " Guanghui Feng
@ 2026-06-03 15:42           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:42 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] arm_smmu_iova_to_phys_hard() returns 0 instead of PHYS_ADDR_MAX on failure, causing memory corruption via unpinned physical page 0.
- [High] arm_smmu_iova_to_phys_length fails to populate mapped_length in the hardware translation path, causing potential DoS via log spam.
- [Medium] qcom_smmu_context_fault uses incorrect error checking (!phys_soft) for the new iova_to_phys_length API.
--

commit b1a65029d801a1b04d4b609842ab52ac8eea5e30
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/arm-smmu: implement iova_to_phys_length

Migrate ARM SMMU to implement iova_to_phys_length, calling
ops->iova_to_phys_length on the io-pgtable layer. Update qcom-debug
caller accordingly.

> diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> index 65e0ef6539fe7..4fd01341157f3 100644
> --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom-debug.c
> @@ -415,7 +415,7 @@ irqreturn_t qcom_smmu_context_fault(int irq, void *dev)
>  		return IRQ_HANDLED;
>  	}
>  
> -	phys_soft = ops->iova_to_phys(ops, cfi.iova);
> +	phys_soft = ops->iova_to_phys_length(ops, cfi.iova, NULL);

[Severity: Medium]
Will this result in skipped error reporting later in this function?

Since io-pgtable implementations of iova_to_phys_length now return
PHYS_ADDR_MAX on error instead of 0, the check further down in
qcom_smmu_context_fault:

	if (!phys_soft)
		dev_err(smmu->dev,
			"SOFTWARE TABLE WALK FAILED! Looks like %s accessed an unmapped address!\n",
			dev_name(smmu->dev));

will evaluate to false when the table walk fails. Can this cause
PHYS_ADDR_MAX to be erroneously treated and printed as a successfully
resolved physical address?

[ ... ]

> diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> index 0bd21d206eb3e..5c9ec7c937632 100644
> --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
> +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> @@ -1366,7 +1366,7 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
>  			"iova to phys timed out on %pad. Falling back to software table walk.\n",
>  			&iova);
>  		arm_smmu_rpm_put(smmu);
> -		return ops->iova_to_phys(ops, iova);
> +		return ops->iova_to_phys_length(ops, iova, NULL);

[Severity: High]
Are we discarding the mapping size by explicitly passing NULL for
mapped_length?

If callers like vfio_unmap_unpin receive a pgsize of 0, will they trigger a
WARN_ON trace for every unmapped page, potentially spamming the kernel log
when unmapping large amounts of memory?

>  	}
>  
>  	phys = arm_smmu_cb_readq(smmu, idx, ARM_SMMU_CB_PAR);
> @@ -1384,20 +1384,21 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
>  	return addr;
>  }
>  
> -static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
> -					dma_addr_t iova)
> +static phys_addr_t arm_smmu_iova_to_phys_length(struct iommu_domain *domain,
> +					dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
>  	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
>  
> +
>  	if (!ops)
> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS &&
>  			smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
>  		return arm_smmu_iova_to_phys_hard(domain, iova);

[Severity: High]
Could this delegation to hardware translation fail to populate mapped_length?

Since arm_smmu_iova_to_phys_hard does not take a mapped_length parameter, it
leaves the size unpopulated. Similar to the software fallback above, can
this cause callers to receive a pgsize of 0?

[Severity: Critical]
Is arm_smmu_iova_to_phys_hard still returning 0 on failure?

The updated API requires returning PHYS_ADDR_MAX upon translation failure,
but arm_smmu_iova_to_phys_hard returns 0 when arm_smmu_rpm_get fails, and
also when a translation fault occurs:

	phys = arm_smmu_cb_readq(smmu, idx, ARM_SMMU_CB_PAR);
	spin_unlock_irqrestore(&smmu_domain->cb_lock, flags);
	if (phys & ARM_SMMU_CB_PAR_F) {
		dev_err(dev, "translation fault!\n");
		dev_err(dev, "PAR = 0x%llx\n", phys);
		goto out;
	}

Because 0 is a valid physical address, will callers erroneously treat this
as a successfully mapped address, which could result in unpinning physical
page 0?

>  
> -	return ops->iova_to_phys(ops, iova);
> +	return ops->iova_to_phys_length(ops, iova, mapped_length);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=7

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 12/32] iommu/exynos: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 12/32] iommu/exynos: " Guanghui Feng
@ 2026-06-03 15:46           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:46 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the driver accesses little-endian page table entries using native CPU endianness, leading to corruption and incorrect page table walks on big-endian systems. The function `exynos_iommu_set_pte()` correctly writes entries as little-endian using `cpu_to_le32()`. However, macros like `lv1ent_section` and `lv1ent_page` directly dereference the `sysmmu_pte_t *` pointer (e.g., `*(sent) & 3`), which evaluates the native 32-bit value. On a big-endian system, this bitwise operation checks the most significant byte of the little-endian value in memory, always failing the structure checks. Furthermore, some assignments like `domain->pgtable[i] = ZERO_LV2LINK;` bypass endian conversion entirely, writing big-endian values that the little-endian hardware will misinterpret.
--

commit 097549f5c7fdecb9881aa9c7d6c8964f12985a19
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/exynos: implement iova_to_phys_length

This commit implements the iova_to_phys_length callback for the Exynos IOMMU
driver. It traverses the page table to return both the physical address and
the actual PTE mapping size for a given IOVA.

> diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c
> --- a/drivers/iommu/exynos-iommu.c
> +++ b/drivers/iommu/exynos-iommu.c
[ ... ]
> @@ -1386,13 +1387,20 @@ static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain,
>  
>  	if (lv1ent_section(entry)) {
>  		phys = section_phys(entry) + section_offs(iova);

[Severity: High]
This is a pre-existing issue, but does exynos_iommu_iova_to_phys_length()
correctly handle endianness on big-endian systems?

The macros like lv1ent_section() and lv1ent_page() directly dereference the
sysmmu_pte_t pointer using native CPU endianness. For example, lv1ent_section
evaluates to (*(sent) & 3).

Since the hardware uses little-endian page tables, a big-endian CPU
evaluating this bitwise operation will check the most significant byte of
the little-endian value in memory. This will cause the structure checks to
fail, leading to incorrect page table walks and IOMMU translation faults.

While exynos_iommu_set_pte() correctly writes entries using cpu_to_le32(),
the read paths do not seem to do the reverse conversion. Would it be
appropriate to convert the values using le32_to_cpu() when reading these
page table entries to prevent corruption on big-endian architectures?

> +		if (mapped_length)
> +			*mapped_length = SECT_SIZE;
>  	} else if (lv1ent_page(entry)) {
>  		entry = page_entry(entry, iova);
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=12

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 13/32] iommu/fsl_pamu: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 13/32] iommu/fsl_pamu: " Guanghui Feng
@ 2026-06-03 15:48           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:48 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] iommufd and vfio misinterpret mapped_length as the remaining contiguous length, leading to out-of-bounds physical address batching.
- [Critical] Assigning 64GB (`1ULL << 36`) to `size_t *mapped_length` silently truncates to `0` on 32-bit kernels.
--

commit bd63c934fd433e9c7d21862a31a674fe50af2eae
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/fsl_pamu: implement iova_to_phys_length

This commit implements the iova_to_phys_length callback for the FSL PAMU
IOMMU driver. It returns the actual PTE mapping size for a given IOVA.

> diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c
> index 9664ef9840d2c..60abd497dc639 100644
> --- a/drivers/iommu/fsl_pamu_domain.c
> +++ b/drivers/iommu/fsl_pamu_domain.c
[ ... ]
> @@ -169,12 +169,30 @@ static void attach_device(struct fsl_dma_domain *dma_domain, int liodn, struct d
>  	spin_unlock_irqrestore(&device_domain_lock, flags);
>  }
>  
> -static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain,
> -					 dma_addr_t iova)
> +static phys_addr_t fsl_pamu_iova_to_phys_length(struct iommu_domain *domain,
> +						dma_addr_t iova,
> +						size_t *mapped_length)
>  {
> +
>  	if (iova < domain->geometry.aperture_start ||
>  	    iova > domain->geometry.aperture_end)
> -		return 0;
> +		return PHYS_ADDR_MAX;
> +
> +	/*
> +	 * PAMU configures exactly one Primary PAACE entry per LIODN with the
> +	 * Multi-Window (MW) bit cleared and ATM = PAACE_ATM_WINDOW_XLATE,
> +	 * WBAL/TWBAL = 0. That is, every LIODN is backed by a single hardware
> +	 * mapping window of fixed size (1ULL << 36, i.e. 64GB, see
> +	 * pamu_config_ppaace()) performing an identity translation. The driver
> +	 * does not split this window into SPAACE sub-windows, so the entire
> +	 * aperture is one PAACE "PTE" entry. Return that single entry's size
> +	 * as mapped_length, matching the iova_to_phys_length contract that
> +	 * mapped_length reports the full size of the mapping entry which
> +	 * covers iova (not the remaining bytes from iova to its end).
> +	 */
> +	if (mapped_length)
> +		*mapped_length = 1ULL << 36;

[Severity: Critical]
Does this assignment silently truncate to 0 on 32-bit kernels?

Since mapped_length is a size_t pointer, assigning 1ULL << 36 (which is 64GB)
will truncate to 0 when size_t is 32-bit, potentially causing caller infinite
loops.

[Severity: Critical]
Could this implementation cause out-of-bounds physical address batching in
callers?

The comment mentions that mapped_length reports the full size of the mapping
entry rather than the remaining bytes from iova to its end.

However, callers appear to misinterpret mapped_length as the remaining
contiguous length. For example, in drivers/iommu/iommufd/pages.c,
batch_from_domain() fails to account for the IOVA offset within the PTE:

	npages = pgsize / PAGE_SIZE;

Similarly, in drivers/vfio/vfio_iommu_type1.c, vfio_unmap_unpin() directly
uses pgsize without adjusting for the offset:

	len = min_t(size_t, pgsize, dma->size - pos);

By returning the full PTE size without adjusting for the offset within the PTE,
can this lead to memory corruption when callers over-batch physical pages?

> +
>  	return iova;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=13

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 14/32] iommu/msm: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 14/32] iommu/msm: " Guanghui Feng
@ 2026-06-03 15:51           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:51 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] When `__enable_clocks()` fails, its negative error code is incorrectly returned cast to `phys_addr_t` instead of `PHYS_ADDR_MAX`.
- [High] Unsafe `list_first_entry()` on a potentially empty list leads to out-of-bounds memory accesses.
- [High] Concurrent traversal and modification of `priv->list_attached` without adequate common locking.
--

commit 48fb553fe9efe8fd4fc6286128070ec03e449c35
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/msm: implement iova_to_phys_length

Implement iova_to_phys_length for MSM IOMMU driver, returning the actual
PTE mapping size.

> diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c
> index 0ad5ff431d5b6..1038e8141223f 100644
> --- a/drivers/iommu/msm_iommu.c
> +++ b/drivers/iommu/msm_iommu.c
> @@ -523,15 +523,16 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
>  	return ret;
>  }
>  
> -static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
> -					  dma_addr_t va)
> +static phys_addr_t msm_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +						 dma_addr_t va,
> +						 size_t *mapped_length)
>  {
>  	struct msm_priv *priv;
>  	struct msm_iommu_dev *iommu;
>  	struct msm_iommu_ctx_dev *master;
>  	unsigned int par;
>  	unsigned long flags;
> -	phys_addr_t ret = 0;
> +	phys_addr_t ret = PHYS_ADDR_MAX;

[Severity: High]
This isn't a bug introduced by this patch, but if
msm_iommu_iova_to_phys_length() is called on an IOMMU domain before any
devices are attached, priv->list_attached would be empty. In that case,
does list_first_entry() calculate an invalid pointer?

If so, would dereferencing this pointer in the subsequent
list_empty(&iommu->ctx_list) and __enable_clocks(iommu) calls lead to
out-of-bounds memory accesses?

[Severity: High]
This is a pre-existing issue and was not introduced by the patch under
review, but when __enable_clocks() fails, it returns a negative integer
error code. Since ret is initialized to PHYS_ADDR_MAX but gets overwritten
shortly after:

msm_iommu_iova_to_phys_length() {
        ...
        ret = __enable_clocks(iommu);
        if (ret)
                goto fail;
        ...

Could this negative error code be incorrectly cast to phys_addr_t and
returned on the failure path instead of PHYS_ADDR_MAX?

[Severity: High]
This isn't a bug introduced by this patch, but is there a risk of
concurrent modification and traversal of priv->list_attached?

It looks like msm_iommu_attach_dev() modifies the list using list_add()
while holding only the msm_iommu_lock spinlock. However,
msm_iommu_sync_map() traverses this same list locklessly by calling
__flush_iotlb_range() without taking any locks, and msm_iommu_unmap()
traverses it holding only priv->pgtlock. Can this lack of synchronization
lead to list corruption?

>  	spin_lock_irqsave(&msm_iommu_lock, flags);
>  

[ ... ]

> @@ -558,13 +559,19 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
>  	par = GET_PAR(iommu->base, master->num);
>  
>  	/* We are dealing with a supersection */
> -	if (GET_NOFAULT_SS(iommu->base, master->num))
> +	if (GET_NOFAULT_SS(iommu->base, master->num)) {
>  		ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
> -	else	/* Upper 20 bits from PAR, lower 12 from VA */
> +		if (mapped_length)
> +			*mapped_length = SZ_16M;
> +	} else {
> +		/* Upper 20 bits from PAR, lower 12 from VA */
>  		ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
> +		if (mapped_length)
> +			*mapped_length = SZ_4K;
> +	}
>  
>  	if (GET_FAULT(iommu->base, master->num))
> -		ret = 0;
> +		ret = PHYS_ADDR_MAX;
>  
>  	__disable_clocks(iommu);
>  fail:

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=14

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 17/32] iommu/rockchip: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 17/32] iommu/rockchip: " Guanghui Feng
@ 2026-06-03 15:53           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:53 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The driver incorrectly passes a DMA address to phys_to_virt() to access and free page tables, causing severe memory corruption and memory leaks if SWIOTLB or an IOMMU translates the mapping.
--

commit 0aedb97879160a60d3200236cd038dfd8a203524
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/rockchip: implement iova_to_phys_length

Implement iova_to_phys_length for Rockchip IOMMU driver,
returning the actual PTE mapping size.

> diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c
> index 87ae036d64145..94ee300983aee 100644
> --- a/drivers/iommu/rockchip-iommu.c
> +++ b/drivers/iommu/rockchip-iommu.c
> @@ -650,12 +650,12 @@ static irqreturn_t rk_iommu_irq(int irq, void *dev_id)
>  	return ret;
>  }
>  
> -static phys_addr_t rk_iommu_iova_to_phys(struct iommu_domain *domain,
> -					 dma_addr_t iova)
> +static phys_addr_t rk_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +					 dma_addr_t iova, size_t *mapped_length)
>  {

[Severity: High]
This is a pre-existing issue, but does this function incorrectly pass a DMA
address to phys_to_virt()?

In rk_iommu_iova_to_phys_length(), we have this sequence:

	pt_phys = rk_ops->pt_address(dte);
	page_table = (u32 *)phys_to_virt(pt_phys);

Since the page tables are mapped via dma_map_single() and the DTE stores a
dma_addr_t, pt_phys holds a DMA address rather than a physical address.

If dma_map_single() uses a SWIOTLB bounce buffer or returns a translated
address, phys_to_virt() will yield an invalid pointer, potentially causing
memory corruption when dereferenced. This same pattern also appears in
rk_dte_get_page_table() and rk_iommu_domain_free().

>  	struct rk_iommu_domain *rk_domain = to_rk_domain(domain);
>  	unsigned long flags;
> -	phys_addr_t pt_phys, phys = 0;
> +	phys_addr_t pt_phys, phys = PHYS_ADDR_MAX;
>  	u32 dte, pte;
>  	u32 *page_table;

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=17

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 19/32] iommu/sprd: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 19/32] iommu/sprd: " Guanghui Feng
@ 2026-06-03 15:57           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:57 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] Unchecked dereference of lazily-allocated `dom->pgt_va` when called on an unattached domain.
- [High] Missing memory barrier before IOMMU TLB invalidate allows the hardware to fetch stale page table entries.
- [High] Physical addresses above 4GB are silently truncated before being mapped into the IOMMU page table.
--

commit 5443e99a8531bdfb126ef548ec91ab715067b91b
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/sprd: implement iova_to_phys_length

Implement iova_to_phys_length for Spreadtrum IOMMU driver,
returning the actual PTE mapping size.

> diff --git a/drivers/iommu/sprd-iommu.c b/drivers/iommu/sprd-iommu.c
> index c1a34445d244f..3c04ec040d96e 100644
> --- a/drivers/iommu/sprd-iommu.c
> +++ b/drivers/iommu/sprd-iommu.c

[ ... ]

> @@ -376,13 +377,19 @@ static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
>  	unsigned long end = domain->geometry.aperture_end;
>  
>  	if (WARN_ON(iova < start || iova > end))
> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	spin_lock_irqsave(&dom->pgtlock, flags);
>  	pa = *(dom->pgt_va + ((iova - start) >> SPRD_IOMMU_PAGE_SHIFT));

[Severity: High]
This isn't a bug introduced by this patch, but could this lead to a NULL
pointer dereference if called on an unattached domain?

Since dom->pgt_va is lazily allocated during sprd_iommu_attach_device(),
dom->pgt_va will be NULL if the IOMMU API queries the mapping on an unattached
domain (such as via a debug interface or an error recovery path).

> -	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
>  	spin_unlock_irqrestore(&dom->pgtlock, flags);
>  
> +	if (!pa)
> +		return PHYS_ADDR_MAX;
> +
> +	pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
> +	if (mapped_length)
> +		*mapped_length = SPRD_IOMMU_PAGE_SIZE;
> +
>  	return pa;
>  }

I also noticed a few other pre-existing issues in this file:

[Severity: High]
This is a pre-existing issue, but is there a missing memory barrier before the
IOMMU TLB invalidate in sprd_iommu_sync_map()?

drivers/iommu/sprd-iommu.c:sprd_iommu_sync_map() {
    ...
	/* clear IOMMU TLB buffer after page table updated */
	sprd_iommu_write(dom->sdev, reg, 0xffffffff);
	return 0;
}

Because sprd_iommu_write() uses writel_relaxed(), it omits the write memory
barrier. Since the CPU updates the page table entries using standard memory
writes in sprd_iommu_map(), there is no memory barrier (wmb() or dma_wmb())
ordering these memory writes before the MMIO register write.

Could the CPU reorder the MMIO write before the page table writes are globally
visible, causing the IOMMU device to fetch stale PTEs?


[Severity: High]
This is a pre-existing issue, but does sprd_iommu_map() silently truncate
physical addresses above 4GB?

drivers/iommu/sprd-iommu.c:sprd_iommu_map() {
    ...
	u32 pabase = (u32)paddr;
    ...
}

The 64-bit phys_addr_t paddr is explicitly cast to a 32-bit integer before
computing the Page Frame Number (PFN), discarding the upper 32 bits.

If the target memory resides above the 4GB boundary, wouldn't the IOMMU be
programmed to map the device DMA to an incorrect lower physical memory address?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=19

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 15/32] iommu/mtk_v1: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 15/32] iommu/mtk_v1: " Guanghui Feng
@ 2026-06-03 15:58           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 15:58 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Missing IOVA bounds checking leads to out-of-bounds array read/write on the `pgt_va` array.
--

commit 3d307c9faf5208f019d6a8f030da9330ce0aa29b
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/mtk_v1: implement iova_to_phys_length
    
Implement iova_to_phys_length for MediaTek v1 IOMMU driver,
returning the actual PTE mapping size.
Also fix pre-existing bug: add page offset to physical address.

> diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c
> index ac97dd2868d4b..da41dda7620b8 100644
> --- a/drivers/iommu/mtk_iommu_v1.c
> +++ b/drivers/iommu/mtk_iommu_v1.c
[ ... ]
> -static phys_addr_t mtk_iommu_v1_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
> +static phys_addr_t mtk_iommu_v1_iova_to_phys_length(struct iommu_domain *domain,
> +						    dma_addr_t iova,
> +						    size_t *mapped_length)
>  {
>  	struct mtk_iommu_v1_domain *dom = to_mtk_domain(domain);
>  	unsigned long flags;
> @@ -404,6 +406,13 @@ static phys_addr_t mtk_iommu_v1_iova_to_phys(struct iommu_domain *domain, dma_ad
>  	pa = pa & (~(MT2701_IOMMU_PAGE_SIZE - 1));

[Severity: High]
This is a pre-existing issue, but does this array access lack bounds
checking on the iova parameter?

Looking at the code just above this line in mtk_iommu_v1_iova_to_phys_length():
    pa = *(dom->pgt_va + (iova >> MT2701_IOMMU_PAGE_SHIFT));

Since the driver doesn't appear to initialize domain.geometry.aperture_end
or set force_aperture = true, the generic IOMMU layer won't enforce an
upper limit on the IOVA.

If a user-space program using VFIO or a device with a >32-bit DMA mask
requests an IOVA >= 4GB, the index calculated by
(iova >> MT2701_IOMMU_PAGE_SHIFT) could exceed the bounds of the 4MB
pgt_va array, leading to an out-of-bounds read here.

This same out-of-bounds access pattern also seems to occur in
mtk_iommu_v1_map() and mtk_iommu_v1_unmap() when calculating pgt_base_iova,
where it can lead to memory corruption during out-of-bounds writes.

>  	spin_unlock_irqrestore(&dom->pgtlock, flags);
>  
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=15

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 18/32] iommu/s390: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 18/32] iommu/s390: " Guanghui Feng
@ 2026-06-03 16:03           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:03 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [Critical] NULL Pointer Dereference translating unmapped IOVA in RFX domains
- [High] Missing Page Offset in Physical Address translation
--

commit bd4ad597aec5185d8df34a68d40de0a381a961b3
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Subject: iommu/s390: implement iova_to_phys_length

This commit implements the iova_to_phys_length callback for the s390 IOMMU
driver. It returns the actual PTE mapping size via the mapped_length
parameter alongside the translated physical address.

> diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c
> --- a/drivers/iommu/s390-iommu.c
> +++ b/drivers/iommu/s390-iommu.c
[ ... ]
> -static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
> -					   dma_addr_t iova)
> +static phys_addr_t s390_iommu_iova_to_phys_length(struct iommu_domain *domain,
> +						  dma_addr_t iova,
> +						  size_t *mapped_length)
>  {
[ ... ]
>  	if (iova < domain->geometry.aperture_start ||
>  	    iova > domain->geometry.aperture_end)
> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	rto = get_rto_from_iova(s390_domain, iova);

[Severity: High]
This isn't a regression introduced by this patch, but could this code lead to
a NULL pointer dereference when querying an unmapped IOVA in an RFX domain?

If the top-level Region First Entry is unmapped, get_rso_from_iova()
returns NULL. However, get_rto_from_iova() appears to immediately
dereference it without checking:

drivers/iommu/s390-iommu.c:get_rto_from_iova() {
        ...
	case ZPCI_TABLE_TYPE_RFX:
	case ZPCI_TABLE_TYPE_RSX:
		rso = get_rso_from_iova(domain, iova);
		rsx = calc_rsx(iova);
		rse = READ_ONCE(rso[rsx]);
        ...
}

>  	if (!rto)
> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	rtx = calc_rtx(iova);
>  	sx = calc_sx(iova);
> @@ -1014,8 +1015,11 @@ static phys_addr_t s390_iommu_iova_to_phys(struct iommu_domain *domain,
>  		if (reg_entry_isvalid(ste)) {
>  			pto = get_st_pto(ste);
>  			pte = READ_ONCE(pto[px]);
> -			if (pt_entry_isvalid(pte))
> +			if (pt_entry_isvalid(pte)) {
>  				phys = pte & ZPCI_PTE_ADDR_MASK;

[Severity: High]
This isn't a regression introduced by this patch, but does this calculation
drop the intra-page offset from the returned physical address?

By returning the page-aligned PTE address without adding back the offset
(iova & ~PAGE_MASK), callers passing an unaligned IOVA might receive an
incorrectly truncated physical address. The core IOMMU framework's
iommu_iova_to_phys() doesn't add this offset back either.

> +				if (mapped_length)
> +					*mapped_length = SZ_4K;
> +			}
>  		}
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=18

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 21/32] iommu/tegra-smmu: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 21/32] iommu/tegra-smmu: " Guanghui Feng
@ 2026-06-03 16:04           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:04 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Lockless page table access in `tegra_smmu_iova_to_phys_length` can race with concurrent `unmap` operations, resulting in a Use-After-Free (UAF) on the page table memory.
--

commit 8bec8e55c0ad853218bde06b33f63906725b5a20
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/tegra-smmu: implement iova_to_phys_length

This commit implements the iova_to_phys_length callback for the Tegra
SMMU IOMMU driver. It returns the physical address and sets the mapped
length to 4K.

> diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
> index 67e7a7b925f0c..12f9bb623d874 100644
> --- a/drivers/iommu/tegra-smmu.c
> +++ b/drivers/iommu/tegra-smmu.c
[ ... ]
> @@ -813,10 +813,13 @@ static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain,
>  
>  	pte = tegra_smmu_pte_lookup(as, iova, &pte_dma);
>  	if (!pte || !*pte)

[Severity: High]
This isn't a bug introduced by this patch, but does this lockless page
table access race with concurrent unmap operations?

tegra_smmu_iova_to_phys_length() calls tegra_smmu_pte_lookup() without holding
as->lock. tegra_smmu_pte_lookup() retrieves a pointer to a PTE within a page
table. 

Concurrently, if an unmap operation for a different IOVA in the same page
table removes the last mapping, __tegra_smmu_unmap() (which holds the lock)
can call tegra_smmu_pte_put_use() to decrement the count and free the page
table via iommu_free_pages().

Could the lockless dereference of *pte here (and in the mask operation below)
read from freed page table memory if it races with such an unmap operation?
Should as->lock be held before calling tegra_smmu_pte_lookup() and across
the pointer dereference?

> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	pfn = *pte & as->smmu->pfn_mask;
>  
> +	if (mapped_length)
> +		*mapped_length = SZ_4K;
> +
>  	return SMMU_PFN_PHYS(pfn) + SMMU_OFFSET_IN_PAGE(iova);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=21

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 22/32] iommu/virtio: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 22/32] iommu/virtio: " Guanghui Feng
@ 2026-06-03 16:10           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:10 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] viommu_iova_to_phys_length incorrectly returns the total mapping span size instead of the remaining mapped length.
- [High] Calculation of mapped_length wraps around to 0 when a mapping covers the entire address space.

Pre-existing issues:
- [High] On 32-bit systems with 64-bit DMA addresses, the interval tree node silently truncates 64-bit IOVAs to 32 bits, leading to mapping collisions and incorrect physical address translations.
--

commit 4bfdcec317099653f8ccbaeac00ce9bc697a92cf
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/virtio: implement iova_to_phys_length
    
Implement iova_to_phys_length for virtio IOMMU driver, returning the actual
PTE mapping size.

> diff --git a/drivers/iommu/virtio-iommu.c b/drivers/iommu/virtio-iommu.c
> --- a/drivers/iommu/virtio-iommu.c
> +++ b/drivers/iommu/virtio-iommu.c
> @@ -912,10 +912,11 @@ static size_t viommu_unmap_pages(struct iommu_domain *domain, unsigned long iova
[ ... ]
> -static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
> -				       dma_addr_t iova)
> +static phys_addr_t viommu_iova_to_phys_length(struct iommu_domain *domain,
> +					      dma_addr_t iova,
> +					      size_t *mapped_length)
>  {
> -	u64 paddr = 0;
> +	u64 paddr = PHYS_ADDR_MAX;
>  	unsigned long flags;
>  	struct viommu_mapping *mapping;
>  	struct interval_tree_node *node;
> @@ -926,6 +927,9 @@ static phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
>  	if (node) {
>  		mapping = container_of(node, struct viommu_mapping, iova);
>  		paddr = mapping->paddr + (iova - mapping->iova.start);

[Severity: High]
This isn't a bug introduced by this patch, but on 32-bit systems with 64-bit
DMA addresses, is it possible that interval_tree_node truncates the 64-bit
IOVAs to 32 bits?

Because mapping->iova.start is an unsigned long, mixing the 64-bit iova
input with the truncated 32-bit mapping->iova.start can result in wild
physical address offsets when calculating paddr in
viommu_iova_to_phys_length.

> +		if (mapped_length)
> +			*mapped_length = mapping->iova.last -
> +					 mapping->iova.start + 1;

[Severity: High]
Can this logic in viommu_iova_to_phys_length overestimate the remaining
valid contiguous length?

By subtracting mapping->iova.start instead of the passed-in iova offset,
this evaluates to the total size of the mapping block. If a caller requests
translation at an offset within the block, returning the full block size can
cause callers to assume subsequent out-of-bounds physical frames are valid
and mapped.

[Severity: High]
Could this calculation in viommu_iova_to_phys_length also wrap around to 0
when a mapping covers the entire address space?

If the identity mapping has start = 0 and last = ULONG_MAX, the result of
ULONG_MAX - 0 + 1 overflows to 0. Returning a mapped_length of 0 can
trigger kernel warnings or infinite loops in iteration callers.

>  	}
>  	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=22

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 26/32] drm/panfrost: switch to iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 26/32] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
@ 2026-06-03 16:13           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:13 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Concurrent page fault can trigger use-after-free and double-free by acquiring a reference to a mapping whose refcount is already 0.
--

commit b6d0dc27aaa83b48f982d46bb758b6be17d2c8ce
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

drm/panfrost: switch to iova_to_phys_length

Migrate panfrost_mmu to use ops->iova_to_phys_length(ops, iova, NULL)
instead of the deprecated ops->iova_to_phys.

> diff --git a/drivers/gpu/drm/panfrost/panfrost_mmu.c b/drivers/gpu/drm/panfrost/panfrost_mmu.c
> index 4a3162c3b659f..aa0bc82deaf68 100644
> --- a/drivers/gpu/drm/panfrost/panfrost_mmu.c
> +++ b/drivers/gpu/drm/panfrost/panfrost_mmu.c
> @@ -514,7 +514,7 @@ void panfrost_mmu_unmap(struct panfrost_gem_mapping *mapping)
>  
>  		if (bo->is_heap)
>  			pgcount = 1;
> -		if (!bo->is_heap || ops->iova_to_phys(ops, iova)) {
> +		if (!bo->is_heap || ops->iova_to_phys_length(ops, iova, NULL) != PHYS_ADDR_MAX) {
>  			unmapped_page = ops->unmap_pages(ops, iova, pgsize, pgcount, NULL);

[Severity: High]
This isn't a bug introduced by this patch, but can the execution of
panfrost_mmu_unmap() race with a concurrent page fault and trigger a
use-after-free or double-free?

When a mapping's refcount drops to 0, panfrost_gem_mapping_release() calls
panfrost_gem_teardown_mapping(), which executes panfrost_mmu_unmap() while the
mapping is still in the mmu->mm tree:

drivers/gpu/drm/panfrost/panfrost_gem.c:panfrost_gem_teardown_mapping() {
	if (mapping->active)
		panfrost_mmu_unmap(mapping);

	spin_lock(&mapping->mmu->mm_lock);
	if (drm_mm_node_allocated(&mapping->mmnode))
		drm_mm_remove_node(&mapping->mmnode);
	spin_unlock(&mapping->mmu->mm_lock);
}

If a GPU page fault occurs concurrently, panfrost_mmu_map_fault_addr() calls
addr_to_mapping(), which finds the mapping in the tree and blindly increments
its refcount:

drivers/gpu/drm/panfrost/panfrost_mmu.c:addr_to_mapping() {
			...
			kref_get(&mapping->refcount);
			...
}

Since the refcount is already 0, using kref_get() here seems like it could
cause a use-after-free when the fault handler drops the reference via
panfrost_gem_mapping_put(). Should addr_to_mapping() use
kref_get_unless_zero() instead, or should the mapping be removed from the
tree before unmapping?

>  			WARN_ON(unmapped_page != pgsize * pgcount);
>  		}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=26

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap
  2026-06-03 15:17         ` [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap Guanghui Feng
@ 2026-06-03 16:14           ` sashiko-bot
  2026-06-04 14:27           ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:14 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] ARM SMMU translation fault mishandling leads to unbounded WARN_ON spam and a massive leak of pinned pages.
--

commit 4e6f222bdc9e32de4a4536a57f7ce9b046abb017
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

vfio: use iova_to_phys_length for efficient unmap
    
Use iommu_iova_to_phys_length() to get PTE page size, allowing
traversal by actual mapping granularity instead of PAGE_SIZE steps.

> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index c8151ba54de33..115d88d7003e8 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -1177,25 +1177,42 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
>  
>  	iommu_iotlb_gather_init(&iotlb_gather);
>  	while (pos < dma->size) {
> -		size_t unmapped, len;
> +		size_t unmapped, len, pgsize;
>  		phys_addr_t phys, next;
>  		dma_addr_t iova = dma->iova + pos;
>  
> -		phys = iommu_iova_to_phys(domain->domain, iova);
> -		if (WARN_ON(!phys)) {
> +		/* Single page table walk returns both phys and PTE size */
> +		phys = iommu_iova_to_phys_length(domain->domain, iova,
> +						  &pgsize);
> +		if (WARN_ON(phys == PHYS_ADDR_MAX)) {
>  			pos += PAGE_SIZE;
>  			continue;
>  		}
> +		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
> +			pgsize = PAGE_SIZE;

[Severity: High]
How does this handle ARM SMMU Stage-1 domains where the iova_to_phys_length
callback delegates to arm_smmu_iova_to_phys_hard()?

When a translation fault occurs, arm_smmu_iova_to_phys_hard() returns 0 rather
than PHYS_ADDR_MAX, and leaves pgsize as 0. Because 0 != PHYS_ADDR_MAX, this
check doesn't catch the fault.

Could this regression cause the WARN_ON(!pgsize) below to trigger and dump a
stack trace for every unmapped page in the region?

Additionally, when this falls through to unmap the page, unmap_unpin_fast()
correctly returns 0 unmapped bytes. This later hits the unmap loop break:

drivers/vfio/vfio_iommu_type1.c:vfio_unmap_unpin() {
        ...
        unmapped = unmap_unpin_fast(domain, dma, iova, len, phys, ...);
        if (!unmapped) {
                unmapped = unmap_unpin_slow(domain, dma, iova, len,
                                            phys, &unlocked);
                if (WARN_ON(!unmapped))
                        break;
        }
        ...
}

Does breaking out of this loop on the 0-byte unmap permanently skip and leak
any remaining pinned memory pages in the DMA region?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=23

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 24/32] iommufd: use iova_to_phys_length for efficient unmap
  2026-06-03 15:17         ` [PATCH v3 24/32] iommufd: " Guanghui Feng
@ 2026-06-03 16:14           ` sashiko-bot
  2026-06-04 14:26           ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:14 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] batch_from_domain and raw_pages_from_domain misinterpret the mapped_length returned by iommu_iova_to_phys_length() as the remaining contiguous length, resulting in out-of-bounds physical memory exposure and IOVA loop corruption.
- [High] Returning early from batch_from_domain when iommu_iova_to_phys_length fails causes a hard infinite loop in iopt_area_unpin_domain.
- [High] Silent failure when iommu_iova_to_phys_length() returns PHYS_ADDR_MAX leads to an uninitialized array of page pointers being returned and used by callers.
--

commit fbf04fecd109bf03fe5fc6f1c1c874977e8904ac
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommufd: use iova_to_phys_length for efficient unmap

This commit updates batch_from_domain and raw_pages_from_domain to use
iommu_iova_to_phys_length to retrieve both the physical address and mapping
contiguous length. This allows the unmap path to process actual mapping
granularities instead of walking page tables for every PAGE_SIZE.

> diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
> index 9bdb2945afe1e..40a2fe9adf9c9 100644
> --- a/drivers/iommu/iommufd/pages.c
> +++ b/drivers/iommu/iommufd/pages.c
> @@ -417,17 +417,42 @@ static void batch_from_domain(struct pfn_batch *batch,
>  	if (start_index == iopt_area_index(area))
>  		page_offset = area->page_offset;
>  	while (start_index <= last_index) {
> +		size_t pgsize;
> +		unsigned long npages;
> +		unsigned long i;
> +
>  		/*
> -		 * This is pretty slow, it would be nice to get the page size
> -		 * back from the driver, or have the driver directly fill the
> -		 * batch.
> +		 * Use iova_to_phys_length to get both the physical address
> +		 * and the contiguous mapped length in a single page table
> +		 * walk, allowing us to skip ahead by the contiguous region
> +		 * size instead of walking page tables for every PAGE_SIZE.
> +		 * Query at page-aligned iova so pgsize covers from page start.
>  		 */
> -		phys = iommu_iova_to_phys(domain, iova) - page_offset;
> -		if (!batch_add_pfn(batch, PHYS_PFN(phys)))
> -			return;
> -		iova += PAGE_SIZE - page_offset;
> +		phys = iommu_iova_to_phys_length(domain, iova - page_offset,
> +						  &pgsize);
> +		if (WARN_ON(phys == PHYS_ADDR_MAX))
> +			break;

[Severity: High]
If iommu_iova_to_phys_length() returns PHYS_ADDR_MAX on the first iteration
and triggers this break, the function returns with 0 PFNs added to the batch.

When the caller iopt_area_unpin_domain() processes this, it computes:

batch_last_index = start_index + batch->total_pfns - 1;
start_index = batch_last_index + 1;

If total_pfns is 0, start_index does not advance. Could this cause an
infinite loop in iopt_area_unpin_domain() while holding locks?

> +		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
> +			pgsize = PAGE_SIZE;
> +
> +		/*
> +		 * pgsize is the contiguous length from the page-aligned
> +		 * iova, so npages is simply pgsize / PAGE_SIZE.
> +		 */
> +		npages = pgsize / PAGE_SIZE;
> +		npages = min_t(unsigned long, npages,
> +			       last_index - start_index + 1);
> +		if (!npages)
> +			npages = 1;
> +
> +		for (i = 0; i < npages; i++) {
> +			if (!batch_add_pfn(batch, PHYS_PFN(phys) + i))
> +				return;
> +		}

[Severity: Critical]
Since iommu_iova_to_phys_length() returns the total size of the mapped block
in pgsize, what happens if the iova being queried is unaligned within that
block?

For example, if the function is querying a 1MB offset into a 2MB huge page,
phys will correctly be the offset physical address, but pgsize will still be
2MB. The npages calculation would result in 512 pages. Adding the full 512
pages starting from the offset physical address would overshoot the mapped
block by 1MB.

Does this expose unmapped or unrelated physical memory to the batch, and
cause iova to be advanced incorrectly, skipping valid IOVAs?

[ ... ]

> @@ -445,11 +470,36 @@ static struct page **raw_pages_from_domain(struct iommu_domain *domain,
>  	if (start_index == iopt_area_index(area))
>  		page_offset = area->page_offset;
>  	while (start_index <= last_index) {
> -		phys = iommu_iova_to_phys(domain, iova) - page_offset;
> -		*(out_pages++) = pfn_to_page(PHYS_PFN(phys));
> -		iova += PAGE_SIZE - page_offset;
> +		size_t pgsize;
> +		unsigned long npages;
> +		unsigned long i;
> +
> +		/*
> +		 * Resolve the contiguous mapped length together with the
> +		 * physical address so we can fill multiple struct page
> +		 * pointers per page table walk when the IOMMU uses large
> +		 * pages. Query at page-aligned iova so pgsize covers from
> +		 * page start.
> +		 */
> +		phys = iommu_iova_to_phys_length(domain, iova - page_offset,
> +						  &pgsize);
> +		if (WARN_ON(phys == PHYS_ADDR_MAX))
> +			break;

[Severity: High]
If phys is PHYS_ADDR_MAX and triggers this early break,
raw_pages_from_domain() returns the out_pages pointer without fully
populating the requested array.

Its caller iopt_pages_fill_from_domain() ignores the populated count and
returns success. Because the underlying array in iopt_pages_fill_xarray()
may be allocated without __GFP_ZERO, the unpopulated entries contain
uninitialized heap data.

When these pages are eventually stored in the xarray and unmapped, will
unpin_user_page() be called on these uninitialized pointers and cause
kernel memory corruption or panics?

> +		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
> +			pgsize = PAGE_SIZE;
> +
> +		npages = pgsize / PAGE_SIZE;
> +		npages = min_t(unsigned long, npages,
> +			       last_index - start_index + 1);
> +		if (!npages)
> +			npages = 1;
> +
> +		for (i = 0; i < npages; i++)
> +			*(out_pages++) = pfn_to_page(PHYS_PFN(phys) + i);

[Severity: Critical]
Similar to the issue in batch_from_domain(), if the queried iova is unaligned
within a larger mapped block, pgsize will reflect the entire block's size.

Calculating npages from the total pgsize instead of the remaining length in
the block will cause this loop to extrapolate struct page pointers past the
bounds of the mapped block.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=24

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 27/32] drm/panthor: switch to iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 27/32] drm/panthor: " Guanghui Feng
@ 2026-06-03 16:16           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:16 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Critical] The `iommufd` page batching incorrectly uses `pgsize` returned by `iommu_iova_to_phys_length` as the contiguous length from an unaligned IOVA, leading to physical page boundary overshoots and unpinning of incorrect pages.

Pre-existing issues:
- [High] The fallback loop in `panthor_vm_unmap_pages` incorrectly queries `iova + unmapped_sz` instead of `iova + offset + unmapped_sz`, leading to incorrect address checks and silent page table leaks when `offset > 0`.
--

commit f97ec54057d6791e1130bddbaac127660a0164bb
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

drm/panthor: switch to iova_to_phys_length

Migrate panthor_mmu to use ops->iova_to_phys_length(ops, iova, NULL)
instead of the deprecated ops->iova_to_phys.

> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index 75d98dad7b1dd..3b635fc1f6518 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -903,7 +903,7 @@ static void panthor_vm_unmap_pages(struct panthor_vm *vm, u64 iova, u64 size)
>  			 * are out-of-sync. This is not supposed to happen, hence the
>  			 * above WARN_ON().
>  			 */
> -			while (!ops->iova_to_phys(ops, iova + unmapped_sz) &&
> +			while (ops->iova_to_phys_length(ops, iova + unmapped_sz, NULL) == PHYS_ADDR_MAX &&
>  			       unmapped_sz < pgsize * pgcount)
>  				unmapped_sz += SZ_4K;

[Severity: High]
This is a pre-existing issue, but does this fallback loop in
panthor_vm_unmap_pages() incorrectly query the page table when offset > 0?

By passing iova + unmapped_sz instead of iova + offset + unmapped_sz, it seems
this completely omits the block's offset. Since earlier blocks might have been
successfully unmapped, won't iova_to_phys_length() spuriously return
PHYS_ADDR_MAX?

This could cause the fallback loop to improperly advance unmapped_sz to its
maximum (pgsize * pgcount), blindly skipping the failing block without
unmapping any remaining mapped pages inside it, permanently leaking page table
entries and defeating the recovery logic.

I also noticed an issue in drivers/iommu/iommufd/pages.c that appears to
have the same root cause.

In batch_from_domain():

>		phys = iommu_iova_to_phys_length(domain, iova - page_offset,
>						  &pgsize);
>		if (WARN_ON(phys == PHYS_ADDR_MAX))
>			break;
>		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
>			pgsize = PAGE_SIZE;
>
>		/*
>		 * pgsize is the contiguous length from the page-aligned
>		 * iova, so npages is simply pgsize / PAGE_SIZE.
>		 */
>		npages = pgsize / PAGE_SIZE;
>		npages = min_t(unsigned long, npages,
>			       last_index - start_index + 1);

[Severity: Critical]
Does this calculation blindly use the full block size (pgsize) without
subtracting the offset within the block?

Most IOMMU page table implementations return the full block size of the
underlying page table entry, regardless of whether iova is unaligned.
If queried at an offset of 4KB into a 2MB block, iommu_iova_to_phys_length()
returns phys corresponding to the 4KB offset and pgsize = 2M.

Since iommufd allows unmapping or tracking sub-regions, start_index can
correspond to an IOVA in the middle of a huge page. The code then computes
npages = 2M / 4K = 512 and iterates from i = 0 to 511, adding
PHYS_PFN(phys) + i.

Since there are only 511 pages remaining in the block, won't this loop
overshoot the physical block boundary by 1 page, incorrectly appending the
first page of the adjacent physical block?

When these collected PFNs are later passed to batch_unpin(), the kernel will
call unpin_user_page() on a page that is not part of the mapping, decrementing
its refcount. Could this create a use-after-free vulnerability?

The identical logic flaw exists in raw_pages_from_domain():

>		phys = iommu_iova_to_phys_length(domain, iova - page_offset,
>						  &pgsize);
>		if (WARN_ON(phys == PHYS_ADDR_MAX))
>			break;
>		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
>			pgsize = PAGE_SIZE;
>
>		npages = pgsize / PAGE_SIZE;
>		npages = min_t(unsigned long, npages,
>			       last_index - start_index + 1);

[Severity: Critical]
Does this code also overestimate the remaining contiguous pages when reading
from an unaligned IOVA?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=27

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 25/32] iommufd/selftest: switch to iommu_iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 25/32] iommufd/selftest: switch to iommu_iova_to_phys_length Guanghui Feng
@ 2026-06-03 16:17           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:17 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Race condition and lockless access in IOMMU_TEST_OP_MD_CHECK_MAP leading to UAF
--

commit bb925df740c15b99ff28ec1c448ae8c5dd87cd5d
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommufd/selftest: switch to iommu_iova_to_phys_length

This commit replaces the direct domain->ops->iova_to_phys() call with
the new iommu_iova_to_phys_length() interface in the iommufd selftest.

> diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
> index af07c642a5260..d4dd399302243 100644
> --- a/drivers/iommu/iommufd/selftest.c
> +++ b/drivers/iommu/iommufd/selftest.c
[ ... ]
> @@ -1214,7 +1214,7 @@ static int iommufd_test_md_check_pa(struct iommufd_ucmd *ucmd,
>  		pfn = page_to_pfn(pages[0]);
>  		put_page(pages[0]);
>  
> -		io_phys = mock->domain.ops->iova_to_phys(&mock->domain, iova);
> +		io_phys = iommu_iova_to_phys_length(&mock->domain, iova, NULL);

[Severity: High]
This is a pre-existing issue, but does this lockless call to
iommu_iova_to_phys_length() risk a use-after-free?

In iommufd_test_md_check_pa(), the page table walk is initiated on the
mock domain without holding any IOAS read range lock (like iopt->iova_rwsem).
Since the mock domain page tables use the generic_pt implementation, its API
contract requires the caller to hold a read range lock that includes the iova.

If a concurrent IOMMU_IOAS_UNMAP ioctl frees the intermediate page table
directories while this lockless walk is ongoing, could it lead to a
kernel crash?

>  		if (io_phys !=
>  		    pfn * PAGE_SIZE + ((uintptr_t)uptr % PAGE_SIZE)) {
>  			rc = -EINVAL;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=25

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 32/32] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops
  2026-06-03 15:18         ` [PATCH v3 32/32] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
@ 2026-06-03 16:26           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:26 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The commit removes `.iova_to_phys` from `struct iommu_domain_ops` but fails to update the VeriSilicon IOMMU driver (`drivers/iommu/vsi-iommu.c`), causing a build failure and breaking IOVA translation.
--

commit 07242ed991899ab6da27ae54405dbc3801f90876
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu: remove iova_to_phys from domain_ops and io_pgtable_ops

This patch removes the deprecated .iova_to_phys interface from
struct iommu_domain_ops and struct io_pgtable_ops. It also cleans up the
fallback path in iommu_iova_to_phys_length() since all drivers are expected
to have migrated to .iova_to_phys_length.

> Now that all drivers implement iova_to_phys_length and all callers
> have migrated, remove the deprecated interfaces:

[Severity: High]
Is it possible the VeriSilicon IOMMU driver was missed during this migration?

Looking at drivers/iommu/vsi-iommu.c, it does not implement
.iova_to_phys_length and still assigns the removed field, which will cause
a build failure:

drivers/iommu/vsi-iommu.c:vsi_iommu_ops {
    ...
    .iova_to_phys       = vsi_iommu_iova_to_phys,
    ...
};

> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index 320ea13488e7b..1ad4787925cd3 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
[ ... ]
> @@ -2572,20 +2570,10 @@ phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
>  	if (mapped_length)
>  		*mapped_length = 0;
>  
> -	if (domain->ops->iova_to_phys_length)
> -		return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
> -
> -	/* Fallback to legacy iova_to_phys without length info */
> -	if (!domain->ops->iova_to_phys)
> +	if (!domain->ops->iova_to_phys_length)
>  		return PHYS_ADDR_MAX;

[Severity: High]
Because vsi-iommu.c doesn't implement .iova_to_phys_length, won't this 
cause iommu_iova_to_phys_length() to unconditionally return PHYS_ADDR_MAX
for that driver?  This would break its IOVA-to-physical translation once the
build error is fixed.

> -	phys = domain->ops->iova_to_phys(domain, iova);
> -	if (!phys)
> -		return PHYS_ADDR_MAX;
> -
> -	if (mapped_length)
> -		*mapped_length = PAGE_SIZE;
> -	return phys;
> +	return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
>  }
>  EXPORT_SYMBOL_GPL(iommu_iova_to_phys_length);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=32

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 30/32] iommu/io-pgtable-arm-v7s: remove deprecated iova_to_phys wrapper
  2026-06-03 15:18         ` [PATCH v3 30/32] iommu/io-pgtable-arm-v7s: " Guanghui Feng
@ 2026-06-03 16:31           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:31 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] arm_smmu_iova_to_phys_hard() returns 0 on translation faults instead of PHYS_ADDR_MAX, which is misinterpreted as a valid physical address.
--

commit 559ce0b481cdf56b10eb2174df50ab821772a973
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

iommu/io-pgtable-arm-v7s: remove deprecated iova_to_phys wrapper

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from ARM v7s io-pgtable, as all callers now use iova_to_phys_length.

> diff --git a/drivers/iommu/io-pgtable-arm-v7s.c b/drivers/iommu/io-pgtable-arm-v7s.c
> index 62198e31a3937..da065747e37c7 100644
> --- a/drivers/iommu/io-pgtable-arm-v7s.c
> +++ b/drivers/iommu/io-pgtable-arm-v7s.c
[ ... ]
> @@ -641,18 +641,6 @@ static size_t arm_v7s_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova
>  	return unmapped;
>  }
>  
> -static phys_addr_t arm_v7s_iova_to_phys_length(struct io_pgtable_ops *ops,
> -						unsigned long iova,
> -						size_t *mapped_length);
> -
> -static phys_addr_t arm_v7s_iova_to_phys(struct io_pgtable_ops *ops,
> -					unsigned long iova)
> -{
> -	phys_addr_t phys = arm_v7s_iova_to_phys_length(ops, iova, NULL);
> -
> -	return (phys == PHYS_ADDR_MAX) ? 0 : phys;
> -}
> -

[Severity: High]
While this patch removes the wrapper that converted the error sentinel from
PHYS_ADDR_MAX to 0, was arm_smmu_iova_to_phys_hard() in arm-smmu.c missed
during the transition?

In drivers/iommu/arm/arm-smmu/arm-smmu.c:

    phys_addr_t addr = 0;

    ret = arm_smmu_rpm_get(smmu);
    if (ret < 0)
        return 0;
    ...
    if (phys & ARM_SMMU_CB_PAR_F) {
        dev_err(dev, "translation fault!\n");
        dev_err(dev, "PAR = 0x%llx\n", phys);
        goto out;
    }

    addr = (phys & GENMASK_ULL(39, 12)) | (iova & 0xfff);
out:
    arm_smmu_rpm_put(smmu);

    return addr;

Since arm_smmu_iova_to_phys_length() directly returns the result of this hardware
translation path, it will still return 0 on RPM failures or translation faults.

Will this cause callers like iommufd and vfio, which now check for PHYS_ADDR_MAX,
to mistake the error for a valid physical address at 0?

Also, since mapped_length is not set in this path, will it break the caller's
mapping size calculations?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=30

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 29/32] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper
  2026-06-03 15:18         ` [PATCH v3 29/32] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
@ 2026-06-03 16:32           ` sashiko-bot
  0 siblings, 0 replies; 144+ messages in thread
From: sashiko-bot @ 2026-06-03 16:32 UTC (permalink / raw)
  To: Guanghui Feng; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] The S1 fallback path in `arm_smmu_iova_to_phys_length()` fails to populate the `mapped_length` parameter, leading to uninitialized variable usage in caller loops (e.g., VFIO and iommufd) and massive page refcount corruption.
--

commit a0705b9a6f111fb1cda1da70c89bd7254ec4c5db
Author: Guanghui Feng <guanghuifeng@linux.alibaba.com>

    iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper

Remove the iova_to_phys wrapper function and .iova_to_phys assignment
from ARM LPAE io-pgtable, as all callers now use iova_to_phys_length.

While reviewing the caller migrations and the new iova_to_phys_length
interface finalized by this patch, I noticed a critical issue introduced
in the underlying driver implementations and their callers.

[ ... ]

> diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c
> +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c
> @@ -1384,20 +1384,21 @@ static phys_addr_t arm_smmu_iova_to_phys_hard(struct iommu_domain *domain,
>  	return addr;
>  }
>  
> -static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain,
> -					dma_addr_t iova)
> +static phys_addr_t arm_smmu_iova_to_phys_length(struct iommu_domain *domain,
> +					dma_addr_t iova, size_t *mapped_length)
>  {
>  	struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
>  	struct io_pgtable_ops *ops = smmu_domain->pgtbl_ops;
>  
> +
>  	if (!ops)
> -		return 0;
> +		return PHYS_ADDR_MAX;
>  
>  	if (smmu_domain->smmu->features & ARM_SMMU_FEAT_TRANS_OPS &&
>  			smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
>  		return arm_smmu_iova_to_phys_hard(domain, iova);
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Critical]
Does this fallback path leave mapped_length uninitialized?

If the SMMU domain has ARM_SMMU_FEAT_TRANS_OPS and uses stage 1 translation,
we return directly from arm_smmu_iova_to_phys_hard() without populating the
mapped_length parameter. This leaves the caller's stack variable completely
uninitialized.

>  
> -	return ops->iova_to_phys(ops, iova);
> +	return ops->iova_to_phys_length(ops, iova, mapped_length);
>  }

[ ... ]

> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -1177,25 +1177,42 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
>  
>  	iommu_iotlb_gather_init(&iotlb_gather);
>  	while (pos < dma->size) {
> -		size_t unmapped, len;
> +		size_t unmapped, len, pgsize;
>  		phys_addr_t phys, next;
>  		dma_addr_t iova = dma->iova + pos;
>  
> -		phys = iommu_iova_to_phys(domain->domain, iova);
> -		if (WARN_ON(!phys)) {
> +		/* Single page table walk returns both phys and PTE size */
> +		phys = iommu_iova_to_phys_length(domain->domain, iova,
> +						  &pgsize);
> +		if (WARN_ON(phys == PHYS_ADDR_MAX)) {
>  			pos += PAGE_SIZE;
>  			continue;
>  		}
> +		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
> +			pgsize = PAGE_SIZE;
>  
>  		/*
>  		 * To optimize for fewer iommu_unmap() calls, each of which
>  		 * may require hardware cache flushing, try to find the
>  		 * largest contiguous physical memory chunk to unmap.
> +		 *
> +		 * mapped_length already accounts for contiguous entries
> +		 * from iova, then try to join following physically
> +		 * contiguous PTEs.
>  		 */
> -		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
> -			next = iommu_iova_to_phys(domain->domain, iova + len);
> +		len = min_t(size_t, pgsize, dma->size - pos);
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Critical]
If mapped_length (returned here as pgsize) remains uninitialized from the
SMMU S1 fallback, couldn't this cause massive page refcount corruption?

If pgsize contains a large uninitialized value, the loop logic would assume a
huge contiguous block of physical memory starting from the first translation
result.

When vfio_unpin_pages_remote() sequentially unpins this block, it could
corrupt the page refcounts of random host memory pages that were never
actually pinned or part of this DMA region, potentially leading to
use-after-free or host kernel panics triggered simply by untrusted
userspace.

[ ... ]

> diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
> --- a/drivers/iommu/iommufd/pages.c
> +++ b/drivers/iommu/iommufd/pages.c
> @@ -445,11 +470,36 @@ static struct page **raw_pages_from_domain(struct iommu_domain *domain,
>  	if (start_index == iopt_area_index(area))
>  		page_offset = area->page_offset;
>  	while (start_index <= last_index) {
> -		phys = iommu_iova_to_phys(domain, iova) - page_offset;
> -		*(out_pages++) = pfn_to_page(PHYS_PFN(phys));
> -		iova += PAGE_SIZE - page_offset;
> +		size_t pgsize;
> +		unsigned long npages;
> +		unsigned long i;
> +
> +		/*
> +		 * Resolve the contiguous mapped length together with the
> +		 * physical address so we can fill multiple struct page
> +		 * pointers per page table walk when the IOMMU uses large
> +		 * pages. Query at page-aligned iova so pgsize covers from
> +		 * page start.
> +		 */
> +		phys = iommu_iova_to_phys_length(domain, iova - page_offset,
> +						  &pgsize);
> +		if (WARN_ON(phys == PHYS_ADDR_MAX))
> +			break;
> +		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
> +			pgsize = PAGE_SIZE;
> +
> +		npages = pgsize / PAGE_SIZE;
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Critical]
Could this uninitialized pgsize value cause iommufd to expose arbitrary
physical pages?

Similar to the VFIO case, if pgsize is uninitialized, npages will be
calculated incorrectly. This treats multiple potentially non-contiguous
physical pages as contiguous and exposes them directly.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260603151804.1963871-1-guanghuifeng@linux.alibaba.com?part=29

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-03 15:17         ` [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
  2026-06-03 15:38           ` sashiko-bot
@ 2026-06-04  2:44           ` Baolu Lu
  2026-06-04 14:16           ` Jason Gunthorpe
  2 siblings, 0 replies; 144+ messages in thread
From: Baolu Lu @ 2026-06-04  2:44 UTC (permalink / raw)
  To: Guanghui Feng, jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

On 6/3/26 23:17, Guanghui Feng wrote:
> Add iova_to_phys_length callback to struct iommu_domain_ops alongside
> the existing iova_to_phys. The new callback returns both the physical
> address and the PTE mapping page size in a single page table walk.
> 
> Add iommu_iova_to_phys_length() core function that:
> - Checks ops->iova_to_phys_length first (preferred path)
> - Falls back to ops->iova_to_phys for unmigrated drivers
> 
> This enables callers like VFIO to efficiently traverse IOVA space
> by actual mapping granularity instead of fixed PAGE_SIZE steps.
> 
> Signed-off-by: Guanghui Feng<guanghuifeng@linux.alibaba.com>
> Acked-by: Shiqiang Zhang<shiyu.zsq@linux.alibaba.com>
> Acked-by: Simon Guo<wei.guo.simon@linux.alibaba.com>
> ---
>   drivers/iommu/iommu.c | 50 ++++++++++++++++++++++++++++++++++++++-----
>   include/linux/iommu.h |  9 ++++++++
>   2 files changed, 54 insertions(+), 5 deletions(-)

Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length
  2026-06-03 15:17         ` [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
  2026-06-03 15:39           ` sashiko-bot
@ 2026-06-04  3:30           ` Baolu Lu
  2026-06-04 14:12             ` Jason Gunthorpe
  1 sibling, 1 reply; 144+ messages in thread
From: Baolu Lu @ 2026-06-04  3:30 UTC (permalink / raw)
  To: Guanghui Feng, jgg
  Cc: adrian.larumbe, airlied, alex, alikernel-developer,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

On 6/3/26 23:17, Guanghui Feng wrote:
> Extend the Generic Page Table framework to implement iova_to_phys_length.
> Use pt_entry_oa_lg2sz() to determine PTE block size. Update
> IOMMU_PT_DOMAIN_OPS macro to set .iova_to_phys_length.
> 
> Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
> Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
> Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
> ---
>   drivers/iommu/generic_pt/iommu_pt.h | 84 +++++++++++++++++++++--------
>   include/linux/generic_pt/iommu.h    | 13 ++---
>   2 files changed, 69 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
> index dc91fb4e2f61..e362e819ef9c 100644
> --- a/drivers/iommu/generic_pt/iommu_pt.h
> +++ b/drivers/iommu/generic_pt/iommu_pt.h
> @@ -145,13 +145,21 @@ static inline unsigned int compute_best_pgsize(struct pt_state *pts,
>   				      pts->range->va, pts->range->last_va, oa);
>   }
>   
> -static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
> -					     unsigned int level,
> -					     struct pt_table_p *table,
> -					     pt_level_fn_t descend_fn)
> +struct iova_to_phys_length_data {
> +	pt_oaddr_t phys;
> +	size_t length;
> +};
> +
> +static __always_inline int __do_iova_to_phys_length(struct pt_range *range,
> +					       void *arg, unsigned int level,
> +					       struct pt_table_p *table,
> +					       pt_level_fn_t descend_fn)
>   {
>   	struct pt_state pts = pt_init(range, level, table);
> -	pt_oaddr_t *res = arg;
> +	struct iova_to_phys_length_data *data = arg;
> +	unsigned int entry_lg2sz;
> +	size_t entry_sz;
> +	pt_oaddr_t expected_oa;
>   
>   	switch (pt_load_single_entry(&pts)) {
>   	case PT_ENTRY_EMPTY:
> @@ -159,45 +167,77 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
>   	case PT_ENTRY_TABLE:
>   		return pt_descend(&pts, arg, descend_fn);
>   	case PT_ENTRY_OA:
> -		*res = pt_entry_oa_exact(&pts);
> -		return 0;
> +		break;
>   	}
> -	return -ENOENT;
> +
> +	data->phys = pt_entry_oa_exact(&pts);
> +	entry_lg2sz = pt_entry_oa_lg2sz(&pts);
> +	entry_sz = log2_to_int(entry_lg2sz);
> +
> +	/* Start with the full mapping size of the first entry */
> +	data->length = entry_sz;

data->length doesn't account for iova offset. Is this by design? We
should document this clearly somewhere.

Sashiko reported the same issue too.

[Severity: High]
Does this calculation overstate the mapped length for unaligned IOVAs?
If the IOVA is not aligned to the PTE block size, pt_entry_oa_exact()
includes the intra-page offset in data->phys. However, data->length
is unconditionally initialized to the full entry_sz rather than
entry_sz - offset. Callers relying on mapped_length might operate
on out-of-bounds memory because data->phys + data->length extends
beyond the valid mapped physical memory by the unaligned offset amount.

> +
> +	/* Accumulate subsequent physically contiguous entries */
> +	expected_oa = pt_entry_oa(&pts) + entry_sz;
> +	pts.end_index = log2_to_int(pt_num_items_lg2(&pts));
> +	pt_next_entry(&pts);
> +
> +	while (pts.index < pts.end_index) {
> +		pt_load_entry(&pts);
> +		if (pts.type != PT_ENTRY_OA)
> +			break;
> +		if (pt_entry_oa_lg2sz(&pts) != entry_lg2sz)
> +			break;
> +		if (pt_entry_oa(&pts) != expected_oa)
> +			break;
> +		data->length += entry_sz;
> +		expected_oa += entry_sz;
> +		pt_next_entry(&pts);
> +	}
> +
> +	return 0;
>   }
> -PT_MAKE_LEVELS(__iova_to_phys, __do_iova_to_phys);
> +PT_MAKE_LEVELS(__iova_to_phys_length, __do_iova_to_phys_length);
>   
>   /**
> - * iova_to_phys() - Return the output address for the given IOVA
> + * iova_to_phys_length() - Translate IOVA returning phys and contiguous length
>    * @domain: Table to query
>    * @iova: IO virtual address to query
> + * @mapped_length: Output for the total contiguous mapped length in bytes
>    *
> - * Determine the output address from the given IOVA. @iova may have any
> - * alignment, the returned physical will be adjusted with any sub page offset.
> + * Walk the IOMMU page table to translate @iova to a physical address while
> + * also returning the total contiguous physically mapped length through
> + * @mapped_length. The function accumulates consecutive page table entries that
> + * are physically contiguous, so callers can determine the full contiguous
> + * mapping extent with a single call.
>    *
>    * Context: The caller must hold a read range lock that includes @iova.
>    *
> - * Return: 0 if there is no translation for the given iova.
> + * Return: The physical address, or PHYS_ADDR_MAX if there is no translation.
>    */
> -phys_addr_t DOMAIN_NS(iova_to_phys)(struct iommu_domain *domain,
> -				    dma_addr_t iova)
> +phys_addr_t DOMAIN_NS(iova_to_phys_length)(struct iommu_domain *domain,
> +					    dma_addr_t iova,
> +					    size_t *mapped_length)
>   {
>   	struct pt_iommu *iommu_table =
>   		container_of(domain, struct pt_iommu, domain);
>   	struct pt_range range;
> -	pt_oaddr_t res;
> +	struct iova_to_phys_length_data data;
>   	int ret;
>   
>   	ret = make_range(common_from_iommu(iommu_table), &range, iova, 1);
>   	if (ret)
> -		return ret;
> +		return PHYS_ADDR_MAX;
>   
> -	ret = pt_walk_range(&range, __iova_to_phys, &res);
> -	/* PHYS_ADDR_MAX would be a better error code */
> +	ret = pt_walk_range(&range, __iova_to_phys_length, &data);
>   	if (ret)
> -		return 0;
> -	return res;
> +		return PHYS_ADDR_MAX;
> +
> +	if (mapped_length)
> +		*mapped_length = data.length;
> +	return data.phys;
>   }
> -EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys), "GENERIC_PT_IOMMU");
> +EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(iova_to_phys_length), "GENERIC_PT_IOMMU");
>   
>   struct pt_iommu_dirty_args {
>   	struct iommu_dirty_bitmap *dirty;
> diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
> index dd0edd02a48a..859b853e9dc7 100644
> --- a/include/linux/generic_pt/iommu.h
> +++ b/include/linux/generic_pt/iommu.h
> @@ -249,8 +249,9 @@ struct pt_iommu_cfg {
>   
>   /* Generate the exported function signatures from iommu_pt.h */
>   #define IOMMU_PROTOTYPES(fmt)                                                  \
> -	phys_addr_t pt_iommu_##fmt##_iova_to_phys(struct iommu_domain *domain, \
> -						  dma_addr_t iova);            \
> +	phys_addr_t pt_iommu_##fmt##_iova_to_phys_length(			\
> +		struct iommu_domain *domain, dma_addr_t iova,			\
> +		size_t *mapped_length);						\
>   	int pt_iommu_##fmt##_read_and_clear_dirty(                             \
>   		struct iommu_domain *domain, unsigned long iova, size_t size,  \
>   		unsigned long flags, struct iommu_dirty_bitmap *dirty);        \
> @@ -267,11 +268,11 @@ struct pt_iommu_cfg {
>   	IOMMU_PROTOTYPES(fmt)
>   
>   /*
> - * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for the
> - * iommu_pt
> + * A driver uses IOMMU_PT_DOMAIN_OPS to populate the iommu_domain_ops for
> + * the iommu_pt
>    */
> -#define IOMMU_PT_DOMAIN_OPS(fmt)                        \
> -	.iova_to_phys = &pt_iommu_##fmt##_iova_to_phys
> +#define IOMMU_PT_DOMAIN_OPS(fmt)					\
> +	.iova_to_phys_length = &pt_iommu_##fmt##_iova_to_phys_length
>   #define IOMMU_PT_DIRTY_OPS(fmt) \
>   	.read_and_clear_dirty = &pt_iommu_##fmt##_read_and_clear_dirty
>   

Thanks,
baolu

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length
  2026-06-04  3:30           ` Baolu Lu
@ 2026-06-04 14:12             ` Jason Gunthorpe
  0 siblings, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-04 14:12 UTC (permalink / raw)
  To: Baolu Lu
  Cc: Guanghui Feng, adrian.larumbe, airlied, alex, alikernel-developer,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

On Thu, Jun 04, 2026 at 11:30:37AM +0800, Baolu Lu wrote:
> > -static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
> > -					     unsigned int level,
> > -					     struct pt_table_p *table,
> > -					     pt_level_fn_t descend_fn)
> > +struct iova_to_phys_length_data {
> > +	pt_oaddr_t phys;
> > +	size_t length;
> > +};
> > +
> > +static __always_inline int __do_iova_to_phys_length(struct pt_range *range,
> > +					       void *arg, unsigned int level,
> > +					       struct pt_table_p *table,
> > +					       pt_level_fn_t descend_fn)
> >   {
> >   	struct pt_state pts = pt_init(range, level, table);
> > -	pt_oaddr_t *res = arg;
> > +	struct iova_to_phys_length_data *data = arg;
> > +	unsigned int entry_lg2sz;
> > +	size_t entry_sz;
> > +	pt_oaddr_t expected_oa;
> >   	switch (pt_load_single_entry(&pts)) {
> >   	case PT_ENTRY_EMPTY:
> > @@ -159,45 +167,77 @@ static __always_inline int __do_iova_to_phys(struct pt_range *range, void *arg,
> >   	case PT_ENTRY_TABLE:
> >   		return pt_descend(&pts, arg, descend_fn);
> >   	case PT_ENTRY_OA:
> > -		*res = pt_entry_oa_exact(&pts);
> > -		return 0;
> > +		break;
> >   	}
> > -	return -ENOENT;
> > +
> > +	data->phys = pt_entry_oa_exact(&pts);
> > +	entry_lg2sz = pt_entry_oa_lg2sz(&pts);
> > +	entry_sz = log2_to_int(entry_lg2sz);
> > +
> > +	/* Start with the full mapping size of the first entry */
> > +	data->length = entry_sz;
> 
> data->length doesn't account for iova offset. Is this by design? We
> should document this clearly somewhere.

That's defintaely a mistake, the phys has to be offset by the iova in all cases,
it is part of the API.

Also add kunits tests to the iommupt selftest to cover various
scenarios please.

Also this doesn't look quite right, the walk should look more like
unmap where we just walk and stop walking when we hit a physical
address discontiguity. The stop point defines the result length.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops
  2026-06-03 15:17         ` [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
  2026-06-03 15:38           ` sashiko-bot
  2026-06-04  2:44           ` Baolu Lu
@ 2026-06-04 14:16           ` Jason Gunthorpe
  2 siblings, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-04 14:16 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

On Wed, Jun 03, 2026 at 11:17:33PM +0800, Guanghui Feng wrote:
> +phys_addr_t iommu_iova_to_phys_length(struct iommu_domain *domain,
> +				       dma_addr_t iova,
> +				       size_t *mapped_length)
>  {

This should take in an ending point so the accumulation knows when to
stop, otherwise it is too hard to use.

> -	if (domain->type == IOMMU_DOMAIN_IDENTITY)
> +	phys_addr_t phys;
> +
> +	if (domain->type == IOMMU_DOMAIN_IDENTITY) {
> +		if (mapped_length)
> +			*mapped_length = PAGE_SIZE;
>  		return iova;
> +	}
>  
> -	if (domain->type == IOMMU_DOMAIN_BLOCKED)
> -		return 0;
> +	if (mapped_length)
> +		*mapped_length = 0;
> +
> +	if (domain->ops->iova_to_phys_length)
> +		return domain->ops->iova_to_phys_length(domain, iova, mapped_length);
> +
> +	/* Fallback to legacy iova_to_phys without length info */
> +	if (!domain->ops->iova_to_phys)
> +		return PHYS_ADDR_MAX;
> +
> +	phys = domain->ops->iova_to_phys(domain, iova);
> +	if (!phys)
> +		return PHYS_ADDR_MAX;

And to properly clean up the callers all the non-iommupt paths should
manually do accumulation here as well.

Basically if you call this function you get a maximal contiguous
physical range as efficiently as possible.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 24/32] iommufd: use iova_to_phys_length for efficient unmap
  2026-06-03 15:17         ` [PATCH v3 24/32] iommufd: " Guanghui Feng
  2026-06-03 16:14           ` sashiko-bot
@ 2026-06-04 14:26           ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-04 14:26 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

On Wed, Jun 03, 2026 at 11:17:56PM +0800, Guanghui Feng wrote:
> Use iommu_iova_to_phys_length() to get PTE page size in
> +		for (i = 0; i < npages; i++) {
> +			if (!batch_add_pfn(batch, PHYS_PFN(phys) + i))
> +				return;

batch_add_pfn_num()

Be mindful that the num is purposfully a u32 so that will need some
attention.

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

* Re: [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap
  2026-06-03 15:17         ` [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap Guanghui Feng
  2026-06-03 16:14           ` sashiko-bot
@ 2026-06-04 14:27           ` Jason Gunthorpe
  1 sibling, 0 replies; 144+ messages in thread
From: Jason Gunthorpe @ 2026-06-04 14:27 UTC (permalink / raw)
  To: Guanghui Feng
  Cc: adrian.larumbe, airlied, alex, alikernel-developer, baolu.lu,
	boris.brezillon, dri-devel, dwmw2, iommu, joro, kevin.tian, kvm,
	linux-arm-kernel, linux-kernel, liviu.dudau, maarten.lankhorst,
	mripard, oliver.yang, robh, robin.murphy, shiyu.zsq, steven.price,
	suravee.suthikulpanit, tzimmermann, wei.guo.simon, will, xlpang

On Wed, Jun 03, 2026 at 11:17:55PM +0800, Guanghui Feng wrote:
> Use iommu_iova_to_phys_length() to get PTE page size, allowing
> traversal by actual mapping granularity instead of PAGE_SIZE steps.
> 
> Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
> Acked-by: Shiqiang Zhang <shiyu.zsq@linux.alibaba.com>
> Acked-by: Simon Guo <wei.guo.simon@linux.alibaba.com>
> ---
>  drivers/vfio/vfio_iommu_type1.c | 27 ++++++++++++++++++++++-----
>  1 file changed, 22 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index c8151ba54de3..115d88d7003e 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -1177,25 +1177,42 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
>  
>  	iommu_iotlb_gather_init(&iotlb_gather);
>  	while (pos < dma->size) {
> -		size_t unmapped, len;
> +		size_t unmapped, len, pgsize;
>  		phys_addr_t phys, next;
>  		dma_addr_t iova = dma->iova + pos;
>  
> -		phys = iommu_iova_to_phys(domain->domain, iova);
> -		if (WARN_ON(!phys)) {
> +		/* Single page table walk returns both phys and PTE size */
> +		phys = iommu_iova_to_phys_length(domain->domain, iova,
> +						  &pgsize);
> +		if (WARN_ON(phys == PHYS_ADDR_MAX)) {
>  			pos += PAGE_SIZE;
>  			continue;
>  		}
> +		if (WARN_ON(!pgsize || pgsize < PAGE_SIZE))
> +			pgsize = PAGE_SIZE;
>  
>  		/*
>  		 * To optimize for fewer iommu_unmap() calls, each of which
>  		 * may require hardware cache flushing, try to find the
>  		 * largest contiguous physical memory chunk to unmap.
> +		 *
> +		 * mapped_length already accounts for contiguous entries
> +		 * from iova, then try to join following physically
> +		 * contiguous PTEs.
>  		 */
> -		for (len = PAGE_SIZE; pos + len < dma->size; len += PAGE_SIZE) {
> -			next = iommu_iova_to_phys(domain->domain, iova + len);
> +		len = min_t(size_t, pgsize, dma->size - pos);
> +		for (; pos + len < dma->size; ) {
> +			size_t next_pgsize;
> +
> +			next = iommu_iova_to_phys_length(domain->domain,
> +							  iova + len,
> +							  &next_pgsize);

vfio should not be calling it twice, the core code needs to give the
best length as efficiently as it can. not open coding this in callers.

I think I've said this three times now

Jason

^ permalink raw reply	[flat|nested] 144+ messages in thread

end of thread, other threads:[~2026-06-04 14:27 UTC | newest]

Thread overview: 144+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-29  7:09 [RFC PATCH] Optimize VFIO and IOMMU mapping traversal Guanghui Feng
2026-05-29  7:52 ` sashiko-bot
2026-05-29 11:51 ` Jason Gunthorpe
2026-05-31  9:36   ` [PATCH 0/9] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
2026-05-31  9:36     ` [PATCH 1/9] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
2026-05-31  9:54       ` sashiko-bot
2026-05-31 23:51       ` Jason Gunthorpe
2026-06-01  8:41         ` guanghuifeng
2026-06-01 13:43           ` Jason Gunthorpe
2026-06-01 14:14             ` guanghuifeng
2026-06-01 14:31               ` Jason Gunthorpe
2026-05-31  9:36     ` [PATCH 2/9] iommu/io-pgtable: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
2026-05-31 10:03       ` sashiko-bot
2026-05-31  9:36     ` [PATCH 3/9] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
2026-05-31 10:12       ` sashiko-bot
2026-05-31 23:54       ` Jason Gunthorpe
2026-06-01  9:23         ` guanghuifeng
     [not found]         ` <fa924b86-1ca9-4819-8330-0d5f6ede8923@linux.alibaba.com>
2026-06-01 14:32           ` Jason Gunthorpe
2026-06-02  7:20         ` guanghuifeng
2026-06-02 12:32           ` Jason Gunthorpe
2026-05-31  9:36     ` [PATCH 4/9] iommu/arm-smmu: " Guanghui Feng
2026-05-31 10:22       ` sashiko-bot
2026-05-31  9:36     ` [PATCH 5/9] iommu: apple-dart/ipmmu/mtk_iommu " Guanghui Feng
2026-05-31 10:32       ` sashiko-bot
2026-05-31  9:36     ` [PATCH 6/9] iommu: direct page-table drivers " Guanghui Feng
2026-05-31 10:47       ` sashiko-bot
2026-05-31  9:36     ` [PATCH 7/9] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
2026-05-31 11:01       ` sashiko-bot
2026-05-31 23:58       ` Jason Gunthorpe
2026-05-31  9:36     ` [PATCH 8/9] drm/gpu, iommu/io-pgtable: switch to iova_to_phys_length Guanghui Feng
2026-05-31  9:36     ` [PATCH 9/9] iommu: remove deprecated iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
2026-05-31 11:17       ` sashiko-bot
2026-06-02 10:46     ` [PATCH v2 00/30] iommu: introduce iova_to_phys_length for efficient IOVA-to-physical translation Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 01/30] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
2026-06-02 11:05         ` sashiko-bot
2026-06-03  1:08         ` Jason Gunthorpe
2026-06-02 10:46       ` [PATCH v2 02/30] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
2026-06-02 11:09         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 03/30] iommu/io-pgtable-arm-v7s: " Guanghui Feng
2026-06-02 11:02         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 04/30] iommu/io-pgtable-dart: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 05/30] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
2026-06-02 11:06         ` sashiko-bot
2026-06-03  1:11         ` Jason Gunthorpe
2026-06-02 10:46       ` [PATCH v2 06/30] iommu/arm-smmu-v3: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 07/30] iommu/arm-smmu: " Guanghui Feng
2026-06-02 11:04         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 08/30] iommu/qcom_iommu: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 09/30] iommu/apple-dart: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 10/30] iommu/ipmmu-vmsa: " Guanghui Feng
2026-06-03  1:13         ` Jason Gunthorpe
2026-06-02 10:46       ` [PATCH v2 11/30] iommu/mtk_iommu: " Guanghui Feng
2026-06-03  1:17         ` Jason Gunthorpe
2026-06-02 10:46       ` [PATCH v2 12/30] iommu/exynos: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 13/30] iommu/fsl_pamu: " Guanghui Feng
2026-06-02 11:02         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 14/30] iommu/msm: " Guanghui Feng
2026-06-02 11:04         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 15/30] iommu/mtk_v1: " Guanghui Feng
2026-06-02 11:12         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 16/30] iommu/omap: " Guanghui Feng
2026-06-02 11:09         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 17/30] iommu/rockchip: " Guanghui Feng
2026-06-02 11:03         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 18/30] iommu/s390: " Guanghui Feng
2026-06-02 11:10         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 19/30] iommu/sprd: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 20/30] iommu/sun50i: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 21/30] iommu/tegra-smmu: " Guanghui Feng
2026-06-02 11:10         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 22/30] iommu/virtio: " Guanghui Feng
2026-06-02 11:15         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 23/30] vfio/iommufd: use iova_to_phys_length for efficient unmap Guanghui Feng
2026-06-02 11:16         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 24/30] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
2026-06-02 11:14         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 25/30] drm/panthor: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 26/30] iommu/io-pgtable: selftests " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 27/30] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
2026-06-02 13:22         ` sashiko-bot
2026-06-02 10:46       ` [PATCH v2 28/30] iommu/io-pgtable-arm-v7s: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 29/30] iommu/io-pgtable-dart: " Guanghui Feng
2026-06-02 10:46       ` [PATCH v2 30/30] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
2026-06-02 11:16         ` sashiko-bot
2026-06-03 15:17       ` [PATCH v3 00/32] iommu: introduce iova_to_phys_length and remove iova_to_phys Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 01/32] iommu: introduce iova_to_phys_length in iommu_domain_ops Guanghui Feng
2026-06-03 15:38           ` sashiko-bot
2026-06-04  2:44           ` Baolu Lu
2026-06-04 14:16           ` Jason Gunthorpe
2026-06-03 15:17         ` [PATCH v3 02/32] iommu/io-pgtable-arm: introduce iova_to_phys_length in io_pgtable_ops Guanghui Feng
2026-06-03 15:35           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 03/32] iommu/io-pgtable-arm-v7s: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 04/32] iommu/io-pgtable-dart: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 05/32] iommu/generic_pt: implement iova_to_phys_length Guanghui Feng
2026-06-03 15:39           ` sashiko-bot
2026-06-04  3:30           ` Baolu Lu
2026-06-04 14:12             ` Jason Gunthorpe
2026-06-03 15:17         ` [PATCH v3 06/32] iommu/arm-smmu-v3: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 07/32] iommu/arm-smmu: " Guanghui Feng
2026-06-03 15:42           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 08/32] iommu/qcom_iommu: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 09/32] iommu/apple-dart: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 10/32] iommu/ipmmu-vmsa: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 11/32] iommu/mtk_iommu: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 12/32] iommu/exynos: " Guanghui Feng
2026-06-03 15:46           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 13/32] iommu/fsl_pamu: " Guanghui Feng
2026-06-03 15:48           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 14/32] iommu/msm: " Guanghui Feng
2026-06-03 15:51           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 15/32] iommu/mtk_v1: " Guanghui Feng
2026-06-03 15:58           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 16/32] iommu/omap: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 17/32] iommu/rockchip: " Guanghui Feng
2026-06-03 15:53           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 18/32] iommu/s390: " Guanghui Feng
2026-06-03 16:03           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 19/32] iommu/sprd: " Guanghui Feng
2026-06-03 15:57           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 20/32] iommu/sun50i: " Guanghui Feng
2026-06-03 15:17         ` [PATCH v3 21/32] iommu/tegra-smmu: " Guanghui Feng
2026-06-03 16:04           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 22/32] iommu/virtio: " Guanghui Feng
2026-06-03 16:10           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 23/32] vfio: use iova_to_phys_length for efficient unmap Guanghui Feng
2026-06-03 16:14           ` sashiko-bot
2026-06-04 14:27           ` Jason Gunthorpe
2026-06-03 15:17         ` [PATCH v3 24/32] iommufd: " Guanghui Feng
2026-06-03 16:14           ` sashiko-bot
2026-06-04 14:26           ` Jason Gunthorpe
2026-06-03 15:17         ` [PATCH v3 25/32] iommufd/selftest: switch to iommu_iova_to_phys_length Guanghui Feng
2026-06-03 16:17           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 26/32] drm/panfrost: switch to iova_to_phys_length Guanghui Feng
2026-06-03 16:13           ` sashiko-bot
2026-06-03 15:17         ` [PATCH v3 27/32] drm/panthor: " Guanghui Feng
2026-06-03 16:16           ` sashiko-bot
2026-06-03 15:18         ` [PATCH v3 28/32] iommu/io-pgtable: selftests " Guanghui Feng
2026-06-03 15:18         ` [PATCH v3 29/32] iommu/io-pgtable-arm: remove deprecated iova_to_phys wrapper Guanghui Feng
2026-06-03 16:32           ` sashiko-bot
2026-06-03 15:18         ` [PATCH v3 30/32] iommu/io-pgtable-arm-v7s: " Guanghui Feng
2026-06-03 16:31           ` sashiko-bot
2026-06-03 15:18         ` [PATCH v3 31/32] iommu/io-pgtable-dart: " Guanghui Feng
2026-06-03 15:18         ` [PATCH v3 32/32] iommu: remove iova_to_phys from domain_ops and io_pgtable_ops Guanghui Feng
2026-06-03 16:26           ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox