mirror of
https://github.com/torvalds/linux
synced 2024-11-05 18:23:50 +00:00
b2c5f61920
This abstracts the operations used in the bootwrapper, and defines the operations needed for the bootwrapper to run on an OF platform. The operations have been divided up into platform ops (platform_ops), firmware ops (fw_ops), device tree ops (dt_ops), and console ops (console_ops). The proper operations will be hooked up at runtime to provide the functionality that you need. Signed-off-by: Mark A. Greer <mgreer@mvista.com> Signed-off-by: Paul Mackerras <paulus@samba.org>
354 lines
9.3 KiB
C
354 lines
9.3 KiB
C
/*
|
|
* Copyright (C) Paul Mackerras 1997.
|
|
*
|
|
* Updates for PPC64 by Todd Inglett, Dave Engebretsen & Peter Bergner.
|
|
*
|
|
* 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; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include "elf.h"
|
|
#include "page.h"
|
|
#include "string.h"
|
|
#include "stdio.h"
|
|
#include "zlib.h"
|
|
#include "ops.h"
|
|
#include "flatdevtree.h"
|
|
|
|
extern void flush_cache(void *, unsigned long);
|
|
|
|
extern char _start[];
|
|
extern char __bss_start[];
|
|
extern char _end[];
|
|
extern char _vmlinux_start[];
|
|
extern char _vmlinux_end[];
|
|
extern char _initrd_start[];
|
|
extern char _initrd_end[];
|
|
|
|
struct addr_range {
|
|
unsigned long addr;
|
|
unsigned long size;
|
|
unsigned long memsize;
|
|
};
|
|
static struct addr_range vmlinux;
|
|
static struct addr_range vmlinuz;
|
|
static struct addr_range initrd;
|
|
|
|
static unsigned long elfoffset;
|
|
static int is_64bit;
|
|
|
|
/* scratch space for gunzip; 46912 is from zlib_inflate_workspacesize() */
|
|
static char scratch[46912];
|
|
static char elfheader[256];
|
|
|
|
typedef void (*kernel_entry_t)(unsigned long, unsigned long, void *);
|
|
|
|
#undef DEBUG
|
|
|
|
#define HEAD_CRC 2
|
|
#define EXTRA_FIELD 4
|
|
#define ORIG_NAME 8
|
|
#define COMMENT 0x10
|
|
#define RESERVED 0xe0
|
|
|
|
static void gunzip(void *dst, int dstlen, unsigned char *src, int *lenp)
|
|
{
|
|
z_stream s;
|
|
int r, i, flags;
|
|
|
|
/* skip header */
|
|
i = 10;
|
|
flags = src[3];
|
|
if (src[2] != Z_DEFLATED || (flags & RESERVED) != 0) {
|
|
printf("bad gzipped data\n\r");
|
|
exit();
|
|
}
|
|
if ((flags & EXTRA_FIELD) != 0)
|
|
i = 12 + src[10] + (src[11] << 8);
|
|
if ((flags & ORIG_NAME) != 0)
|
|
while (src[i++] != 0)
|
|
;
|
|
if ((flags & COMMENT) != 0)
|
|
while (src[i++] != 0)
|
|
;
|
|
if ((flags & HEAD_CRC) != 0)
|
|
i += 2;
|
|
if (i >= *lenp) {
|
|
printf("gunzip: ran out of data in header\n\r");
|
|
exit();
|
|
}
|
|
|
|
if (zlib_inflate_workspacesize() > sizeof(scratch)) {
|
|
printf("gunzip needs more mem\n");
|
|
exit();
|
|
}
|
|
memset(&s, 0, sizeof(s));
|
|
s.workspace = scratch;
|
|
r = zlib_inflateInit2(&s, -MAX_WBITS);
|
|
if (r != Z_OK) {
|
|
printf("inflateInit2 returned %d\n\r", r);
|
|
exit();
|
|
}
|
|
s.next_in = src + i;
|
|
s.avail_in = *lenp - i;
|
|
s.next_out = dst;
|
|
s.avail_out = dstlen;
|
|
r = zlib_inflate(&s, Z_FULL_FLUSH);
|
|
if (r != Z_OK && r != Z_STREAM_END) {
|
|
printf("inflate returned %d msg: %s\n\r", r, s.msg);
|
|
exit();
|
|
}
|
|
*lenp = s.next_out - (unsigned char *) dst;
|
|
zlib_inflateEnd(&s);
|
|
}
|
|
|
|
static int is_elf64(void *hdr)
|
|
{
|
|
Elf64_Ehdr *elf64 = hdr;
|
|
Elf64_Phdr *elf64ph;
|
|
unsigned int i;
|
|
|
|
if (!(elf64->e_ident[EI_MAG0] == ELFMAG0 &&
|
|
elf64->e_ident[EI_MAG1] == ELFMAG1 &&
|
|
elf64->e_ident[EI_MAG2] == ELFMAG2 &&
|
|
elf64->e_ident[EI_MAG3] == ELFMAG3 &&
|
|
elf64->e_ident[EI_CLASS] == ELFCLASS64 &&
|
|
elf64->e_ident[EI_DATA] == ELFDATA2MSB &&
|
|
elf64->e_type == ET_EXEC &&
|
|
elf64->e_machine == EM_PPC64))
|
|
return 0;
|
|
|
|
elf64ph = (Elf64_Phdr *)((unsigned long)elf64 +
|
|
(unsigned long)elf64->e_phoff);
|
|
for (i = 0; i < (unsigned int)elf64->e_phnum; i++, elf64ph++)
|
|
if (elf64ph->p_type == PT_LOAD)
|
|
break;
|
|
if (i >= (unsigned int)elf64->e_phnum)
|
|
return 0;
|
|
|
|
elfoffset = (unsigned long)elf64ph->p_offset;
|
|
vmlinux.size = (unsigned long)elf64ph->p_filesz + elfoffset;
|
|
vmlinux.memsize = (unsigned long)elf64ph->p_memsz + elfoffset;
|
|
|
|
is_64bit = 1;
|
|
return 1;
|
|
}
|
|
|
|
static int is_elf32(void *hdr)
|
|
{
|
|
Elf32_Ehdr *elf32 = hdr;
|
|
Elf32_Phdr *elf32ph;
|
|
unsigned int i;
|
|
|
|
if (!(elf32->e_ident[EI_MAG0] == ELFMAG0 &&
|
|
elf32->e_ident[EI_MAG1] == ELFMAG1 &&
|
|
elf32->e_ident[EI_MAG2] == ELFMAG2 &&
|
|
elf32->e_ident[EI_MAG3] == ELFMAG3 &&
|
|
elf32->e_ident[EI_CLASS] == ELFCLASS32 &&
|
|
elf32->e_ident[EI_DATA] == ELFDATA2MSB &&
|
|
elf32->e_type == ET_EXEC &&
|
|
elf32->e_machine == EM_PPC))
|
|
return 0;
|
|
|
|
elf32 = (Elf32_Ehdr *)elfheader;
|
|
elf32ph = (Elf32_Phdr *) ((unsigned long)elf32 + elf32->e_phoff);
|
|
for (i = 0; i < elf32->e_phnum; i++, elf32ph++)
|
|
if (elf32ph->p_type == PT_LOAD)
|
|
break;
|
|
if (i >= elf32->e_phnum)
|
|
return 0;
|
|
|
|
elfoffset = elf32ph->p_offset;
|
|
vmlinux.size = elf32ph->p_filesz + elf32ph->p_offset;
|
|
vmlinux.memsize = elf32ph->p_memsz + elf32ph->p_offset;
|
|
return 1;
|
|
}
|
|
|
|
static void prep_kernel(unsigned long *a1, unsigned long *a2)
|
|
{
|
|
int len;
|
|
|
|
vmlinuz.addr = (unsigned long)_vmlinux_start;
|
|
vmlinuz.size = (unsigned long)(_vmlinux_end - _vmlinux_start);
|
|
|
|
/* gunzip the ELF header of the kernel */
|
|
if (*(unsigned short *)vmlinuz.addr == 0x1f8b) {
|
|
len = vmlinuz.size;
|
|
gunzip(elfheader, sizeof(elfheader),
|
|
(unsigned char *)vmlinuz.addr, &len);
|
|
} else
|
|
memcpy(elfheader, (const void *)vmlinuz.addr,
|
|
sizeof(elfheader));
|
|
|
|
if (!is_elf64(elfheader) && !is_elf32(elfheader)) {
|
|
printf("Error: not a valid PPC32 or PPC64 ELF file!\n\r");
|
|
exit();
|
|
}
|
|
if (platform_ops.image_hdr)
|
|
platform_ops.image_hdr(elfheader);
|
|
|
|
/* We need to alloc the memsize plus the file offset since gzip
|
|
* will expand the header (file offset), then the kernel, then
|
|
* possible rubbish we don't care about. But the kernel bss must
|
|
* be claimed (it will be zero'd by the kernel itself)
|
|
*/
|
|
printf("Allocating 0x%lx bytes for kernel ...\n\r", vmlinux.memsize);
|
|
vmlinux.addr = (unsigned long)malloc(vmlinux.memsize);
|
|
if (vmlinux.addr == 0) {
|
|
printf("Can't allocate memory for kernel image !\n\r");
|
|
exit();
|
|
}
|
|
|
|
/*
|
|
* Now we try to alloc memory for the initrd (and copy it there)
|
|
*/
|
|
initrd.size = (unsigned long)(_initrd_end - _initrd_start);
|
|
initrd.memsize = initrd.size;
|
|
if ( initrd.size > 0 ) {
|
|
printf("Allocating 0x%lx bytes for initrd ...\n\r",
|
|
initrd.size);
|
|
initrd.addr = (unsigned long)malloc((u32)initrd.size);
|
|
if (initrd.addr == 0) {
|
|
printf("Can't allocate memory for initial "
|
|
"ramdisk !\n\r");
|
|
exit();
|
|
}
|
|
*a1 = initrd.addr;
|
|
*a2 = initrd.size;
|
|
printf("initial ramdisk moving 0x%lx <- 0x%lx "
|
|
"(0x%lx bytes)\n\r", initrd.addr,
|
|
(unsigned long)_initrd_start, initrd.size);
|
|
memmove((void *)initrd.addr, (void *)_initrd_start,
|
|
initrd.size);
|
|
printf("initrd head: 0x%lx\n\r",
|
|
*((unsigned long *)initrd.addr));
|
|
}
|
|
|
|
/* Eventually gunzip the kernel */
|
|
if (*(unsigned short *)vmlinuz.addr == 0x1f8b) {
|
|
printf("gunzipping (0x%lx <- 0x%lx:0x%0lx)...",
|
|
vmlinux.addr, vmlinuz.addr, vmlinuz.addr+vmlinuz.size);
|
|
len = vmlinuz.size;
|
|
gunzip((void *)vmlinux.addr, vmlinux.memsize,
|
|
(unsigned char *)vmlinuz.addr, &len);
|
|
printf("done 0x%lx bytes\n\r", len);
|
|
} else {
|
|
memmove((void *)vmlinux.addr,(void *)vmlinuz.addr,
|
|
vmlinuz.size);
|
|
}
|
|
|
|
/* Skip over the ELF header */
|
|
#ifdef DEBUG
|
|
printf("... skipping 0x%lx bytes of ELF header\n\r",
|
|
elfoffset);
|
|
#endif
|
|
vmlinux.addr += elfoffset;
|
|
|
|
flush_cache((void *)vmlinux.addr, vmlinux.size);
|
|
}
|
|
|
|
void __attribute__ ((weak)) ft_init(void *dt_blob)
|
|
{
|
|
}
|
|
|
|
/* A buffer that may be edited by tools operating on a zImage binary so as to
|
|
* edit the command line passed to vmlinux (by setting /chosen/bootargs).
|
|
* The buffer is put in it's own section so that tools may locate it easier.
|
|
*/
|
|
static char builtin_cmdline[COMMAND_LINE_SIZE]
|
|
__attribute__((__section__("__builtin_cmdline")));
|
|
|
|
static void get_cmdline(char *buf, int size)
|
|
{
|
|
void *devp;
|
|
int len = strlen(builtin_cmdline);
|
|
|
|
buf[0] = '\0';
|
|
|
|
if (len > 0) { /* builtin_cmdline overrides dt's /chosen/bootargs */
|
|
len = min(len, size-1);
|
|
strncpy(buf, builtin_cmdline, len);
|
|
buf[len] = '\0';
|
|
}
|
|
else if ((devp = finddevice("/chosen")))
|
|
getprop(devp, "bootargs", buf, size);
|
|
}
|
|
|
|
static void set_cmdline(char *buf)
|
|
{
|
|
void *devp;
|
|
|
|
if ((devp = finddevice("/chosen")))
|
|
setprop(devp, "bootargs", buf, strlen(buf) + 1);
|
|
}
|
|
|
|
/* Section where ft can be tacked on after zImage is built */
|
|
union blobspace {
|
|
struct boot_param_header hdr;
|
|
char space[8*1024];
|
|
} dt_blob __attribute__((__section__("__builtin_ft")));
|
|
|
|
struct platform_ops platform_ops;
|
|
struct dt_ops dt_ops;
|
|
struct console_ops console_ops;
|
|
|
|
void start(unsigned long a1, unsigned long a2, void *promptr, void *sp)
|
|
{
|
|
int have_dt = 0;
|
|
kernel_entry_t kentry;
|
|
char cmdline[COMMAND_LINE_SIZE];
|
|
|
|
memset(__bss_start, 0, _end - __bss_start);
|
|
memset(&platform_ops, 0, sizeof(platform_ops));
|
|
memset(&dt_ops, 0, sizeof(dt_ops));
|
|
memset(&console_ops, 0, sizeof(console_ops));
|
|
|
|
/* Override the dt_ops and device tree if there was an flat dev
|
|
* tree attached to the zImage.
|
|
*/
|
|
if (dt_blob.hdr.magic == OF_DT_HEADER) {
|
|
have_dt = 1;
|
|
ft_init(&dt_blob);
|
|
}
|
|
|
|
if (platform_init(promptr))
|
|
exit();
|
|
if (console_ops.open && (console_ops.open() < 0))
|
|
exit();
|
|
if (platform_ops.fixups)
|
|
platform_ops.fixups();
|
|
|
|
printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r",
|
|
_start, sp);
|
|
|
|
prep_kernel(&a1, &a2);
|
|
|
|
/* If cmdline came from zimage wrapper or if we can edit the one
|
|
* in the dt, print it out and edit it, if possible.
|
|
*/
|
|
if ((strlen(builtin_cmdline) > 0) || console_ops.edit_cmdline) {
|
|
get_cmdline(cmdline, COMMAND_LINE_SIZE);
|
|
printf("\n\rLinux/PowerPC load: %s", cmdline);
|
|
if (console_ops.edit_cmdline)
|
|
console_ops.edit_cmdline(cmdline, COMMAND_LINE_SIZE);
|
|
printf("\n\r");
|
|
set_cmdline(cmdline);
|
|
}
|
|
|
|
if (console_ops.close)
|
|
console_ops.close();
|
|
|
|
kentry = (kernel_entry_t) vmlinux.addr;
|
|
if (have_dt)
|
|
kentry(dt_ops.ft_addr(), 0, NULL);
|
|
else
|
|
/* XXX initrd addr/size should be passed in properties */
|
|
kentry(a1, a2, promptr);
|
|
|
|
/* console closed so printf below may not work */
|
|
printf("Error: Linux kernel returned to zImage boot wrapper!\n\r");
|
|
exit();
|
|
}
|