Commit fc9c112f authored by Youling Tang's avatar Youling Tang Committed by Huacai Chen
Browse files

LoongArch: Add ELF binary support for kexec_file



This patch creates kexec_elf_ops to load ELF binary file for
kexec_file_load() syscall.

However, for `kbuf->memsz` and `kbuf->buf_min`, special handling is
required, and the generic `kexec_elf_load()` cannot be used directly.

$ readelf -l vmlinux
...
   Type           Offset             VirtAddr           PhysAddr
                  FileSiz            MemSiz              Flags Align
   LOAD           0x0000000000010000 0x9000000000200000 0x9000000000200000
                  0x0000000002747a00 0x000000000287a0d8  RWE 0x10000
   NOTE           0x0000000000000000 0x0000000000000000 0x0000000000000000
                  0x0000000000000000 0x0000000000000000  R      0x8

phdr->p_paddr should have been a physical address, but it is a virtual
address on the current LoongArch. This will cause kexec_file to fail
when loading the kernel and need to be converted to a physical address.

From the above MemSiz, it can be seen that 0x287a0d8 isn't page aligned.
Although kexec_add_buffer() will perform PAGE_SIZE alignment on kbuf->
memsz, there is still a stampeding in the loaded kernel space and initrd
space. The initrd resolution failed when starting the second kernel.

It can be known from the link script vmlinux.lds.S that,
    BSS_SECTION(0, SZ_64K, 8)
    . = ALIGN(PECOFF_SEGMENT_ALIGN);

It needs to be aligned according to SZ_64K, so that after alignment, its
size is consistent with _kernel_asize.

The basic usage (vmlinux):

1) Load second kernel image:
 # kexec -s -l vmlinux --initrd=initrd.img --reuse-cmdline

2) Startup second kernel:
 # kexec -e

Signed-off-by: default avatarYouling Tang <tangyouling@kylinos.cn>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent 55d990f0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -639,6 +639,7 @@ config ARCH_SUPPORTS_KEXEC_FILE
config ARCH_SELECTS_KEXEC_FILE
	def_bool 64BIT
	depends on KEXEC_FILE
	select KEXEC_ELF
	select RELOCATABLE
	select HAVE_IMA_KEXEC if IMA

+1 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ struct kimage_arch {

#ifdef CONFIG_KEXEC_FILE
extern const struct kexec_file_ops kexec_efi_ops;
extern const struct kexec_file_ops kexec_elf_ops;

int arch_kimage_file_post_load_cleanup(struct kimage *image);
#define arch_kimage_file_post_load_cleanup arch_kimage_file_post_load_cleanup
+1 −1
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ obj-$(CONFIG_MAGIC_SYSRQ) += sysrq.o
obj-$(CONFIG_RELOCATABLE)	+= relocate.o

obj-$(CONFIG_KEXEC_CORE)	+= machine_kexec.o relocate_kernel.o
obj-$(CONFIG_KEXEC_FILE)	+= machine_kexec_file.o kexec_efi.o
obj-$(CONFIG_KEXEC_FILE)	+= machine_kexec_file.o kexec_efi.o kexec_elf.o
obj-$(CONFIG_CRASH_DUMP)	+= crash_dump.o

obj-$(CONFIG_UNWINDER_GUESS)	+= unwind_guess.o
+105 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Load ELF vmlinux file for the kexec_file_load syscall.
 *
 * Author: Youling Tang <tangyouling@kylinos.cn>
 * Copyright (C) 2025 KylinSoft Corporation.
 */

#define pr_fmt(fmt)	"kexec_file(ELF): " fmt

#include <linux/elf.h>
#include <linux/kexec.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/memblock.h>
#include <asm/setup.h>

#define elf_kexec_probe kexec_elf_probe

static int _elf_kexec_load(struct kimage *image,
			   struct elfhdr *ehdr, struct kexec_elf_info *elf_info,
			   struct kexec_buf *kbuf, unsigned long *text_offset)
{
	int i, ret = -1;

	/* Read in the PT_LOAD segments. */
	for (i = 0; i < ehdr->e_phnum; i++) {
		size_t size;
		const struct elf_phdr *phdr;

		phdr = &elf_info->proghdrs[i];
		if (phdr->p_type != PT_LOAD)
			continue;

		size = phdr->p_filesz;
		if (size > phdr->p_memsz)
			size = phdr->p_memsz;

		kbuf->buffer = (void *)elf_info->buffer + phdr->p_offset;
		kbuf->bufsz = size;
		kbuf->buf_align = phdr->p_align;
		*text_offset = __pa(phdr->p_paddr);
		kbuf->buf_min = *text_offset;
		kbuf->memsz = ALIGN(phdr->p_memsz, SZ_64K);
		kbuf->mem = KEXEC_BUF_MEM_UNKNOWN;
		ret = kexec_add_buffer(kbuf);
		if (ret < 0)
			break;
	}

	return ret;
}

static void *elf_kexec_load(struct kimage *image,
			    char *kernel, unsigned long kernel_len,
			    char *initrd, unsigned long initrd_len,
			    char *cmdline, unsigned long cmdline_len)
{
	int ret;
	unsigned long text_offset, kernel_segment_number;
	struct elfhdr ehdr;
	struct kexec_buf kbuf;
	struct kexec_elf_info elf_info;
	struct kexec_segment *kernel_segment;

	ret = kexec_build_elf_info(kernel, kernel_len, &ehdr, &elf_info);
	if (ret < 0)
		return ERR_PTR(ret);

	/*
	 * Load the kernel
	 * FIXME: Non-relocatable kernel rejected for kexec_file (require CONFIG_RELOCATABLE)
	 */
	kbuf.image = image;
	kbuf.buf_max = ULONG_MAX;
	kbuf.top_down = false;

	kernel_segment_number = image->nr_segments;

	ret = _elf_kexec_load(image, &ehdr, &elf_info, &kbuf, &text_offset);
	if (ret < 0)
		goto out;

	/* 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, cmdline_len);
	if (ret < 0)
		goto out;

	/* Make sure the second kernel jumps to the correct "kernel_entry". */
	image->start = kernel_segment->mem + __pa(ehdr.e_entry) - text_offset;

	kexec_dprintk("Loaded kernel at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
		      kernel_segment->mem, kbuf.bufsz, kernel_segment->memsz);

out:
	kexec_free_elf_info(&elf_info);
	return ret ? ERR_PTR(ret) : NULL;
}

const struct kexec_file_ops kexec_elf_ops = {
	.probe = elf_kexec_probe,
	.load  = elf_kexec_load,
};
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@

const struct kexec_file_ops * const kexec_file_loaders[] = {
	&kexec_efi_ops,
	&kexec_elf_ops,
	NULL
};