main.c 8.5 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10
/*
 * 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.
 */
11 12 13 14 15 16
#include <stdarg.h>
#include <stddef.h>
#include "elf.h"
#include "page.h"
#include "string.h"
#include "stdio.h"
17
#include "ops.h"
18
#include "gunzip_util.h"
19
#include "flatdevtree.h"
20

L
Linus Torvalds 已提交
21
extern char _start[];
22
extern char __bss_start[];
23
extern char _end[];
L
Linus Torvalds 已提交
24 25 26 27
extern char _vmlinux_start[];
extern char _vmlinux_end[];
extern char _initrd_start[];
extern char _initrd_end[];
28 29
extern char _dtb_start[];
extern char _dtb_end[];
L
Linus Torvalds 已提交
30

31 32
static struct gunzip_state gzstate;

L
Linus Torvalds 已提交
33
struct addr_range {
34
	void *addr;
L
Linus Torvalds 已提交
35 36
	unsigned long size;
};
37

38 39 40 41 42
struct elf_info {
	unsigned long loadsize;
	unsigned long memsize;
	unsigned long elfoffset;
};
O
Olaf Hering 已提交
43

44
typedef void (*kernel_entry_t)(unsigned long, unsigned long, void *);
L
Linus Torvalds 已提交
45 46 47

#undef DEBUG

48
static int parse_elf64(void *hdr, struct elf_info *info)
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
{
	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++)
67
		if (elf64ph->p_type == PT_LOAD)
68 69 70 71
			break;
	if (i >= (unsigned int)elf64->e_phnum)
		return 0;

72 73 74
	info->loadsize = (unsigned long)elf64ph->p_filesz;
	info->memsize = (unsigned long)elf64ph->p_memsz;
	info->elfoffset = (unsigned long)elf64ph->p_offset;
75

76 77 78
	return 1;
}

79
static int parse_elf32(void *hdr, struct elf_info *info)
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
{
	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;

	elf32ph = (Elf32_Phdr *) ((unsigned long)elf32 + elf32->e_phoff);
	for (i = 0; i < elf32->e_phnum; i++, elf32ph++)
97
		if (elf32ph->p_type == PT_LOAD)
98 99 100 101
			break;
	if (i >= elf32->e_phnum)
		return 0;

102 103 104
	info->loadsize = elf32ph->p_filesz;
	info->memsize = elf32ph->p_memsz;
	info->elfoffset = elf32ph->p_offset;
105 106 107
	return 1;
}

108
static struct addr_range prep_kernel(void)
L
Linus Torvalds 已提交
109
{
110 111 112 113 114
	char elfheader[256];
	void *vmlinuz_addr = _vmlinux_start;
	unsigned long vmlinuz_size = _vmlinux_end - _vmlinux_start;
	void *addr = 0;
	struct elf_info ei;
115
	int len;
116

117
	/* gunzip the ELF header of the kernel */
118
	gunzip_start(&gzstate, vmlinuz_addr, vmlinuz_size);
119
	gunzip_exactly(&gzstate, elfheader, sizeof(elfheader));
120

121
	if (!parse_elf64(elfheader, &ei) && !parse_elf32(elfheader, &ei)) {
122 123 124
		printf("Error: not a valid PPC32 or PPC64 ELF file!\n\r");
		exit();
	}
125 126
	if (platform_ops.image_hdr)
		platform_ops.image_hdr(elfheader);
127

128 129 130 131
	/* We need to alloc the memsize: gzip will expand the kernel
	 * text/data, then possible rubbish we don't care about. But
	 * the kernel bss must be claimed (it will be zero'd by the
	 * kernel itself)
132
	 */
133 134 135 136 137 138 139 140 141 142
	printf("Allocating 0x%lx bytes for kernel ...\n\r", ei.memsize);

	if (platform_ops.vmlinux_alloc) {
		addr = platform_ops.vmlinux_alloc(ei.memsize);
	} else {
		if ((unsigned long)_start < ei.memsize) {
			printf("Insufficient memory for kernel at address 0!"
			       " (_start=%lx)\n\r", _start);
			exit();
		}
L
Linus Torvalds 已提交
143 144
	}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
	/* Finally, gunzip the kernel */
	printf("gunzipping (0x%p <- 0x%p:0x%p)...", addr,
	       vmlinuz_addr, vmlinuz_addr+vmlinuz_size);
	/* discard up to the actual load data */
	gunzip_discard(&gzstate, ei.elfoffset - sizeof(elfheader));
	len = gunzip_finish(&gzstate, addr, ei.memsize);
	printf("done 0x%lx bytes\n\r", len);

	flush_cache(addr, ei.loadsize);

	return (struct addr_range){addr, ei.memsize};
}

