162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Load ELF vmlinux file for the kexec_file_load syscall.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2004  Adam Litke (agl@us.ibm.com)
662306a36Sopenharmony_ci * Copyright (C) 2004  IBM Corp.
762306a36Sopenharmony_ci * Copyright (C) 2005  R Sharada (sharada@in.ibm.com)
862306a36Sopenharmony_ci * Copyright (C) 2006  Mohan Kumar M (mohan@in.ibm.com)
962306a36Sopenharmony_ci * Copyright (C) 2016  IBM Corporation
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Based on kexec-tools' kexec-elf-exec.c and kexec-elf-ppc64.c.
1262306a36Sopenharmony_ci * Heavily modified for the kernel by
1362306a36Sopenharmony_ci * Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define pr_fmt(fmt)	"kexec_elf: " fmt
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <linux/elf.h>
1962306a36Sopenharmony_ci#include <linux/kexec.h>
2062306a36Sopenharmony_ci#include <linux/libfdt.h>
2162306a36Sopenharmony_ci#include <linux/module.h>
2262306a36Sopenharmony_ci#include <linux/of.h>
2362306a36Sopenharmony_ci#include <linux/of_fdt.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <linux/types.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic void *elf64_load(struct kimage *image, char *kernel_buf,
2862306a36Sopenharmony_ci			unsigned long kernel_len, char *initrd,
2962306a36Sopenharmony_ci			unsigned long initrd_len, char *cmdline,
3062306a36Sopenharmony_ci			unsigned long cmdline_len)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	int ret;
3362306a36Sopenharmony_ci	unsigned long kernel_load_addr;
3462306a36Sopenharmony_ci	unsigned long initrd_load_addr = 0, fdt_load_addr;
3562306a36Sopenharmony_ci	void *fdt;
3662306a36Sopenharmony_ci	const void *slave_code;
3762306a36Sopenharmony_ci	struct elfhdr ehdr;
3862306a36Sopenharmony_ci	char *modified_cmdline = NULL;
3962306a36Sopenharmony_ci	struct kexec_elf_info elf_info;
4062306a36Sopenharmony_ci	struct kexec_buf kbuf = { .image = image, .buf_min = 0,
4162306a36Sopenharmony_ci				  .buf_max = ppc64_rma_size };
4262306a36Sopenharmony_ci	struct kexec_buf pbuf = { .image = image, .buf_min = 0,
4362306a36Sopenharmony_ci				  .buf_max = ppc64_rma_size, .top_down = true,
4462306a36Sopenharmony_ci				  .mem = KEXEC_BUF_MEM_UNKNOWN };
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	ret = kexec_build_elf_info(kernel_buf, kernel_len, &ehdr, &elf_info);
4762306a36Sopenharmony_ci	if (ret)
4862306a36Sopenharmony_ci		return ERR_PTR(ret);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	if (image->type == KEXEC_TYPE_CRASH) {
5162306a36Sopenharmony_ci		/* min & max buffer values for kdump case */
5262306a36Sopenharmony_ci		kbuf.buf_min = pbuf.buf_min = crashk_res.start;
5362306a36Sopenharmony_ci		kbuf.buf_max = pbuf.buf_max =
5462306a36Sopenharmony_ci				((crashk_res.end < ppc64_rma_size) ?
5562306a36Sopenharmony_ci				 crashk_res.end : (ppc64_rma_size - 1));
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	ret = kexec_elf_load(image, &ehdr, &elf_info, &kbuf, &kernel_load_addr);
5962306a36Sopenharmony_ci	if (ret)
6062306a36Sopenharmony_ci		goto out;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	pr_debug("Loaded the kernel at 0x%lx\n", kernel_load_addr);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	ret = kexec_load_purgatory(image, &pbuf);
6562306a36Sopenharmony_ci	if (ret) {
6662306a36Sopenharmony_ci		pr_err("Loading purgatory failed.\n");
6762306a36Sopenharmony_ci		goto out;
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	pr_debug("Loaded purgatory at 0x%lx\n", pbuf.mem);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* Load additional segments needed for panic kernel */
7362306a36Sopenharmony_ci	if (image->type == KEXEC_TYPE_CRASH) {
7462306a36Sopenharmony_ci		ret = load_crashdump_segments_ppc64(image, &kbuf);
7562306a36Sopenharmony_ci		if (ret) {
7662306a36Sopenharmony_ci			pr_err("Failed to load kdump kernel segments\n");
7762306a36Sopenharmony_ci			goto out;
7862306a36Sopenharmony_ci		}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci		/* Setup cmdline for kdump kernel case */
8162306a36Sopenharmony_ci		modified_cmdline = setup_kdump_cmdline(image, cmdline,
8262306a36Sopenharmony_ci						       cmdline_len);
8362306a36Sopenharmony_ci		if (!modified_cmdline) {
8462306a36Sopenharmony_ci			pr_err("Setting up cmdline for kdump kernel failed\n");
8562306a36Sopenharmony_ci			ret = -EINVAL;
8662306a36Sopenharmony_ci			goto out;
8762306a36Sopenharmony_ci		}
8862306a36Sopenharmony_ci		cmdline = modified_cmdline;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (initrd != NULL) {
9262306a36Sopenharmony_ci		kbuf.buffer = initrd;
9362306a36Sopenharmony_ci		kbuf.bufsz = kbuf.memsz = initrd_len;
9462306a36Sopenharmony_ci		kbuf.buf_align = PAGE_SIZE;
9562306a36Sopenharmony_ci		kbuf.top_down = false;
9662306a36Sopenharmony_ci		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
9762306a36Sopenharmony_ci		ret = kexec_add_buffer(&kbuf);
9862306a36Sopenharmony_ci		if (ret)
9962306a36Sopenharmony_ci			goto out;
10062306a36Sopenharmony_ci		initrd_load_addr = kbuf.mem;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		pr_debug("Loaded initrd at 0x%lx\n", initrd_load_addr);
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	fdt = of_kexec_alloc_and_setup_fdt(image, initrd_load_addr,
10662306a36Sopenharmony_ci					   initrd_len, cmdline,
10762306a36Sopenharmony_ci					   kexec_extra_fdt_size_ppc64(image));
10862306a36Sopenharmony_ci	if (!fdt) {
10962306a36Sopenharmony_ci		pr_err("Error setting up the new device tree.\n");
11062306a36Sopenharmony_ci		ret = -EINVAL;
11162306a36Sopenharmony_ci		goto out;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	ret = setup_new_fdt_ppc64(image, fdt, initrd_load_addr,
11562306a36Sopenharmony_ci				  initrd_len, cmdline);
11662306a36Sopenharmony_ci	if (ret)
11762306a36Sopenharmony_ci		goto out_free_fdt;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	fdt_pack(fdt);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	kbuf.buffer = fdt;
12262306a36Sopenharmony_ci	kbuf.bufsz = kbuf.memsz = fdt_totalsize(fdt);
12362306a36Sopenharmony_ci	kbuf.buf_align = PAGE_SIZE;
12462306a36Sopenharmony_ci	kbuf.top_down = true;
12562306a36Sopenharmony_ci	kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
12662306a36Sopenharmony_ci	ret = kexec_add_buffer(&kbuf);
12762306a36Sopenharmony_ci	if (ret)
12862306a36Sopenharmony_ci		goto out_free_fdt;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* FDT will be freed in arch_kimage_file_post_load_cleanup */
13162306a36Sopenharmony_ci	image->arch.fdt = fdt;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	fdt_load_addr = kbuf.mem;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	pr_debug("Loaded device tree at 0x%lx\n", fdt_load_addr);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	slave_code = elf_info.buffer + elf_info.proghdrs[0].p_offset;
13862306a36Sopenharmony_ci	ret = setup_purgatory_ppc64(image, slave_code, fdt, kernel_load_addr,
13962306a36Sopenharmony_ci				    fdt_load_addr);
14062306a36Sopenharmony_ci	if (ret)
14162306a36Sopenharmony_ci		pr_err("Error setting up the purgatory.\n");
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	goto out;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ciout_free_fdt:
14662306a36Sopenharmony_ci	kvfree(fdt);
14762306a36Sopenharmony_ciout:
14862306a36Sopenharmony_ci	kfree(modified_cmdline);
14962306a36Sopenharmony_ci	kexec_free_elf_info(&elf_info);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return ret ? ERR_PTR(ret) : NULL;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ciconst struct kexec_file_ops kexec_elf64_ops = {
15562306a36Sopenharmony_ci	.probe = kexec_elf_probe,
15662306a36Sopenharmony_ci	.load = elf64_load,
15762306a36Sopenharmony_ci};
158