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" 19e5a2072bSDavid Gibson #include "reg.h" 2094b212c2SPaul Mackerras 21ad9d2716SDavid Gibson static struct gunzip_state gzstate; 22ad9d2716SDavid Gibson 2394b212c2SPaul Mackerras struct addr_range { 2479c85419SDavid Gibson void *addr; 2594b212c2SPaul Mackerras unsigned long size; 2694b212c2SPaul Mackerras }; 2794b212c2SPaul Mackerras 2894b212c2SPaul Mackerras #undef DEBUG 2994b212c2SPaul Mackerras 3079c85419SDavid Gibson static struct addr_range prep_kernel(void) 3130d8caf7Smostrows@watson.ibm.com { 3279c85419SDavid Gibson char elfheader[256]; 3379c85419SDavid Gibson void *vmlinuz_addr = _vmlinux_start; 3479c85419SDavid Gibson unsigned long vmlinuz_size = _vmlinux_end - _vmlinux_start; 3579c85419SDavid Gibson void *addr = 0; 3679c85419SDavid Gibson struct elf_info ei; 3730d8caf7Smostrows@watson.ibm.com int len; 3866a45dd3SPaul Mackerras 3994b212c2SPaul Mackerras /* gunzip the ELF header of the kernel */ 4079c85419SDavid Gibson gunzip_start(&gzstate, vmlinuz_addr, vmlinuz_size); 41ad9d2716SDavid Gibson gunzip_exactly(&gzstate, elfheader, sizeof(elfheader)); 4294b212c2SPaul Mackerras 436a923216SMilton Miller if (!parse_elf64(elfheader, &ei) && !parse_elf32(elfheader, &ei)) 446a923216SMilton Miller fatal("Error: not a valid PPC32 or PPC64 ELF file!\n\r"); 456a923216SMilton Miller 46b2c5f619SMark A. Greer if (platform_ops.image_hdr) 47b2c5f619SMark A. Greer platform_ops.image_hdr(elfheader); 4894b212c2SPaul Mackerras 49ad9d2716SDavid Gibson /* We need to alloc the memsize: gzip will expand the kernel 50ad9d2716SDavid Gibson * text/data, then possible rubbish we don't care about. But 51ad9d2716SDavid Gibson * the kernel bss must be claimed (it will be zero'd by the 52ad9d2716SDavid Gibson * kernel itself) 5394b212c2SPaul Mackerras */ 5479c85419SDavid Gibson printf("Allocating 0x%lx bytes for kernel ...\n\r", ei.memsize); 5579c85419SDavid Gibson 5679c85419SDavid Gibson if (platform_ops.vmlinux_alloc) { 5779c85419SDavid Gibson addr = platform_ops.vmlinux_alloc(ei.memsize); 5879c85419SDavid Gibson } else { 59*c10c178aSSebastian Siewior /* 60*c10c178aSSebastian Siewior * Check if the kernel image (without bss) would overwrite the 61*c10c178aSSebastian Siewior * bootwrapper. The device tree has been moved in fdt_init() 62*c10c178aSSebastian Siewior * to an area allocated with malloc() (somewhere past _end). 63*c10c178aSSebastian Siewior */ 64*c10c178aSSebastian Siewior if ((unsigned long)_start < ei.loadsize) 656a923216SMilton Miller fatal("Insufficient memory for kernel at address 0!" 66*c10c178aSSebastian Siewior " (_start=%p, uncomressed size=%08x)\n\r", 67*c10c178aSSebastian Siewior _start, ei.loadsize); 68*c10c178aSSebastian Siewior 69*c10c178aSSebastian Siewior if ((unsigned long)_end < ei.memsize) 70*c10c178aSSebastian Siewior fatal("The final kernel image would overwrite the " 71*c10c178aSSebastian Siewior "device tree\n\r"); 7279c85419SDavid Gibson } 7379c85419SDavid Gibson 7479c85419SDavid Gibson /* Finally, gunzip the kernel */ 7579c85419SDavid Gibson printf("gunzipping (0x%p <- 0x%p:0x%p)...", addr, 7679c85419SDavid Gibson vmlinuz_addr, vmlinuz_addr+vmlinuz_size); 7779c85419SDavid Gibson /* discard up to the actual load data */ 7879c85419SDavid Gibson gunzip_discard(&gzstate, ei.elfoffset - sizeof(elfheader)); 7902cc5114SMilton Miller len = gunzip_finish(&gzstate, addr, ei.loadsize); 8002cc5114SMilton Miller if (len != ei.loadsize) 8102cc5114SMilton Miller fatal("ran out of data! only got 0x%x of 0x%lx bytes.\n\r", 8202cc5114SMilton Miller len, ei.loadsize); 83fae59c39SDavid Gibson printf("done 0x%x bytes\n\r", len); 8479c85419SDavid Gibson 8579c85419SDavid Gibson flush_cache(addr, ei.loadsize); 8679c85419SDavid Gibson 8779c85419SDavid Gibson return (struct addr_range){addr, ei.memsize}; 8879c85419SDavid Gibson } 8979c85419SDavid Gibson 9027fbaa97SDavid Gibson static struct addr_range prep_initrd(struct addr_range vmlinux, void *chosen, 9179c85419SDavid Gibson unsigned long initrd_addr, 9279c85419SDavid Gibson unsigned long initrd_size) 9379c85419SDavid Gibson { 9479c85419SDavid Gibson /* If we have an image attached to us, it overrides anything 9579c85419SDavid Gibson * supplied by the loader. */ 9679c85419SDavid Gibson if (_initrd_end > _initrd_start) { 9779c85419SDavid Gibson printf("Attached initrd image at 0x%p-0x%p\n\r", 9879c85419SDavid Gibson _initrd_start, _initrd_end); 9979c85419SDavid Gibson initrd_addr = (unsigned long)_initrd_start; 10079c85419SDavid Gibson initrd_size = _initrd_end - _initrd_start; 10179c85419SDavid Gibson } else if (initrd_size > 0) { 10279c85419SDavid Gibson printf("Using loader supplied ramdisk at 0x%lx-0x%lx\n\r", 10379c85419SDavid Gibson initrd_addr, initrd_addr + initrd_size); 10479c85419SDavid Gibson } 10579c85419SDavid Gibson 10679c85419SDavid Gibson /* If there's no initrd at all, we're done */ 10779c85419SDavid Gibson if (! initrd_size) 10879c85419SDavid Gibson return (struct addr_range){0, 0}; 10994b212c2SPaul Mackerras 11094b212c2SPaul Mackerras /* 11179c85419SDavid Gibson * If the initrd is too low it will be clobbered when the 11279c85419SDavid Gibson * kernel relocates to its final location. In this case, 11379c85419SDavid Gibson * allocate a safer place and move it. 11494b212c2SPaul Mackerras */ 11579c85419SDavid Gibson if (initrd_addr < vmlinux.size) { 11679c85419SDavid Gibson void *old_addr = (void *)initrd_addr; 11779c85419SDavid Gibson 118b2c5f619SMark A. Greer printf("Allocating 0x%lx bytes for initrd ...\n\r", 11979c85419SDavid Gibson initrd_size); 12079c85419SDavid Gibson initrd_addr = (unsigned long)malloc(initrd_size); 1216a923216SMilton Miller if (! initrd_addr) 1226a923216SMilton Miller fatal("Can't allocate memory for initial " 123b2c5f619SMark A. Greer "ramdisk !\n\r"); 124fae59c39SDavid Gibson printf("Relocating initrd 0x%lx <- 0x%p (0x%lx bytes)\n\r", 12579c85419SDavid Gibson initrd_addr, old_addr, initrd_size); 12679c85419SDavid Gibson memmove((void *)initrd_addr, old_addr, initrd_size); 12794b212c2SPaul Mackerras } 12894b212c2SPaul Mackerras 12979c85419SDavid Gibson printf("initrd head: 0x%lx\n\r", *((unsigned long *)initrd_addr)); 13094b212c2SPaul Mackerras 13179c85419SDavid Gibson /* Tell the kernel initrd address via device tree */ 13227fbaa97SDavid Gibson setprop_val(chosen, "linux,initrd-start", (u32)(initrd_addr)); 13327fbaa97SDavid Gibson setprop_val(chosen, "linux,initrd-end", (u32)(initrd_addr+initrd_size)); 13479c85419SDavid Gibson 13579c85419SDavid Gibson return (struct addr_range){(void *)initrd_addr, initrd_size}; 13694b212c2SPaul Mackerras } 13794b212c2SPaul Mackerras 138b2c5f619SMark A. Greer /* A buffer that may be edited by tools operating on a zImage binary so as to 139b2c5f619SMark A. Greer * edit the command line passed to vmlinux (by setting /chosen/bootargs). 140b2c5f619SMark A. Greer * The buffer is put in it's own section so that tools may locate it easier. 141b2c5f619SMark A. Greer */ 1423af82a8bSDavid Gibson static char cmdline[COMMAND_LINE_SIZE] 143b2c5f619SMark A. Greer __attribute__((__section__("__builtin_cmdline"))); 144b2c5f619SMark A. Greer 1453af82a8bSDavid Gibson static void prep_cmdline(void *chosen) 146b2c5f619SMark A. Greer { 1473af82a8bSDavid Gibson if (cmdline[0] == '\0') 1483af82a8bSDavid Gibson getprop(chosen, "bootargs", cmdline, COMMAND_LINE_SIZE-1); 149b2c5f619SMark A. Greer 1503af82a8bSDavid Gibson printf("\n\rLinux/PowerPC load: %s", cmdline); 1513af82a8bSDavid Gibson /* If possible, edit the command line */ 1523af82a8bSDavid Gibson if (console_ops.edit_cmdline) 1533af82a8bSDavid Gibson console_ops.edit_cmdline(cmdline, COMMAND_LINE_SIZE); 1543af82a8bSDavid Gibson printf("\n\r"); 155b2c5f619SMark A. Greer 1563af82a8bSDavid Gibson /* Put the command line back into the devtree for the kernel */ 1573af82a8bSDavid Gibson setprop_str(chosen, "bootargs", cmdline); 158b2c5f619SMark A. Greer } 159b2c5f619SMark A. Greer 160b2c5f619SMark A. Greer struct platform_ops platform_ops; 161b2c5f619SMark A. Greer struct dt_ops dt_ops; 162b2c5f619SMark A. Greer struct console_ops console_ops; 163cd197ffcSDavid Gibson struct loader_info loader_info; 164b2c5f619SMark A. Greer 165e5a2072bSDavid Gibson void start(void) 166b2c5f619SMark A. Greer { 16779c85419SDavid Gibson struct addr_range vmlinux, initrd; 168b2c5f619SMark A. Greer kernel_entry_t kentry; 16935af89ebSDavid Gibson unsigned long ft_addr = 0; 17027fbaa97SDavid Gibson void *chosen; 171b2c5f619SMark A. Greer 1723af82a8bSDavid Gibson /* Do this first, because malloc() could clobber the loader's 1733af82a8bSDavid Gibson * command line. Only use the loader command line if a 1743af82a8bSDavid Gibson * built-in command line wasn't set by an external tool */ 1753af82a8bSDavid Gibson if ((loader_info.cmdline_len > 0) && (cmdline[0] == '\0')) 1763af82a8bSDavid Gibson memmove(cmdline, loader_info.cmdline, 1773af82a8bSDavid Gibson min(loader_info.cmdline_len, COMMAND_LINE_SIZE-1)); 1783af82a8bSDavid Gibson 179b2c5f619SMark A. Greer if (console_ops.open && (console_ops.open() < 0)) 180b2c5f619SMark A. Greer exit(); 181b2c5f619SMark A. Greer if (platform_ops.fixups) 182b2c5f619SMark A. Greer platform_ops.fixups(); 183b2c5f619SMark A. Greer 184b2c5f619SMark A. Greer printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", 185e5a2072bSDavid Gibson _start, get_sp()); 186b2c5f619SMark A. Greer 18727fbaa97SDavid Gibson /* Ensure that the device tree has a /chosen node */ 18827fbaa97SDavid Gibson chosen = finddevice("/chosen"); 18927fbaa97SDavid Gibson if (!chosen) 19027fbaa97SDavid Gibson chosen = create_node(NULL, "chosen"); 19127fbaa97SDavid Gibson 19279c85419SDavid Gibson vmlinux = prep_kernel(); 19327fbaa97SDavid Gibson initrd = prep_initrd(vmlinux, chosen, 19427fbaa97SDavid Gibson loader_info.initrd_addr, loader_info.initrd_size); 1953af82a8bSDavid Gibson prep_cmdline(chosen); 196b2c5f619SMark A. Greer 19735af89ebSDavid Gibson printf("Finalizing device tree..."); 19835af89ebSDavid Gibson if (dt_ops.finalize) 19935af89ebSDavid Gibson ft_addr = dt_ops.finalize(); 20035af89ebSDavid Gibson if (ft_addr) 20135af89ebSDavid Gibson printf(" flat tree at 0x%lx\n\r", ft_addr); 20235af89ebSDavid Gibson else 203cd197ffcSDavid Gibson printf(" using OF tree (promptr=%p)\n\r", loader_info.promptr); 20435af89ebSDavid Gibson 205b2c5f619SMark A. Greer if (console_ops.close) 206b2c5f619SMark A. Greer console_ops.close(); 207b2c5f619SMark A. Greer 208b2c5f619SMark A. Greer kentry = (kernel_entry_t) vmlinux.addr; 20935af89ebSDavid Gibson if (ft_addr) 21035af89ebSDavid Gibson kentry(ft_addr, 0, NULL); 211b2c5f619SMark A. Greer else 212cd197ffcSDavid Gibson kentry((unsigned long)initrd.addr, initrd.size, 213cd197ffcSDavid Gibson loader_info.promptr); 214b2c5f619SMark A. Greer 2156a923216SMilton Miller /* console closed so printf in fatal below may not work */ 2166a923216SMilton Miller fatal("Error: Linux kernel returned to zImage boot wrapper!\n\r"); 217b2c5f619SMark A. Greer } 218