kexec_file_load needs to set up the device tree that will be used by the next kernel and check whether it provides a console that can be used by the purgatory. [akpm at linux-foundation.org: coding-style fixes] Signed-off-by: Thiago Jung Bauermann <bauerman at linux.vnet.ibm.com> Signed-off-by: Andrew Morton <akpm at linux-foundation.org> --- arch/powerpc/include/asm/kexec.h | 3 + arch/powerpc/kernel/machine_kexec_64.c | 221 +++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/arch/powerpc/include/asm/kexec.h b/arch/powerpc/include/asm/kexec.h index 0c7e020d935a..73f88b5f9bd1 100644 --- a/arch/powerpc/include/asm/kexec.h +++ b/arch/powerpc/include/asm/kexec.h @@ -96,6 +96,9 @@ int setup_purgatory(struct kimage *image, const void *slave_code, const void *fdt, unsigned long kernel_load_addr, unsigned long fdt_load_addr, unsigned long stack_top, int debug); +int setup_new_fdt(void *fdt, unsigned long initrd_load_addr, + unsigned long initrd_len, const char *cmdline); +bool find_debug_console(const void *fdt); #endif /* CONFIG_KEXEC_FILE */ #else /* !CONFIG_KEXEC_CORE */ diff --git a/arch/powerpc/kernel/machine_kexec_64.c b/arch/powerpc/kernel/machine_kexec_64.c index 1e678dc5096a..31c5090705e0 100644 --- a/arch/powerpc/kernel/machine_kexec_64.c +++ b/arch/powerpc/kernel/machine_kexec_64.c @@ -683,4 +683,225 @@ int setup_purgatory(struct kimage *image, const void *slave_code, return 0; } +/* + * setup_new_fdt() - modify /chosen and memory reservation for the next kernel + * @fdt: + * @initrd_load_addr: Address where the next initrd will be loaded. + * @initrd_len: Size of the next initrd, or 0 if there will be none. + * @cmdline: Command line for the next kernel, or NULL if there will + * be none. + * + * Return: 0 on success, or negative errno on error. + */ +int setup_new_fdt(void *fdt, unsigned long initrd_load_addr, + unsigned long initrd_len, const char *cmdline) +{ + uint64_t oldfdt_addr; + int i, ret, chosen_node; + const void *prop; + + /* Remove memory reservation for the current device tree. */ + oldfdt_addr = __pa(initial_boot_params); + for (i = 0; i < fdt_num_mem_rsv(fdt); i++) { + uint64_t rsv_start, rsv_size; + + ret = fdt_get_mem_rsv(fdt, i, &rsv_start, &rsv_size); + if (ret) { + pr_err("Malformed device tree.\n"); + return -EINVAL; + } + + if (rsv_start == oldfdt_addr && + rsv_size == fdt_totalsize(initial_boot_params)) { + ret = fdt_del_mem_rsv(fdt, i); + if (ret) { + pr_err("Error deleting fdt reservation.\n"); + return -EINVAL; + } + + pr_debug("Removed old device tree reservation.\n"); + break; + } + } + + chosen_node = fdt_path_offset(fdt, "/chosen"); + if (chosen_node == -FDT_ERR_NOTFOUND) { + chosen_node = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"), + "chosen"); + if (chosen_node < 0) { + pr_err("Error creating /chosen.\n"); + return -EINVAL; + } + } else if (chosen_node < 0) { + pr_err("Malformed device tree: error reading /chosen.\n"); + return -EINVAL; + } + + /* Did we boot using an initrd? */ + prop = fdt_getprop(fdt, chosen_node, "linux,initrd-start", NULL); + if (prop) { + uint64_t tmp_start, tmp_end, tmp_size, tmp_sizepg; + + tmp_start = fdt64_to_cpu(*((const fdt64_t *) prop)); + + prop = fdt_getprop(fdt, chosen_node, "linux,initrd-end", NULL); + if (!prop) { + pr_err("Malformed device tree.\n"); + return -EINVAL; + } + tmp_end = fdt64_to_cpu(*((const fdt64_t *) prop)); + + /* + * kexec reserves exact initrd size, while firmware may + * reserve a multiple of PAGE_SIZE, so check for both. + */ + tmp_size = tmp_end - tmp_start; + tmp_sizepg = round_up(tmp_size, PAGE_SIZE); + + /* Remove memory reservation for the current initrd. */ + for (i = 0; i < fdt_num_mem_rsv(fdt); i++) { + uint64_t rsv_start, rsv_size; + + ret = fdt_get_mem_rsv(fdt, i, &rsv_start, &rsv_size); + if (ret) { + pr_err("Malformed device tree.\n"); + return -EINVAL; + } + + if (rsv_start == tmp_start && + (rsv_size == tmp_size || rsv_size == tmp_sizepg)) { + ret = fdt_del_mem_rsv(fdt, i); + if (ret) { + pr_err("Error deleting fdt reservation.\n"); + return -EINVAL; + } + pr_debug("Removed old initrd reservation.\n"); + + break; + } + } + + /* If there's no new initrd, delete the old initrd's info. */ + if (initrd_len == 0) { + ret = fdt_delprop(fdt, chosen_node, + "linux,initrd-start"); + if (ret) { + pr_err("Error deleting linux,initrd-start.\n"); + return -EINVAL; + } + + ret = fdt_delprop(fdt, chosen_node, "linux,initrd-end"); + if (ret) { + pr_err("Error deleting linux,initrd-end.\n"); + return -EINVAL; + } + } + } + + if (initrd_len) { + ret = fdt_setprop_u64(fdt, chosen_node, + "linux,initrd-start", + initrd_load_addr); + if (ret < 0) { + pr_err("Error setting up the new device tree.\n"); + return -EINVAL; + } + + /* initrd-end is the first address after the initrd image. */ + ret = fdt_setprop_u64(fdt, chosen_node, "linux,initrd-end", + initrd_load_addr + initrd_len); + if (ret < 0) { + pr_err("Error setting up the new device tree.\n"); + return -EINVAL; + } + + ret = fdt_add_mem_rsv(fdt, initrd_load_addr, initrd_len); + if (ret) { + pr_err("Error reserving initrd memory: %s\n", + fdt_strerror(ret)); + return -EINVAL; + } + } + + if (cmdline != NULL) { + ret = fdt_setprop_string(fdt, chosen_node, "bootargs", cmdline); + if (ret < 0) { + pr_err("Error setting up the new device tree.\n"); + return -EINVAL; + } + } else { + ret = fdt_delprop(fdt, chosen_node, "bootargs"); + if (ret && ret != -FDT_ERR_NOTFOUND) { + pr_err("Error deleting bootargs.\n"); + return -EINVAL; + } + } + + ret = fdt_setprop(fdt, chosen_node, "linux,booted-from-kexec", NULL, 0); + if (ret) { + pr_err("Error setting up the new device tree.\n"); + return -EINVAL; + } + + return 0; +} + +/** + * find_debug_console() - find out whether there is a console for the purgatory + * @fdt: Flattened device tree to search. + */ +bool find_debug_console(const void *fdt) +{ + int len; + int console_node, chosen_node; + const void *prop, *colon; + + chosen_node = fdt_path_offset(fdt, "/chosen"); + if (chosen_node < 0) { + pr_err("Malformed device tree: /chosen not found.\n"); + return false; + } + + prop = fdt_getprop(fdt, chosen_node, "stdout-path", &len); + if (prop == NULL) { + if (len == -FDT_ERR_NOTFOUND) { + prop = fdt_getprop(fdt, chosen_node, + "linux,stdout-path", &len); + if (prop == NULL) { + pr_debug("Unable to find [linux,]stdout-path.\n"); + return false; + } + } else { + pr_debug("Error finding console: %s\n", + fdt_strerror(len)); + return false; + } + } + + /* + * stdout-path can have a ':' separating the path from device-specific + * information, so we should only consider what's before it. + */ + colon = strchr(prop, ':'); + if (colon != NULL) + len = colon - prop; + else + len -= 1; /* Ignore the terminating NUL. */ + + console_node = fdt_path_offset_namelen(fdt, prop, len); + if (console_node < 0) { + pr_debug("Error finding console: %s\n", + fdt_strerror(console_node)); + return false; + } + + if (fdt_node_check_compatible(fdt, console_node, "hvterm1") == 0) + return true; + else if (fdt_node_check_compatible(fdt, console_node, + "hvterm-protocol") == 0) + return true; + + return false; +} + #endif /* CONFIG_KEXEC_FILE */ -- 1.9.1