kexec_alloc_contig() picks the destination of the next kernel out of the default CMA area, and the next kernel then runs from there in place instead of being copied out. That is only safe if the area will not exist at the same address in the kernel being loaded. For an area whose base came from the device tree, it will. The loaded kernel parses the same description, declares the range it is executing from as a shared-dma-pool, and cma_activate_area() releases the pages holding its own image to the page allocator. Distinguish the two cases. An area placed by cma_declare_contiguous_nid() without a fixed base lands wherever this kernel found room, and the next kernel repeats that search against its own memblock state, so it cannot collide. Record the difference as CMA_FIXED_BASE and refuse to use such an area for the payload, which leaves kexec_locate_mem_hole() to fall back to the memblock hole search as it did before CMA placement existed. This is what makes the KEXEC_FILE_NO_CMA flag unnecessary on platforms that describe their CMA pools in the device tree. Tested on a AMD Versal AI Edge Series Gen 2 VEK385 Evaluation Kit whose device tree declares three CMA pools, the first of which is linux,cma-default: cma_reserved@800000000 4 GiB at 0x800000000 (default) cma_reserved@50000000000 6 GiB at 0x50000000000 cma_reserved@60000000000 4 GiB at 0x60000000000 All three have a fixed base, so kexec_alloc_contig() declines the default area and kexec_locate_mem_hole() falls through to the memblock search, which places the Image segment at 0x9c00000 size 0x1de0000. The kernel that boots from it reports its image as [0x09c00000-0x0b9e0000] and 14680064K cma-reserved, i.e. all three pools still present and none of them overlapping the running kernel. Fixes: 07d24902977e ("kexec: enable CMA based contiguous allocation") Assisted-by: Claude:claude-opus-5 Signed-off-by: Radhey Shyam Pandey --- include/linux/cma.h | 10 ++++++++++ kernel/kexec_file.c | 12 ++++++++++++ mm/cma.c | 16 ++++++++++++++++ mm/cma.h | 2 ++ 4 files changed, 40 insertions(+) diff --git a/include/linux/cma.h b/include/linux/cma.h index 8555d38a97b1..2fe66377e5d3 100644 --- a/include/linux/cma.h +++ b/include/linux/cma.h @@ -61,4 +61,14 @@ extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data) extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end); extern void cma_reserve_pages_on_error(struct cma *cma); + +#ifdef CONFIG_CMA +bool cma_has_fixed_base(const struct cma *cma); +#else +static inline bool cma_has_fixed_base(const struct cma *cma) +{ + return false; +} +#endif + #endif diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index b668ce8d68ee..10e770bcee0b 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "kexec_internal.h" #ifdef CONFIG_KEXEC_SIG @@ -691,6 +692,7 @@ static int kexec_walk_resources(struct kexec_buf *kbuf, static int kexec_alloc_contig(struct kexec_buf *kbuf) { size_t nr_pages = kbuf->memsz >> PAGE_SHIFT; + struct cma *cma; unsigned long mem; struct page *p; @@ -702,6 +704,16 @@ static int kexec_alloc_contig(struct kexec_buf *kbuf) if (kbuf->image->type == KEXEC_TYPE_CRASH) return -EPERM; + /* + * The next kernel runs from here rather than being copied out, so it + * would declare this area again from the same description and release + * the pages holding its own image to the page allocator. Only an area + * this kernel placed itself is safe to use. + */ + cma = dev_get_cma_area(NULL); + if (!cma || cma_has_fixed_base(cma)) + return -EOPNOTSUPP; + p = dma_alloc_from_contiguous(NULL, nr_pages, get_order(kbuf->buf_align), true); if (!p) return -ENOMEM; diff --git a/mm/cma.c b/mm/cma.c index a10ea37a261d..1f4d3d48a3bb 100644 --- a/mm/cma.c +++ b/mm/cma.c @@ -55,6 +55,19 @@ const char *cma_get_name(const struct cma *cma) } EXPORT_SYMBOL_GPL(cma_get_name); +/** + * cma_has_fixed_base() - was the area placed at an address it did not choose? + * @cma: Contiguous memory region + * + * Such an area is described outside the kernel, typically by a device tree + * reserved-memory node, and so lands at the same address in every kernel + * booted with that description. + */ +bool cma_has_fixed_base(const struct cma *cma) +{ + return test_bit(CMA_FIXED_BASE, &cma->flags); +} + static unsigned long cma_bitmap_aligned_mask(const struct cma *cma, unsigned int align_order) { @@ -304,6 +317,7 @@ int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size, cma->ranges[0].count = cma->count; cma->nranges = 1; cma->nid = NUMA_NO_NODE; + set_bit(CMA_FIXED_BASE, &cma->flags); *res_cma = cma; @@ -517,6 +531,8 @@ static int __init __cma_declare_contiguous_nid(phys_addr_t *basep, } (*res_cma)->nid = nid; + if (!fixed) + clear_bit(CMA_FIXED_BASE, &(*res_cma)->flags); *basep = base; return 0; diff --git a/mm/cma.h b/mm/cma.h index 68e574b0f951..1bab24033284 100644 --- a/mm/cma.h +++ b/mm/cma.h @@ -80,6 +80,8 @@ enum cma_flags { CMA_ZONES_VALID, CMA_ZONES_INVALID, CMA_ACTIVATED, + /* Base address was dictated by the caller, not picked by CMA. */ + CMA_FIXED_BASE, }; extern struct cma cma_areas[MAX_CMA_AREAS]; -- 2.43.0