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 Acked-by: Shiqiang Zhang Acked-by: Simon Guo --- 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