diff mbox series

[5/5] arm: kexec_file: load zImage or uImage, initrd and dtb

Message ID 20200601142754.26139-6-l.stelmach@samsung.com (mailing list archive)
State New, archived
Headers show
Series kexec_file_load() for arm | expand

Commit Message

Łukasz Stelmach June 1, 2020, 2:27 p.m. UTC
This is kexec_file_load implementation for ARM. It loads zImage and
initrd from file descripters and resuses DTB.

Most code is derived from arm64 kexec_file_load implementation
and from kexec-tools.

Cc: AKASHI Takahiro <takahiro.akashi@linaro.org>
Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
---
 arch/arm/Kconfig                     |  15 ++
 arch/arm/include/asm/image.h         |  26 ++++
 arch/arm/include/asm/kexec.h         |  14 ++
 arch/arm/kernel/Makefile             |   5 +-
 arch/arm/kernel/kexec_uimage.c       |  80 ++++++++++
 arch/arm/kernel/kexec_zimage.c       | 199 +++++++++++++++++++++++++
 arch/arm/kernel/machine_kexec.c      |  11 +-
 arch/arm/kernel/machine_kexec_file.c | 209 +++++++++++++++++++++++++++
 8 files changed, 554 insertions(+), 5 deletions(-)
 create mode 100644 arch/arm/kernel/kexec_uimage.c
 create mode 100644 arch/arm/kernel/kexec_zimage.c
 create mode 100644 arch/arm/kernel/machine_kexec_file.c

Comments

Russell King (Oracle) June 1, 2020, 3:07 p.m. UTC | #1
On Mon, Jun 01, 2020 at 04:27:54PM +0200, Łukasz Stelmach wrote:
> This is kexec_file_load implementation for ARM. It loads zImage and
> initrd from file descripters and resuses DTB.
> 
> Most code is derived from arm64 kexec_file_load implementation
> and from kexec-tools.

Please make the uImage loader able to be configured out of the
kernel; it's a legacy image format now, and some of us just don't
use it anymore.  That way, those who wish to have it can, and
those who wish not to can have a smaller kernel.

