Atomic DMA allocations cannot perform a potentially sleeping private/shared state transition at allocation time. The atomic DMA pools avoid this by transitioning their backing allocations in a sleepable context and suballocating memory that is already shared.
Architectures may require these transitions to use a shared granule size larger than PAGE_SIZE. The existing fallback loop can reduce the backing allocation below the order required by that size, producing a range that cannot be safely transitioned.
For pools marked cc_shared, round the requested pool size up to a multiple of the shared granule size and prevent allocation fallback below the order required by that size. Pass this minimum order to dma_alloc_from_contiguous() as a required alignment so that CMA cannot silently clamp it. If CMA cannot satisfy the alignment, fall back to the buddy allocator. Non-shared pools pass zero and retain the existing CMA alignment policy.
Use the cc_make_shared() and cc_make_private() helpers to transition the backing allocation and validate its address and size.
Individual atomic allocations may remain smaller than the shared granule size because the backing allocation remains owned by the pool and in the shared state.
Signed-off-by: Aneesh Kumar K.V (Arm) aneesh.kumar@kernel.org --- kernel/dma/pool.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c index 70b7f64b17ab..651d3a99c574 100644 --- a/kernel/dma/pool.c +++ b/kernel/dma/pool.c @@ -4,12 +4,12 @@ * Copyright (C) 2020 Google LLC */ #include <linux/cma.h> +#include <linux/cc_shared.h> #include <linux/debugfs.h> #include <linux/dma-map-ops.h> #include <linux/dma-direct.h> #include <linux/init.h> #include <linux/genalloc.h> -#include <linux/set_memory.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/cc_platform.h> @@ -85,6 +85,8 @@ static bool cma_in_zone(gfp_t gfp) static int atomic_pool_expand(struct dma_gen_pool *dma_pool, size_t pool_size, gfp_t gfp) { + struct cc_shared_layout layout; + unsigned int min_order = 0; unsigned int order; struct page *page = NULL; bool leak_pages = false; @@ -92,6 +94,16 @@ static int atomic_pool_expand(struct dma_gen_pool *dma_pool, size_t pool_size, int ret = -ENOMEM; pgprot_t prot __maybe_unused;
+ if (dma_pool->cc_shared) { + ret = cc_shared_calc_layout(pool_size, &layout); + if (ret) + goto out; + pool_size = layout.shared_size; + min_order = get_order(layout.alignment); + if (min_order > MAX_PAGE_ORDER) + return -E2BIG; + } + /* Cannot allocate larger than MAX_PAGE_ORDER */ order = min(get_order(pool_size), MAX_PAGE_ORDER);
@@ -99,10 +111,10 @@ static int atomic_pool_expand(struct dma_gen_pool *dma_pool, size_t pool_size, pool_size = 1 << (PAGE_SHIFT + order); if (cma_in_zone(gfp)) page = dma_alloc_from_contiguous(NULL, 1 << order, - order, 0, false); + order, min_order, false); if (!page) page = alloc_pages(gfp | __GFP_NOWARN, order); - } while (!page && order-- > 0); + } while (!page && order-- > min_order); if (!page) goto out;
@@ -126,8 +138,7 @@ static int atomic_pool_expand(struct dma_gen_pool *dma_pool, size_t pool_size, * shrink so no re-encryption occurs in dma_direct_free(). */ if (dma_pool->cc_shared) { - ret = set_memory_decrypted((unsigned long)page_to_virt(page), - 1 << order); + ret = cc_make_shared(page_to_virt(page), pool_size); if (ret) { leak_pages = true; goto remove_mapping; @@ -144,7 +155,7 @@ static int atomic_pool_expand(struct dma_gen_pool *dma_pool, size_t pool_size,
encrypt_mapping: if (dma_pool->cc_shared && - set_memory_encrypted((unsigned long)page_to_virt(page), 1 << order)) + cc_make_private(page_to_virt(page), pool_size)) leak_pages = true;
remove_mapping: