Commit 108aa503 authored by Benjamin Gwin's avatar Benjamin Gwin Committed by Will Deacon

arm64: kexec_file: try more regions if loading segments fails

It's possible that the first region picked for the new kernel will make
it impossible to fit the other segments in the required 32GB window,
especially if we have a very large initrd.

Instead of giving up, we can keep testing other regions for the kernel
until we find one that works.
Suggested-by: default avatarRyan O'Leary <ryanoleary@google.com>
Signed-off-by: default avatarBenjamin Gwin <bgwin@google.com>
Link: https://lore.kernel.org/r/20201103201106.2397844-1-bgwin@google.comSigned-off-by: default avatarWill Deacon <will@kernel.org>
parent 7ee31a3a
...@@ -43,7 +43,7 @@ static void *image_load(struct kimage *image, ...@@ -43,7 +43,7 @@ static void *image_load(struct kimage *image,
u64 flags, value; u64 flags, value;
bool be_image, be_kernel; bool be_image, be_kernel;
struct kexec_buf kbuf; struct kexec_buf kbuf;
unsigned long text_offset; unsigned long text_offset, kernel_segment_number;
struct kexec_segment *kernel_segment; struct kexec_segment *kernel_segment;
int ret; int ret;
...@@ -88,11 +88,37 @@ static void *image_load(struct kimage *image, ...@@ -88,11 +88,37 @@ static void *image_load(struct kimage *image,
/* Adjust kernel segment with TEXT_OFFSET */ /* Adjust kernel segment with TEXT_OFFSET */
kbuf.memsz += text_offset; kbuf.memsz += text_offset;
ret = kexec_add_buffer(&kbuf); kernel_segment_number = image->nr_segments;
if (ret)
/*
* The location of the kernel segment may make it impossible to satisfy
* the other segment requirements, so we try repeatedly to find a
* location that will work.
*/
while ((ret = kexec_add_buffer(&kbuf)) == 0) {
/* Try to load additional data */
kernel_segment = &image->segment[kernel_segment_number];
ret = load_other_segments(image, kernel_segment->mem,
kernel_segment->memsz, initrd,
initrd_len, cmdline);
if (!ret)
break;
/*
* We couldn't find space for the other segments; erase the
* kernel segment and try the next available hole.
*/
image->nr_segments -= 1;
kbuf.buf_min = kernel_segment->mem + kernel_segment->memsz;
kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
}
if (ret) {
pr_err("Could not find any suitable kernel location!");
return ERR_PTR(ret); return ERR_PTR(ret);
}
kernel_segment = &image->segment[image->nr_segments - 1]; kernel_segment = &image->segment[kernel_segment_number];
kernel_segment->mem += text_offset; kernel_segment->mem += text_offset;
kernel_segment->memsz -= text_offset; kernel_segment->memsz -= text_offset;
image->start = kernel_segment->mem; image->start = kernel_segment->mem;
...@@ -101,12 +127,7 @@ static void *image_load(struct kimage *image, ...@@ -101,12 +127,7 @@ static void *image_load(struct kimage *image,
kernel_segment->mem, kbuf.bufsz, kernel_segment->mem, kbuf.bufsz,
kernel_segment->memsz); kernel_segment->memsz);
/* Load additional data */ return 0;
ret = load_other_segments(image,
kernel_segment->mem, kernel_segment->memsz,
initrd, initrd_len, cmdline);
return ERR_PTR(ret);
} }
#ifdef CONFIG_KEXEC_IMAGE_VERIFY_SIG #ifdef CONFIG_KEXEC_IMAGE_VERIFY_SIG
......
...@@ -240,6 +240,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz) ...@@ -240,6 +240,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
return ret; return ret;
} }
/*
* Tries to add the initrd and DTB to the image. If it is not possible to find
* valid locations, this function will undo changes to the image and return non
* zero.
*/
int load_other_segments(struct kimage *image, int load_other_segments(struct kimage *image,
unsigned long kernel_load_addr, unsigned long kernel_load_addr,
unsigned long kernel_size, unsigned long kernel_size,
...@@ -248,7 +253,8 @@ int load_other_segments(struct kimage *image, ...@@ -248,7 +253,8 @@ int load_other_segments(struct kimage *image,
{ {
struct kexec_buf kbuf; struct kexec_buf kbuf;
void *headers, *dtb = NULL; void *headers, *dtb = NULL;
unsigned long headers_sz, initrd_load_addr = 0, dtb_len; unsigned long headers_sz, initrd_load_addr = 0, dtb_len,
orig_segments = image->nr_segments;
int ret = 0; int ret = 0;
kbuf.image = image; kbuf.image = image;
...@@ -334,6 +340,7 @@ int load_other_segments(struct kimage *image, ...@@ -334,6 +340,7 @@ int load_other_segments(struct kimage *image,
return 0; return 0;
out_err: out_err:
image->nr_segments = orig_segments;
vfree(dtb); vfree(dtb);
return ret; return ret;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment