18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * powerpc code to implement the kexec_file_load syscall
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2004  Adam Litke (agl@us.ibm.com)
68c2ecf20Sopenharmony_ci * Copyright (C) 2004  IBM Corp.
78c2ecf20Sopenharmony_ci * Copyright (C) 2004,2005  Milton D Miller II, IBM Corporation
88c2ecf20Sopenharmony_ci * Copyright (C) 2005  R Sharada (sharada@in.ibm.com)
98c2ecf20Sopenharmony_ci * Copyright (C) 2006  Mohan Kumar M (mohan@in.ibm.com)
108c2ecf20Sopenharmony_ci * Copyright (C) 2016  IBM Corporation
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * Based on kexec-tools' kexec-elf-ppc64.c, fs2dt.c.
138c2ecf20Sopenharmony_ci * Heavily modified for the kernel by
148c2ecf20Sopenharmony_ci * Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>.
158c2ecf20Sopenharmony_ci */
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/kexec.h>
198c2ecf20Sopenharmony_ci#include <linux/of_fdt.h>
208c2ecf20Sopenharmony_ci#include <linux/libfdt.h>
218c2ecf20Sopenharmony_ci#include <asm/setup.h>
228c2ecf20Sopenharmony_ci#include <asm/ima.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define SLAVE_CODE_SIZE		256	/* First 0x100 bytes */
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/**
278c2ecf20Sopenharmony_ci * setup_kdump_cmdline - Prepend "elfcorehdr=<addr> " to command line
288c2ecf20Sopenharmony_ci *                       of kdump kernel for exporting the core.
298c2ecf20Sopenharmony_ci * @image:               Kexec image
308c2ecf20Sopenharmony_ci * @cmdline:             Command line parameters to update.
318c2ecf20Sopenharmony_ci * @cmdline_len:         Length of the cmdline parameters.
328c2ecf20Sopenharmony_ci *
338c2ecf20Sopenharmony_ci * kdump segment must be setup before calling this function.
348c2ecf20Sopenharmony_ci *
358c2ecf20Sopenharmony_ci * Returns new cmdline buffer for kdump kernel on success, NULL otherwise.
368c2ecf20Sopenharmony_ci */
378c2ecf20Sopenharmony_cichar *setup_kdump_cmdline(struct kimage *image, char *cmdline,
388c2ecf20Sopenharmony_ci			  unsigned long cmdline_len)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	int elfcorehdr_strlen;
418c2ecf20Sopenharmony_ci	char *cmdline_ptr;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	cmdline_ptr = kzalloc(COMMAND_LINE_SIZE, GFP_KERNEL);
448c2ecf20Sopenharmony_ci	if (!cmdline_ptr)
458c2ecf20Sopenharmony_ci		return NULL;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	elfcorehdr_strlen = sprintf(cmdline_ptr, "elfcorehdr=0x%lx ",
488c2ecf20Sopenharmony_ci				    image->arch.elfcorehdr_addr);
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (elfcorehdr_strlen + cmdline_len > COMMAND_LINE_SIZE) {
518c2ecf20Sopenharmony_ci		pr_err("Appending elfcorehdr=<addr> exceeds cmdline size\n");
528c2ecf20Sopenharmony_ci		kfree(cmdline_ptr);
538c2ecf20Sopenharmony_ci		return NULL;
548c2ecf20Sopenharmony_ci	}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	memcpy(cmdline_ptr + elfcorehdr_strlen, cmdline, cmdline_len);
578c2ecf20Sopenharmony_ci	// Ensure it's nul terminated
588c2ecf20Sopenharmony_ci	cmdline_ptr[COMMAND_LINE_SIZE - 1] = '\0';
598c2ecf20Sopenharmony_ci	return cmdline_ptr;
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci/**
638c2ecf20Sopenharmony_ci * setup_purgatory - initialize the purgatory's global variables
648c2ecf20Sopenharmony_ci * @image:		kexec image.
658c2ecf20Sopenharmony_ci * @slave_code:		Slave code for the purgatory.
668c2ecf20Sopenharmony_ci * @fdt:		Flattened device tree for the next kernel.
678c2ecf20Sopenharmony_ci * @kernel_load_addr:	Address where the kernel is loaded.
688c2ecf20Sopenharmony_ci * @fdt_load_addr:	Address where the flattened device tree is loaded.
698c2ecf20Sopenharmony_ci *
708c2ecf20Sopenharmony_ci * Return: 0 on success, or negative errno on error.
718c2ecf20Sopenharmony_ci */
728c2ecf20Sopenharmony_ciint setup_purgatory(struct kimage *image, const void *slave_code,
738c2ecf20Sopenharmony_ci		    const void *fdt, unsigned long kernel_load_addr,
748c2ecf20Sopenharmony_ci		    unsigned long fdt_load_addr)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	unsigned int *slave_code_buf, master_entry;
778c2ecf20Sopenharmony_ci	int ret;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	slave_code_buf = kmalloc(SLAVE_CODE_SIZE, GFP_KERNEL);
808c2ecf20Sopenharmony_ci	if (!slave_code_buf)
818c2ecf20Sopenharmony_ci		return -ENOMEM;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/* Get the slave code from the new kernel and put it in purgatory. */
848c2ecf20Sopenharmony_ci	ret = kexec_purgatory_get_set_symbol(image, "purgatory_start",
858c2ecf20Sopenharmony_ci					     slave_code_buf, SLAVE_CODE_SIZE,
868c2ecf20Sopenharmony_ci					     true);
878c2ecf20Sopenharmony_ci	if (ret) {
888c2ecf20Sopenharmony_ci		kfree(slave_code_buf);
898c2ecf20Sopenharmony_ci		return ret;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	master_entry = slave_code_buf[0];
938c2ecf20Sopenharmony_ci	memcpy(slave_code_buf, slave_code, SLAVE_CODE_SIZE);
948c2ecf20Sopenharmony_ci	slave_code_buf[0] = master_entry;
958c2ecf20Sopenharmony_ci	ret = kexec_purgatory_get_set_symbol(image, "purgatory_start",
968c2ecf20Sopenharmony_ci					     slave_code_buf, SLAVE_CODE_SIZE,
978c2ecf20Sopenharmony_ci					     false);
988c2ecf20Sopenharmony_ci	kfree(slave_code_buf);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	ret = kexec_purgatory_get_set_symbol(image, "kernel", &kernel_load_addr,
1018c2ecf20Sopenharmony_ci					     sizeof(kernel_load_addr), false);
1028c2ecf20Sopenharmony_ci	if (ret)
1038c2ecf20Sopenharmony_ci		return ret;
1048c2ecf20Sopenharmony_ci	ret = kexec_purgatory_get_set_symbol(image, "dt_offset", &fdt_load_addr,
1058c2ecf20Sopenharmony_ci					     sizeof(fdt_load_addr), false);
1068c2ecf20Sopenharmony_ci	if (ret)
1078c2ecf20Sopenharmony_ci		return ret;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return 0;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci/**
1138c2ecf20Sopenharmony_ci * delete_fdt_mem_rsv - delete memory reservation with given address and size
1148c2ecf20Sopenharmony_ci *
1158c2ecf20Sopenharmony_ci * Return: 0 on success, or negative errno on error.
1168c2ecf20Sopenharmony_ci */
1178c2ecf20Sopenharmony_ciint delete_fdt_mem_rsv(void *fdt, unsigned long start, unsigned long size)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	int i, ret, num_rsvs = fdt_num_mem_rsv(fdt);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	for (i = 0; i < num_rsvs; i++) {
1228c2ecf20Sopenharmony_ci		uint64_t rsv_start, rsv_size;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci		ret = fdt_get_mem_rsv(fdt, i, &rsv_start, &rsv_size);
1258c2ecf20Sopenharmony_ci		if (ret) {
1268c2ecf20Sopenharmony_ci			pr_err("Malformed device tree.\n");
1278c2ecf20Sopenharmony_ci			return -EINVAL;
1288c2ecf20Sopenharmony_ci		}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci		if (rsv_start == start && rsv_size == size) {
1318c2ecf20Sopenharmony_ci			ret = fdt_del_mem_rsv(fdt, i);
1328c2ecf20Sopenharmony_ci			if (ret) {
1338c2ecf20Sopenharmony_ci				pr_err("Error deleting device tree reservation.\n");
1348c2ecf20Sopenharmony_ci				return -EINVAL;
1358c2ecf20Sopenharmony_ci			}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci			return 0;
1388c2ecf20Sopenharmony_ci		}
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	return -ENOENT;
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci/*
1458c2ecf20Sopenharmony_ci * setup_new_fdt - modify /chosen and memory reservation for the next kernel
1468c2ecf20Sopenharmony_ci * @image:		kexec image being loaded.
1478c2ecf20Sopenharmony_ci * @fdt:		Flattened device tree for the next kernel.
1488c2ecf20Sopenharmony_ci * @initrd_load_addr:	Address where the next initrd will be loaded.
1498c2ecf20Sopenharmony_ci * @initrd_len:		Size of the next initrd, or 0 if there will be none.
1508c2ecf20Sopenharmony_ci * @cmdline:		Command line for the next kernel, or NULL if there will
1518c2ecf20Sopenharmony_ci *			be none.
1528c2ecf20Sopenharmony_ci *
1538c2ecf20Sopenharmony_ci * Return: 0 on success, or negative errno on error.
1548c2ecf20Sopenharmony_ci */
1558c2ecf20Sopenharmony_ciint setup_new_fdt(const struct kimage *image, void *fdt,
1568c2ecf20Sopenharmony_ci		  unsigned long initrd_load_addr, unsigned long initrd_len,
1578c2ecf20Sopenharmony_ci		  const char *cmdline)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	int ret, chosen_node;
1608c2ecf20Sopenharmony_ci	const void *prop;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	/* Remove memory reservation for the current device tree. */
1638c2ecf20Sopenharmony_ci	ret = delete_fdt_mem_rsv(fdt, __pa(initial_boot_params),
1648c2ecf20Sopenharmony_ci				 fdt_totalsize(initial_boot_params));
1658c2ecf20Sopenharmony_ci	if (ret == 0)
1668c2ecf20Sopenharmony_ci		pr_debug("Removed old device tree reservation.\n");
1678c2ecf20Sopenharmony_ci	else if (ret != -ENOENT)
1688c2ecf20Sopenharmony_ci		return ret;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	chosen_node = fdt_path_offset(fdt, "/chosen");
1718c2ecf20Sopenharmony_ci	if (chosen_node == -FDT_ERR_NOTFOUND) {
1728c2ecf20Sopenharmony_ci		chosen_node = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"),
1738c2ecf20Sopenharmony_ci					      "chosen");
1748c2ecf20Sopenharmony_ci		if (chosen_node < 0) {
1758c2ecf20Sopenharmony_ci			pr_err("Error creating /chosen.\n");
1768c2ecf20Sopenharmony_ci			return -EINVAL;
1778c2ecf20Sopenharmony_ci		}
1788c2ecf20Sopenharmony_ci	} else if (chosen_node < 0) {
1798c2ecf20Sopenharmony_ci		pr_err("Malformed device tree: error reading /chosen.\n");
1808c2ecf20Sopenharmony_ci		return -EINVAL;
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	/* Did we boot using an initrd? */
1848c2ecf20Sopenharmony_ci	prop = fdt_getprop(fdt, chosen_node, "linux,initrd-start", NULL);
1858c2ecf20Sopenharmony_ci	if (prop) {
1868c2ecf20Sopenharmony_ci		uint64_t tmp_start, tmp_end, tmp_size;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci		tmp_start = fdt64_to_cpu(*((const fdt64_t *) prop));
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci		prop = fdt_getprop(fdt, chosen_node, "linux,initrd-end", NULL);
1918c2ecf20Sopenharmony_ci		if (!prop) {
1928c2ecf20Sopenharmony_ci			pr_err("Malformed device tree.\n");
1938c2ecf20Sopenharmony_ci			return -EINVAL;
1948c2ecf20Sopenharmony_ci		}
1958c2ecf20Sopenharmony_ci		tmp_end = fdt64_to_cpu(*((const fdt64_t *) prop));
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci		/*
1988c2ecf20Sopenharmony_ci		 * kexec reserves exact initrd size, while firmware may
1998c2ecf20Sopenharmony_ci		 * reserve a multiple of PAGE_SIZE, so check for both.
2008c2ecf20Sopenharmony_ci		 */
2018c2ecf20Sopenharmony_ci		tmp_size = tmp_end - tmp_start;
2028c2ecf20Sopenharmony_ci		ret = delete_fdt_mem_rsv(fdt, tmp_start, tmp_size);
2038c2ecf20Sopenharmony_ci		if (ret == -ENOENT)
2048c2ecf20Sopenharmony_ci			ret = delete_fdt_mem_rsv(fdt, tmp_start,
2058c2ecf20Sopenharmony_ci						 round_up(tmp_size, PAGE_SIZE));
2068c2ecf20Sopenharmony_ci		if (ret == 0)
2078c2ecf20Sopenharmony_ci			pr_debug("Removed old initrd reservation.\n");
2088c2ecf20Sopenharmony_ci		else if (ret != -ENOENT)
2098c2ecf20Sopenharmony_ci			return ret;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci		/* If there's no new initrd, delete the old initrd's info. */
2128c2ecf20Sopenharmony_ci		if (initrd_len == 0) {
2138c2ecf20Sopenharmony_ci			ret = fdt_delprop(fdt, chosen_node,
2148c2ecf20Sopenharmony_ci					  "linux,initrd-start");
2158c2ecf20Sopenharmony_ci			if (ret) {
2168c2ecf20Sopenharmony_ci				pr_err("Error deleting linux,initrd-start.\n");
2178c2ecf20Sopenharmony_ci				return -EINVAL;
2188c2ecf20Sopenharmony_ci			}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci			ret = fdt_delprop(fdt, chosen_node, "linux,initrd-end");
2218c2ecf20Sopenharmony_ci			if (ret) {
2228c2ecf20Sopenharmony_ci				pr_err("Error deleting linux,initrd-end.\n");
2238c2ecf20Sopenharmony_ci				return -EINVAL;
2248c2ecf20Sopenharmony_ci			}
2258c2ecf20Sopenharmony_ci		}
2268c2ecf20Sopenharmony_ci	}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	if (initrd_len) {
2298c2ecf20Sopenharmony_ci		ret = fdt_setprop_u64(fdt, chosen_node,
2308c2ecf20Sopenharmony_ci				      "linux,initrd-start",
2318c2ecf20Sopenharmony_ci				      initrd_load_addr);
2328c2ecf20Sopenharmony_ci		if (ret < 0)
2338c2ecf20Sopenharmony_ci			goto err;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci		/* initrd-end is the first address after the initrd image. */
2368c2ecf20Sopenharmony_ci		ret = fdt_setprop_u64(fdt, chosen_node, "linux,initrd-end",
2378c2ecf20Sopenharmony_ci				      initrd_load_addr + initrd_len);
2388c2ecf20Sopenharmony_ci		if (ret < 0)
2398c2ecf20Sopenharmony_ci			goto err;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci		ret = fdt_add_mem_rsv(fdt, initrd_load_addr, initrd_len);
2428c2ecf20Sopenharmony_ci		if (ret) {
2438c2ecf20Sopenharmony_ci			pr_err("Error reserving initrd memory: %s\n",
2448c2ecf20Sopenharmony_ci			       fdt_strerror(ret));
2458c2ecf20Sopenharmony_ci			return -EINVAL;
2468c2ecf20Sopenharmony_ci		}
2478c2ecf20Sopenharmony_ci	}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	if (cmdline != NULL) {
2508c2ecf20Sopenharmony_ci		ret = fdt_setprop_string(fdt, chosen_node, "bootargs", cmdline);
2518c2ecf20Sopenharmony_ci		if (ret < 0)
2528c2ecf20Sopenharmony_ci			goto err;
2538c2ecf20Sopenharmony_ci	} else {
2548c2ecf20Sopenharmony_ci		ret = fdt_delprop(fdt, chosen_node, "bootargs");
2558c2ecf20Sopenharmony_ci		if (ret && ret != -FDT_ERR_NOTFOUND) {
2568c2ecf20Sopenharmony_ci			pr_err("Error deleting bootargs.\n");
2578c2ecf20Sopenharmony_ci			return -EINVAL;
2588c2ecf20Sopenharmony_ci		}
2598c2ecf20Sopenharmony_ci	}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	if (image->type == KEXEC_TYPE_CRASH) {
2628c2ecf20Sopenharmony_ci		/*
2638c2ecf20Sopenharmony_ci		 * Avoid elfcorehdr from being stomped on in kdump kernel by
2648c2ecf20Sopenharmony_ci		 * setting up memory reserve map.
2658c2ecf20Sopenharmony_ci		 */
2668c2ecf20Sopenharmony_ci		ret = fdt_add_mem_rsv(fdt, image->arch.elfcorehdr_addr,
2678c2ecf20Sopenharmony_ci				      image->arch.elf_headers_sz);
2688c2ecf20Sopenharmony_ci		if (ret) {
2698c2ecf20Sopenharmony_ci			pr_err("Error reserving elfcorehdr memory: %s\n",
2708c2ecf20Sopenharmony_ci			       fdt_strerror(ret));
2718c2ecf20Sopenharmony_ci			goto err;
2728c2ecf20Sopenharmony_ci		}
2738c2ecf20Sopenharmony_ci	}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	ret = setup_ima_buffer(image, fdt, chosen_node);
2768c2ecf20Sopenharmony_ci	if (ret) {
2778c2ecf20Sopenharmony_ci		pr_err("Error setting up the new device tree.\n");
2788c2ecf20Sopenharmony_ci		return ret;
2798c2ecf20Sopenharmony_ci	}
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	ret = fdt_setprop(fdt, chosen_node, "linux,booted-from-kexec", NULL, 0);
2828c2ecf20Sopenharmony_ci	if (ret)
2838c2ecf20Sopenharmony_ci		goto err;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	return 0;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cierr:
2888c2ecf20Sopenharmony_ci	pr_err("Error setting up the new device tree.\n");
2898c2ecf20Sopenharmony_ci	return -EINVAL;
2908c2ecf20Sopenharmony_ci}
291