[RFC PATCH v7 06/13] dma-contiguous: Accept an explicit minimum alignment

From: Aneesh Kumar K.V (Arm)

Date: Mon Sep 21 2026 - 10:51:17 EST


Confidential-computing shared allocations need CMA backing allocations
to be aligned to the architecture private/shared state-change granule
size. Passing a DMA attribute into CMA would make a generic physical
allocator interpret DMA and confidential-computing policy that has
already been resolved by its caller.

Add an explicit minimum alignment order to dma_alloc_contiguous().
Preserve the existing size-derived alignment for ordinary callers by
passing zero, and reject a request that exceeds CONFIG_CMA_ALIGNMENT.
For supported requests, use the larger of the size-derived and requested
orders, capped by the configured CMA alignment as before.

Also distinguish preferred and required alignment for
dma_alloc_from_contiguous(). Existing callers pass zero as the required
alignment and retain the current clamping behavior. Callers that require
a minimum alignment can request it explicitly and receive NULL when CMA
cannot satisfy it.

Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@xxxxxxxxxx>
---
arch/arm/mm/dma-mapping.c | 5 +++--
drivers/iommu/dma-iommu.c | 2 +-
include/linux/dma-map-ops.h | 10 ++++++----
kernel/dma/contiguous.c | 33 +++++++++++++++++++++++----------
kernel/dma/direct.c | 2 +-
kernel/dma/ops_helpers.c | 2 +-
kernel/dma/pool.c | 2 +-
kernel/kexec_file.c | 3 ++-
8 files changed, 38 insertions(+), 21 deletions(-)

diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index 7761099dde9e..9714fcd51941 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -398,7 +398,8 @@ static void *__alloc_from_contiguous(struct device *dev, size_t size,
struct page *page;
void *ptr = NULL;

- page = dma_alloc_from_contiguous(dev, count, order, gfp & __GFP_NOWARN);
+ page = dma_alloc_from_contiguous(dev, count, order, 0,
+ gfp & __GFP_NOWARN);
if (!page)
return NULL;

@@ -866,7 +867,7 @@ static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
unsigned long order = get_order(size);
struct page *page;

- page = dma_alloc_from_contiguous(dev, count, order,
+ page = dma_alloc_from_contiguous(dev, count, order, 0,
gfp & __GFP_NOWARN);
if (!page)
goto error;
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 58c624513cd4..59baf2687612 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -1627,7 +1627,7 @@ static void *iommu_dma_alloc_pages(struct device *dev, size_t size,
struct page *page = NULL;
void *cpu_addr;

- page = dma_alloc_contiguous(dev, alloc_size, gfp);
+ page = dma_alloc_contiguous(dev, alloc_size, gfp, 0);
if (!page)
page = alloc_pages_node(node, gfp, get_order(alloc_size));
if (!page)
diff --git a/include/linux/dma-map-ops.h b/include/linux/dma-map-ops.h
index 8fae2b7deb20..5fccda7e5c69 100644
--- a/include/linux/dma-map-ops.h
+++ b/include/linux/dma-map-ops.h
@@ -99,10 +99,11 @@ int __init dma_contiguous_reserve_area(phys_addr_t size, phys_addr_t base,
phys_addr_t limit, struct cma **res_cma, bool fixed);

struct page *dma_alloc_from_contiguous(struct device *dev, size_t count,
- unsigned int order, bool no_warn);
+ unsigned int order, unsigned int required_order, bool no_warn);
bool dma_release_from_contiguous(struct device *dev, struct page *pages,
int count);
-struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp);
+struct page *dma_alloc_contiguous(struct device *dev, size_t size,
+ gfp_t gfp, unsigned int align_order);
void dma_free_contiguous(struct device *dev, struct page *page, size_t size);

void dma_contiguous_early_fixup(phys_addr_t base, unsigned long size);
@@ -125,7 +126,8 @@ static inline int dma_contiguous_reserve_area(phys_addr_t size,
return -ENOSYS;
}
static inline struct page *dma_alloc_from_contiguous(struct device *dev,
- size_t count, unsigned int order, bool no_warn)
+ size_t count, unsigned int order, unsigned int required_order,
+ bool no_warn)
{
return NULL;
}
@@ -136,7 +138,7 @@ static inline bool dma_release_from_contiguous(struct device *dev,
}
/* Use fallback alloc() and free() when CONFIG_DMA_CMA=n */
static inline struct page *dma_alloc_contiguous(struct device *dev, size_t size,
- gfp_t gfp)
+ gfp_t gfp, unsigned int align_order)
{
return NULL;
}
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index 66093460584e..a3eb3299b817 100644
--- a/kernel/dma/contiguous.c
+++ b/kernel/dma/contiguous.c
@@ -357,19 +357,25 @@ int __init dma_contiguous_reserve_area(phys_addr_t size, phys_addr_t base,
* dma_alloc_from_contiguous() - allocate pages from contiguous area
* @dev: Pointer to device for which the allocation is performed.
* @count: Requested number of pages.
- * @align: Requested alignment of pages (in PAGE_SIZE order).
+ * @align: Preferred alignment of pages (in PAGE_SIZE order).
+ * @required_align: Minimum required alignment (in PAGE_SIZE order).
* @no_warn: Avoid printing message about failed allocation.
*
* This function allocates memory buffer for specified device. It uses
* device specific contiguous memory area if available or the default
* global one. Requires architecture specific dev_get_cma_area() helper
* function.
+ *
+ * The preferred alignment is capped at CONFIG_CMA_ALIGNMENT. Return NULL
+ * if the required alignment exceeds this limit. A required alignment of
+ * zero preserves the preferred-alignment clamping behavior.
*/
struct page *dma_alloc_from_contiguous(struct device *dev, size_t count,
- unsigned int align, bool no_warn)
+ unsigned int align, unsigned int required_align, bool no_warn)
{
- if (align > CONFIG_CMA_ALIGNMENT)
- align = CONFIG_CMA_ALIGNMENT;
+ if (required_align > CONFIG_CMA_ALIGNMENT)
+ return NULL;
+ align = min(max(align, required_align), CONFIG_CMA_ALIGNMENT);

return cma_alloc(dev_get_cma_area(dev), count, align, no_warn);
}
@@ -390,9 +396,14 @@ bool dma_release_from_contiguous(struct device *dev, struct page *pages,
return cma_release(dev_get_cma_area(dev), pages, count);
}

