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
[ Upstream commit 108aa50 ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Benjamin Gwin authored and gregkh committed Nov 18, 2020
1 parent f3c3bb3 commit 950fd0d
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 @@ -242,6 +242,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 @@ -250,7 +255,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 @@ -336,6 +342,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 950fd0d

Please sign in to comment.