dma_direct_alloc_pages() may satisfy atomic allocations from the coherent
atomic pools. The pool allocation is keyed by the virtual address stored in
the gen_pool, but the pages API returns only the backing struct page.

On architectures with CONFIG_DMA_DIRECT_REMAP, atomic pool chunks are added
to the gen_pool using their remapped virtual address.
dma_direct_free_pages() reconstructs a linear-map address with
page_address(page) and passes that to dma_free_from_pool(). That address
does not match the gen_pool virtual range, so the pool lookup can fail and
the code can fall through to freeing a pool-owned page through the normal
page allocator path.

Add a page-based pool free helper that looks up the owning pool chunk by
physical address, translates it back to the gen_pool virtual address, and
frees that address to the pool. Use it from dma_direct_free_pages() while
keeping the existing virtual-address helper for coherent allocation frees.

Signed-off-by: Aneesh Kumar K.V (Arm) <[email protected]>
---
 include/linux/dma-map-ops.h |  1 +
 kernel/dma/direct.c         |  4 +--
 kernel/dma/pool.c           | 54 +++++++++++++++++++++++++++++++++++++
 3 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/include/linux/dma-map-ops.h b/include/linux/dma-map-ops.h
index 696b2c3a2305..8be059e69935 100644
--- a/include/linux/dma-map-ops.h
+++ b/include/linux/dma-map-ops.h
@@ -215,6 +215,7 @@ struct page *dma_alloc_from_pool(struct device *dev, size_t 
size,
                void **cpu_addr, gfp_t flags, unsigned long attrs,
                bool (*phys_addr_ok)(struct device *, phys_addr_t, size_t));
 bool dma_free_from_pool(struct device *dev, void *start, size_t size);
+bool dma_free_from_pool_page(struct device *dev, struct page *page, size_t 
size);
 
 int dma_direct_set_offset(struct device *dev, phys_addr_t cpu_start,
                dma_addr_t dma_start, u64 size);
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index 907c6084c616..488d53ed21f3 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -488,9 +488,9 @@ void dma_direct_free_pages(struct device *dev, size_t size,
         */
        bool mark_mem_encrypted = force_dma_unencrypted(dev);
 
-       /* If cpu_addr is not from an atomic pool, dma_free_from_pool() fails */
+       /* If page is not from an atomic pool, dma_free_from_pool_page() fails 
*/
        if (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) &&
-           dma_free_from_pool(dev, vaddr, size))
+           dma_free_from_pool_page(dev, page, size))
                return;
 
        phys = page_to_phys(page);
diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c
index e7df8d279e75..43b8101d860f 100644
--- a/kernel/dma/pool.c
+++ b/kernel/dma/pool.c
@@ -356,3 +356,57 @@ bool dma_free_from_pool(struct device *dev, void *start, 
size_t size)
 
        return false;
 }
+
+struct dma_pool_phys_match {
+       phys_addr_t phys;
+       size_t size;
+       unsigned long addr;
+       bool found;
+};
+
+static void dma_pool_find_phys(struct gen_pool *pool, struct gen_pool_chunk 
*chunk,
+                              void *data)
+{
+       struct dma_pool_phys_match *match = data;
+       phys_addr_t end = match->phys + match->size - 1;
+       phys_addr_t chunk_end;
+
+       if (match->found)
+               return;
+
+       chunk_end = chunk->phys_addr + (chunk->end_addr - chunk->start_addr);
+       if (match->phys < chunk->phys_addr || end > chunk_end)
+               return;
+
+       match->addr = chunk->start_addr + (match->phys - chunk->phys_addr);
+       match->found = true;
+}
+
+static bool dma_free_from_pool_phys(struct dma_gen_pool *dma_pool, phys_addr_t 
phys,
+                                   size_t size)
+{
+       struct dma_pool_phys_match match = {
+               .phys = phys,
+               .size = size,
+       };
+
+       gen_pool_for_each_chunk(dma_pool->pool, dma_pool_find_phys, &match);
+       if (!match.found)
+               return false;
+
+       gen_pool_free(dma_pool->pool, match.addr, size);
+       return true;
+}
+
+bool dma_free_from_pool_page(struct device *dev, struct page *page, size_t 
size)
+{
+       struct dma_gen_pool *dma_pool = NULL;
+       phys_addr_t phys = page_to_phys(page);
+
+       while ((dma_pool = dma_guess_pool(dma_pool, 0))) {
+               if (dma_free_from_pool_phys(dma_pool, phys, size))
+                       return true;
+       }
+
+       return false;
+}
-- 
2.43.0


Reply via email to