Skip to content

Commit

Permalink
arm64: kexec_file: try more regions if loading segments fails
Browse files Browse the repository at this point in the history
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: Ryan O'Leary <ryanoleary@google.com>
Signed-off-by: Benjamin Gwin <bgwin@google.com>
Link: https://lore.kernel.org/r/20201103201106.2397844-1-bgwin@google.com
Signed-off-by: Will Deacon <will@kernel.org>
  • Loading branch information
Benjamin Gwin authored and Will Deacon committed Nov 5, 2020
1 parent 7ee31a3 commit 108aa50
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 11 deletions.
41 changes: 31 additions & 10 deletions arch/arm64/kernel/kexec_image.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ static void *image_load(struct kimage *image,
u64 flags, value;
bool be_image, be_kernel;
struct kexec_buf kbuf;
unsigned long text_offset;
unsigned long text_offset, kernel_segment_number;
struct kexec_segment *kernel_segment;
int ret;

Expand Down Expand Up @@ -88,11 +88,37 @@ static void *image_load(struct kimage *image,
/* Adjust kernel segment with TEXT_OFFSET */
kbuf.memsz += text_offset;

ret = kexec_add_buffer(&kbuf);
if (ret)
kernel_segment_number = image->nr_segments;

/*
* 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);
}

kernel_segment = &image->segment[image->nr_segments - 1];
kernel_segment = &image->segment[kernel_segment_number];
kernel_segment->mem += text_offset;
kernel_segment->memsz -= text_offset;
image->start = kernel_segment->mem;
Expand All @@ -101,12 +127,7 @@ static void *image_load(struct kimage *image,
kernel_segment->mem, kbuf.bufsz,
kernel_segment->memsz);

/* Load additional data */
ret = load_other_segments(image,
kernel_segment->mem, kernel_segment->memsz,
initrd, initrd_len, cmdline);

return ERR_PTR(ret);
return 0;
}

#ifdef CONFIG_KEXEC_IMAGE_VERIFY_SIG
Expand Down
9 changes: 8 additions & 1 deletion arch/arm64/kernel/machine_kexec_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
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,
unsigned long kernel_load_addr,
unsigned long kernel_size,
Expand All @@ -248,7 +253,8 @@ int load_other_segments(struct kimage *image,
{
struct kexec_buf kbuf;
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;

kbuf.image = image;
Expand Down Expand Up @@ -334,6 +340,7 @@ int load_other_segments(struct kimage *image,
return 0;

out_err:
image->nr_segments = orig_segments;
vfree(dtb);
return ret;
}

0 comments on commit 108aa50

Please sign in to comment.