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 <[email protected]>
Acked-by: Shiqiang Zhang <[email protected]>
Acked-by: Simon Guo <[email protected]>
---
 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

Reply via email to