> Cc: AKASHI Takahiro <takahiro.akashi@linaro.org>
> Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
> ---
>  arch/arm/Kconfig                     |  15 ++
>  arch/arm/include/asm/image.h         |  26 ++++
>  arch/arm/include/asm/kexec.h         |  14 ++
>  arch/arm/kernel/Makefile             |   5 +-
>  arch/arm/kernel/kexec_uimage.c       |  80 ++++++++++
>  arch/arm/kernel/kexec_zimage.c       | 199 +++++++++++++++++++++++++
>  arch/arm/kernel/machine_kexec.c      |  11 +-
>  arch/arm/kernel/machine_kexec_file.c | 209 +++++++++++++++++++++++++++
>  8 files changed, 554 insertions(+), 5 deletions(-)
>  create mode 100644 arch/arm/kernel/kexec_uimage.c
>  create mode 100644 arch/arm/kernel/kexec_zimage.c
>  create mode 100644 arch/arm/kernel/machine_kexec_file.c
> 
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index c77c93c485a0..6adb849cb304 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -1917,6 +1917,21 @@ config KEXEC
>  	  is properly shutdown, so do not be surprised if this code does not
>  	  initially work for you.
>  
> +config KEXEC_FILE
> +	bool "Kexec file based system call (EXPERIMENTAL)"
> +	depends on (!SMP || PM_SLEEP_SMP)
> +	depends on USE_OF
> +	select KEXEC_CORE
> +	select CRC32
> +	help
> +	  This is new version of kexec system call. This system call is
> +	  file based and takes file descriptors as system call argument
> +	  for kernel and initramfs as opposed to list of segments as
> +	  accepted by previous system call.
> +
> +	  The kernel to be loaded MUST support Flattened Device Tree
> +	  (selected with CONFIG_USE_OF).
> +
>  config ATAGS_PROC
>  	bool "Export atags in procfs"
>  	depends on ATAGS && KEXEC
> diff --git a/arch/arm/include/asm/image.h b/arch/arm/include/asm/image.h
> index 624438740f23..95f23837b04f 100644
> --- a/arch/arm/include/asm/image.h
> +++ b/arch/arm/include/asm/image.h
> @@ -8,8 +8,13 @@
>  			 (((x) >>  8) & 0x0000ff00) |  \
>  			 (((x) <<  8) & 0x00ff0000) |  \
>  			 (((x) << 24) & 0xff000000))
> +#define UIMAGE_MAGIC(x) (x)
>  #else
>  #define ZIMAGE_MAGIC(x) (x)
> +#define UIMAGE_MAGIC(x) ((((x) >> 24) & 0x000000ff) | \
> +			 (((x) >>  8) & 0x0000ff00) |  \
> +			 (((x) <<  8) & 0x00ff0000) |  \
> +			 (((x) << 24) & 0xff000000))
>  #endif
>  
>  #define ARM_ZIMAGE_MAGIC1 ZIMAGE_MAGIC(0x016f2818)
> @@ -17,6 +22,12 @@
>  #define ARM_ZIMAGE_MAGIC3 ZIMAGE_MAGIC(0x5a534c4b)
>  #define ARM_ZIMAGE_MAGIC4 ZIMAGE_MAGIC(0x5a534344)
>  
> +#define ARM_UIMAGE_MAGIC UIMAGE_MAGIC(0x27051956)
> +#define ARM_UIMAGE_NAME_LEN		32
> +#define ARM_UIMAGE_TYPE_KERNEL		2
> +#define ARM_UIMAGE_TYPE_KERNEL_NOLOAD	14
> +#define ARM_UIMAGE_ARCH_ARM		2
> +
>  #ifndef __ASSEMBLY__
>  
>  #include <linux/types.h>
> @@ -33,6 +44,21 @@ struct arm_zimage_header {
>  	__le32 extension_tag_offset;
>  };
>  
> +struct arm_uimage_header {
> +	__be32 magic;
> +	__be32 hdr_crc;
> +	__be32 time;
> +	__be32 size;
> +	__be32 load;
> +	__be32 entry;
> +	__be32 crc;
> +	__u8   os;
> +	__u8   arch;
> +	__u8   type;
> +	__u8   comp;
> +	__u8   name[ARM_UIMAGE_NAME_LEN];
> +};
> +
>  struct arm_zimage_tag {
>  	struct tag_header hdr;
>  	union {
> diff --git a/arch/arm/include/asm/kexec.h b/arch/arm/include/asm/kexec.h
> index 22751b5b5735..fda35afa7195 100644
> --- a/arch/arm/include/asm/kexec.h
> +++ b/arch/arm/include/asm/kexec.h
> @@ -83,6 +83,20 @@ static inline struct page *boot_pfn_to_page(unsigned long boot_pfn)
>  }
>  #define boot_pfn_to_page boot_pfn_to_page
>  
> +#ifdef CONFIG_KEXEC_FILE
> +
> +extern const struct kexec_file_ops kexec_zimage_ops;
> +extern const struct kexec_file_ops kexec_uimage_ops;
> +
> +struct kimage;
> +
> +extern int load_other_segments(struct kimage *image,
> +		unsigned long kernel_load_addr, unsigned long kernel_size,
> +		char *initrd, unsigned long initrd_len,
> +		unsigned long initrd_offset, char *cmdline);
> +
> +#endif /* CONFIG_KEXEC_FILE */
> +
>  #endif /* __ASSEMBLY__ */
>  
>  #endif /* CONFIG_KEXEC */
> diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
> index 89e5d864e923..466c683bb551 100644
> --- a/arch/arm/kernel/Makefile
> +++ b/arch/arm/kernel/Makefile
> @@ -3,6 +3,7 @@
>  # Makefile for the linux kernel.
>  #
>  
> +CFLAGS_kexec_zimage.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
>  CPPFLAGS_vmlinux.lds := -DTEXT_OFFSET=$(TEXT_OFFSET)
>  AFLAGS_head.o        := -DTEXT_OFFSET=$(TEXT_OFFSET)
>  
> @@ -56,7 +57,9 @@ obj-$(CONFIG_FUNCTION_TRACER)	+= entry-ftrace.o
>  obj-$(CONFIG_DYNAMIC_FTRACE)	+= ftrace.o insn.o patch.o
>  obj-$(CONFIG_FUNCTION_GRAPH_TRACER)	+= ftrace.o insn.o patch.o
>  obj-$(CONFIG_JUMP_LABEL)	+= jump_label.o insn.o patch.o
> -obj-$(CONFIG_KEXEC)		+= machine_kexec.o relocate_kernel.o
> +obj-$(CONFIG_KEXEC_CORE)	+= machine_kexec.o relocate_kernel.o
> +obj-$(CONFIG_KEXEC_FILE)	+= machine_kexec_file.o kexec_zimage.o	\
> +				   kexec_uimage.o
>  # Main staffs in KPROBES are in arch/arm/probes/ .
>  obj-$(CONFIG_KPROBES)		+= patch.o insn.o
>  obj-$(CONFIG_OABI_COMPAT)	+= sys_oabi-compat.o
> diff --git a/arch/arm/kernel/kexec_uimage.c b/arch/arm/kernel/kexec_uimage.c
> new file mode 100644
> index 000000000000..47033574e24e
> --- /dev/null
> +++ b/arch/arm/kernel/kexec_uimage.c
> @@ -0,0 +1,80 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Kexec uImage loader
> + *
> + * Copyright (C) 2020 Samsung Electronics
> + * Author: Łukasz Stelmach <l.stelmach@samsung.com>
> + */
> +
> +#define pr_fmt(fmt)	"kexec_file(uImage): " fmt
> +
> +#include <asm/image.h>
> +#include <linux/crc32.h>
> +#include <linux/err.h>
> +#include <linux/kexec.h>
> +
> +#define crc32_ones(crc, buf, len) \
> +	(crc32(crc ^ 0xffffffff, buf, len) ^ 0xffffffff)
> +
> +static int uimage_probe(const char *uimage_buf, unsigned long uimage_len)
> +{
> +	const struct arm_uimage_header *h =
> +		(struct arm_uimage_header *) uimage_buf;
> +	struct arm_uimage_header uhdr;
> +	unsigned long zoff = sizeof(struct arm_uimage_header);
> +	uint32_t crc;
> +
> +	if (h->magic != ARM_UIMAGE_MAGIC)
> +		return -EINVAL;
> +
> +	if (h->type != ARM_UIMAGE_TYPE_KERNEL &&
> +	    h->type != ARM_UIMAGE_TYPE_KERNEL_NOLOAD){
> +		pr_debug("Invalid image type: %d\n", h->type);
> +		return -EINVAL;
> +	}
> +
> +	if (h->arch != ARM_UIMAGE_ARCH_ARM) {
> +		pr_debug("Invalidy image arch: %d\n", h->arch);
> +		return -EINVAL;
> +	}
> +
> +	memcpy((char *)&uhdr, h, sizeof(uhdr));
> +	crc = be32_to_cpu(uhdr.hdr_crc);
> +	uhdr.hdr_crc = 0;
> +
> +	if (crc32_ones(0, (char *)&uhdr, sizeof(uhdr)) != crc) {
> +		pr_debug("Corrupt header, CRC do not match\n");
> +		return -EINVAL;
> +	}
> +
> +	crc = be32_to_cpu(uhdr.crc);
> +	if (crc32_ones(0, uimage_buf + zoff, uimage_len - zoff) != crc) {
> +		pr_debug("Corrupt zImage, CRC do not match\n");
> +		return -EINVAL;
> +	}
> +
> +	return kexec_zimage_ops.probe(uimage_buf + zoff,
> +				      uimage_len - zoff);
> +}
> +
> +static void *uimage_load(struct kimage *image,
> +				char *uimage, unsigned long uimage_len,
> +				char *initrd, unsigned long initrd_len,
> +				char *cmdline, unsigned long cmdline_len)
> +{
> +	const struct arm_uimage_header *h =
> +		(struct arm_uimage_header *) uimage;
> +	unsigned long zimage_offset = sizeof(struct arm_uimage_header);
> +
> +	pr_debug("Loading uImage");
> +	return kexec_zimage_ops.load(image,
> +				     uimage + zimage_offset,
> +				     uimage_len - zimage_offset,
> +				     initrd, initrd_len,
> +				     cmdline, cmdline_len);
> +}
> +
> +const struct kexec_file_ops kexec_uimage_ops = {
> +	.probe = uimage_probe,
> +	.load = uimage_load,
> +};
> diff --git a/arch/arm/kernel/kexec_zimage.c b/arch/arm/kernel/kexec_zimage.c
> new file mode 100644
> index 000000000000..d09795fc9072
> --- /dev/null
> +++ b/arch/arm/kernel/kexec_zimage.c
> @@ -0,0 +1,199 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Kexec zImage loader
> + *
> + * Copyright (C) 2020 Samsung Electronics
> + * Author: Łukasz Stelmach <l.stelmach@samsung.com>

Please credit me as part author of this - you have taken some of my
code from the userspace kexec tool (such as the contents of
find_extension_tag()) and copied it in here, so this is not all your
own work.

> + */
> +
> +#define pr_fmt(fmt)	"kexec_file(zImage): " fmt
> +
> +#include <asm/image.h>
> +#include <asm/unaligned.h>
> +#include <linux/err.h>
> +#include <linux/kexec.h>
> +#include <linux/memblock.h>
> +
> +#define byte_size(t)    ((t)->hdr.size << 2)
> +
> +static const void *find_extension_tag(const char *buf,
> +						       unsigned long len,
> +						       uint32_t tag_id)
> +{
> +	const struct arm_zimage_header *h = (const struct arm_zimage_header *)buf;
> +	const struct arm_zimage_tag *tag;
> +	uint32_t offset, size;
> +	uint32_t max = len - sizeof(struct tag_header);
> +
> +	if (len < sizeof(*h) ||
> +	    h->magic != ARM_ZIMAGE_MAGIC1 ||
> +	    h->magic2 != ARM_ZIMAGE_MAGIC2)
> +		return NULL;
> +
> +	for (offset = h->extension_tag_offset;
> +	     (tag = (void *)(buf + offset)) != NULL &&
> +		     offset < max &&
> +		     (size = le32_to_cpu(byte_size(tag))) != 0 &&
> +		     offset + size < len;
> +	     offset += size) {
> +		pr_debug("  offset 0x%08x tag 0x%08x size %u\n",
> +			  offset, le32_to_cpu(tag->hdr.tag), size);
> +		if (tag->hdr.tag == tag_id)
> +			return tag;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int zimage_probe(const char *kernel_buf, unsigned long kernel_len)
> +{
> +	const struct arm_zimage_header *h =
> +		(struct arm_zimage_header *)(kernel_buf);
> +
> +	if (!h || (kernel_len < sizeof(*h)))
> +		return -EINVAL;
> +
> +	if ((h->magic != ARM_ZIMAGE_MAGIC1) ||
> +	    (h->magic2 != ARM_ZIMAGE_MAGIC2))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +
> +#if defined(DEBUG)
> +#define debug_offsets() ({			\
> +	pr_debug("Image offsets:\n");		\
> +	pr_debug("  kernel 0x%08lx 0x%08lx\n", kernel_offset, kernel_len); \
> +	pr_debug("  zimage 0x%08lx 0x%08lx\n", zimage_offset, zimage_len); \
> +	pr_debug("  initrd 0x%08lx 0x%08lx\n", initrd_offset, initrd_len); \
> +})
> +#else
> +#define debug_offsets()
> +#endif
> +
> +static void *zimage_load(struct kimage *image,
> +				char *zimage, unsigned long zimage_len,
> +				char *initrd, unsigned long initrd_len,
> +				char *cmdline, unsigned long cmdline_len)
> +{
> +	struct arm_zimage_header *h;
> +	struct kexec_buf kbuf;
> +	struct kexec_segment *zimage_segment;
> +	const struct arm_zimage_tag *klsz_tag;
> +	const struct arm_zimage_tag_dc *dcsz_tag;
> +	int ret = -EINVAL;
> +
> +	unsigned long zimage_mem = 0x20000; /* malloc 64kB + stack 4 kB + some bss */
> +	unsigned long kernel_len = zimage_len * 4; /* 4:1 compression */

This has been proven wrong.

> +	unsigned long kernel_offset = memblock_start_of_DRAM() +
> +		ALIGN(TEXT_OFFSET, PAGE_SIZE);

TEXT_OFFSET is actually a property of the loaded kernel, not of the
currently running kernel.  I have a patch to add that into the zImage
tag.

> +	unsigned long zimage_offset = kernel_offset +
> +		ALIGN(kernel_len, PAGE_SIZE);
> +	unsigned long initrd_offset = zimage_offset +
> +		ALIGN(zimage_len + zimage_mem, PAGE_SIZE);

Since kernel_len is wrong, these will be wrong... please only fall
back to this if you don't find the extension tag - in other words,
declare the variables here, but don't initialise them, set them
lower down in the file if we fail to find the extension tag.

> +
> +	if (image->type == KEXEC_TYPE_CRASH) {
> +		kernel_offset += crashk_res.start;
> +		zimage_offset += crashk_res.start;
> +		initrd_offset += crashk_res.start;
> +	}
> +	debug_offsets();
> +
> +	h = (struct arm_zimage_header *)zimage;
> +
> +	klsz_tag = find_extension_tag(zimage, zimage_len, ZIMAGE_TAG_KRNL_SIZE);
> +	if (klsz_tag) {
> +		uint32_t *p = (void *)zimage +
> +			le32_to_cpu(klsz_tag->u.krnl_size.size_ptr);
> +		uint32_t edata_size = le32_to_cpu(get_unaligned(p));
> +		uint32_t bss_size = le32_to_cpu(klsz_tag->u.krnl_size.bss_size);
> +
> +		kernel_len = edata_size + bss_size;
> +
> +		pr_debug("Decompressed kernel sizes:\n");
> +		pr_debug(" text+data 0x%08lx bss 0x%08lx total 0x%08lx\n",
> +			 (unsigned long)edata_size,
> +			 (unsigned long)bss_size,
> +			 (unsigned long)kernel_len);
> +
> +		zimage_offset = kernel_offset + ALIGN(edata_size, PAGE_SIZE);
> +		initrd_offset = zimage_offset +
> +			max(ALIGN(zimage_len + 0x20000, PAGE_SIZE),
> +			    ALIGN((unsigned long)bss_size, PAGE_SIZE));
> +		debug_offsets();

This looks incorrect to me.  Please see the kexec tool - what its doing
in its corresponding section that you copied some of this code from is
carefully thought out and can't be simplified.  Ergo, I think this is
wrong.

> +	}
> +
> +	dcsz_tag = find_extension_tag(zimage, zimage_len,
> +				      ZIMAGE_TAG_DECOMP_SIZE);
> +	if (dcsz_tag) {
> +		uint32_t bss_size = le32_to_cpu(dcsz_tag->u.decomp_size.bss_size);
> +		uint32_t stack_size = le32_to_cpu(dcsz_tag->u.decomp_size.stack_size);
> +		uint32_t malloc_size = le32_to_cpu(dcsz_tag->u.decomp_size.malloc_size);
> +
> +		zimage_mem = bss_size + stack_size + malloc_size;
> +
> +		pr_debug("Decompressor memory requirements:\n");
> +		pr_debug(" bss 0x%08lx stack 0x%08lx malloc 0x%08lx total 0x%08lx\n",
> +			 (unsigned long)bss_size,
> +			 (unsigned long)stack_size,
> +			 (unsigned long)malloc_size,
> +			 zimage_mem);
> +
> +		initrd_offset = max(ALIGN(zimage_offset + zimage_len +
> +					  bss_size + stack_size +
> +					  malloc_size, PAGE_SIZE),
> +				    ALIGN(kernel_offset + kernel_len, PAGE_SIZE));
> +		debug_offsets();
> +	}
> +
> +	/*
> +	 * zImage MUST be loaded into the first 128 MiB of physical
> +	 * memory for proper memory detection. Should the uncompressed
> +	 * kernel be larger than 128 MiB, zImage relocation becomes
> +	 * unavoidable and it is best to rely on the relocation code.
> +	 */
> +	if (((zimage_offset - kernel_offset) + PAGE_SIZE + 0x8000) >= SZ_128M) {
> +		pr_debug("The kernel is too big (%ld MiB) to avoid "
> +			 "zImage relocation. Loading zimage at 0x%08lx\n",
> +			 ((zimage_offset - kernel_offset) >> 20),
> +			 kernel_offset);
> +		zimage_offset = kernel_offset;
> +	}
> +
> +	kbuf.image = image;
> +	kbuf.top_down = false;
> +
> +	kbuf.buf_min = zimage_offset;
> +	kbuf.buf_max = ULONG_MAX;
> +	kbuf.buffer = zimage;
> +	kbuf.bufsz = zimage_len;
> +	kbuf.buf_align = PAGE_SIZE;
> +
> +	kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
> +	kbuf.memsz = zimage_len;
> +
> +	ret = kexec_add_buffer(&kbuf);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	pr_debug("Loaded zImage at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
> +		 kbuf.mem, kbuf.bufsz, kbuf.memsz);
> +
> +	initrd_offset += kbuf.mem - zimage_offset;
> +	debug_offsets();
> +
> +	zimage_segment = &image->segment[image->nr_segments - 1];
> +	image->start = zimage_segment->mem;
> +
> +	ret = load_other_segments(image,
> +				  zimage_segment->mem, zimage_segment->memsz,
> +				  initrd, initrd_len, initrd_offset,
> +				  cmdline);
> +	return ERR_PTR(ret);
> +}
> +
> +const struct kexec_file_ops kexec_zimage_ops = {
> +	.probe = zimage_probe,
> +	.load = zimage_load,
> +};
> diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c
> index c10a2dfd53d1..2e4780efabb4 100644
> --- a/arch/arm/kernel/machine_kexec.c
> +++ b/arch/arm/kernel/machine_kexec.c
> @@ -93,10 +93,13 @@ int machine_kexec_prepare(struct kimage *image)
>  					       current_segment->memsz))
>  			return -EINVAL;
>  
> -		err = get_user(header, (__be32*)current_segment->buf);
> -		if (err)
> -			return err;
> -
> +		if (image->file_mode) {
> +			header = *(__be32 *)current_segment->buf;
> +		} else {
> +			err = get_user(header, (__be32 *)current_segment->buf);
> +			if (err)
> +				return err;
> +		}
>  		if (header == cpu_to_be32(OF_DT_HEADER))
>  			image->arch.kernel_r2 = current_segment->mem;
>  	}
> diff --git a/arch/arm/kernel/machine_kexec_file.c b/arch/arm/kernel/machine_kexec_file.c
> new file mode 100644
> index 000000000000..ead680f1e795
> --- /dev/null
> +++ b/arch/arm/kernel/machine_kexec_file.c
> @@ -0,0 +1,209 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * kexec_file for arm
> + *
> + * Copyright (C) 2018 Linaro Limited
> + * Copyright (C) 2020 Samsung Electronics
> + * Authors:
> + *     AKASHI Takahiro <takahiro.akashi@linaro.org>
> + *     Łukasz Stelmach <l.stelmach@samsung.com>
> + *
> + */
> +
> +#define pr_fmt(fmt) "kexec_file: " fmt
> +
> +#include <linux/kexec.h>
> +#include <linux/libfdt.h>
> +#include <linux/of_fdt.h>
> +#include <linux/random.h>
> +
> +/* relevant device tree properties */
> +#define FDT_PROP_INITRD_START	"linux,initrd-start"
> +#define FDT_PROP_INITRD_END	"linux,initrd-end"
> +#define FDT_PROP_BOOTARGS	"bootargs"
> +#define FDT_PROP_RNG_SEED	"rng-seed"
> +
> +static int setup_dtb(struct kimage *image,
> +		     unsigned long initrd_load_addr, unsigned long initrd_len,
> +		     char *cmdline, void *dtb)
> +{
> +	int off, ret;
> +
> +	ret = fdt_path_offset(dtb, "/chosen");
> +	if (ret < 0)
> +		goto out;
> +
> +	off = ret;
> +
> +	/* add bootargs */
> +	if (cmdline) {
> +		ret = fdt_setprop_string(dtb, off, FDT_PROP_BOOTARGS, cmdline);
> +		if (ret)
> +			goto out;
> +	} else {
> +		ret = fdt_delprop(dtb, off, FDT_PROP_BOOTARGS);
> +		if (ret && (ret != -FDT_ERR_NOTFOUND))
> +			goto out;
> +	}
> +
> +	/* add initrd-* */
> +	if (initrd_load_addr) {
> +		ret = fdt_setprop_u64(dtb, off, FDT_PROP_INITRD_START,
> +				      initrd_load_addr);
> +		if (ret)
> +			goto out;
> +
> +		ret = fdt_setprop_u64(dtb, off, FDT_PROP_INITRD_END,
> +				      initrd_load_addr + initrd_len);
> +		if (ret)
> +			goto out;
> +	} else {
> +		ret = fdt_delprop(dtb, off, FDT_PROP_INITRD_START);
> +		if (ret && (ret != -FDT_ERR_NOTFOUND))
> +			goto out;
> +
> +		ret = fdt_delprop(dtb, off, FDT_PROP_INITRD_END);
> +		if (ret && (ret != -FDT_ERR_NOTFOUND))
> +			goto out;
> +	}
> +
> +	/* add rng-seed */
> +	if (rng_is_initialized()) {
> +		char seed[128];
> +		get_random_bytes(seed, sizeof(seed));
> +
> +		ret = fdt_setprop(dtb, off, FDT_PROP_RNG_SEED,
> +				  seed, sizeof(seed));
> +		if (ret)
> +			goto out;
> +	} else {
> +		pr_notice("RNG is not initialised: omitting \"%s\" property\n",
> +				FDT_PROP_RNG_SEED);
> +		ret = 0;
> +	}
> +
> +out:
> +	if (ret)
> +		return (ret == -FDT_ERR_NOSPACE) ? -ENOMEM : -EINVAL;
> +
> +	return 0;
> +}
> +/*
> + * More space needed so that we can add initrd, bootargs and kaslr-seed.
> + */
> +#define DTB_EXTRA_SPACE 0x1000
> +
> +static int create_dtb(struct kimage *image,
> +		      unsigned long initrd_load_addr, unsigned long initrd_len,
> +		      char *cmdline, void **dtb)
> +{
> +	void *buf;
> +	size_t buf_size;
> +	size_t cmdline_len;
> +	int ret;
> +
> +	cmdline_len = cmdline ? strlen(cmdline) : 0;
> +	buf_size = fdt_totalsize(initial_boot_params)
> +			+ cmdline_len + DTB_EXTRA_SPACE;
> +
> +	for (;;) {
> +		buf = vmalloc(buf_size);
> +		if (!buf)
> +			return -ENOMEM;
> +
> +		/* duplicate a device tree blob */
> +		ret = fdt_open_into(initial_boot_params, buf, buf_size);
> +		if (ret)
> +			return -EINVAL;
> +
> +		ret = setup_dtb(image, initrd_load_addr, initrd_len,
> +				cmdline, buf);
> +		if (ret) {
> +			vfree(buf);
> +			if (ret == -ENOMEM) {
> +				/* unlikely, but just in case */
> +				buf_size += DTB_EXTRA_SPACE;
> +				continue;
> +			} else {
> +				return ret;
> +			}
> +		}
> +
> +		/* trim it */
> +		fdt_pack(buf);
> +		*dtb = buf;
> +
> +		return 0;
> +	}
> +}
> +
> +int load_other_segments(struct kimage *image,
> +			unsigned long zimage_load_addr,
> +			unsigned long zimage_len,
> +			char *initrd,
> +			unsigned long initrd_len,
> +			unsigned long initrd_offset,
> +			char *cmdline)
> +{
> +	struct kexec_buf kbuf;
> +	void *dtb = NULL;
> +	unsigned long initrd_load_addr = 0;
> +	unsigned long dtb_len;
> +	int ret = 0;
> +
> +	kbuf.image = image;
> +	/* not allocate anything below the kernel */
> +	kbuf.buf_min = initrd_offset;
> +	if (initrd) {
> +		kbuf.buffer = initrd;
> +		kbuf.bufsz = initrd_len;
> +		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
> +		kbuf.memsz = initrd_len;
> +		kbuf.buf_align = PAGE_SIZE;
> +		kbuf.buf_max = ULONG_MAX;
> +		kbuf.top_down = false;
> +
> +		ret = kexec_add_buffer(&kbuf);
> +		if (ret)
> +			goto out_err;
> +
> +		pr_debug("Loaded initrd at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
> +				kbuf.mem, kbuf.bufsz, kbuf.memsz);
> +
> +		initrd_load_addr = kbuf.mem;
> +		kbuf.buf_min = initrd_load_addr + kbuf.memsz;
> +	}
> +
> +	/* load dtb */
> +	ret = create_dtb(image, initrd_load_addr, initrd_len, cmdline, &dtb);
> +	if (ret) {
> +		pr_err("Preparing for new dtb failed\n");
> +		goto out_err;
> +	}
> +
> +	dtb_len = fdt_totalsize(dtb);
> +	kbuf.buffer = dtb;
> +	kbuf.bufsz = dtb_len;
> +	kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
> +	kbuf.memsz = dtb_len;
> +	kbuf.buf_align = PAGE_SIZE;
> +	kbuf.buf_max = ULONG_MAX;
> +	kbuf.top_down = false;
> +
> +	ret = kexec_add_buffer(&kbuf);
> +	if (ret)
> +		goto out_err;
> +
> +	pr_debug("Loaded dtb at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
> +		 kbuf.mem, kbuf.bufsz, kbuf.memsz);
> +	return 0;
> +out_err:
> +	vfree(dtb);
> +	return ret;
> +}
> +
> +const struct kexec_file_ops * const kexec_file_loaders[] = {
> +	&kexec_uimage_ops,
> +	&kexec_zimage_ops,
> +	NULL
> +};
> -- 
> 2.26.2
> 
>
Russell King (Oracle) June 1, 2020, 3:14 p.m. UTC | #2
On Mon, Jun 01, 2020 at 04:07:45PM +0100, Russell King - ARM Linux admin wrote:
> On Mon, Jun 01, 2020 at 04:27:54PM +0200, Łukasz Stelmach wrote:
> > diff --git a/arch/arm/kernel/kexec_zimage.c b/arch/arm/kernel/kexec_zimage.c
> > new file mode 100644
> > index 000000000000..d09795fc9072
> > --- /dev/null
> > +++ b/arch/arm/kernel/kexec_zimage.c
> > @@ -0,0 +1,199 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Kexec zImage loader
> > + *
> > + * Copyright (C) 2020 Samsung Electronics
> > + * Author: Łukasz Stelmach <l.stelmach@samsung.com>
> 
> Please credit me as part author of this - you have taken some of my
> code from the userspace kexec tool (such as the contents of
> find_extension_tag()) and copied it in here, so this is not all your
> own work.

