summaryrefslogtreecommitdiffstats
path: root/kexec/arch/ppc64/kexec-elf-ppc64.c
diff options
context:
space:
mode:
Diffstat (limited to 'kexec/arch/ppc64/kexec-elf-ppc64.c')
-rw-r--r--kexec/arch/ppc64/kexec-elf-ppc64.c496
1 files changed, 496 insertions, 0 deletions
diff --git a/kexec/arch/ppc64/kexec-elf-ppc64.c b/kexec/arch/ppc64/kexec-elf-ppc64.c
new file mode 100644
index 0000000..01d045f
--- /dev/null
+++ b/kexec/arch/ppc64/kexec-elf-ppc64.c
@@ -0,0 +1,496 @@
+/*
+ * kexec: Linux boots Linux
+ *
+ * Copyright (C) 2004 Adam Litke (agl@us.ibm.com)
+ * Copyright (C) 2004 IBM Corp.
+ * Copyright (C) 2005 R Sharada (sharada@in.ibm.com)
+ * Copyright (C) 2006 Mohan Kumar M (mohan@in.ibm.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License).
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <linux/elf.h>
+#include "../../kexec.h"
+#include "../../kexec-elf.h"
+#include "../../kexec-syscall.h"
+#include "kexec-ppc64.h"
+#include "../../fs2dt.h"
+#include "crashdump-ppc64.h"
+#include <libfdt.h>
+#include <arch/fdt.h>
+#include <arch/options.h>
+
+uint64_t initrd_base, initrd_size;
+unsigned char reuse_initrd = 0;
+const char *ramdisk;
+
+int elf_ppc64_probe(const char *buf, off_t len)
+{
+ struct mem_ehdr ehdr;
+ int result;
+ result = build_elf_exec_info(buf, len, &ehdr, 0);
+ if (result < 0) {
+ goto out;
+ }
+
+ /* Verify the architecuture specific bits */
+ if ((ehdr.e_machine != EM_PPC64) && (ehdr.e_machine != EM_PPC)) {
+ /* for a different architecture */
+ result = -1;
+ goto out;
+ }
+ result = 0;
+ out:
+ free_elf_info(&ehdr);
+ return result;
+}
+
+void arch_reuse_initrd(void)
+{
+ reuse_initrd = 1;
+}
+
+static int read_prop(char *name, void *value, size_t len)
+{
+ int fd;
+ size_t rlen;
+
+ fd = open(name, O_RDONLY);
+ if (fd == -1)
+ return -1;
+
+ rlen = read(fd, value, len);
+ if (rlen < 0)
+ fprintf(stderr, "Warning : Can't read %s : %s",
+ name, strerror(errno));
+ else if (rlen != len)
+ fprintf(stderr, "Warning : short read from %s", name);
+
+ close(fd);
+ return 0;
+}
+
+static int elf_ppc64_load_file(int argc, char **argv, struct kexec_info *info)
+{
+ int ret = 0;
+ char *cmdline, *dtb;
+ char *append_cmdline = NULL;
+ char *reuse_cmdline = NULL;
+ int opt, cmdline_len = 0;
+
+ /* See options.h -- add any more there, too. */
+ static const struct option options[] = {
+ KEXEC_ARCH_OPTIONS
+ { "command-line", 1, NULL, OPT_APPEND },
+ { "append", 1, NULL, OPT_APPEND },
+ { "ramdisk", 1, NULL, OPT_RAMDISK },
+ { "initrd", 1, NULL, OPT_RAMDISK },
+ { "devicetreeblob", 1, NULL, OPT_DEVICETREEBLOB },
+ { "dtb", 1, NULL, OPT_DEVICETREEBLOB },
+ { "args-linux", 0, NULL, OPT_ARGS_IGNORE },
+ { "reuse-cmdline", 0, NULL, OPT_REUSE_CMDLINE},
+ { 0, 0, NULL, 0 },
+ };
+
+ static const char short_options[] = KEXEC_OPT_STR "";
+
+ /* Parse command line arguments */
+ cmdline = 0;
+ dtb = 0;
+ ramdisk = 0;
+
+ while ((opt = getopt_long(argc, argv, short_options,
+ options, 0)) != -1) {
+ switch (opt) {
+ default:
+ /* Ignore core options */
+ if (opt < OPT_ARCH_MAX)
+ break;
+ case OPT_APPEND:
+ append_cmdline = optarg;
+ break;
+ case OPT_RAMDISK:
+ ramdisk = optarg;
+ break;
+ case OPT_DEVICETREEBLOB:
+ dtb = optarg;
+ break;
+ case OPT_ARGS_IGNORE:
+ break;
+ case OPT_REUSE_CMDLINE:
+ reuse_cmdline = get_command_line();
+ break;
+ }
+ }
+
+ if (dtb)
+ die("--dtb not supported while using --kexec-file-syscall.\n");
+
+ if (reuse_initrd)
+ die("--reuseinitrd not supported with --kexec-file-syscall.\n");
+
+ cmdline = concat_cmdline(reuse_cmdline, append_cmdline);
+ if (!reuse_cmdline)
+ free(reuse_cmdline);
+
+ if (cmdline) {
+ cmdline_len = strlen(cmdline) + 1;
+ } else {
+ cmdline = strdup("\0");
+ cmdline_len = 1;
+ }
+
+ if (ramdisk) {
+ info->initrd_fd = open(ramdisk, O_RDONLY);
+ if (info->initrd_fd == -1) {
+ fprintf(stderr, "Could not open initrd file %s:%s\n",
+ ramdisk, strerror(errno));
+ ret = -1;
+ goto out;
+ }
+ }
+
+ info->command_line = cmdline;
+ info->command_line_len = cmdline_len;
+ return ret;
+out:
+ if (cmdline_len == 1)
+ free(cmdline);
+ return ret;
+}
+
+int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info)
+{
+ struct mem_ehdr ehdr;
+ char *cmdline, *modified_cmdline = NULL;
+ char *reuse_cmdline = NULL;
+ char *append_cmdline = NULL;
+ const char *devicetreeblob;
+ uint64_t max_addr, hole_addr;
+ char *seg_buf = NULL;
+ off_t seg_size = 0;
+ struct mem_phdr *phdr;
+ size_t size;
+#ifdef NEED_RESERVE_DTB
+ uint64_t *rsvmap_ptr;
+ struct bootblock *bb_ptr;
+#endif
+ int result, opt;
+ uint64_t my_kernel, my_dt_offset;
+ uint64_t my_opal_base = 0, my_opal_entry = 0;
+ unsigned int my_panic_kernel;
+ uint64_t my_stack, my_backup_start;
+ uint64_t toc_addr;
+ uint32_t my_run_at_load;
+ unsigned int slave_code[256/sizeof (unsigned int)], master_entry;
+
+ /* See options.h -- add any more there, too. */
+ static const struct option options[] = {
+ KEXEC_ARCH_OPTIONS
+ { "command-line", 1, NULL, OPT_APPEND },
+ { "append", 1, NULL, OPT_APPEND },
+ { "ramdisk", 1, NULL, OPT_RAMDISK },
+ { "initrd", 1, NULL, OPT_RAMDISK },
+ { "devicetreeblob", 1, NULL, OPT_DEVICETREEBLOB },
+ { "dtb", 1, NULL, OPT_DEVICETREEBLOB },
+ { "args-linux", 0, NULL, OPT_ARGS_IGNORE },
+ { "reuse-cmdline", 0, NULL, OPT_REUSE_CMDLINE},
+ { 0, 0, NULL, 0 },
+ };
+
+ static const char short_options[] = KEXEC_OPT_STR "";
+
+ if (info->file_mode)
+ return elf_ppc64_load_file(argc, argv, info);
+
+ /* Parse command line arguments */
+ initrd_base = 0;
+ initrd_size = 0;
+ cmdline = 0;
+ ramdisk = 0;
+ devicetreeblob = 0;
+ max_addr = 0xFFFFFFFFFFFFFFFFULL;
+ hole_addr = 0;
+
+ while ((opt = getopt_long(argc, argv, short_options,
+ options, 0)) != -1) {
+ switch (opt) {
+ default:
+ /* Ignore core options */
+ if (opt < OPT_ARCH_MAX)
+ break;
+ case OPT_APPEND:
+ append_cmdline = optarg;
+ break;
+ case OPT_RAMDISK:
+ ramdisk = optarg;
+ break;
+ case OPT_DEVICETREEBLOB:
+ devicetreeblob = optarg;
+ break;
+ case OPT_ARGS_IGNORE:
+ break;
+ case OPT_REUSE_CMDLINE:
+ reuse_cmdline = get_command_line();
+ break;
+ }
+ }
+
+ cmdline = concat_cmdline(reuse_cmdline, append_cmdline);
+ if (!reuse_cmdline)
+ free(reuse_cmdline);
+
+ if (!cmdline)
+ fprintf(stdout, "Warning: append= option is not passed. Using the first kernel root partition\n");
+
+ if (ramdisk && reuse_initrd)
+ die("Can't specify --ramdisk or --initrd with --reuseinitrd\n");
+
+ /* Need to append some command line parameters internally in case of
+ * taking crash dumps.
+ */
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ modified_cmdline = xmalloc(COMMAND_LINE_SIZE);
+ memset((void *)modified_cmdline, 0, COMMAND_LINE_SIZE);
+ if (cmdline) {
+ strncpy(modified_cmdline, cmdline, COMMAND_LINE_SIZE);
+ modified_cmdline[COMMAND_LINE_SIZE - 1] = '\0';
+ }
+ }
+
+ /* Parse the Elf file */
+ result = build_elf_exec_info(buf, len, &ehdr, 0);
+ if (result < 0) {
+ free_elf_info(&ehdr);
+ return result;
+ }
+
+ /* Load the Elf data. Physical load addresses in elf64 header do not
+ * show up correctly. Use user supplied address for now to patch the
+ * elf header
+ */
+
+ phdr = &ehdr.e_phdr[0];
+ size = phdr->p_filesz;
+ if (size > phdr->p_memsz)
+ size = phdr->p_memsz;
+
+ my_kernel = hole_addr = locate_hole(info, size, 0, 0, max_addr, 1);
+ ehdr.e_phdr[0].p_paddr = hole_addr;
+ result = elf_exec_load(&ehdr, info);
+ if (result < 0) {
+ free_elf_info(&ehdr);
+ return result;
+ }
+
+ /* If panic kernel is being loaded, additional segments need
+ * to be created.
+ */
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ result = load_crashdump_segments(info, modified_cmdline,
+ max_addr, 0);
+ if (result < 0)
+ return -1;
+ /* Use new command line. */
+ cmdline = modified_cmdline;
+ }
+
+ /* Add v2wrap to the current image */
+ elf_rel_build_load(info, &info->rhdr, purgatory,
+ purgatory_size, 0, max_addr, 1, 0);
+
+ /* Add a ram-disk to the current image
+ * Note: Add the ramdisk after elf_rel_build_load
+ */
+ if (ramdisk) {
+ if (devicetreeblob) {
+ fprintf(stderr,
+ "Can't use ramdisk with device tree blob input\n");
+ return -1;
+ }
+ seg_buf = slurp_file(ramdisk, &seg_size);
+ hole_addr = add_buffer(info, seg_buf, seg_size, seg_size,
+ 0, 0, max_addr, 1);
+ initrd_base = hole_addr;
+ initrd_size = seg_size;
+ } /* ramdisk */
+
+ if (devicetreeblob) {
+ /* Grab device tree from buffer */
+ seg_buf = slurp_file(devicetreeblob, &seg_size);
+ } else {
+ /* create from fs2dt */
+ create_flatten_tree(&seg_buf, &seg_size, cmdline);
+ }
+
+ result = fixup_dt(&seg_buf, &seg_size);
+ if (result < 0)
+ return result;
+
+ my_dt_offset = add_buffer(info, seg_buf, seg_size, seg_size,
+ 0, 0, max_addr, -1);
+
+#ifdef NEED_RESERVE_DTB
+ /* patch reserve map address for flattened device-tree
+ * find last entry (both 0) in the reserve mem list. Assume DT
+ * entry is before this one
+ */
+ bb_ptr = (struct bootblock *)(seg_buf);
+ rsvmap_ptr = (uint64_t *)(seg_buf + be32_to_cpu(bb_ptr->off_mem_rsvmap));
+ while (*rsvmap_ptr || *(rsvmap_ptr+1))
+ rsvmap_ptr += 2;
+ rsvmap_ptr -= 2;
+ *rsvmap_ptr = cpu_to_be64(my_dt_offset);
+ rsvmap_ptr++;
+ *rsvmap_ptr = cpu_to_be64((uint64_t)be32_to_cpu(bb_ptr->totalsize));
+#endif
+
+ if (read_prop("/proc/device-tree/ibm,opal/opal-base-address",
+ &my_opal_base, sizeof(my_opal_base)) == 0) {
+ my_opal_base = be64_to_cpu(my_opal_base);
+ elf_rel_set_symbol(&info->rhdr, "opal_base",
+ &my_opal_base, sizeof(my_opal_base));
+ }
+
+ if (read_prop("/proc/device-tree/ibm,opal/opal-entry-address",
+ &my_opal_entry, sizeof(my_opal_entry)) == 0) {
+ my_opal_entry = be64_to_cpu(my_opal_entry);
+ elf_rel_set_symbol(&info->rhdr, "opal_entry",
+ &my_opal_entry, sizeof(my_opal_entry));
+ }
+
+ /* Set kernel */
+ elf_rel_set_symbol(&info->rhdr, "kernel", &my_kernel, sizeof(my_kernel));
+
+ /* Set dt_offset */
+ elf_rel_set_symbol(&info->rhdr, "dt_offset", &my_dt_offset,
+ sizeof(my_dt_offset));
+
+ /* get slave code from new kernel, put in purgatory */
+ elf_rel_get_symbol(&info->rhdr, "purgatory_start", slave_code,
+ sizeof(slave_code));
+ master_entry = slave_code[0];
+ memcpy(slave_code, phdr->p_data, sizeof(slave_code));
+ slave_code[0] = master_entry;
+ elf_rel_set_symbol(&info->rhdr, "purgatory_start", slave_code,
+ sizeof(slave_code));
+
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ my_panic_kernel = 1;
+ /* Set panic flag */
+ elf_rel_set_symbol(&info->rhdr, "panic_kernel",
+ &my_panic_kernel, sizeof(my_panic_kernel));
+
+ /* Set backup address */
+ my_backup_start = info->backup_start;
+ elf_rel_set_symbol(&info->rhdr, "backup_start",
+ &my_backup_start, sizeof(my_backup_start));
+
+ /* Tell relocatable kernel to run at load address
+ * via word before slave code in purgatory
+ */
+
+ elf_rel_get_symbol(&info->rhdr, "run_at_load", &my_run_at_load,
+ sizeof(my_run_at_load));
+ if (my_run_at_load == KERNEL_RUN_AT_ZERO_MAGIC)
+ my_run_at_load = 1;
+ /* else it should be a fixed offset image */
+ elf_rel_set_symbol(&info->rhdr, "run_at_load", &my_run_at_load,
+ sizeof(my_run_at_load));
+ }
+
+ /* Set stack address */
+ my_stack = locate_hole(info, 16*1024, 0, 0, max_addr, 1);
+ my_stack += 16*1024;
+ elf_rel_set_symbol(&info->rhdr, "stack", &my_stack, sizeof(my_stack));
+
+ /* Set toc */
+ toc_addr = my_r2(&info->rhdr);
+ elf_rel_set_symbol(&info->rhdr, "my_toc", &toc_addr, sizeof(toc_addr));
+
+ /* Set debug */
+ elf_rel_set_symbol(&info->rhdr, "debug", &my_debug, sizeof(my_debug));
+
+ my_kernel = 0;
+ my_dt_offset = 0;
+ my_panic_kernel = 0;
+ my_backup_start = 0;
+ my_stack = 0;
+ toc_addr = 0;
+ my_run_at_load = 0;
+ my_debug = 0;
+ my_opal_base = 0;
+ my_opal_entry = 0;
+
+ elf_rel_get_symbol(&info->rhdr, "opal_base", &my_opal_base,
+ sizeof(my_opal_base));
+ elf_rel_get_symbol(&info->rhdr, "opal_entry", &my_opal_entry,
+ sizeof(my_opal_entry));
+ elf_rel_get_symbol(&info->rhdr, "kernel", &my_kernel, sizeof(my_kernel));
+ elf_rel_get_symbol(&info->rhdr, "dt_offset", &my_dt_offset,
+ sizeof(my_dt_offset));
+ elf_rel_get_symbol(&info->rhdr, "run_at_load", &my_run_at_load,
+ sizeof(my_run_at_load));
+ elf_rel_get_symbol(&info->rhdr, "panic_kernel", &my_panic_kernel,
+ sizeof(my_panic_kernel));
+ elf_rel_get_symbol(&info->rhdr, "backup_start", &my_backup_start,
+ sizeof(my_backup_start));
+ elf_rel_get_symbol(&info->rhdr, "stack", &my_stack, sizeof(my_stack));
+ elf_rel_get_symbol(&info->rhdr, "my_toc", &toc_addr,
+ sizeof(toc_addr));
+ elf_rel_get_symbol(&info->rhdr, "debug", &my_debug, sizeof(my_debug));
+
+ dbgprintf("info->entry is %p\n", info->entry);
+ dbgprintf("kernel is %llx\n", (unsigned long long)my_kernel);
+ dbgprintf("dt_offset is %llx\n",
+ (unsigned long long)my_dt_offset);
+ dbgprintf("run_at_load flag is %x\n", my_run_at_load);
+ dbgprintf("panic_kernel is %x\n", my_panic_kernel);
+ dbgprintf("backup_start is %llx\n",
+ (unsigned long long)my_backup_start);
+ dbgprintf("stack is %llx\n", (unsigned long long)my_stack);
+ dbgprintf("toc_addr is %llx\n", (unsigned long long)toc_addr);
+ dbgprintf("purgatory size is %zu\n", purgatory_size);
+ dbgprintf("debug is %d\n", my_debug);
+ dbgprintf("opal_base is %llx\n", (unsigned long long) my_opal_base);
+ dbgprintf("opal_entry is %llx\n", (unsigned long long) my_opal_entry);
+
+ return 0;
+}
+
+void elf_ppc64_usage(void)
+{
+ fprintf(stderr, " --command-line=<Command line> command line to append.\n");
+ fprintf(stderr, " --append=<Command line> same as --command-line.\n");
+ fprintf(stderr, " --ramdisk=<filename> Initial RAM disk.\n");
+ fprintf(stderr, " --initrd=<filename> same as --ramdisk.\n");
+ fprintf(stderr, " --devicetreeblob=<filename> Specify device tree blob file.\n");
+ fprintf(stderr, " ");
+ fprintf(stderr, "Not applicable while using --kexec-file-syscall.\n");
+ fprintf(stderr, " --reuse-cmdline Use kernel command line from running system.\n");
+ fprintf(stderr, " --dtb=<filename> same as --devicetreeblob.\n");
+
+ fprintf(stderr, "elf support is still broken\n");
+}