static struct addr_range prep_initrd(struct addr_range vmlinux,
				     unsigned long initrd_addr,
				     unsigned long initrd_size)
{
	void *devp;
	u32 initrd_start, initrd_end;

	/* If we have an image attached to us, it overrides anything
	 * supplied by the loader. */
	if (_initrd_end > _initrd_start) {
		printf("Attached initrd image at 0x%p-0x%p\n\r",
		       _initrd_start, _initrd_end);
		initrd_addr = (unsigned long)_initrd_start;
		initrd_size = _initrd_end - _initrd_start;
	} else if (initrd_size > 0) {
		printf("Using loader supplied ramdisk at 0x%lx-0x%lx\n\r",
		       initrd_addr, initrd_addr + initrd_size);
	}

	/* If there's no initrd at all, we're done */
	if (! initrd_size)
		return (struct addr_range){0, 0};

L
Linus Torvalds 已提交
181
	/*
182 183 184
	 * If the initrd is too low it will be clobbered when the
	 * kernel relocates to its final location.  In this case,
	 * allocate a safer place and move it.
L
Linus Torvalds 已提交
185
	 */
186 187 188
	if (initrd_addr < vmlinux.size) {
		void *old_addr = (void *)initrd_addr;

189
		printf("Allocating 0x%lx bytes for initrd ...\n\r",
190 191 192
		       initrd_size);
		initrd_addr = (unsigned long)malloc(initrd_size);
		if (! initrd_addr) {
193
			printf("Can't allocate memory for initial "
194
			       "ramdisk !\n\r");
L
Linus Torvalds 已提交
195 196
			exit();
		}
197 198 199
		printf("Relocating initrd 0x%p <- 0x%p (0x%lx bytes)\n\r",
		       initrd_addr, old_addr, initrd_size);
		memmove((void *)initrd_addr, old_addr, initrd_size);
L
Linus Torvalds 已提交
200 201
	}

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
	printf("initrd head: 0x%lx\n\r", *((unsigned long *)initrd_addr));

	/* Tell the kernel initrd address via device tree */
	devp = finddevice("/chosen");
	if (! devp) {
		printf("Device tree has no chosen node!\n\r");
		exit();
	}

	initrd_start = (u32)initrd_addr;
	initrd_end = (u32)initrd_addr + initrd_size;

	setprop(devp, "linux,initrd-start", &initrd_start,
		sizeof(initrd_start));
	setprop(devp, "linux,initrd-end", &initrd_end, sizeof(initrd_end));
L
Linus Torvalds 已提交
217

218
	return (struct addr_range){(void *)initrd_addr, initrd_size};
219
}
L
Linus Torvalds 已提交
220

221 222 223 224 225 226
/* 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")));
L
Linus Torvalds 已提交
227

228 229 230 231
static void get_cmdline(char *buf, int size)
{
	void *devp;
	int len = strlen(builtin_cmdline);
L
Linus Torvalds 已提交
232

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
	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);
L
Linus Torvalds 已提交
250 251
}

252 253 254
struct platform_ops platform_ops;
struct dt_ops dt_ops;
struct console_ops console_ops;
255
struct loader_info loader_info;
256

257
void start(void *sp)
258
{
259
	struct addr_range vmlinux, initrd;
260 261
	kernel_entry_t kentry;
	char cmdline[COMMAND_LINE_SIZE];
262
	unsigned long ft_addr = 0;
263 264 265 266 267 268 269 270 271

	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);

272
	vmlinux = prep_kernel();
273 274
	initrd = prep_initrd(vmlinux, loader_info.initrd_addr,
			     loader_info.initrd_size);
275 276 277 278 279 280 281 282 283 284 285 286 287

	/* 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);
	}

288 289 290 291 292 293
	printf("Finalizing device tree...");
	if (dt_ops.finalize)
		ft_addr = dt_ops.finalize();
	if (ft_addr)
		printf(" flat tree at 0x%lx\n\r", ft_addr);
	else
294
		printf(" using OF tree (promptr=%p)\n\r", loader_info.promptr);
295

296 297 298 299
	if (console_ops.close)
		console_ops.close();

	kentry = (kernel_entry_t) vmlinux.addr;
300 301
	if (ft_addr)
		kentry(ft_addr, 0, NULL);
302
	else
303 304
		kentry((unsigned long)initrd.addr, initrd.size,
		       loader_info.promptr);
305 306 307 308 309

	/* console closed so printf below may not work */
	printf("Error: Linux kernel returned to zImage boot wrapper!\n\r");
	exit();
}