| /* |
| * 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; |
| const char *ramdisk; |
| 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; |
| int command_line_len; |
| char *dtb; |
| int result; |
| 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 = NULL; |
| dtb = NULL; |
| max_addr = LONG_MAX; |
| hole_addr = 0; |
| kernel_addr = 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 '?': |
| usage(); |
| return -1; |
| case OPT_APPEND: |
| command_line = 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) { |
| fprintf(stderr, "The number of entries for the fixup is too large\n"); |
| exit(1); |
| } |
| fixup_nodes[cur_fixup] = optarg; |
| cur_fixup++; |
| break; |
| } |
| } |
| |
| command_line_len = 0; |
| if (command_line) { |
| command_line_len = strlen(command_line) + 1; |
| } else { |
| command_line = get_command_line(); |
| command_line_len = strlen(command_line) + 1; |
| } |
| |
| if (ramdisk && reuse_initrd) |
| die("Can't specify --ramdisk or --initrd with --reuseinitrd\n"); |
| |
| fixup_nodes[cur_fixup] = NULL; |
| |
| /* Need to append some command line parameters internally in case of |
| * taking crash dumps. |
| */ |
| if (info->kexec_flags & KEXEC_ON_CRASH) { |
| crash_cmdline = xmalloc(COMMAND_LINE_SIZE); |
| memset((void *)crash_cmdline, 0, COMMAND_LINE_SIZE); |
| } else |
| crash_cmdline = NULL; |
| |
| /* Parse the Elf file */ |
| result = build_elf_exec_info(buf, len, &ehdr, 0); |
| if (result < 0) { |
| free_elf_info(&ehdr); |
| return result; |
| } |
| |
| #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) { |
| 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, crash_cmdline, |
| max_addr, 0); |
| if (result < 0) { |
| free(crash_cmdline); |
| 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); |
| |
| /* |
| * 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 |
| 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) |
| die("Device tree seems to be an empty file.\n"); |
| |
| /* initial fixup for device tree */ |
| blob_buf = fixup_dtb_init(info, blob_buf, &blob_size, kernel_addr, &dtb_addr); |
| |
| if (ramdisk) { |
| seg_buf = slurp_file(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) { |
| die("Error device tree not loadded to address it was expecting to be loaded too!\n"); |
| } |
| |
| /* |
| * 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; |
| #endif |
| |
| return 0; |
| } |