It would also be a very good idea to indicate _where_ you copied some
of this code from.
Łukasz Stelmach June 1, 2020, 4:46 p.m. UTC | #3
It was <2020-06-01 pon 16:14>, when Russell King - ARM Linux admin wrote:
> On Mon, Jun 01, 2020 at 04:07:45PM +0100, Russell King - ARM Linux admin wrote:
>> On Mon, Jun 01, 2020 at 04:27:54PM +0200, Łukasz Stelmach wrote:
>> > diff --git a/arch/arm/kernel/kexec_zimage.c b/arch/arm/kernel/kexec_zimage.c
>> > new file mode 100644
>> > index 000000000000..d09795fc9072
>> > --- /dev/null
>> > +++ b/arch/arm/kernel/kexec_zimage.c
>> > @@ -0,0 +1,199 @@
>> > +// SPDX-License-Identifier: GPL-2.0
>> > +/*
>> > + * Kexec zImage loader
>> > + *
>> > + * Copyright (C) 2020 Samsung Electronics
>> > + * Author: Łukasz Stelmach <l.stelmach@samsung.com>
>> 
>> Please credit me as part author of this - you have taken some of my
>> code from the userspace kexec tool (such as the contents of
>> find_extension_tag()) and copied it in here, so this is not all your
>> own work.
>
> It would also be a very good idea to indicate _where_ you copied some
> of this code from.

Sure thing. Done.
Łukasz Stelmach June 1, 2020, 6:48 p.m. UTC | #4
It was <2020-06-01 pon 16:07>, when Russell King - ARM Linux admin wrote:
> On Mon, Jun 01, 2020 at 04:27:54PM +0200, Łukasz Stelmach wrote:
>> This is kexec_file_load implementation for ARM. It loads zImage and
>> initrd from file descripters and resuses DTB.
>> 
>> Most code is derived from arm64 kexec_file_load implementation
>> and from kexec-tools.
>
> Please make the uImage loader able to be configured out of the
> kernel; it's a legacy image format now, and some of us just don't
> use it anymore.  That way, those who wish to have it can, and
> those who wish not to can have a smaller kernel.

The difference in size of arch/arm/boot/Image is 160 bytes, but
OK. Done.

>> Cc: AKASHI Takahiro <takahiro.akashi@linaro.org>
>> Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
>> ---
>>  arch/arm/Kconfig                     |  15 ++
>>  arch/arm/include/asm/image.h         |  26 ++++
>>  arch/arm/include/asm/kexec.h         |  14 ++
>>  arch/arm/kernel/Makefile             |   5 +-
>>  arch/arm/kernel/kexec_uimage.c       |  80 ++++++++++
>>  arch/arm/kernel/kexec_zimage.c       | 199 +++++++++++++++++++++++++
>>  arch/arm/kernel/machine_kexec.c      |  11 +-
>>  arch/arm/kernel/machine_kexec_file.c | 209 +++++++++++++++++++++++++++
>>  8 files changed, 554 insertions(+), 5 deletions(-)
>>  create mode 100644 arch/arm/kernel/kexec_uimage.c
>>  create mode 100644 arch/arm/kernel/kexec_zimage.c
>>  create mode 100644 arch/arm/kernel/machine_kexec_file.c
>> 
>> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
>> index c77c93c485a0..6adb849cb304 100644
>> --- a/arch/arm/Kconfig
>> +++ b/arch/arm/Kconfig
>> @@ -1917,6 +1917,21 @@ config KEXEC
>>  	  is properly shutdown, so do not be surprised if this code does not
>>  	  initially work for you.
>>  
>> +config KEXEC_FILE
>> +	bool "Kexec file based system call (EXPERIMENTAL)"
>> +	depends on (!SMP || PM_SLEEP_SMP)
>> +	depends on USE_OF
>> +	select KEXEC_CORE
>> +	select CRC32
>> +	help
>> +	  This is new version of kexec system call. This system call is
>> +	  file based and takes file descriptors as system call argument
>> +	  for kernel and initramfs as opposed to list of segments as
>> +	  accepted by previous system call.
>> +
>> +	  The kernel to be loaded MUST support Flattened Device Tree
>> +	  (selected with CONFIG_USE_OF).
>> +
>>  config ATAGS_PROC
>>  	bool "Export atags in procfs"
>>  	depends on ATAGS && KEXEC
>> diff --git a/arch/arm/include/asm/image.h b/arch/arm/include/asm/image.h
>> index 624438740f23..95f23837b04f 100644
>> --- a/arch/arm/include/asm/image.h
>> +++ b/arch/arm/include/asm/image.h
>> @@ -8,8 +8,13 @@
>>  			 (((x) >>  8) & 0x0000ff00) |  \
>>  			 (((x) <<  8) & 0x00ff0000) |  \
>>  			 (((x) << 24) & 0xff000000))
>> +#define UIMAGE_MAGIC(x) (x)
>>  #else
>>  #define ZIMAGE_MAGIC(x) (x)
>> +#define UIMAGE_MAGIC(x) ((((x) >> 24) & 0x000000ff) | \
>> +			 (((x) >>  8) & 0x0000ff00) |  \
>> +			 (((x) <<  8) & 0x00ff0000) |  \
>> +			 (((x) << 24) & 0xff000000))
>>  #endif
>>  
>>  #define ARM_ZIMAGE_MAGIC1 ZIMAGE_MAGIC(0x016f2818)
>> @@ -17,6 +22,12 @@
>>  #define ARM_ZIMAGE_MAGIC3 ZIMAGE_MAGIC(0x5a534c4b)
>>  #define ARM_ZIMAGE_MAGIC4 ZIMAGE_MAGIC(0x5a534344)
>>  
>> +#define ARM_UIMAGE_MAGIC UIMAGE_MAGIC(0x27051956)
>> +#define ARM_UIMAGE_NAME_LEN		32
>> +#define ARM_UIMAGE_TYPE_KERNEL		2
>> +#define ARM_UIMAGE_TYPE_KERNEL_NOLOAD	14
>> +#define ARM_UIMAGE_ARCH_ARM		2
>> +
>>  #ifndef __ASSEMBLY__
>>  
>>  #include <linux/types.h>
>> @@ -33,6 +44,21 @@ struct arm_zimage_header {
>>  	__le32 extension_tag_offset;
>>  };
>>  
>> +struct arm_uimage_header {
>> +	__be32 magic;
>> +	__be32 hdr_crc;
>> +	__be32 time;
>> +	__be32 size;
>> +	__be32 load;
>> +	__be32 entry;
>> +	__be32 crc;
>> +	__u8   os;
>> +	__u8   arch;
>> +	__u8   type;
>> +	__u8   comp;
>> +	__u8   name[ARM_UIMAGE_NAME_LEN];
>> +};
>> +
>>  struct arm_zimage_tag {
>>  	struct tag_header hdr;
>>  	union {
>> diff --git a/arch/arm/include/asm/kexec.h b/arch/arm/include/asm/kexec.h
>> index 22751b5b5735..fda35afa7195 100644
>> --- a/arch/arm/include/asm/kexec.h
>> +++ b/arch/arm/include/asm/kexec.h
>> @@ -83,6 +83,20 @@ static inline struct page *boot_pfn_to_page(unsigned long boot_pfn)
>>  }
>>  #define boot_pfn_to_page boot_pfn_to_page
>>  
>> +#ifdef CONFIG_KEXEC_FILE
>> +
>> +extern const struct kexec_file_ops kexec_zimage_ops;
>> +extern const struct kexec_file_ops kexec_uimage_ops;
>> +
>> +struct kimage;
>> +
>> +extern int load_other_segments(struct kimage *image,
>> +		unsigned long kernel_load_addr, unsigned long kernel_size,
>> +		char *initrd, unsigned long initrd_len,
>> +		unsigned long initrd_offset, char *cmdline);
>> +
>> +#endif /* CONFIG_KEXEC_FILE */
>> +
>>  #endif /* __ASSEMBLY__ */
>>  
>>  #endif /* CONFIG_KEXEC */
>> diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
>> index 89e5d864e923..466c683bb551 100644
>> --- a/arch/arm/kernel/Makefile
>> +++ b/arch/arm/kernel/Makefile
>> @@ -3,6 +3,7 @@
>>  # Makefile for the linux kernel.
>>  #
>>  
>> +CFLAGS_kexec_zimage.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
>>  CPPFLAGS_vmlinux.lds := -DTEXT_OFFSET=$(TEXT_OFFSET)
>>  AFLAGS_head.o        := -DTEXT_OFFSET=$(TEXT_OFFSET)
>>  
>> @@ -56,7 +57,9 @@ obj-$(CONFIG_FUNCTION_TRACER)	+= entry-ftrace.o
>>  obj-$(CONFIG_DYNAMIC_FTRACE)	+= ftrace.o insn.o patch.o
>>  obj-$(CONFIG_FUNCTION_GRAPH_TRACER)	+= ftrace.o insn.o patch.o
>>  obj-$(CONFIG_JUMP_LABEL)	+= jump_label.o insn.o patch.o
>> -obj-$(CONFIG_KEXEC)		+= machine_kexec.o relocate_kernel.o
>> +obj-$(CONFIG_KEXEC_CORE)	+= machine_kexec.o relocate_kernel.o
>> +obj-$(CONFIG_KEXEC_FILE)	+= machine_kexec_file.o kexec_zimage.o	\
>> +				   kexec_uimage.o
>>  # Main staffs in KPROBES are in arch/arm/probes/ .
>>  obj-$(CONFIG_KPROBES)		+= patch.o insn.o
>>  obj-$(CONFIG_OABI_COMPAT)	+= sys_oabi-compat.o
>> diff --git a/arch/arm/kernel/kexec_uimage.c b/arch/arm/kernel/kexec_uimage.c
>> new file mode 100644
>> index 000000000000..47033574e24e
>> --- /dev/null
>> +++ b/arch/arm/kernel/kexec_uimage.c
>> @@ -0,0 +1,80 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Kexec uImage loader
>> + *
>> + * Copyright (C) 2020 Samsung Electronics
>> + * Author: Łukasz Stelmach <l.stelmach@samsung.com>
>> + */
>> +
>> +#define pr_fmt(fmt)	"kexec_file(uImage): " fmt
>> +
>> +#include <asm/image.h>
>> +#include <linux/crc32.h>
>> +#include <linux/err.h>
>> +#include <linux/kexec.h>
>> +
>> +#define crc32_ones(crc, buf, len) \
>> +	(crc32(crc ^ 0xffffffff, buf, len) ^ 0xffffffff)
>> +
>> +static int uimage_probe(const char *uimage_buf, unsigned long uimage_len)
>> +{
>> +	const struct arm_uimage_header *h =
>> +		(struct arm_uimage_header *) uimage_buf;
>> +	struct arm_uimage_header uhdr;
>> +	unsigned long zoff = sizeof(struct arm_uimage_header);
>> +	uint32_t crc;
>> +
>> +	if (h->magic != ARM_UIMAGE_MAGIC)
>> +		return -EINVAL;
>> +
>> +	if (h->type != ARM_UIMAGE_TYPE_KERNEL &&
>> +	    h->type != ARM_UIMAGE_TYPE_KERNEL_NOLOAD){
>> +		pr_debug("Invalid image type: %d\n", h->type);
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (h->arch != ARM_UIMAGE_ARCH_ARM) {
>> +		pr_debug("Invalidy image arch: %d\n", h->arch);
>> +		return -EINVAL;
>> +	}
>> +
>> +	memcpy((char *)&uhdr, h, sizeof(uhdr));
>> +	crc = be32_to_cpu(uhdr.hdr_crc);
>> +	uhdr.hdr_crc = 0;
>> +
>> +	if (crc32_ones(0, (char *)&uhdr, sizeof(uhdr)) != crc) {
>> +		pr_debug("Corrupt header, CRC do not match\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	crc = be32_to_cpu(uhdr.crc);
>> +	if (crc32_ones(0, uimage_buf + zoff, uimage_len - zoff) != crc) {
>> +		pr_debug("Corrupt zImage, CRC do not match\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return kexec_zimage_ops.probe(uimage_buf + zoff,
>> +				      uimage_len - zoff);
>> +}
>> +
>> +static void *uimage_load(struct kimage *image,
>> +				char *uimage, unsigned long uimage_len,
>> +				char *initrd, unsigned long initrd_len,
>> +				char *cmdline, unsigned long cmdline_len)
>> +{
>> +	const struct arm_uimage_header *h =
>> +		(struct arm_uimage_header *) uimage;
>> +	unsigned long zimage_offset = sizeof(struct arm_uimage_header);
>> +
>> +	pr_debug("Loading uImage");
>> +	return kexec_zimage_ops.load(image,
>> +				     uimage + zimage_offset,
>> +				     uimage_len - zimage_offset,
>> +				     initrd, initrd_len,
>> +				     cmdline, cmdline_len);
>> +}
>> +
>> +const struct kexec_file_ops kexec_uimage_ops = {
>> +	.probe = uimage_probe,
>> +	.load = uimage_load,
>> +};
>> diff --git a/arch/arm/kernel/kexec_zimage.c b/arch/arm/kernel/kexec_zimage.c
>> new file mode 100644
>> index 000000000000..d09795fc9072
>> --- /dev/null
>> +++ b/arch/arm/kernel/kexec_zimage.c
>> @@ -0,0 +1,199 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Kexec zImage loader
>> + *
>> + * Copyright (C) 2020 Samsung Electronics
>> + * Author: Łukasz Stelmach <l.stelmach@samsung.com>
>
> Please credit me as part author of this - you have taken some of my
> code from the userspace kexec tool (such as the contents of
> find_extension_tag()) and copied it in here, so this is not all your
> own work.

Done.

>> + */
>> +
>> +#define pr_fmt(fmt)	"kexec_file(zImage): " fmt
>> +
>> +#include <asm/image.h>
>> +#include <asm/unaligned.h>
>> +#include <linux/err.h>
>> +#include <linux/kexec.h>
>> +#include <linux/memblock.h>
>> +
>> +#define byte_size(t)    ((t)->hdr.size << 2)
>> +
>> +static const void *find_extension_tag(const char *buf,
>> +						       unsigned long len,
>> +						       uint32_t tag_id)
>> +{
>> +	const struct arm_zimage_header *h = (const struct arm_zimage_header *)buf;
>> +	const struct arm_zimage_tag *tag;
>> +	uint32_t offset, size;
>> +	uint32_t max = len - sizeof(struct tag_header);
>> +
>> +	if (len < sizeof(*h) ||
>> +	    h->magic != ARM_ZIMAGE_MAGIC1 ||
>> +	    h->magic2 != ARM_ZIMAGE_MAGIC2)
>> +		return NULL;
>> +
>> +	for (offset = h->extension_tag_offset;
>> +	     (tag = (void *)(buf + offset)) != NULL &&
>> +		     offset < max &&
>> +		     (size = le32_to_cpu(byte_size(tag))) != 0 &&
>> +		     offset + size < len;
>> +	     offset += size) {
>> +		pr_debug("  offset 0x%08x tag 0x%08x size %u\n",
>> +			  offset, le32_to_cpu(tag->hdr.tag), size);
>> +		if (tag->hdr.tag == tag_id)
>> +			return tag;
>> +	}
>> +
>> +	return NULL;
>> +}
>> +
>> +static int zimage_probe(const char *kernel_buf, unsigned long kernel_len)
>> +{
>> +	const struct arm_zimage_header *h =
>> +		(struct arm_zimage_header *)(kernel_buf);
>> +
>> +	if (!h || (kernel_len < sizeof(*h)))
>> +		return -EINVAL;
>> +
>> +	if ((h->magic != ARM_ZIMAGE_MAGIC1) ||
>> +	    (h->magic2 != ARM_ZIMAGE_MAGIC2))
>> +		return -EINVAL;
>> +
>> +	return 0;
>> +}
>> +
>> +
>> +#if defined(DEBUG)
>> +#define debug_offsets() ({			\
>> +	pr_debug("Image offsets:\n");		\
>> +	pr_debug("  kernel 0x%08lx 0x%08lx\n", kernel_offset, kernel_len); \
>> +	pr_debug("  zimage 0x%08lx 0x%08lx\n", zimage_offset, zimage_len); \
>> +	pr_debug("  initrd 0x%08lx 0x%08lx\n", initrd_offset, initrd_len); \
>> +})
>> +#else
>> +#define debug_offsets()
>> +#endif
>> +
>> +static void *zimage_load(struct kimage *image,
>> +				char *zimage, unsigned long zimage_len,
>> +				char *initrd, unsigned long initrd_len,
>> +				char *cmdline, unsigned long cmdline_len)
>> +{
>> +	struct arm_zimage_header *h;
>> +	struct kexec_buf kbuf;
>> +	struct kexec_segment *zimage_segment;
>> +	const struct arm_zimage_tag *klsz_tag;
>> +	const struct arm_zimage_tag_dc *dcsz_tag;
>> +	int ret = -EINVAL;
>> +
>> +	unsigned long zimage_mem = 0x20000; /* malloc 64kB + stack 4 kB + some bss */
>> +	unsigned long kernel_len = zimage_len * 4; /* 4:1 compression */
>
> This has been proven wrong.

5:1? The comment and the code in kexec-tools are inconsisten in this
regard. The comment says 4:1, but the code multiplies by 5. Your
recommendation? This value is used as a fallback anyway in a very
unlikely case, when tags are missing (see below).

>> +	unsigned long kernel_offset = memblock_start_of_DRAM() +
>> +		ALIGN(TEXT_OFFSET, PAGE_SIZE);
>
> TEXT_OFFSET is actually a property of the loaded kernel, not of the
> currently running kernel.

Indeed.

> I have a patch to add that into the zImage tag.

I am afraid, I haven't seen it before. Where can I find it?

>> +	unsigned long zimage_offset = kernel_offset +
>> +		ALIGN(kernel_len, PAGE_SIZE);
>> +	unsigned long initrd_offset = zimage_offset +
>> +		ALIGN(zimage_len + zimage_mem, PAGE_SIZE);
>
> Since kernel_len is wrong, these will be wrong... please only fall
> back to this if you don't find the extension tag - in other words,
> declare the variables here, but don't initialise them, set them
> lower down in the file if we fail to find the extension tag.

That is how it works now. This is the structure I have taken from
kexec-tools: initialize and change if additional information is
available. If the tags are not available use these values as a fallback.

>> +
>> +	if (image->type == KEXEC_TYPE_CRASH) {
>> +		kernel_offset += crashk_res.start;
>> +		zimage_offset += crashk_res.start;
>> +		initrd_offset += crashk_res.start;
>> +	}
>> +	debug_offsets();
>> +
>> +	h = (struct arm_zimage_header *)zimage;
>> +
>> +	klsz_tag = find_extension_tag(zimage, zimage_len, ZIMAGE_TAG_KRNL_SIZE);
>> +	if (klsz_tag) {
>> +		uint32_t *p = (void *)zimage +
>> +			le32_to_cpu(klsz_tag->u.krnl_size.size_ptr);
>> +		uint32_t edata_size = le32_to_cpu(get_unaligned(p));
>> +		uint32_t bss_size = le32_to_cpu(klsz_tag->u.krnl_size.bss_size);
>> +
>> +		kernel_len = edata_size + bss_size;
>> +
>> +		pr_debug("Decompressed kernel sizes:\n");
>> +		pr_debug(" text+data 0x%08lx bss 0x%08lx total 0x%08lx\n",
>> +			 (unsigned long)edata_size,
>> +			 (unsigned long)bss_size,
>> +			 (unsigned long)kernel_len);
>> +
>> +		zimage_offset = kernel_offset + ALIGN(edata_size, PAGE_SIZE);
>> +		initrd_offset = zimage_offset +
>> +			max(ALIGN(zimage_len + 0x20000, PAGE_SIZE),
>> +			    ALIGN((unsigned long)bss_size, PAGE_SIZE));
>> +		debug_offsets();
>
> This looks incorrect to me.  Please see the kexec tool - what its doing
> in its corresponding section that you copied some of this code from is
> carefully thought out and can't be simplified.  Ergo, I think this is
> wrong.

As I explained in <dleftjwo4qsqqf.fsf%l.stelmach@samsung.com> my
approach is slightly different. I am trying to avoid copying zImage by
putting it hight enough in the first place.


Kind regards,
diff mbox series

Patch

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index c77c93c485a0..6adb849cb304 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1917,6 +1917,21 @@  config KEXEC
 	  is properly shutdown, so do not be surprised if this code does not
 	  initially work for you.
 
+config KEXEC_FILE
+	bool "Kexec file based system call (EXPERIMENTAL)"
+	depends on (!SMP || PM_SLEEP_SMP)
+	depends on USE_OF
+	select KEXEC_CORE
+	select CRC32
+	help
+	  This is new version of kexec system call. This system call is
+	  file based and takes file descriptors as system call argument
+	  for kernel and initramfs as opposed to list of segments as
+	  accepted by previous system call.
+
+	  The kernel to be loaded MUST support Flattened Device Tree
+	  (selected with CONFIG_USE_OF).
+
 config ATAGS_PROC
 	bool "Export atags in procfs"
 	depends on ATAGS && KEXEC
diff --git a/arch/arm/include/asm/image.h b/arch/arm/include/asm/image.h
index 624438740f23..95f23837b04f 100644
--- a/arch/arm/include/asm/image.h
+++ b/arch/arm/include/asm/image.h
@@ -8,8 +8,13 @@ 
 			 (((x) >>  8) & 0x0000ff00) |  \
 			 (((x) <<  8) & 0x00ff0000) |  \
 			 (((x) << 24) & 0xff000000))
+#define UIMAGE_MAGIC(x) (x)
 #else
 #define ZIMAGE_MAGIC(x) (x)
+#define UIMAGE_MAGIC(x) ((((x) >> 24) & 0x000000ff) | \
+			 (((x) >>  8) & 0x0000ff00) |  \
+			 (((x) <<  8) & 0x00ff0000) |  \
+			 (((x) << 24) & 0xff000000))
 #endif
 
 #define ARM_ZIMAGE_MAGIC1 ZIMAGE_MAGIC(0x016f2818)
@@ -17,6 +22,12 @@ 
 #define ARM_ZIMAGE_MAGIC3 ZIMAGE_MAGIC(0x5a534c4b)
 #define ARM_ZIMAGE_MAGIC4 ZIMAGE_MAGIC(0x5a534344)
 
+#define ARM_UIMAGE_MAGIC UIMAGE_MAGIC(0x27051956)
+#define ARM_UIMAGE_NAME_LEN		32
+#define ARM_UIMAGE_TYPE_KERNEL		2
+#define ARM_UIMAGE_TYPE_KERNEL_NOLOAD	14
+#define ARM_UIMAGE_ARCH_ARM		2
+
 #ifndef __ASSEMBLY__
 
 #include <linux/types.h>
@@ -33,6 +44,21 @@  struct arm_zimage_header {
 	__le32 extension_tag_offset;
 };
 
+struct arm_uimage_header {
+	__be32 magic;
+	__be32 hdr_crc;
+	__be32 time;
+	__be32 size;
+	__be32 load;
+	__be32 entry;
+	__be32 crc;
+	__u8   os;
+	__u8   arch;
+	__u8   type;
+	__u8   comp;
+	__u8   name[ARM_UIMAGE_NAME_LEN];
+};
+
 struct arm_zimage_tag {
 	struct tag_header hdr;
 	union {
diff --git a/arch/arm/include/asm/kexec.h b/arch/arm/include/asm/kexec.h
index 22751b5b5735..fda35afa7195 100644
--- a/arch/arm/include/asm/kexec.h
+++ b/arch/arm/include/asm/kexec.h
@@ -83,6 +83,20 @@  static inline struct page *boot_pfn_to_page(unsigned long boot_pfn)
 }
 #define boot_pfn_to_page boot_pfn_to_page
 
+#ifdef CONFIG_KEXEC_FILE
+
+extern const struct kexec_file_ops kexec_zimage_ops;
+extern const struct kexec_file_ops kexec_uimage_ops;
+
+struct kimage;
+
+extern int load_other_segments(struct kimage *image,
+		unsigned long kernel_load_addr, unsigned long kernel_size,
+		char *initrd, unsigned long initrd_len,
+		unsigned long initrd_offset, char *cmdline);
+
+#endif /* CONFIG_KEXEC_FILE */
+
 #endif /* __ASSEMBLY__ */
 
 #endif /* CONFIG_KEXEC */
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
index 89e5d864e923..466c683bb551 100644
--- a/arch/arm/kernel/Makefile
+++ b/arch/arm/kernel/Makefile
@@ -3,6 +3,7 @@ 
 # Makefile for the linux kernel.
 #
 
+CFLAGS_kexec_zimage.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
 CPPFLAGS_vmlinux.lds := -DTEXT_OFFSET=$(TEXT_OFFSET)
 AFLAGS_head.o        := -DTEXT_OFFSET=$(TEXT_OFFSET)
 
@@ -56,7 +57,9 @@  obj-$(CONFIG_FUNCTION_TRACER)	+= entry-ftrace.o
 obj-$(CONFIG_DYNAMIC_FTRACE)	+= ftrace.o insn.o patch.o
 obj-$(CONFIG_FUNCTION_GRAPH_TRACER)	+= ftrace.o insn.o patch.o
 obj-$(CONFIG_JUMP_LABEL)	+= jump_label.o insn.o patch.o
-obj-$(CONFIG_KEXEC)		+= machine_kexec.o relocate_kernel.o
+obj-$(CONFIG_KEXEC_CORE)	+= machine_kexec.o relocate_kernel.o
+obj-$(CONFIG_KEXEC_FILE)	+= machine_kexec_file.o kexec_zimage.o	\
+				   kexec_uimage.o
 # Main staffs in KPROBES are in arch/arm/probes/ .
 obj-$(CONFIG_KPROBES)		+= patch.o insn.o
 obj-$(CONFIG_OABI_COMPAT)	+= sys_oabi-compat.o
diff --git a/arch/arm/kernel/kexec_uimage.c b/arch/arm/kernel/kexec_uimage.c
new file mode 100644
index 000000000000..47033574e24e
--- /dev/null
+++ b/arch/arm/kernel/kexec_uimage.c
@@ -0,0 +1,80 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kexec uImage loader
+ *
+ * Copyright (C) 2020 Samsung Electronics
+ * Author: Łukasz Stelmach <l.stelmach@samsung.com>
+ */
+
+#define pr_fmt(fmt)	"kexec_file(uImage): " fmt
+
+#include <asm/image.h>
+#include <linux/crc32.h>
+#include <linux/err.h>
+#include <linux/kexec.h>
+
+#define crc32_ones(crc, buf, len) \
+	(crc32(crc ^ 0xffffffff, buf, len) ^ 0xffffffff)
+
+static int uimage_probe(const char *uimage_buf, unsigned long uimage_len)
+{
+	const struct arm_uimage_header *h =
+		(struct arm_uimage_header *) uimage_buf;
+	struct arm_uimage_header uhdr;
+	unsigned long zoff = sizeof(struct arm_uimage_header);
+	uint32_t crc;
+
+	if (h->magic != ARM_UIMAGE_MAGIC)
+		return -EINVAL;
+
+	if (h->type != ARM_UIMAGE_TYPE_KERNEL &&
+	    h->type != ARM_UIMAGE_TYPE_KERNEL_NOLOAD){
+		pr_debug("Invalid image type: %d\n", h->type);
+		return -EINVAL;
+	}
+
+	if (h->arch != ARM_UIMAGE_ARCH_ARM) {
+		pr_debug("Invalidy image arch: %d\n", h->arch);
+		return -EINVAL;
+	}
+
+	memcpy((char *)&uhdr, h, sizeof(uhdr));
+	crc = be32_to_cpu(uhdr.hdr_crc);
+	uhdr.hdr_crc = 0;
+
+	if (crc32_ones(0, (char *)&uhdr, sizeof(uhdr)) != crc) {
+		pr_debug("Corrupt header, CRC do not match\n");
+		return -EINVAL;
+	}
+
+	crc = be32_to_cpu(uhdr.crc);
+	if (crc32_ones(0, uimage_buf + zoff, uimage_len - zoff) != crc) {
+		pr_debug("Corrupt zImage, CRC do not match\n");
+		return -EINVAL;
+	}
+
+	return kexec_zimage_ops.probe(uimage_buf + zoff,
+				      uimage_len - zoff);
+}
+
+static void *uimage_load(struct kimage *image,
+				char *uimage, unsigned long uimage_len,
+				char *initrd, unsigned long initrd_len,
+				char *cmdline, unsigned long cmdline_len)
+{
+	const struct arm_uimage_header *h =
+		(struct arm_uimage_header *) uimage;
+	unsigned long zimage_offset = sizeof(struct arm_uimage_header);
+
+	pr_debug("Loading uImage");
+	return kexec_zimage_ops.load(image,
+				     uimage + zimage_offset,
+				     uimage_len - zimage_offset,
+				     initrd, initrd_len,
+				     cmdline, cmdline_len);
+}
+
+const struct kexec_file_ops kexec_uimage_ops = {
+	.probe = uimage_probe,
+	.load = uimage_load,
+};
diff --git a/arch/arm/kernel/kexec_zimage.c b/arch/arm/kernel/kexec_zimage.c
new file mode 100644
index 000000000000..d09795fc9072
--- /dev/null
+++ b/arch/arm/kernel/kexec_zimage.c
@@ -0,0 +1,199 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kexec zImage loader
+ *
+ * Copyright (C) 2020 Samsung Electronics
+ * Author: Łukasz Stelmach <l.stelmach@samsung.com>
+ */
+
+#define pr_fmt(fmt)	"kexec_file(zImage): " fmt
+
+#include <asm/image.h>
+#include <asm/unaligned.h>
+#include <linux/err.h>
+#include <linux/kexec.h>
+#include <linux/memblock.h>
+
+#define byte_size(t)    ((t)->hdr.size << 2)
+
+static const void *find_extension_tag(const char *buf,
+						       unsigned long len,
+						       uint32_t tag_id)
+{
+	const struct arm_zimage_header *h = (const struct arm_zimage_header *)buf;
+	const struct arm_zimage_tag *tag;
+	uint32_t offset, size;
+	uint32_t max = len - sizeof(struct tag_header);
+
+	if (len < sizeof(*h) ||
+	    h->magic != ARM_ZIMAGE_MAGIC1 ||
+	    h->magic2 != ARM_ZIMAGE_MAGIC2)
+		return NULL;
+
+	for (offset = h->extension_tag_offset;
+	     (tag = (void *)(buf + offset)) != NULL &&
+		     offset < max &&
+		     (size = le32_to_cpu(byte_size(tag))) != 0 &&
+		     offset + size < len;
+	     offset += size) {
+		pr_debug("  offset 0x%08x tag 0x%08x size %u\n",
+			  offset, le32_to_cpu(tag->hdr.tag), size);
+		if (tag->hdr.tag == tag_id)
+			return tag;
+	}
+
+	return NULL;
+}
+
+static int zimage_probe(const char *kernel_buf, unsigned long kernel_len)
+{
+	const struct arm_zimage_header *h =
+		(struct arm_zimage_header *)(kernel_buf);
+
+	if (!h || (kernel_len < sizeof(*h)))
+		return -EINVAL;
+
+	if ((h->magic != ARM_ZIMAGE_MAGIC1) ||
+	    (h->magic2 != ARM_ZIMAGE_MAGIC2))
+		return -EINVAL;
+
+	return 0;
+}
+
+
+#if defined(DEBUG)
+#define debug_offsets() ({			\
+	pr_debug("Image offsets:\n");		\
+	pr_debug("  kernel 0x%08lx 0x%08lx\n", kernel_offset, kernel_len); \
+	pr_debug("  zimage 0x%08lx 0x%08lx\n", zimage_offset, zimage_len); \
+	pr_debug("  initrd 0x%08lx 0x%08lx\n", initrd_offset, initrd_len); \
+})
+#else
+#define debug_offsets()
+#endif
+
+static void *zimage_load(struct kimage *image,
+				char *zimage, unsigned long zimage_len,
+				char *initrd, unsigned long initrd_len,
+				char *cmdline, unsigned long cmdline_len)
+{
+	struct arm_zimage_header *h;
+	struct kexec_buf kbuf;
+	struct kexec_segment *zimage_segment;
+	const struct arm_zimage_tag *klsz_tag;
+	const struct arm_zimage_tag_dc *dcsz_tag;
+	int ret = -EINVAL;
+
+	unsigned long zimage_mem = 0x20000; /* malloc 64kB + stack 4 kB + some bss */
+	unsigned long kernel_len = zimage_len * 4; /* 4:1 compression */
+	unsigned long kernel_offset = memblock_start_of_DRAM() +
+		ALIGN(TEXT_OFFSET, PAGE_SIZE);
+	unsigned long zimage_offset = kernel_offset +
+		ALIGN(kernel_len, PAGE_SIZE);
+	unsigned long initrd_offset = zimage_offset +
+		ALIGN(zimage_len + zimage_mem, PAGE_SIZE);
+
+	if (image->type == KEXEC_TYPE_CRASH) {
+		kernel_offset += crashk_res.start;
+		zimage_offset += crashk_res.start;
+		initrd_offset += crashk_res.start;
+	}
+	debug_offsets();
+
+	h = (struct arm_zimage_header *)zimage;
+
+	klsz_tag = find_extension_tag(zimage, zimage_len, ZIMAGE_TAG_KRNL_SIZE);
+	if (klsz_tag) {
+		uint32_t *p = (void *)zimage +
+			le32_to_cpu(klsz_tag->u.krnl_size.size_ptr);
+		uint32_t edata_size = le32_to_cpu(get_unaligned(p));
+		uint32_t bss_size = le32_to_cpu(klsz_tag->u.krnl_size.bss_size);
+
+		kernel_len = edata_size + bss_size;
+
+		pr_debug("Decompressed kernel sizes:\n");
+		pr_debug(" text+data 0x%08lx bss 0x%08lx total 0x%08lx\n",
+			 (unsigned long)edata_size,
+			 (unsigned long)bss_size,
+			 (unsigned long)kernel_len);
+
+		zimage_offset = kernel_offset + ALIGN(edata_size, PAGE_SIZE);
+		initrd_offset = zimage_offset +
+			max(ALIGN(zimage_len + 0x20000, PAGE_SIZE),
+			    ALIGN((unsigned long)bss_size, PAGE_SIZE));
+		debug_offsets();
+	}
+
+	dcsz_tag = find_extension_tag(zimage, zimage_len,
+				      ZIMAGE_TAG_DECOMP_SIZE);
+	if (dcsz_tag) {
+		uint32_t bss_size = le32_to_cpu(dcsz_tag->u.decomp_size.bss_size);
+		uint32_t stack_size = le32_to_cpu(dcsz_tag->u.decomp_size.stack_size);
+		uint32_t malloc_size = le32_to_cpu(dcsz_tag->u.decomp_size.malloc_size);
+
+		zimage_mem = bss_size + stack_size + malloc_size;
+
+		pr_debug("Decompressor memory requirements:\n");
+		pr_debug(" bss 0x%08lx stack 0x%08lx malloc 0x%08lx total 0x%08lx\n",
+			 (unsigned long)bss_size,
+			 (unsigned long)stack_size,
+			 (unsigned long)malloc_size,
+			 zimage_mem);
+
+		initrd_offset = max(ALIGN(zimage_offset + zimage_len +
+					  bss_size + stack_size +
+					  malloc_size, PAGE_SIZE),
+				    ALIGN(kernel_offset + kernel_len, PAGE_SIZE));
+		debug_offsets();
+	}
+
+	/*
+	 * zImage MUST be loaded into the first 128 MiB of physical
+	 * memory for proper memory detection. Should the uncompressed
+	 * kernel be larger than 128 MiB, zImage relocation becomes
+	 * unavoidable and it is best to rely on the relocation code.
+	 */
+	if (((zimage_offset - kernel_offset) + PAGE_SIZE + 0x8000) >= SZ_128M) {
+		pr_debug("The kernel is too big (%ld MiB) to avoid "
+			 "zImage relocation. Loading zimage at 0x%08lx\n",
+			 ((zimage_offset - kernel_offset) >> 20),
+			 kernel_offset);
+		zimage_offset = kernel_offset;
+	}
+
+	kbuf.image = image;
+	kbuf.top_down = false;
+
+	kbuf.buf_min = zimage_offset;
+	kbuf.buf_max = ULONG_MAX;
+	kbuf.buffer = zimage;
+	kbuf.bufsz = zimage_len;
+	kbuf.buf_align = PAGE_SIZE;
+
+	kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+	kbuf.memsz = zimage_len;
+
+	ret = kexec_add_buffer(&kbuf);
+	if (ret)
+		return ERR_PTR(ret);
+
+	pr_debug("Loaded zImage at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
+		 kbuf.mem, kbuf.bufsz, kbuf.memsz);
+
+	initrd_offset += kbuf.mem - zimage_offset;
+	debug_offsets();
+
+	zimage_segment = &image->segment[image->nr_segments - 1];
+	image->start = zimage_segment->mem;
+
+	ret = load_other_segments(image,
+				  zimage_segment->mem, zimage_segment->memsz,
+				  initrd, initrd_len, initrd_offset,
+				  cmdline);
+	return ERR_PTR(ret);
+}
+
+const struct kexec_file_ops kexec_zimage_ops = {
+	.probe = zimage_probe,
+	.load = zimage_load,
+};
diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c
index c10a2dfd53d1..2e4780efabb4 100644
--- a/arch/arm/kernel/machine_kexec.c
+++ b/arch/arm/kernel/machine_kexec.c
@@ -93,10 +93,13 @@  int machine_kexec_prepare(struct kimage *image)
 					       current_segment->memsz))
 			return -EINVAL;
 
