13ddd9992SAKASHI Takahiro // SPDX-License-Identifier: GPL-2.0
23ddd9992SAKASHI Takahiro /*
33ddd9992SAKASHI Takahiro  * kexec_file for arm64
43ddd9992SAKASHI Takahiro  *
53ddd9992SAKASHI Takahiro  * Copyright (C) 2018 Linaro Limited
63ddd9992SAKASHI Takahiro  * Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
73ddd9992SAKASHI Takahiro  *
852b2a8afSAKASHI Takahiro  * Most code is derived from arm64 port of kexec-tools
93ddd9992SAKASHI Takahiro  */
103ddd9992SAKASHI Takahiro 
113ddd9992SAKASHI Takahiro #define pr_fmt(fmt) "kexec_file: " fmt
123ddd9992SAKASHI Takahiro 
1352b2a8afSAKASHI Takahiro #include <linux/ioport.h>
1452b2a8afSAKASHI Takahiro #include <linux/kernel.h>
153ddd9992SAKASHI Takahiro #include <linux/kexec.h>
1652b2a8afSAKASHI Takahiro #include <linux/libfdt.h>
1752b2a8afSAKASHI Takahiro #include <linux/memblock.h>
1852b2a8afSAKASHI Takahiro #include <linux/of_fdt.h>
19884143f6SAKASHI Takahiro #include <linux/random.h>
2052b2a8afSAKASHI Takahiro #include <linux/string.h>
2152b2a8afSAKASHI Takahiro #include <linux/types.h>
2252b2a8afSAKASHI Takahiro #include <asm/byteorder.h>
2352b2a8afSAKASHI Takahiro 
2452b2a8afSAKASHI Takahiro /* relevant device tree properties */
2552b2a8afSAKASHI Takahiro #define FDT_PSTR_INITRD_STA	"linux,initrd-start"
2652b2a8afSAKASHI Takahiro #define FDT_PSTR_INITRD_END	"linux,initrd-end"
2752b2a8afSAKASHI Takahiro #define FDT_PSTR_BOOTARGS	"bootargs"
28884143f6SAKASHI Takahiro #define FDT_PSTR_KASLR_SEED	"kaslr-seed"
293ddd9992SAKASHI Takahiro 
303ddd9992SAKASHI Takahiro const struct kexec_file_ops * const kexec_file_loaders[] = {
31f3b70e50SAKASHI Takahiro 	&kexec_image_ops,
323ddd9992SAKASHI Takahiro 	NULL
333ddd9992SAKASHI Takahiro };
3452b2a8afSAKASHI Takahiro 
3552b2a8afSAKASHI Takahiro int arch_kimage_file_post_load_cleanup(struct kimage *image)
3652b2a8afSAKASHI Takahiro {
3752b2a8afSAKASHI Takahiro 	vfree(image->arch.dtb);
3852b2a8afSAKASHI Takahiro 	image->arch.dtb = NULL;
3952b2a8afSAKASHI Takahiro 
4052b2a8afSAKASHI Takahiro 	return kexec_image_post_load_cleanup_default(image);
4152b2a8afSAKASHI Takahiro }
4252b2a8afSAKASHI Takahiro 
4352b2a8afSAKASHI Takahiro static int setup_dtb(struct kimage *image,
4452b2a8afSAKASHI Takahiro 		     unsigned long initrd_load_addr, unsigned long initrd_len,
4552b2a8afSAKASHI Takahiro 		     char *cmdline, void *dtb)
4652b2a8afSAKASHI Takahiro {
4752b2a8afSAKASHI Takahiro 	int nodeoffset;
4852b2a8afSAKASHI Takahiro 	int ret;
4952b2a8afSAKASHI Takahiro 
5052b2a8afSAKASHI Takahiro 	nodeoffset = fdt_path_offset(dtb, "/chosen");
5152b2a8afSAKASHI Takahiro 	if (nodeoffset < 0)
5252b2a8afSAKASHI Takahiro 		return -EINVAL;
5352b2a8afSAKASHI Takahiro 
5452b2a8afSAKASHI Takahiro 	/* add bootargs */
5552b2a8afSAKASHI Takahiro 	if (cmdline) {
5652b2a8afSAKASHI Takahiro 		ret = fdt_setprop_string(dtb, nodeoffset, FDT_PSTR_BOOTARGS,
5752b2a8afSAKASHI Takahiro 							cmdline);
5852b2a8afSAKASHI Takahiro 		if (ret)
5952b2a8afSAKASHI Takahiro 			return (ret == -FDT_ERR_NOSPACE ? -ENOMEM : -EINVAL);
6052b2a8afSAKASHI Takahiro 	} else {
6152b2a8afSAKASHI Takahiro 		ret = fdt_delprop(dtb, nodeoffset, FDT_PSTR_BOOTARGS);
6252b2a8afSAKASHI Takahiro 		if (ret && (ret != -FDT_ERR_NOTFOUND))
6352b2a8afSAKASHI Takahiro 			return -EINVAL;
6452b2a8afSAKASHI Takahiro 	}
6552b2a8afSAKASHI Takahiro 
6652b2a8afSAKASHI Takahiro 	/* add initrd-* */
6752b2a8afSAKASHI Takahiro 	if (initrd_load_addr) {
6852b2a8afSAKASHI Takahiro 		ret = fdt_setprop_u64(dtb, nodeoffset, FDT_PSTR_INITRD_STA,
6952b2a8afSAKASHI Takahiro 							initrd_load_addr);
7052b2a8afSAKASHI Takahiro 		if (ret)
7152b2a8afSAKASHI Takahiro 			return (ret == -FDT_ERR_NOSPACE ? -ENOMEM : -EINVAL);
7252b2a8afSAKASHI Takahiro 
7352b2a8afSAKASHI Takahiro 		ret = fdt_setprop_u64(dtb, nodeoffset, FDT_PSTR_INITRD_END,
7452b2a8afSAKASHI Takahiro 						initrd_load_addr + initrd_len);
7552b2a8afSAKASHI Takahiro 		if (ret)
7652b2a8afSAKASHI Takahiro 			return (ret == -FDT_ERR_NOSPACE ? -ENOMEM : -EINVAL);
7752b2a8afSAKASHI Takahiro 	} else {
7852b2a8afSAKASHI Takahiro 		ret = fdt_delprop(dtb, nodeoffset, FDT_PSTR_INITRD_STA);
7952b2a8afSAKASHI Takahiro 		if (ret && (ret != -FDT_ERR_NOTFOUND))
8052b2a8afSAKASHI Takahiro 			return -EINVAL;
8152b2a8afSAKASHI Takahiro 
8252b2a8afSAKASHI Takahiro 		ret = fdt_delprop(dtb, nodeoffset, FDT_PSTR_INITRD_END);
8352b2a8afSAKASHI Takahiro 		if (ret && (ret != -FDT_ERR_NOTFOUND))
8452b2a8afSAKASHI Takahiro 			return -EINVAL;
8552b2a8afSAKASHI Takahiro 	}
8652b2a8afSAKASHI Takahiro 
87884143f6SAKASHI Takahiro 	/* add kaslr-seed */
88884143f6SAKASHI Takahiro 	ret = fdt_delprop(dtb, nodeoffset, FDT_PSTR_KASLR_SEED);
89884143f6SAKASHI Takahiro 	if (ret && (ret != -FDT_ERR_NOTFOUND))
90884143f6SAKASHI Takahiro 		return -EINVAL;
91884143f6SAKASHI Takahiro 
92884143f6SAKASHI Takahiro 	if (rng_is_initialized()) {
93884143f6SAKASHI Takahiro 		u64 r = get_random_u64();
94884143f6SAKASHI Takahiro 		ret = fdt_setprop_u64(dtb, nodeoffset, FDT_PSTR_KASLR_SEED, r);
95884143f6SAKASHI Takahiro 		if (ret)
96884143f6SAKASHI Takahiro 			return (ret == -FDT_ERR_NOSPACE ? -ENOMEM : -EINVAL);
97884143f6SAKASHI Takahiro 	} else {
98884143f6SAKASHI Takahiro 		pr_notice("RNG is not initialised: omitting \"%s\" property\n",
99884143f6SAKASHI Takahiro 				FDT_PSTR_KASLR_SEED);
100884143f6SAKASHI Takahiro 	}
101884143f6SAKASHI Takahiro 
10252b2a8afSAKASHI Takahiro 	return 0;
10352b2a8afSAKASHI Takahiro }
10452b2a8afSAKASHI Takahiro 
10552b2a8afSAKASHI Takahiro /*
106884143f6SAKASHI Takahiro  * More space needed so that we can add initrd, bootargs and kaslr-seed.
10752b2a8afSAKASHI Takahiro  */
10852b2a8afSAKASHI Takahiro #define DTB_EXTRA_SPACE 0x1000
10952b2a8afSAKASHI Takahiro 
11052b2a8afSAKASHI Takahiro static int create_dtb(struct kimage *image,
11152b2a8afSAKASHI Takahiro 		      unsigned long initrd_load_addr, unsigned long initrd_len,
11252b2a8afSAKASHI Takahiro 		      char *cmdline, void **dtb)
11352b2a8afSAKASHI Takahiro {
11452b2a8afSAKASHI Takahiro 	void *buf;
11552b2a8afSAKASHI Takahiro 	size_t buf_size;
11652b2a8afSAKASHI Takahiro 	int ret;
11752b2a8afSAKASHI Takahiro 
11852b2a8afSAKASHI Takahiro 	buf_size = fdt_totalsize(initial_boot_params)
11952b2a8afSAKASHI Takahiro 			+ strlen(cmdline) + DTB_EXTRA_SPACE;
12052b2a8afSAKASHI Takahiro 
12152b2a8afSAKASHI Takahiro 	for (;;) {
12252b2a8afSAKASHI Takahiro 		buf = vmalloc(buf_size);
12352b2a8afSAKASHI Takahiro 		if (!buf)
12452b2a8afSAKASHI Takahiro 			return -ENOMEM;
12552b2a8afSAKASHI Takahiro 
12652b2a8afSAKASHI Takahiro 		/* duplicate a device tree blob */
12752b2a8afSAKASHI Takahiro 		ret = fdt_open_into(initial_boot_params, buf, buf_size);
12852b2a8afSAKASHI Takahiro 		if (ret)
12952b2a8afSAKASHI Takahiro 			return -EINVAL;
13052b2a8afSAKASHI Takahiro 
13152b2a8afSAKASHI Takahiro 		ret = setup_dtb(image, initrd_load_addr, initrd_len,
13252b2a8afSAKASHI Takahiro 				cmdline, buf);
13352b2a8afSAKASHI Takahiro 		if (ret) {
13452b2a8afSAKASHI Takahiro 			vfree(buf);
13552b2a8afSAKASHI Takahiro 			if (ret == -ENOMEM) {
13652b2a8afSAKASHI Takahiro 				/* unlikely, but just in case */
13752b2a8afSAKASHI Takahiro 				buf_size += DTB_EXTRA_SPACE;
13852b2a8afSAKASHI Takahiro 				continue;
13952b2a8afSAKASHI Takahiro 			} else {
14052b2a8afSAKASHI Takahiro 				return ret;
14152b2a8afSAKASHI Takahiro 			}
14252b2a8afSAKASHI Takahiro 		}
14352b2a8afSAKASHI Takahiro 
14452b2a8afSAKASHI Takahiro 		/* trim it */
14552b2a8afSAKASHI Takahiro 		fdt_pack(buf);
14652b2a8afSAKASHI Takahiro 		*dtb = buf;
14752b2a8afSAKASHI Takahiro 
14852b2a8afSAKASHI Takahiro 		return 0;
14952b2a8afSAKASHI Takahiro 	}
15052b2a8afSAKASHI Takahiro }
15152b2a8afSAKASHI Takahiro 
15252b2a8afSAKASHI Takahiro int load_other_segments(struct kimage *image,
15352b2a8afSAKASHI Takahiro 			unsigned long kernel_load_addr,
15452b2a8afSAKASHI Takahiro 			unsigned long kernel_size,
15552b2a8afSAKASHI Takahiro 			char *initrd, unsigned long initrd_len,
15652b2a8afSAKASHI Takahiro 			char *cmdline)
15752b2a8afSAKASHI Takahiro {
15852b2a8afSAKASHI Takahiro 	struct kexec_buf kbuf;
15952b2a8afSAKASHI Takahiro 	void *dtb = NULL;
16052b2a8afSAKASHI Takahiro 	unsigned long initrd_load_addr = 0, dtb_len;
16152b2a8afSAKASHI Takahiro 	int ret = 0;
16252b2a8afSAKASHI Takahiro 
16352b2a8afSAKASHI Takahiro 	kbuf.image = image;
16452b2a8afSAKASHI Takahiro 	/* not allocate anything below the kernel */
16552b2a8afSAKASHI Takahiro 	kbuf.buf_min = kernel_load_addr + kernel_size;
16652b2a8afSAKASHI Takahiro 
16752b2a8afSAKASHI Takahiro 	/* load initrd */
16852b2a8afSAKASHI Takahiro 	if (initrd) {
16952b2a8afSAKASHI Takahiro 		kbuf.buffer = initrd;
17052b2a8afSAKASHI Takahiro 		kbuf.bufsz = initrd_len;
17152b2a8afSAKASHI Takahiro 		kbuf.mem = 0;
17252b2a8afSAKASHI Takahiro 		kbuf.memsz = initrd_len;
17352b2a8afSAKASHI Takahiro 		kbuf.buf_align = 0;
17452b2a8afSAKASHI Takahiro 		/* within 1GB-aligned window of up to 32GB in size */
17552b2a8afSAKASHI Takahiro 		kbuf.buf_max = round_down(kernel_load_addr, SZ_1G)
17652b2a8afSAKASHI Takahiro 						+ (unsigned long)SZ_1G * 32;
17752b2a8afSAKASHI Takahiro 		kbuf.top_down = false;
17852b2a8afSAKASHI Takahiro 
17952b2a8afSAKASHI Takahiro 		ret = kexec_add_buffer(&kbuf);
18052b2a8afSAKASHI Takahiro 		if (ret)
18152b2a8afSAKASHI Takahiro 			goto out_err;
18252b2a8afSAKASHI Takahiro 		initrd_load_addr = kbuf.mem;
18352b2a8afSAKASHI Takahiro 
18452b2a8afSAKASHI Takahiro 		pr_debug("Loaded initrd at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
18552b2a8afSAKASHI Takahiro 				initrd_load_addr, initrd_len, initrd_len);
18652b2a8afSAKASHI Takahiro 	}
18752b2a8afSAKASHI Takahiro 
18852b2a8afSAKASHI Takahiro 	/* load dtb */
18952b2a8afSAKASHI Takahiro 	ret = create_dtb(image, initrd_load_addr, initrd_len, cmdline, &dtb);
19052b2a8afSAKASHI Takahiro 	if (ret) {
19152b2a8afSAKASHI Takahiro 		pr_err("Preparing for new dtb failed\n");
19252b2a8afSAKASHI Takahiro 		goto out_err;
19352b2a8afSAKASHI Takahiro 	}
19452b2a8afSAKASHI Takahiro 
19552b2a8afSAKASHI Takahiro 	dtb_len = fdt_totalsize(dtb);
19652b2a8afSAKASHI Takahiro 	kbuf.buffer = dtb;
19752b2a8afSAKASHI Takahiro 	kbuf.bufsz = dtb_len;
19852b2a8afSAKASHI Takahiro 	kbuf.mem = 0;
19952b2a8afSAKASHI Takahiro 	kbuf.memsz = dtb_len;
20052b2a8afSAKASHI Takahiro 	/* not across 2MB boundary */
20152b2a8afSAKASHI Takahiro 	kbuf.buf_align = SZ_2M;
20252b2a8afSAKASHI Takahiro 	kbuf.buf_max = ULONG_MAX;
20352b2a8afSAKASHI Takahiro 	kbuf.top_down = true;
20452b2a8afSAKASHI Takahiro 
20552b2a8afSAKASHI Takahiro 	ret = kexec_add_buffer(&kbuf);
20652b2a8afSAKASHI Takahiro 	if (ret)
20752b2a8afSAKASHI Takahiro 		goto out_err;
20852b2a8afSAKASHI Takahiro 	image->arch.dtb = dtb;
20952b2a8afSAKASHI Takahiro 	image->arch.dtb_mem = kbuf.mem;
21052b2a8afSAKASHI Takahiro 
21152b2a8afSAKASHI Takahiro 	pr_debug("Loaded dtb at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
21252b2a8afSAKASHI Takahiro 			kbuf.mem, dtb_len, dtb_len);
21352b2a8afSAKASHI Takahiro 
21452b2a8afSAKASHI Takahiro 	return 0;
21552b2a8afSAKASHI Takahiro 
21652b2a8afSAKASHI Takahiro out_err:
21752b2a8afSAKASHI Takahiro 	vfree(dtb);
21852b2a8afSAKASHI Takahiro 	return ret;
21952b2a8afSAKASHI Takahiro }
220