The file-based kexec system call places segments with kexec_add_buffer(),
which takes them from CMA when kexec_alloc_contig() succeeds, to avoid
copying the payload at kexec time.
When load_other_segments() cannot place the initrd or the DTB, the
segments of the attempt are dropped but their CMA pages are leaked.
Release them with kexec_rollback_segments() in the retry loop and in the
error path of load_other_segments().
The loop makes progress by advancing buf_min past a segment placed by the
walk. kexec_alloc_contig() ignores buf_min, so once the pages of a CMA
segment are released, the next attempt can get the same region again and
the retry makes no progress. Disable CMA for the retry.
A CMA segment can be anywhere in RAM, so moving buf_min to its end can
raise the walk's lower bound close to the top of memory and leave the
retry with too little space for the initrd and DTB, failing with
-EADDRNOTAVAIL. Only advance buf_min for segments found by the walk.
Tested with a non-crash kexec_file_load() on QEMU virt, 1 GiB RAM and
cma=64M, which places CMA at the top of RAM.
Before the fix:
The retry leaks the CMA pages and kexec_file_load() returns
-EADDRNOTAVAIL even though RAM below CMA is free.
After the fix:
The kernel and initrd are placed below CMA and the second
kernel boots.
Cc: Catalin Marinas <[email protected]>
Cc: Will Deacon <[email protected]>
Cc: Breno Leitao <[email protected]>
Cc: Pratyush Yadav <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Yeoreum Yun <[email protected]>
Cc: Baoquan He <[email protected]>
Cc: [email protected]
Fixes: 07d24902977e ("kexec: enable CMA based contiguous allocation")
Signed-off-by: Jinjie Ruan <[email protected]>
---
arch/arm64/kernel/kexec_image.c | 12 ++++++++----
arch/arm64/kernel/machine_kexec_file.c | 2 +-
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kernel/kexec_image.c b/arch/arm64/kernel/kexec_image.c
index a2a7c283b331..1cbb3c73c6e9 100644
--- a/arch/arm64/kernel/kexec_image.c
+++ b/arch/arm64/kernel/kexec_image.c
@@ -109,11 +109,15 @@ static void *image_load(struct kimage *image,
break;
/*
- * We couldn't find space for the other segments; erase the
- * kernel segment and try the next available hole.
+ * kexec_alloc_contig() ignores buf_min and always returns
+ * CMA memory, so disable it and keep buf_min.
*/
- image->nr_segments -= 1;
- kbuf.buf_min = kernel_segment->mem + kernel_segment->memsz;
+ if (image->segment_cma[kernel_segment_number])
+ image->no_cma = true;
+ else
+ kbuf.buf_min = kernel_segment->mem +
kernel_segment->memsz;
+
+ kexec_rollback_segments(image, kernel_segment_number);
kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
}
diff --git a/arch/arm64/kernel/machine_kexec_file.c
b/arch/arm64/kernel/machine_kexec_file.c
index 82122ded1467..8747daa23079 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -132,7 +132,7 @@ int load_other_segments(struct kimage *image, unsigned long
kernel_load_addr,
return 0;
out_err:
- image->nr_segments = orig_segments;
+ kexec_rollback_segments(image, orig_segments);
kvfree(dtb);
return ret;
}
--
2.34.1