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>
1952b2a8afSAKASHI Takahiro #include <linux/string.h>
2052b2a8afSAKASHI Takahiro #include <linux/types.h>
2152b2a8afSAKASHI Takahiro #include <asm/byteorder.h>
2252b2a8afSAKASHI Takahiro 
2352b2a8afSAKASHI Takahiro /* relevant device tree properties */
2452b2a8afSAKASHI Takahiro #define FDT_PSTR_INITRD_STA	"linux,initrd-start"
2552b2a8afSAKASHI Takahiro #define FDT_PSTR_INITRD_END	"linux,initrd-end"
2652b2a8afSAKASHI Takahiro #define FDT_PSTR_BOOTARGS	"bootargs"
273ddd9992SAKASHI Takahiro 
283ddd9992SAKASHI Takahiro const struct kexec_file_ops * const kexec_file_loaders[] = {
293ddd9992SAKASHI Takahiro 	NULL
303ddd9992SAKASHI Takahiro };
3152b2a8afSAKASHI Takahiro 
3252b2a8afSAKASHI Takahiro int arch_kimage_file_post_load_cleanup(struct kimage *image)
3352b2a8afSAKASHI Takahiro {
3452b2a8afSAKASHI Takahiro 	vfree(image->arch.dtb);
3552b2a8afSAKASHI Takahiro 	image->arch.dtb = NULL;
3652b2a8afSAKASHI Takahiro 
3752b2a8afSAKASHI Takahiro 	return kexec_image_post_load_cleanup_default(image);
3852b2a8afSAKASHI Takahiro }
3952b2a8afSAKASHI Takahiro 
4052b2a8afSAKASHI Takahiro static int setup_dtb(struct kimage *image,
4152b2a8afSAKASHI Takahiro 		     unsigned long initrd_load_addr, unsigned long initrd_len,
4252b2a8afSAKASHI Takahiro 		     char *cmdline, void *dtb)
4352b2a8afSAKASHI Takahiro {
4452b2a8afSAKASHI Takahiro 	int nodeoffset;
4552b2a8afSAKASHI Takahiro 	int ret;
4652b2a8afSAKASHI Takahiro 
4752b2a8afSAKASHI Takahiro 	nodeoffset = fdt_path_offset(dtb, "/chosen");
4852b2a8afSAKASHI Takahiro 	if (nodeoffset < 0)
4952b2a8afSAKASHI Takahiro 		return -EINVAL;
5052b2a8afSAKASHI Takahiro 
5152b2a8afSAKASHI Takahiro 	/* add bootargs */
5252b2a8afSAKASHI Takahiro 	if (cmdline) {
5352b2a8afSAKASHI Takahiro 		ret = fdt_setprop_string(dtb, nodeoffset, FDT_PSTR_BOOTARGS,
5452b2a8afSAKASHI Takahiro 							cmdline);
5552b2a8afSAKASHI Takahiro 		if (ret)
5652b2a8afSAKASHI Takahiro 			return (ret == -FDT_ERR_NOSPACE ? -ENOMEM : -EINVAL);
5752b2a8afSAKASHI Takahiro 	} else {
5852b2a8afSAKASHI Takahiro 		ret = fdt_delprop(dtb, nodeoffset, FDT_PSTR_BOOTARGS);
5952b2a8afSAKASHI Takahiro 		if (ret && (ret != -FDT_ERR_NOTFOUND))
6052b2a8afSAKASHI Takahiro 			return -EINVAL;
6152b2a8afSAKASHI Takahiro 	}
6252b2a8afSAKASHI Takahiro 
6352b2a8afSAKASHI Takahiro 	/* add initrd-* */
6452b2a8afSAKASHI Takahiro 	if (initrd_load_addr) {
6552b2a8afSAKASHI Takahiro 		ret = fdt_setprop_u64(dtb, nodeoffset, FDT_PSTR_INITRD_STA,
6652b2a8afSAKASHI Takahiro 							initrd_load_addr);
6752b2a8afSAKASHI Takahiro 		if (ret)
6852b2a8afSAKASHI Takahiro 			return (ret == -FDT_ERR_NOSPACE ? -ENOMEM : -EINVAL);
6952b2a8afSAKASHI Takahiro 
7052b2a8afSAKASHI Takahiro 		ret = fdt_setprop_u64(dtb, nodeoffset, FDT_PSTR_INITRD_END,
7152b2a8afSAKASHI Takahiro 						initrd_load_addr + initrd_len);
7252b2a8afSAKASHI Takahiro 		if (ret)
7352b2a8afSAKASHI Takahiro 			return (ret == -FDT_ERR_NOSPACE ? -ENOMEM : -EINVAL);
7452b2a8afSAKASHI Takahiro 	} else {
7552b2a8afSAKASHI Takahiro 		ret = fdt_delprop(dtb, nodeoffset, FDT_PSTR_INITRD_STA);
7652b2a8afSAKASHI Takahiro 		if (ret && (ret != -FDT_ERR_NOTFOUND))
7752b2a8afSAKASHI Takahiro 			return -EINVAL;
7852b2a8afSAKASHI Takahiro 
7952b2a8afSAKASHI Takahiro 		ret = fdt_delprop(dtb, nodeoffset, FDT_PSTR_INITRD_END);
8052b2a8afSAKASHI Takahiro 		if (ret && (ret != -FDT_ERR_NOTFOUND))
8152b2a8afSAKASHI Takahiro 			return -EINVAL;
8252b2a8afSAKASHI Takahiro 	}
8352b2a8afSAKASHI Takahiro 
8452b2a8afSAKASHI Takahiro 	return 0;
8552b2a8afSAKASHI Takahiro }
8652b2a8afSAKASHI Takahiro 
8752b2a8afSAKASHI Takahiro /*
8852b2a8afSAKASHI Takahiro  * More space needed so that we can add initrd and bootargs.
8952b2a8afSAKASHI Takahiro  */
9052b2a8afSAKASHI Takahiro #define DTB_EXTRA_SPACE 0x1000
9152b2a8afSAKASHI Takahiro 
9252b2a8afSAKASHI Takahiro static int create_dtb(struct kimage *image,
9352b2a8afSAKASHI Takahiro 		      unsigned long initrd_load_addr, unsigned long initrd_len,
9452b2a8afSAKASHI Takahiro 		      char *cmdline, void **dtb)
9552b2a8afSAKASHI Takahiro {
9652b2a8afSAKASHI Takahiro 	void *buf;
9752b2a8afSAKASHI Takahiro 	size_t buf_size;
9852b2a8afSAKASHI Takahiro 	int ret;
9952b2a8afSAKASHI Takahiro 
10052b2a8afSAKASHI Takahiro 	buf_size = fdt_totalsize(initial_boot_params)
10152b2a8afSAKASHI Takahiro 			+ strlen(cmdline) + DTB_EXTRA_SPACE;
10252b2a8afSAKASHI Takahiro 
10352b2a8afSAKASHI Takahiro 	for (;;) {
10452b2a8afSAKASHI Takahiro 		buf = vmalloc(buf_size);
10552b2a8afSAKASHI Takahiro 		if (!buf)
10652b2a8afSAKASHI Takahiro 			return -ENOMEM;
10752b2a8afSAKASHI Takahiro 
10852b2a8afSAKASHI Takahiro 		/* duplicate a device tree blob */
10952b2a8afSAKASHI Takahiro 		ret = fdt_open_into(initial_boot_params, buf, buf_size);
11052b2a8afSAKASHI Takahiro 		if (ret)
11152b2a8afSAKASHI Takahiro 			return -EINVAL;
11252b2a8afSAKASHI Takahiro 
11352b2a8afSAKASHI Takahiro 		ret = setup_dtb(image, initrd_load_addr, initrd_len,
11452b2a8afSAKASHI Takahiro 				cmdline, buf);
11552b2a8afSAKASHI Takahiro 		if (ret) {
11652b2a8afSAKASHI Takahiro 			vfree(buf);
11752b2a8afSAKASHI Takahiro 			if (ret == -ENOMEM) {
11852b2a8afSAKASHI Takahiro 				/* unlikely, but just in case */
11952b2a8afSAKASHI Takahiro 				buf_size += DTB_EXTRA_SPACE;
12052b2a8afSAKASHI Takahiro 				continue;
12152b2a8afSAKASHI Takahiro 			} else {
12252b2a8afSAKASHI Takahiro 				return ret;
12352b2a8afSAKASHI Takahiro 			}
12452b2a8afSAKASHI Takahiro 		}
12552b2a8afSAKASHI Takahiro 
12652b2a8afSAKASHI Takahiro 		/* trim it */
12752b2a8afSAKASHI Takahiro 		fdt_pack(buf);
12852b2a8afSAKASHI Takahiro 		*dtb = buf;
12952b2a8afSAKASHI Takahiro 
13052b2a8afSAKASHI Takahiro 		return 0;
13152b2a8afSAKASHI Takahiro 	}
13252b2a8afSAKASHI Takahiro }
13352b2a8afSAKASHI Takahiro 
13452b2a8afSAKASHI Takahiro int load_other_segments(struct kimage *image,
13552b2a8afSAKASHI Takahiro 			unsigned long kernel_load_addr,
13652b2a8afSAKASHI Takahiro 			unsigned long kernel_size,
13752b2a8afSAKASHI Takahiro 			char *initrd, unsigned long initrd_len,
13852b2a8afSAKASHI Takahiro 			char *cmdline)
13952b2a8afSAKASHI Takahiro {
14052b2a8afSAKASHI Takahiro 	struct kexec_buf kbuf;
14152b2a8afSAKASHI Takahiro 	void *dtb = NULL;
14252b2a8afSAKASHI Takahiro 	unsigned long initrd_load_addr = 0, dtb_len;
14352b2a8afSAKASHI Takahiro 	int ret = 0;
14452b2a8afSAKASHI Takahiro 
14552b2a8afSAKASHI Takahiro 	kbuf.image = image;
14652b2a8afSAKASHI Takahiro 	/* not allocate anything below the kernel */
14752b2a8afSAKASHI Takahiro 	kbuf.buf_min = kernel_load_addr + kernel_size;
14852b2a8afSAKASHI Takahiro 
14952b2a8afSAKASHI Takahiro 	/* load initrd */
15052b2a8afSAKASHI Takahiro 	if (initrd) {
15152b2a8afSAKASHI Takahiro 		kbuf.buffer = initrd;
15252b2a8afSAKASHI Takahiro 		kbuf.bufsz = initrd_len;
15352b2a8afSAKASHI Takahiro 		kbuf.mem = 0;
15452b2a8afSAKASHI Takahiro 		kbuf.memsz = initrd_len;
15552b2a8afSAKASHI Takahiro 		kbuf.buf_align = 0;
15652b2a8afSAKASHI Takahiro 		/* within 1GB-aligned window of up to 32GB in size */
15752b2a8afSAKASHI Takahiro 		kbuf.buf_max = round_down(kernel_load_addr, SZ_1G)
15852b2a8afSAKASHI Takahiro 						+ (unsigned long)SZ_1G * 32;
15952b2a8afSAKASHI Takahiro 		kbuf.top_down = false;
16052b2a8afSAKASHI Takahiro 
16152b2a8afSAKASHI Takahiro 		ret = kexec_add_buffer(&kbuf);
16252b2a8afSAKASHI Takahiro 		if (ret)
16352b2a8afSAKASHI Takahiro 			goto out_err;
16452b2a8afSAKASHI Takahiro 		initrd_load_addr = kbuf.mem;
16552b2a8afSAKASHI Takahiro 
16652b2a8afSAKASHI Takahiro 		pr_debug("Loaded initrd at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
16752b2a8afSAKASHI Takahiro 				initrd_load_addr, initrd_len, initrd_len);
16852b2a8afSAKASHI Takahiro 	}
16952b2a8afSAKASHI Takahiro 
17052b2a8afSAKASHI Takahiro 	/* load dtb */
17152b2a8afSAKASHI Takahiro 	ret = create_dtb(image, initrd_load_addr, initrd_len, cmdline, &dtb);
17252b2a8afSAKASHI Takahiro 	if (ret) {
17352b2a8afSAKASHI Takahiro 		pr_err("Preparing for new dtb failed\n");
17452b2a8afSAKASHI Takahiro 		goto out_err;
17552b2a8afSAKASHI Takahiro 	}
17652b2a8afSAKASHI Takahiro 
17752b2a8afSAKASHI Takahiro 	dtb_len = fdt_totalsize(dtb);
17852b2a8afSAKASHI Takahiro 	kbuf.buffer = dtb;
17952b2a8afSAKASHI Takahiro 	kbuf.bufsz = dtb_len;
18052b2a8afSAKASHI Takahiro 	kbuf.mem = 0;
18152b2a8afSAKASHI Takahiro 	kbuf.memsz = dtb_len;
18252b2a8afSAKASHI Takahiro 	/* not across 2MB boundary */
18352b2a8afSAKASHI Takahiro 	kbuf.buf_align = SZ_2M;
18452b2a8afSAKASHI Takahiro 	kbuf.buf_max = ULONG_MAX;
18552b2a8afSAKASHI Takahiro 	kbuf.top_down = true;
18652b2a8afSAKASHI Takahiro 
18752b2a8afSAKASHI Takahiro 	ret = kexec_add_buffer(&kbuf);
18852b2a8afSAKASHI Takahiro 	if (ret)
18952b2a8afSAKASHI Takahiro 		goto out_err;
19052b2a8afSAKASHI Takahiro 	image->arch.dtb = dtb;
19152b2a8afSAKASHI Takahiro 	image->arch.dtb_mem = kbuf.mem;
19252b2a8afSAKASHI Takahiro 
19352b2a8afSAKASHI Takahiro 	pr_debug("Loaded dtb at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
19452b2a8afSAKASHI Takahiro 			kbuf.mem, dtb_len, dtb_len);
19552b2a8afSAKASHI Takahiro 
19652b2a8afSAKASHI Takahiro 	return 0;
19752b2a8afSAKASHI Takahiro 
19852b2a8afSAKASHI Takahiro out_err:
19952b2a8afSAKASHI Takahiro 	vfree(dtb);
20052b2a8afSAKASHI Takahiro 	return ret;
20152b2a8afSAKASHI Takahiro }
202