-static struct page *cma_alloc_aligned(struct cma *cma, size_t size, gfp_t gfp)
+static struct page *cma_alloc_aligned(struct cma *cma, size_t size,
+ gfp_t gfp, unsigned int align_order)
{
- unsigned int align = min(get_order(size), CONFIG_CMA_ALIGNMENT);
+ unsigned int align;
+
+ if (align_order > CONFIG_CMA_ALIGNMENT)
+ return NULL;
+ align = min(max(get_order(size), align_order), CONFIG_CMA_ALIGNMENT);

return cma_alloc(cma, size >> PAGE_SHIFT, align, gfp & __GFP_NOWARN);
}
@@ -402,6 +413,7 @@ static struct page *cma_alloc_aligned(struct cma *cma, size_t size, gfp_t gfp)
* @dev: Pointer to device for which the allocation is performed.
* @size: Requested allocation size.
* @gfp: Allocation flags.
+ * @align_order: Minimum alignment as a power-of-two page order.
*
* tries to use device specific contiguous memory area if available, or it
* tries to use per-numa cma, if the allocation fails, it will fallback to
@@ -412,7 +424,8 @@ static struct page *cma_alloc_aligned(struct cma *cma, size_t size, gfp_t gfp)
* there is no need to waste CMA pages for that kind; it also helps reduce
* fragmentations.
*/
-struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp)
+struct page *dma_alloc_contiguous(struct device *dev, size_t size,
+ gfp_t gfp, unsigned int align_order)
{
#ifdef CONFIG_DMA_NUMA_CMA
int nid = dev_to_node(dev);
@@ -422,7 +435,7 @@ struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp)
if (!gfpflags_allow_blocking(gfp))
return NULL;
if (dev->cma_area)
- return cma_alloc_aligned(dev->cma_area, size, gfp);
+ return cma_alloc_aligned(dev->cma_area, size, gfp, align_order);
if (size <= PAGE_SIZE)
return NULL;

@@ -431,7 +444,7 @@ struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp)
struct cma *cma = dma_contiguous_numa_area[nid];
struct page *page;
if (cma) {
- page = cma_alloc_aligned(cma, size, gfp);
+ page = cma_alloc_aligned(cma, size, gfp, align_order);
if (page)
return page;
}
@@ -440,7 +453,7 @@ struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp)
if (!dma_contiguous_default_area)
return NULL;

- return cma_alloc_aligned(dma_contiguous_default_area, size, gfp);
+ return cma_alloc_aligned(dma_contiguous_default_area, size, gfp, align_order);
}

/**
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index da665ca22d5c..d968a0c81e73 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -124,7 +124,7 @@ static struct page *__dma_direct_alloc_pages(struct device *dev, size_t size,
WARN_ON_ONCE(!PAGE_ALIGNED(size));

gfp |= dma_direct_optimal_gfp_mask(dev, &phys_limit);
- page = dma_alloc_contiguous(dev, size, gfp);
+ page = dma_alloc_contiguous(dev, size, gfp, 0);
if (page) {
if (dma_coherent_ok(dev, page_to_phys(page), size) &&
(allow_highmem || !PageHighMem(page)))
diff --git a/kernel/dma/ops_helpers.c b/kernel/dma/ops_helpers.c
index 6b5f9208d31c..8320cc0fada5 100644
--- a/kernel/dma/ops_helpers.c
+++ b/kernel/dma/ops_helpers.c
@@ -66,7 +66,7 @@ struct page *dma_common_alloc_pages(struct device *dev, size_t size,
struct page *page;
phys_addr_t phys;

- page = dma_alloc_contiguous(dev, size, gfp);
+ page = dma_alloc_contiguous(dev, size, gfp, 0);
if (!page)
page = alloc_pages_node(dev_to_node(dev), gfp, get_order(size));
if (!page)
diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c
index 00f422a1e896..70b7f64b17ab 100644
--- a/kernel/dma/pool.c
+++ b/kernel/dma/pool.c
@@ -99,7 +99,7 @@ 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, false);
+ order, 0, false);
if (!page)
page = alloc_pages(gfp | __GFP_NOWARN, order);
} while (!page && order-- > 0);
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 59fb9d71e9d8..2a337ce7264e 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -682,7 +682,8 @@ static int kexec_alloc_contig(struct kexec_buf *kbuf)
if (kbuf->image->type == KEXEC_TYPE_CRASH)
return -EPERM;

- p = dma_alloc_from_contiguous(NULL, nr_pages, get_order(kbuf->buf_align), true);
+ p = dma_alloc_from_contiguous(NULL, nr_pages,
+ get_order(kbuf->buf_align), 0, true);
if (!p)
return -ENOMEM;

--
2.43.0