-		err = get_user(header, (__be32*)current_segment->buf);
-		if (err)
-			return err;
-
+		if (image->file_mode) {
+			header = *(__be32 *)current_segment->buf;
+		} else {
+			err = get_user(header, (__be32 *)current_segment->buf);
+			if (err)
+				return err;
+		}
 		if (header == cpu_to_be32(OF_DT_HEADER))
 			image->arch.kernel_r2 = current_segment->mem;
 	}
diff --git a/arch/arm/kernel/machine_kexec_file.c b/arch/arm/kernel/machine_kexec_file.c
new file mode 100644
index 000000000000..ead680f1e795
--- /dev/null
+++ b/arch/arm/kernel/machine_kexec_file.c
@@ -0,0 +1,209 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kexec_file for arm
+ *
+ * Copyright (C) 2018 Linaro Limited
+ * Copyright (C) 2020 Samsung Electronics
+ * Authors:
+ *     AKASHI Takahiro <takahiro.akashi@linaro.org>
+ *     Łukasz Stelmach <l.stelmach@samsung.com>
+ *
+ */
+
+#define pr_fmt(fmt) "kexec_file: " fmt
+
+#include <linux/kexec.h>
+#include <linux/libfdt.h>
+#include <linux/of_fdt.h>
+#include <linux/random.h>
+
+/* relevant device tree properties */
+#define FDT_PROP_INITRD_START	"linux,initrd-start"
+#define FDT_PROP_INITRD_END	"linux,initrd-end"
+#define FDT_PROP_BOOTARGS	"bootargs"
+#define FDT_PROP_RNG_SEED	"rng-seed"
+
+static int setup_dtb(struct kimage *image,
+		     unsigned long initrd_load_addr, unsigned long initrd_len,
+		     char *cmdline, void *dtb)
+{
+	int off, ret;
+
+	ret = fdt_path_offset(dtb, "/chosen");
+	if (ret < 0)
+		goto out;
+
+	off = ret;
+
+	/* add bootargs */
+	if (cmdline) {
+		ret = fdt_setprop_string(dtb, off, FDT_PROP_BOOTARGS, cmdline);
+		if (ret)
+			goto out;
+	} else {
+		ret = fdt_delprop(dtb, off, FDT_PROP_BOOTARGS);
+		if (ret && (ret != -FDT_ERR_NOTFOUND))
+			goto out;
+	}
+
+	/* add initrd-* */
+	if (initrd_load_addr) {
+		ret = fdt_setprop_u64(dtb, off, FDT_PROP_INITRD_START,
+				      initrd_load_addr);
+		if (ret)
+			goto out;
+
+		ret = fdt_setprop_u64(dtb, off, FDT_PROP_INITRD_END,
+				      initrd_load_addr + initrd_len);
+		if (ret)
+			goto out;
+	} else {
+		ret = fdt_delprop(dtb, off, FDT_PROP_INITRD_START);
+		if (ret && (ret != -FDT_ERR_NOTFOUND))
+			goto out;
+
+		ret = fdt_delprop(dtb, off, FDT_PROP_INITRD_END);
+		if (ret && (ret != -FDT_ERR_NOTFOUND))
+			goto out;
+	}
+
+	/* add rng-seed */
+	if (rng_is_initialized()) {
+		char seed[128];
+		get_random_bytes(seed, sizeof(seed));
+
+		ret = fdt_setprop(dtb, off, FDT_PROP_RNG_SEED,
+				  seed, sizeof(seed));
+		if (ret)
+			goto out;
+	} else {
+		pr_notice("RNG is not initialised: omitting \"%s\" property\n",
+				FDT_PROP_RNG_SEED);
+		ret = 0;
+	}
+
+out:
+	if (ret)
+		return (ret == -FDT_ERR_NOSPACE) ? -ENOMEM : -EINVAL;
+
+	return 0;
+}
+/*
+ * More space needed so that we can add initrd, bootargs and kaslr-seed.
+ */
+#define DTB_EXTRA_SPACE 0x1000
+
+static int create_dtb(struct kimage *image,
+		      unsigned long initrd_load_addr, unsigned long initrd_len,
+		      char *cmdline, void **dtb)
+{
+	void *buf;
+	size_t buf_size;
+	size_t cmdline_len;
+	int ret;
+
+	cmdline_len = cmdline ? strlen(cmdline) : 0;
+	buf_size = fdt_totalsize(initial_boot_params)
+			+ cmdline_len + DTB_EXTRA_SPACE;
+
+	for (;;) {
+		buf = vmalloc(buf_size);
+		if (!buf)
+			return -ENOMEM;
+
+		/* duplicate a device tree blob */
+		ret = fdt_open_into(initial_boot_params, buf, buf_size);
+		if (ret)
+			return -EINVAL;
+
+		ret = setup_dtb(image, initrd_load_addr, initrd_len,
+				cmdline, buf);
+		if (ret) {
+			vfree(buf);
+			if (ret == -ENOMEM) {
+				/* unlikely, but just in case */
+				buf_size += DTB_EXTRA_SPACE;
+				continue;
+			} else {
+				return ret;
+			}
+		}
+
+		/* trim it */
+		fdt_pack(buf);
+		*dtb = buf;
+
+		return 0;
+	}
+}
+
+int load_other_segments(struct kimage *image,
+			unsigned long zimage_load_addr,
+			unsigned long zimage_len,
+			char *initrd,
+			unsigned long initrd_len,
+			unsigned long initrd_offset,
+			char *cmdline)
+{
+	struct kexec_buf kbuf;
+	void *dtb = NULL;
+	unsigned long initrd_load_addr = 0;
+	unsigned long dtb_len;
+	int ret = 0;
+
+	kbuf.image = image;
+	/* not allocate anything below the kernel */
+	kbuf.buf_min = initrd_offset;
+	if (initrd) {
+		kbuf.buffer = initrd;
+		kbuf.bufsz = initrd_len;
+		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+		kbuf.memsz = initrd_len;
+		kbuf.buf_align = PAGE_SIZE;
+		kbuf.buf_max = ULONG_MAX;
+		kbuf.top_down = false;
+
+		ret = kexec_add_buffer(&kbuf);
+		if (ret)
+			goto out_err;
+
+		pr_debug("Loaded initrd at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
+				kbuf.mem, kbuf.bufsz, kbuf.memsz);
+
+		initrd_load_addr = kbuf.mem;
+		kbuf.buf_min = initrd_load_addr + kbuf.memsz;
+	}
+
+	/* load dtb */
+	ret = create_dtb(image, initrd_load_addr, initrd_len, cmdline, &dtb);
+	if (ret) {
+		pr_err("Preparing for new dtb failed\n");
+		goto out_err;
+	}
+
+	dtb_len = fdt_totalsize(dtb);
+	kbuf.buffer = dtb;
+	kbuf.bufsz = dtb_len;
+	kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+	kbuf.memsz = dtb_len;
+	kbuf.buf_align = PAGE_SIZE;
+	kbuf.buf_max = ULONG_MAX;
+	kbuf.top_down = false;
+
+	ret = kexec_add_buffer(&kbuf);
+	if (ret)
+		goto out_err;
+
+	pr_debug("Loaded dtb at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
+		 kbuf.mem, kbuf.bufsz, kbuf.memsz);
+	return 0;
+out_err:
+	vfree(dtb);
+	return ret;
+}
+
+const struct kexec_file_ops * const kexec_file_loaders[] = {
+	&kexec_uimage_ops,
+	&kexec_zimage_ops,
+	NULL
+};