diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index e58534f5a48d..0e6a77626aec 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -35,6 +35,22 @@ config IOMMU_IOVA_ALIGNMENT The order is expressed a power of two multiplied by the PAGE_SIZE. If unsure, leave the default value "9". + +config DMA_CONFIGURE_ALIGNMENT + bool "Configure IOVA alignment" + help + Currently the IOVA framework automatically applies IOVA + alignment to provided IOVAs based on the requested IOVA size and + the alignment configs which are enabled. + + Some clients who don't require any IOVA alignment would + benefit from the reduced fragmentation that would result from + disabling IOVA alignment. + + Enable this option to allow clients to disable IOVA alignment on + an IOMMU domain. + + If unsure, say N. endif menuconfig IOMMU_SUPPORT diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index a06832d10321..f708eb80247f 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -400,6 +400,27 @@ int iommu_dma_enable_best_fit_algo(struct device *dev) } EXPORT_SYMBOL(iommu_dma_enable_best_fit_algo); +#ifdef CONFIG_DMA_CONFIGURE_ALIGNMENT +/* + * Should be called prior to using dma-apis. + */ +int iommu_dma_configure_alignment(struct device *dev, bool force_no_align) +{ + struct iommu_domain *domain; + struct iova_domain *iovad; + + domain = iommu_get_domain_for_dev(dev); + if (!domain || !domain->iova_cookie) + return -EINVAL; + + iovad = &((struct iommu_dma_cookie *)domain->iova_cookie)->iovad; + + iovad->force_no_align = force_no_align; + return 0; +} +EXPORT_SYMBOL(iommu_dma_configure_alignment); +#endif + /** * dma_info_to_prot - Translate DMA API directions and attributes to IOMMU API * page flags. diff --git a/drivers/iommu/iova.c b/drivers/iommu/iova.c index 4eec92be81fe..e9dc3e3cdf7f 100644 --- a/drivers/iommu/iova.c +++ b/drivers/iommu/iova.c @@ -51,6 +51,9 @@ init_iova_domain(struct iova_domain *iovad, unsigned long granule, rb_link_node(&iovad->anchor.node, NULL, &iovad->rbroot.rb_node); rb_insert_color(&iovad->anchor.node, &iovad->rbroot); iovad->best_fit = false; +#ifdef CONFIG_DMA_CONFIGURE_ALIGNMENT + iovad->force_no_align = false; +#endif init_iova_rcaches(iovad); } EXPORT_SYMBOL_GPL(init_iova_domain); @@ -385,6 +388,11 @@ alloc_iova(struct iova_domain *iovad, unsigned long size, if (!new_iova) return NULL; +#ifdef CONFIG_DMA_CONFIGURE_ALIGNMENT + if (iovad->force_no_align) + size_aligned = false; +#endif + if (iovad->best_fit) { ret = __alloc_and_insert_iova_best_fit(iovad, size, limit_pfn + 1, new_iova, size_aligned); diff --git a/include/linux/dma-iommu.h b/include/linux/dma-iommu.h index 42f60dbdc1ef..794a1e11d855 100644 --- a/include/linux/dma-iommu.h +++ b/include/linux/dma-iommu.h @@ -58,6 +58,16 @@ int iommu_dma_reserve_iova(struct device *dev, dma_addr_t base, int iommu_dma_enable_best_fit_algo(struct device *dev); +#ifdef CONFIG_DMA_CONFIGURE_ALIGNMENT +int iommu_dma_configure_alignment(struct device *dev, bool force_no_align); +#else /* CONFIG_DMA_CONFIGURE_ALIGNMENT */ +static inline int iommu_dma_configure_alignment(struct device *dev, + bool force_no_align) +{ + return -ENOTSUPP; +} +#endif + #else /* CONFIG_IOMMU_DMA */ struct iommu_domain; @@ -110,5 +120,11 @@ static inline int iommu_dma_enable_best_fit_algo(struct device *dev) return -ENODEV; } +static inline int iommu_dma_configure_alignment(struct device *dev, + bool force_no_align) +{ + return -ENODEV; +} + #endif /* CONFIG_IOMMU_DMA */ #endif /* __DMA_IOMMU_H */ diff --git a/include/linux/iova.h b/include/linux/iova.h index 58713bb72a48..e2da73217721 100644 --- a/include/linux/iova.h +++ b/include/linux/iova.h @@ -96,6 +96,9 @@ struct iova_domain { atomic_t fq_timer_on; /* 1 when timer is active, 0 when not */ bool best_fit; +#ifdef CONFIG_DMA_CONFIGURE_ALIGNMENT + bool force_no_align; +#endif }; static inline unsigned long iova_size(struct iova *iova)