summaryrefslogtreecommitdiffstats
path: root/kexec/arch/ppc/kexec-elf-ppc.c
diff options
context:
space:
mode:
Diffstat (limited to 'kexec/arch/ppc/kexec-elf-ppc.c')
-rw-r--r--kexec/arch/ppc/kexec-elf-ppc.c458
1 files changed, 458 insertions, 0 deletions
diff --git a/kexec/arch/ppc/kexec-elf-ppc.c b/kexec/arch/ppc/kexec-elf-ppc.c
new file mode 100644
index 0000000..4a4886e
--- /dev/null
+++ b/kexec/arch/ppc/kexec-elf-ppc.c
@@ -0,0 +1,458 @@
+/*
+ * kexec-elf-ppc.c - kexec Elf loader for the PowerPC
+ * Copyright (C) 2004 Albert Herranz
+ *
+ * This source code is licensed under the GNU General Public License,
+ * Version 2. See the file COPYING for more details.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <elf.h>
+#include <boot/elf_boot.h>
+#include <ip_checksum.h>
+#include "../../kexec.h"
+#include "../../kexec-elf.h"
+#include "kexec-ppc.h"
+#include <arch/options.h>
+#include "../../kexec-syscall.h"
+#include "crashdump-powerpc.h"
+
+#include "config.h"
+#include "fixup_dtb.h"
+
+static const int probe_debug = 0;
+
+unsigned char reuse_initrd;
+int create_flatten_tree(struct kexec_info *, unsigned char **, unsigned long *,
+ char *);
+
+#define UPSZ(X) _ALIGN_UP(sizeof(X), 4);
+#ifdef WITH_GAMECUBE
+static struct boot_notes {
+ Elf_Bhdr hdr;
+ Elf_Nhdr bl_hdr;
+ unsigned char bl_desc[UPSZ(BOOTLOADER)];
+ Elf_Nhdr blv_hdr;
+ unsigned char blv_desc[UPSZ(BOOTLOADER_VERSION)];
+ Elf_Nhdr cmd_hdr;
+ unsigned char command_line[0];
+} elf_boot_notes = {
+ .hdr = {
+ .b_signature = 0x0E1FB007,
+ .b_size = sizeof(elf_boot_notes),
+ .b_checksum = 0,
+ .b_records = 3,
+ },
+ .bl_hdr = {
+ .n_namesz = 0,
+ .n_descsz = sizeof(BOOTLOADER),
+ .n_type = EBN_BOOTLOADER_NAME,
+ },
+ .bl_desc = BOOTLOADER,
+ .blv_hdr = {
+ .n_namesz = 0,
+ .n_descsz = sizeof(BOOTLOADER_VERSION),
+ .n_type = EBN_BOOTLOADER_VERSION,
+ },
+ .blv_desc = BOOTLOADER_VERSION,
+ .cmd_hdr = {
+ .n_namesz = 0,
+ .n_descsz = 0,
+ .n_type = EBN_COMMAND_LINE,
+ },
+};
+#endif
+
+int elf_ppc_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_PPC) {
+ /* for a different architecture */
+ if (probe_debug) {
+ fprintf(stderr, "Not for this architecture.\n");
+ }
+ result = -1;
+ goto out;
+ }
+ result = 0;
+ out:
+ free_elf_info(&ehdr);
+ return result;
+}
+
+#ifdef WITH_GAMECUBE
+static void gamecube_hack_addresses(struct mem_ehdr *ehdr)
+{
+ struct mem_phdr *phdr, *phdr_end;
+ phdr_end = ehdr->e_phdr + ehdr->e_phnum;
+ for(phdr = ehdr->e_phdr; phdr != phdr_end; phdr++) {
+ /*
+ * GameCube ELF kernel is linked with memory mapped
+ * this way (to easily transform it into a DOL
+ * suitable for being loaded with psoload):
+ *
+ * 80000000 - 817fffff 24MB RAM, cached
+ * c0000000 - c17fffff 24MB RAM, not cached
+ *
+ * kexec, instead, needs physical memory layout, so
+ * we clear the upper bits of the address.
+ * (2 bits should be enough, indeed)
+ */
+ phdr->p_paddr &= ~0xf0000000; /* clear bits 0-3, ibm syntax */
+ }
+}
+#endif
+
+/* See options.h -- add any more there, too. */
+static const struct option options[] = {
+ KEXEC_ARCH_OPTIONS
+ {"command-line", 1, 0, OPT_APPEND},
+ {"append", 1, 0, OPT_APPEND},
+ {"ramdisk", 1, 0, OPT_RAMDISK},
+ {"initrd", 1, 0, OPT_RAMDISK},
+ {"gamecube", 1, 0, OPT_GAMECUBE},
+ {"dtb", 1, 0, OPT_DTB},
+ {"reuse-node", 1, 0, OPT_NODES},
+ {0, 0, 0, 0},
+};
+static const char short_options[] = KEXEC_ARCH_OPT_STR;
+
+void elf_ppc_usage(void)
+{
+ printf(
+ " --command-line=STRING Set the kernel command line to STRING.\n"
+ " --append=STRING Set the kernel command line to STRING.\n"
+ " --ramdisk=<filename> Initial RAM disk.\n"
+ " --initrd=<filename> same as --ramdisk\n"
+ " --gamecube=1|0 Enable/disable support for ELFs with changed\n"
+ " addresses suitable for the GameCube.\n"
+ " --dtb=<filename> Specify device tree blob file.\n"
+ " --reuse-node=node Specify nodes which should be taken from /proc/device-tree.\n"
+ " Can be set multiple times.\n"
+ );
+}
+
+int elf_ppc_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info)
+{
+ struct mem_ehdr ehdr;
+ char *command_line, *crash_cmdline, *cmdline_buf;
+ char *tmp_cmdline;
+ int command_line_len, crash_cmdline_len;
+ char *dtb;
+ int result;
+ char *error_msg;
+ unsigned long max_addr, hole_addr;
+ struct mem_phdr *phdr;
+ size_t size;
+#ifdef CONFIG_PPC64
+ unsigned long toc_addr;
+#endif
+#ifdef WITH_GAMECUBE
+ int target_is_gamecube = 1;
+ char *arg_buf;
+ size_t arg_bytes;
+ unsigned long arg_base;
+ struct boot_notes *notes;
+ size_t note_bytes;
+ unsigned char *setup_start;
+ uint32_t setup_size;
+#else
+ char *seg_buf = NULL;
+ off_t seg_size = 0;
+ int target_is_gamecube = 0;
+ unsigned int addr;
+ unsigned long dtb_addr;
+ unsigned long dtb_addr_actual;
+#endif
+ unsigned long kernel_addr;
+#define FIXUP_ENTRYS (20)
+ char *fixup_nodes[FIXUP_ENTRYS + 1];
+ int cur_fixup = 0;
+ int opt;
+ char *blob_buf = NULL;
+ off_t blob_size = 0;
+
+ command_line = tmp_cmdline = NULL;
+ dtb = NULL;
+ max_addr = LONG_MAX;
+ hole_addr = 0;
+ kernel_addr = 0;
+ ramdisk = 0;
+ result = 0;
+ error_msg = NULL;
+
+ 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:
+ tmp_cmdline = optarg;
+ break;
+ case OPT_RAMDISK:
+ ramdisk = optarg;
+ break;
+ case OPT_GAMECUBE:
+ target_is_gamecube = atoi(optarg);
+ break;
+
+ case OPT_DTB:
+ dtb = optarg;
+ break;
+
+ case OPT_NODES:
+ if (cur_fixup >= FIXUP_ENTRYS) {
+ die("The number of entries for the fixup is too large\n");
+ }
+ fixup_nodes[cur_fixup] = optarg;
+ cur_fixup++;
+ break;
+ }
+ }
+
+ if (ramdisk && reuse_initrd)
+ die("Can't specify --ramdisk or --initrd with --reuseinitrd\n");
+
+ command_line_len = 0;
+ if (tmp_cmdline) {
+ command_line = tmp_cmdline;
+ } else {
+ command_line = get_command_line();
+ }
+ command_line_len = strlen(command_line);
+
+ fixup_nodes[cur_fixup] = NULL;
+
+ /* Parse the Elf file */
+ result = build_elf_exec_info(buf, len, &ehdr, 0);
+ if (result < 0) {
+ goto out;
+ }
+
+#ifdef WITH_GAMECUBE
+ if (target_is_gamecube) {
+ gamecube_hack_addresses(&ehdr);
+ }
+#endif
+
+ /* 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;
+
+ kernel_addr = locate_hole(info, size, 0, 0, max_addr, 1);
+#ifdef CONFIG_PPC64
+ ehdr.e_phdr[0].p_paddr = (Elf64_Addr)kernel_addr;
+#else
+ ehdr.e_phdr[0].p_paddr = kernel_addr;
+#endif
+
+ /* Load the Elf data */
+ result = elf_exec_load(&ehdr, info);
+ if (result < 0) {
+ goto out;
+ }
+
+ /*
+ * Need to append some command line parameters internally in case of
+ * taking crash dumps. Additional segments need to be created.
+ */
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ crash_cmdline = xmalloc(COMMAND_LINE_SIZE);
+ memset((void *)crash_cmdline, 0, COMMAND_LINE_SIZE);
+ result = load_crashdump_segments(info, crash_cmdline,
+ max_addr, 0);
+ if (result < 0) {
+ result = -1;
+ goto out;
+ }
+ crash_cmdline_len = strlen(crash_cmdline);
+ } else {
+ crash_cmdline = NULL;
+ crash_cmdline_len = 0;
+ }
+
+ /*
+ * In case of a toy we take the hardcoded things and an easy setup via
+ * one of the assembly startups. Every thing else should be grown up
+ * and go through the purgatory.
+ */
+#ifdef WITH_GAMECUBE
+ if (target_is_gamecube) {
+ setup_start = setup_dol_start;
+ setup_size = setup_dol_size;
+ setup_dol_regs.spr8 = ehdr.e_entry; /* Link Register */
+ } else {
+ setup_start = setup_simple_start;
+ setup_size = setup_simple_size;
+ setup_simple_regs.spr8 = ehdr.e_entry; /* Link Register */
+ }
+ note_bytes = sizeof(elf_boot_notes) + _ALIGN(command_line_len, 4);
+ arg_bytes = note_bytes + _ALIGN(setup_size, 4);
+
+ arg_buf = xmalloc(arg_bytes);
+ arg_base = add_buffer(info,
+ arg_buf, arg_bytes, arg_bytes, 4, 0, elf_max_addr(&ehdr), 1);
+
+ notes = (struct boot_notes *)(arg_buf + _ALIGN(setup_size, 4));
+
+ memcpy(arg_buf, setup_start, setup_size);
+ memcpy(notes, &elf_boot_notes, sizeof(elf_boot_notes));
+ memcpy(notes->command_line, command_line, command_line_len);
+ notes->hdr.b_size = note_bytes;
+ notes->cmd_hdr.n_descsz = command_line_len;
+ notes->hdr.b_checksum = compute_ip_checksum(notes, note_bytes);
+
+ info->entry = (void *)arg_base;
+#else
+ if (crash_cmdline_len + command_line_len + 1 > COMMAND_LINE_SIZE) {
+ printf("Kernel command line exceeds size\n");
+ return -1;
+ }
+
+ cmdline_buf = xmalloc(COMMAND_LINE_SIZE);
+ memset((void *)cmdline_buf, 0, COMMAND_LINE_SIZE);
+ if (command_line)
+ strncat(cmdline_buf, command_line, command_line_len);
+ if (crash_cmdline)
+ strncat(cmdline_buf, crash_cmdline,
+ sizeof(crash_cmdline) -
+ strlen(crash_cmdline) - 1);
+
+ elf_rel_build_load(info, &info->rhdr, (const char *)purgatory,
+ purgatory_size, 0, elf_max_addr(&ehdr), 1, 0);
+
+ /* Here we need to initialize the device tree, and find out where
+ * it is going to live so we can place it directly after the
+ * kernel image */
+ if (dtb) {
+ /* Grab device tree from buffer */
+ blob_buf = slurp_file(dtb, &blob_size);
+ } else {
+ create_flatten_tree(info, (unsigned char **)&blob_buf,
+ (unsigned long *)&blob_size, cmdline_buf);
+ }
+ if (!blob_buf || !blob_size) {
+ error_msg = "Device tree seems to be an empty file.\n";
+ goto out2;
+ }
+
+ /* initial fixup for device tree */
+ blob_buf = fixup_dtb_init(info, blob_buf, &blob_size, kernel_addr, &dtb_addr);
+
+ if (ramdisk) {
+ seg_buf = slurp_ramdisk_ppc(ramdisk, &seg_size);
+ /* load the ramdisk *above* the device tree */
+ hole_addr = add_buffer(info, seg_buf, seg_size, seg_size,
+ 0, dtb_addr + blob_size + 1, max_addr, -1);
+ ramdisk_base = hole_addr;
+ ramdisk_size = seg_size;
+ }
+ if (reuse_initrd) {
+ ramdisk_base = initrd_base;
+ ramdisk_size = initrd_size;
+ }
+
+ if (info->kexec_flags & KEXEC_ON_CRASH && ramdisk_base != 0) {
+ if ( (ramdisk_base < crash_base) ||
+ (ramdisk_base > crash_base + crash_size) ) {
+ printf("WARNING: ramdisk is above crashkernel region!\n");
+ }
+ else if (ramdisk_base + ramdisk_size > crash_base + crash_size) {
+ printf("WARNING: ramdisk overflows crashkernel region!\n");
+ }
+ }
+
+ /* Perform final fixup on devie tree, i.e. everything beside what
+ * was done above */
+ fixup_dtb_finalize(info, blob_buf, &blob_size, fixup_nodes,
+ cmdline_buf);
+ dtb_addr_actual = add_buffer(info, blob_buf, blob_size, blob_size, 0, dtb_addr,
+ kernel_addr + KERNEL_ACCESS_TOP, 1);
+ if (dtb_addr_actual != dtb_addr) {
+ error_msg = "Error device tree not loadded to address it was expecting to be loaded too!\n";
+ goto out2;
+ }
+
+ /*
+ * set various variables for the purgatory.
+ * ehdr.e_entry is a virtual address. we know physical start
+ * address of the kernel (kernel_addr). Find the offset of
+ * e_entry from the virtual start address(e_phdr[0].p_vaddr)
+ * and calculate the actual physical address of the 'kernel entry'.
+ */
+ addr = kernel_addr + (ehdr.e_entry - ehdr.e_phdr[0].p_vaddr);
+ elf_rel_set_symbol(&info->rhdr, "kernel", &addr, sizeof(addr));
+
+ addr = dtb_addr;
+ elf_rel_set_symbol(&info->rhdr, "dt_offset",
+ &addr, sizeof(addr));
+
+#define PUL_STACK_SIZE (16 * 1024)
+ addr = locate_hole(info, PUL_STACK_SIZE, 0, 0,
+ elf_max_addr(&ehdr), 1);
+ addr += PUL_STACK_SIZE;
+ elf_rel_set_symbol(&info->rhdr, "stack", &addr, sizeof(addr));
+#undef PUL_STACK_SIZE
+
+ /*
+ * Fixup ThreadPointer(r2) for purgatory.
+ * PPC32 ELF ABI expects :
+ * ThreadPointer (TP) = TCB + 0x7000
+ * We manually allocate a TCB space and set the TP
+ * accordingly.
+ */
+#define TCB_SIZE 1024
+#define TCB_TP_OFFSET 0x7000 /* PPC32 ELF ABI */
+
+ addr = locate_hole(info, TCB_SIZE, 0, 0,
+ ((unsigned long)elf_max_addr(&ehdr) - TCB_TP_OFFSET),
+ 1);
+ addr += TCB_SIZE + TCB_TP_OFFSET;
+ elf_rel_set_symbol(&info->rhdr, "my_thread_ptr", &addr, sizeof(addr));
+
+#undef TCB_SIZE
+#undef TCB_TP_OFFSET
+
+ addr = elf_rel_get_addr(&info->rhdr, "purgatory_start");
+ info->entry = (void *)addr;
+
+out2:
+ free(cmdline_buf);
+#endif
+out:
+ free_elf_info(&ehdr);
+ free(crash_cmdline);
+ if (!tmp_cmdline)
+ free(command_line);
+ if (error_msg)
+ die("%s", error_msg);
+
+ return result;
+}