194b212c2SPaul Mackerras /* 294b212c2SPaul Mackerras * Copyright (C) Paul Mackerras 1997. 394b212c2SPaul Mackerras * 494b212c2SPaul Mackerras * Updates for PPC64 by Todd Inglett, Dave Engebretsen & Peter Bergner. 594b212c2SPaul Mackerras * 694b212c2SPaul Mackerras * This program is free software; you can redistribute it and/or 794b212c2SPaul Mackerras * modify it under the terms of the GNU General Public License 894b212c2SPaul Mackerras * as published by the Free Software Foundation; either version 994b212c2SPaul Mackerras * 2 of the License, or (at your option) any later version. 1094b212c2SPaul Mackerras */ 1194b212c2SPaul Mackerras #include <stdarg.h> 1294b212c2SPaul Mackerras #include <stddef.h> 1394b212c2SPaul Mackerras #include "elf.h" 1494b212c2SPaul Mackerras #include "page.h" 1594b212c2SPaul Mackerras #include "string.h" 1694b212c2SPaul Mackerras #include "stdio.h" 17b2c5f619SMark A. Greer #include "ops.h" 18ad9d2716SDavid Gibson #include "gunzip_util.h" 19b2c5f619SMark A. Greer #include "flatdevtree.h" 20e5a2072bSDavid Gibson #include "reg.h" 2194b212c2SPaul Mackerras 2294b212c2SPaul Mackerras extern char _start[]; 2394b212c2SPaul Mackerras extern char __bss_start[]; 2494b212c2SPaul Mackerras extern char _end[]; 2594b212c2SPaul Mackerras extern char _vmlinux_start[]; 2694b212c2SPaul Mackerras extern char _vmlinux_end[]; 2794b212c2SPaul Mackerras extern char _initrd_start[]; 2894b212c2SPaul Mackerras extern char _initrd_end[]; 29c888554bSMark A. Greer extern char _dtb_start[]; 30c888554bSMark A. Greer extern char _dtb_end[]; 3194b212c2SPaul Mackerras 32ad9d2716SDavid Gibson static struct gunzip_state gzstate; 33ad9d2716SDavid Gibson 3494b212c2SPaul Mackerras struct addr_range { 3579c85419SDavid Gibson void *addr; 3694b212c2SPaul Mackerras unsigned long size; 3794b212c2SPaul Mackerras }; 3894b212c2SPaul Mackerras 39b2c5f619SMark A. Greer typedef void (*kernel_entry_t)(unsigned long, unsigned long, void *); 4094b212c2SPaul Mackerras 4194b212c2SPaul Mackerras #undef DEBUG 4294b212c2SPaul Mackerras 4379c85419SDavid Gibson static struct addr_range prep_kernel(void) 4430d8caf7Smostrows@watson.ibm.com { 4579c85419SDavid Gibson char elfheader[256]; 4679c85419SDavid Gibson void *vmlinuz_addr = _vmlinux_start; 4779c85419SDavid Gibson unsigned long vmlinuz_size = _vmlinux_end - _vmlinux_start; 4879c85419SDavid Gibson void *addr = 0; 4979c85419SDavid Gibson struct elf_info ei; 5030d8caf7Smostrows@watson.ibm.com int len; 5166a45dd3SPaul Mackerras 5294b212c2SPaul Mackerras /* gunzip the ELF header of the kernel */ 5379c85419SDavid Gibson gunzip_start(&gzstate, vmlinuz_addr, vmlinuz_size); 54ad9d2716SDavid Gibson gunzip_exactly(&gzstate, elfheader, sizeof(elfheader)); 5594b212c2SPaul Mackerras 566a923216SMilton Miller if (!parse_elf64(elfheader, &ei) && !parse_elf32(elfheader, &ei)) 576a923216SMilton Miller fatal("Error: not a valid PPC32 or PPC64 ELF file!\n\r"); 586a923216SMilton Miller 59b2c5f619SMark A. Greer if (platform_ops.image_hdr) 60b2c5f619SMark A. Greer platform_ops.image_hdr(elfheader); 6194b212c2SPaul Mackerras 62ad9d2716SDavid Gibson /* We need to alloc the memsize: gzip will expand the kernel 63ad9d2716SDavid Gibson * text/data, then possible rubbish we don't care about. But 64ad9d2716SDavid Gibson * the kernel bss must be claimed (it will be zero'd by the 65ad9d2716SDavid Gibson * kernel itself) 6694b212c2SPaul Mackerras */ 6779c85419SDavid Gibson printf("Allocating 0x%lx bytes for kernel ...\n\r", ei.memsize); 6879c85419SDavid Gibson 6979c85419SDavid Gibson if (platform_ops.vmlinux_alloc) { 7079c85419SDavid Gibson addr = platform_ops.vmlinux_alloc(ei.memsize); 7179c85419SDavid Gibson } else { 726a923216SMilton Miller if ((unsigned long)_start < ei.memsize) 736a923216SMilton Miller fatal("Insufficient memory for kernel at address 0!" 74fae59c39SDavid Gibson " (_start=%p)\n\r", _start); 7579c85419SDavid Gibson } 7679c85419SDavid Gibson 7779c85419SDavid Gibson /* Finally, gunzip the kernel */ 7879c85419SDavid Gibson printf("gunzipping (0x%p <- 0x%p:0x%p)...", addr, 7979c85419SDavid Gibson vmlinuz_addr, vmlinuz_addr+vmlinuz_size); 8079c85419SDavid Gibson /* discard up to the actual load data */ 8179c85419SDavid Gibson gunzip_discard(&gzstate, ei.elfoffset - sizeof(elfheader)); 82*02cc5114SMilton Miller len = gunzip_finish(&gzstate, addr, ei.loadsize); 83*02cc5114SMilton Miller if (len != ei.loadsize) 84*02cc5114SMilton Miller fatal("ran out of data! only got 0x%x of 0x%lx bytes.\n\r", 85*02cc5114SMilton Miller len, ei.loadsize); 86fae59c39SDavid Gibson printf("done 0x%x bytes\n\r", len); 8779c85419SDavid Gibson 8879c85419SDavid Gibson flush_cache(addr, ei.loadsize); 8979c85419SDavid Gibson 9079c85419SDavid Gibson return (struct addr_range){addr, ei.memsize}; 9179c85419SDavid Gibson } 9279c85419SDavid Gibson 9327fbaa97SDavid Gibson static struct addr_range prep_initrd(struct addr_range vmlinux, void *chosen, 9479c85419SDavid Gibson unsigned long initrd_addr, 9579c85419SDavid Gibson unsigned long initrd_size) 9679c85419SDavid Gibson { 9779c85419SDavid Gibson /* If we have an image attached to us, it overrides anything 9879c85419SDavid Gibson * supplied by the loader. */ 9979c85419SDavid Gibson if (_initrd_end > _initrd_start) { 10079c85419SDavid Gibson printf("Attached initrd image at 0x%p-0x%p\n\r", 10179c85419SDavid Gibson _initrd_start, _initrd_end); 10279c85419SDavid Gibson initrd_addr = (unsigned long)_initrd_start; 10379c85419SDavid Gibson initrd_size = _initrd_end - _initrd_start; 10479c85419SDavid Gibson } else if (initrd_size > 0) { 10579c85419SDavid Gibson printf("Using loader supplied ramdisk at 0x%lx-0x%lx\n\r", 10679c85419SDavid Gibson initrd_addr, initrd_addr + initrd_size); 10779c85419SDavid Gibson } 10879c85419SDavid Gibson 10979c85419SDavid Gibson /* If there's no initrd at all, we're done */ 11079c85419SDavid Gibson if (! initrd_size) 11179c85419SDavid Gibson return (struct addr_range){0, 0}; 11294b212c2SPaul Mackerras 11394b212c2SPaul Mackerras /* 11479c85419SDavid Gibson * If the initrd is too low it will be clobbered when the 11579c85419SDavid Gibson * kernel relocates to its final location. In this case, 11679c85419SDavid Gibson * allocate a safer place and move it. 11794b212c2SPaul Mackerras */ 11879c85419SDavid Gibson if (initrd_addr < vmlinux.size) { 11979c85419SDavid Gibson void *old_addr = (void *)initrd_addr; 12079c85419SDavid Gibson 121b2c5f619SMark A. Greer printf("Allocating 0x%lx bytes for initrd ...\n\r", 12279c85419SDavid Gibson initrd_size); 12379c85419SDavid Gibson initrd_addr = (unsigned long)malloc(initrd_size); 1246a923216SMilton Miller if (! initrd_addr) 1256a923216SMilton Miller fatal("Can't allocate memory for initial " 126b2c5f619SMark A. Greer "ramdisk !\n\r"); 127fae59c39SDavid Gibson printf("Relocating initrd 0x%lx <- 0x%p (0x%lx bytes)\n\r", 12879c85419SDavid Gibson initrd_addr, old_addr, initrd_size); 12979c85419SDavid Gibson memmove((void *)initrd_addr, old_addr, initrd_size); 13094b212c2SPaul Mackerras } 13194b212c2SPaul Mackerras 13279c85419SDavid Gibson printf("initrd head: 0x%lx\n\r", *((unsigned long *)initrd_addr)); 13394b212c2SPaul Mackerras 13479c85419SDavid Gibson /* Tell the kernel initrd address via device tree */ 13527fbaa97SDavid Gibson setprop_val(chosen, "linux,initrd-start", (u32)(initrd_addr)); 13627fbaa97SDavid Gibson setprop_val(chosen, "linux,initrd-end", (u32)(initrd_addr+initrd_size)); 13779c85419SDavid Gibson 13879c85419SDavid Gibson return (struct addr_range){(void *)initrd_addr, initrd_size}; 13994b212c2SPaul Mackerras } 14094b212c2SPaul Mackerras 141b2c5f619SMark A. Greer /* A buffer that may be edited by tools operating on a zImage binary so as to 142b2c5f619SMark A. Greer * edit the command line passed to vmlinux (by setting /chosen/bootargs). 143b2c5f619SMark A. Greer * The buffer is put in it's own section so that tools may locate it easier. 144b2c5f619SMark A. Greer */ 1453af82a8bSDavid Gibson static char cmdline[COMMAND_LINE_SIZE] 146b2c5f619SMark A. Greer __attribute__((__section__("__builtin_cmdline"))); 147b2c5f619SMark A. Greer 1483af82a8bSDavid Gibson static void prep_cmdline(void *chosen) 149b2c5f619SMark A. Greer { 1503af82a8bSDavid Gibson if (cmdline[0] == '\0') 1513af82a8bSDavid Gibson getprop(chosen, "bootargs", cmdline, COMMAND_LINE_SIZE-1); 152b2c5f619SMark A. Greer 1533af82a8bSDavid Gibson printf("\n\rLinux/PowerPC load: %s", cmdline); 1543af82a8bSDavid Gibson /* If possible, edit the command line */ 1553af82a8bSDavid Gibson if (console_ops.edit_cmdline) 1563af82a8bSDavid Gibson console_ops.edit_cmdline(cmdline, COMMAND_LINE_SIZE); 1573af82a8bSDavid Gibson printf("\n\r"); 158b2c5f619SMark A. Greer 1593af82a8bSDavid Gibson /* Put the command line back into the devtree for the kernel */ 1603af82a8bSDavid Gibson setprop_str(chosen, "bootargs", cmdline); 161b2c5f619SMark A. Greer } 162b2c5f619SMark A. Greer 163b2c5f619SMark A. Greer struct platform_ops platform_ops; 164b2c5f619SMark A. Greer struct dt_ops dt_ops; 165b2c5f619SMark A. Greer struct console_ops console_ops; 166cd197ffcSDavid Gibson struct loader_info loader_info; 167b2c5f619SMark A. Greer 168e5a2072bSDavid Gibson void start(void) 169b2c5f619SMark A. Greer { 17079c85419SDavid Gibson struct addr_range vmlinux, initrd; 171b2c5f619SMark A. Greer kernel_entry_t kentry; 17235af89ebSDavid Gibson unsigned long ft_addr = 0; 17327fbaa97SDavid Gibson void *chosen; 174b2c5f619SMark A. Greer 1753af82a8bSDavid Gibson /* Do this first, because malloc() could clobber the loader's 1763af82a8bSDavid Gibson * command line. Only use the loader command line if a 1773af82a8bSDavid Gibson * built-in command line wasn't set by an external tool */ 1783af82a8bSDavid Gibson if ((loader_info.cmdline_len > 0) && (cmdline[0] == '\0')) 1793af82a8bSDavid Gibson memmove(cmdline, loader_info.cmdline, 1803af82a8bSDavid Gibson min(loader_info.cmdline_len, COMMAND_LINE_SIZE-1)); 1813af82a8bSDavid Gibson 182b2c5f619SMark A. Greer if (console_ops.open && (console_ops.open() < 0)) 183b2c5f619SMark A. Greer exit(); 184b2c5f619SMark A. Greer if (platform_ops.fixups) 185b2c5f619SMark A. Greer platform_ops.fixups(); 186b2c5f619SMark A. Greer 187b2c5f619SMark A. Greer printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", 188e5a2072bSDavid Gibson _start, get_sp()); 189b2c5f619SMark A. Greer 19027fbaa97SDavid Gibson /* Ensure that the device tree has a /chosen node */ 19127fbaa97SDavid Gibson chosen = finddevice("/chosen"); 19227fbaa97SDavid Gibson if (!chosen) 19327fbaa97SDavid Gibson chosen = create_node(NULL, "chosen"); 19427fbaa97SDavid Gibson 19579c85419SDavid Gibson vmlinux = prep_kernel(); 19627fbaa97SDavid Gibson initrd = prep_initrd(vmlinux, chosen, 19727fbaa97SDavid Gibson loader_info.initrd_addr, loader_info.initrd_size); 1983af82a8bSDavid Gibson prep_cmdline(chosen); 199b2c5f619SMark A. Greer 20035af89ebSDavid Gibson printf("Finalizing device tree..."); 20135af89ebSDavid Gibson if (dt_ops.finalize) 20235af89ebSDavid Gibson ft_addr = dt_ops.finalize(); 20335af89ebSDavid Gibson if (ft_addr) 20435af89ebSDavid Gibson printf(" flat tree at 0x%lx\n\r", ft_addr); 20535af89ebSDavid Gibson else 206cd197ffcSDavid Gibson printf(" using OF tree (promptr=%p)\n\r", loader_info.promptr); 20735af89ebSDavid Gibson 208b2c5f619SMark A. Greer if (console_ops.close) 209b2c5f619SMark A. Greer console_ops.close(); 210b2c5f619SMark A. Greer 211b2c5f619SMark A. Greer kentry = (kernel_entry_t) vmlinux.addr; 21235af89ebSDavid Gibson if (ft_addr) 21335af89ebSDavid Gibson kentry(ft_addr, 0, NULL); 214b2c5f619SMark A. Greer else 215cd197ffcSDavid Gibson kentry((unsigned long)initrd.addr, initrd.size, 216cd197ffcSDavid Gibson loader_info.promptr); 217b2c5f619SMark A. Greer 2186a923216SMilton Miller /* console closed so printf in fatal below may not work */ 2196a923216SMilton Miller fatal("Error: Linux kernel returned to zImage boot wrapper!\n\r"); 220b2c5f619SMark A. Greer } 221