blob: 65a65cc9e40f965827b987a07eedec40f82ad1bc [file] [log] [blame]
/*
* 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;
}