DMA-BUF attachments provide DMA addresses, not CPU-side pages. iommu_map_sgtable() needs those pages, so private-domain importers such as Rockchip cannot use it with strict DMABUF_DEBUG.
Add an iommu-dma helper to map a live attachment into another domain by translating through the device's default DMA domain. Roll back target mappings on error. This still recovers physical addresses internally. Only non-bounced iommu-dma mappings are supported. Other DMA backends, marked bus addresses and zero translations return -EOPNOTSUPP, as does the IOMMU_DMA=n stub. A zero lookup cannot distinguish a hole from PA0. Reject bounce slots because a second mapping cannot keep them in sync with writes to the original buffer. DMABUF_DEBUG currently drops DMA flags from its copy, so the bus-address check depends on the input retaining that mark. Assisted-by: LLM Signed-off-by: Karl Mehltretter <[email protected]> --- drivers/iommu/dma-iommu.c | 131 ++++++++++++++++++++++++++++++++++++++ include/linux/iommu.h | 12 ++++ 2 files changed, 143 insertions(+) diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 58c624513cd4..b9a1f91ec5db 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -38,6 +38,137 @@ #include "dma-iommu.h" #include "iommu-pages.h" +/** + * iommu_map_sgtable_dma - map the DMA side of an sg_table into a domain + * @domain: domain to map into + * @iova: IOVA of the first byte + * @dev: device for which @sgt is mapped + * @sgt: live DMA-API mapping for @dev, backed by non-bounced RAM + * @prot: IOMMU protection flags + * + * Translate the DMA addresses through @dev's default iommu-dma domain and + * map the backing memory into @domain without reading the CPU side of @sgt. + * Direct DMA and other DMA backends are not supported. Entries marked as + * PCI P2P bus addresses and SWIOTLB bounce buffers are also not supported. + * + * The caller must keep the source mapping, DMA backend and default domain + * unchanged until the target mapping is removed. This function may sleep. + * DMA addresses and lengths must be aligned to the smaller of the source + * and target domains' minimum page sizes. @iova and the total length must + * be aligned to the target domain's minimum page size. + * + * A zero reverse translation is unsupported: iommu_iova_to_phys() cannot + * distinguish an absent mapping from a mapping to physical address zero. + * + * Return: the number of bytes mapped, -EOPNOTSUPP for unsupported source + * mappings or source alignment, or another negative errno on error. Any + * target mappings installed by this call are removed on error. + */ +ssize_t iommu_map_sgtable_dma(struct iommu_domain *domain, unsigned long iova, + struct device *dev, struct sg_table *sgt, + int prot) +{ + struct iommu_domain *dma_domain; + size_t len = 0, mapped = 0, total = 0; + struct scatterlist *sg; + size_t granule, target_granule; + phys_addr_t start = 0; + unsigned long last_iova; + unsigned int i; + int ret; + + if (!domain->pgsize_bitmap) + return -EINVAL; + + target_granule = 1UL << __ffs(domain->pgsize_bitmap); + if (!use_dma_iommu(dev)) + return -EOPNOTSUPP; + dma_domain = iommu_get_dma_domain(dev); + if (!dma_domain || !dma_domain->pgsize_bitmap) + return -EOPNOTSUPP; + granule = min(target_granule, + 1UL << __ffs(dma_domain->pgsize_bitmap)); + + for_each_sgtable_dma_sg(sgt, sg, i) { + dma_addr_t last_dma; + size_t dma_len = sg_dma_len(sg); + + if (sg_dma_is_bus_address(sg)) + return -EOPNOTSUPP; + if (!dma_len || !IS_ALIGNED(sg_dma_address(sg), granule) || + !IS_ALIGNED(dma_len, granule)) + return -EOPNOTSUPP; + if (check_add_overflow(sg_dma_address(sg), dma_len - 1, + &last_dma) || + check_add_overflow(total, dma_len, &total)) + return -EOVERFLOW; + } + if (!total) + return 0; + if (total > SSIZE_MAX || !IS_ALIGNED(iova, target_granule) || + !IS_ALIGNED(total, target_granule)) + return -EINVAL; + if (check_add_overflow(iova, total - 1, &last_iova)) + return -EOVERFLOW; + + for_each_sgtable_dma_sg(sgt, sg, i) { + dma_addr_t dma_addr = sg_dma_address(sg); + size_t dma_len = sg_dma_len(sg); + + while (dma_len) { + phys_addr_t next; + phys_addr_t phys; + + phys = iommu_iova_to_phys(dma_domain, dma_addr); + if (!phys || swiotlb_find_pool(dev, phys)) { + ret = -EOPNOTSUPP; + goto out_err; + } + + if (len && check_add_overflow(start, len, &next)) { + ret = -EOVERFLOW; + goto out_err; + } + if (len && phys != next) { + ret = iommu_map_nosync(domain, iova + mapped, + start, len, prot, + GFP_KERNEL); + if (ret) + goto out_err; + mapped += len; + len = 0; + } + if (!len) + start = phys; + if (check_add_overflow(len, granule, &len)) { + ret = -EOVERFLOW; + goto out_err; + } + dma_addr += granule; + dma_len -= granule; + } + } + + if (len) { + ret = iommu_map_nosync(domain, iova + mapped, start, len, prot, + GFP_KERNEL); + if (ret) + goto out_err; + mapped += len; + } + + ret = iommu_sync_map(domain, iova, mapped); + if (ret) + goto out_err; + + return mapped; + +out_err: + iommu_unmap(domain, iova, mapped); + return ret; +} +EXPORT_SYMBOL_GPL(iommu_map_sgtable_dma); + struct iommu_dma_msi_page { struct list_head list; dma_addr_t iova; diff --git a/include/linux/iommu.h b/include/linux/iommu.h index ac43b8b93f14..95d90c7f953b 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -1604,8 +1604,20 @@ static inline void iommu_debugfs_setup(void) {} #endif #ifdef CONFIG_IOMMU_DMA +ssize_t iommu_map_sgtable_dma(struct iommu_domain *domain, unsigned long iova, + struct device *dev, struct sg_table *sgt, + int prot); int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base); #else /* CONFIG_IOMMU_DMA */ +static inline ssize_t iommu_map_sgtable_dma(struct iommu_domain *domain, + unsigned long iova, + struct device *dev, + struct sg_table *sgt, + int prot) +{ + return -EOPNOTSUPP; +} + static inline int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base) { return -ENODEV; -- 2.53.0
