162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) Paul Mackerras 1997. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Updates for PPC64 by Todd Inglett, Dave Engebretsen & Peter Bergner. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <stdarg.h> 862306a36Sopenharmony_ci#include <stddef.h> 962306a36Sopenharmony_ci#include "elf.h" 1062306a36Sopenharmony_ci#include "page.h" 1162306a36Sopenharmony_ci#include "string.h" 1262306a36Sopenharmony_ci#include "stdio.h" 1362306a36Sopenharmony_ci#include "ops.h" 1462306a36Sopenharmony_ci#include "reg.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistruct addr_range { 1762306a36Sopenharmony_ci void *addr; 1862306a36Sopenharmony_ci unsigned long size; 1962306a36Sopenharmony_ci}; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#undef DEBUG 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic struct addr_range prep_kernel(void) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci char elfheader[256]; 2662306a36Sopenharmony_ci unsigned char *vmlinuz_addr = (unsigned char *)_vmlinux_start; 2762306a36Sopenharmony_ci unsigned long vmlinuz_size = _vmlinux_end - _vmlinux_start; 2862306a36Sopenharmony_ci void *addr = 0; 2962306a36Sopenharmony_ci struct elf_info ei; 3062306a36Sopenharmony_ci long len; 3162306a36Sopenharmony_ci int uncompressed_image = 0; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci len = partial_decompress(vmlinuz_addr, vmlinuz_size, 3462306a36Sopenharmony_ci elfheader, sizeof(elfheader), 0); 3562306a36Sopenharmony_ci /* assume uncompressed data if -1 is returned */ 3662306a36Sopenharmony_ci if (len == -1) { 3762306a36Sopenharmony_ci uncompressed_image = 1; 3862306a36Sopenharmony_ci memcpy(elfheader, vmlinuz_addr, sizeof(elfheader)); 3962306a36Sopenharmony_ci printf("No valid compressed data found, assume uncompressed data\n\r"); 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (!parse_elf64(elfheader, &ei) && !parse_elf32(elfheader, &ei)) 4362306a36Sopenharmony_ci fatal("Error: not a valid PPC32 or PPC64 ELF file!\n\r"); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci if (platform_ops.image_hdr) 4662306a36Sopenharmony_ci platform_ops.image_hdr(elfheader); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci /* We need to alloc the memsize: gzip will expand the kernel 4962306a36Sopenharmony_ci * text/data, then possible rubbish we don't care about. But 5062306a36Sopenharmony_ci * the kernel bss must be claimed (it will be zero'd by the 5162306a36Sopenharmony_ci * kernel itself) 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci printf("Allocating 0x%lx bytes for kernel...\n\r", ei.memsize); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (platform_ops.vmlinux_alloc) { 5662306a36Sopenharmony_ci addr = platform_ops.vmlinux_alloc(ei.memsize); 5762306a36Sopenharmony_ci } else { 5862306a36Sopenharmony_ci /* 5962306a36Sopenharmony_ci * Check if the kernel image (without bss) would overwrite the 6062306a36Sopenharmony_ci * bootwrapper. The device tree has been moved in fdt_init() 6162306a36Sopenharmony_ci * to an area allocated with malloc() (somewhere past _end). 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci if ((unsigned long)_start < ei.loadsize) 6462306a36Sopenharmony_ci fatal("Insufficient memory for kernel at address 0!" 6562306a36Sopenharmony_ci " (_start=%p, uncompressed size=%08lx)\n\r", 6662306a36Sopenharmony_ci _start, ei.loadsize); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if ((unsigned long)_end < ei.memsize) 6962306a36Sopenharmony_ci fatal("The final kernel image would overwrite the " 7062306a36Sopenharmony_ci "device tree\n\r"); 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci if (uncompressed_image) { 7462306a36Sopenharmony_ci memcpy(addr, vmlinuz_addr + ei.elfoffset, ei.loadsize); 7562306a36Sopenharmony_ci printf("0x%lx bytes of uncompressed data copied\n\r", 7662306a36Sopenharmony_ci ei.loadsize); 7762306a36Sopenharmony_ci goto out; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* Finally, decompress the kernel */ 8162306a36Sopenharmony_ci printf("Decompressing (0x%p <- 0x%p:0x%p)...\n\r", addr, 8262306a36Sopenharmony_ci vmlinuz_addr, vmlinuz_addr+vmlinuz_size); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci len = partial_decompress(vmlinuz_addr, vmlinuz_size, 8562306a36Sopenharmony_ci addr, ei.loadsize, ei.elfoffset); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (len < 0) 8862306a36Sopenharmony_ci fatal("Decompression failed with error code %ld\n\r", len); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci if (len != ei.loadsize) 9162306a36Sopenharmony_ci fatal("Decompression error: got 0x%lx bytes, expected 0x%lx.\n\r", 9262306a36Sopenharmony_ci len, ei.loadsize); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci printf("Done! Decompressed 0x%lx bytes\n\r", len); 9562306a36Sopenharmony_ciout: 9662306a36Sopenharmony_ci flush_cache(addr, ei.loadsize); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci return (struct addr_range){addr, ei.memsize}; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic struct addr_range prep_initrd(struct addr_range vmlinux, void *chosen, 10262306a36Sopenharmony_ci unsigned long initrd_addr, 10362306a36Sopenharmony_ci unsigned long initrd_size) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci /* If we have an image attached to us, it overrides anything 10662306a36Sopenharmony_ci * supplied by the loader. */ 10762306a36Sopenharmony_ci if (&_initrd_end > &_initrd_start) { 10862306a36Sopenharmony_ci printf("Attached initrd image at 0x%p-0x%p\n\r", 10962306a36Sopenharmony_ci _initrd_start, _initrd_end); 11062306a36Sopenharmony_ci initrd_addr = (unsigned long)_initrd_start; 11162306a36Sopenharmony_ci initrd_size = _initrd_end - _initrd_start; 11262306a36Sopenharmony_ci } else if (initrd_size > 0) { 11362306a36Sopenharmony_ci printf("Using loader supplied ramdisk at 0x%lx-0x%lx\n\r", 11462306a36Sopenharmony_ci initrd_addr, initrd_addr + initrd_size); 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci /* If there's no initrd at all, we're done */ 11862306a36Sopenharmony_ci if (! initrd_size) 11962306a36Sopenharmony_ci return (struct addr_range){0, 0}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* 12262306a36Sopenharmony_ci * If the initrd is too low it will be clobbered when the 12362306a36Sopenharmony_ci * kernel relocates to its final location. In this case, 12462306a36Sopenharmony_ci * allocate a safer place and move it. 12562306a36Sopenharmony_ci */ 12662306a36Sopenharmony_ci if (initrd_addr < vmlinux.size) { 12762306a36Sopenharmony_ci void *old_addr = (void *)initrd_addr; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci printf("Allocating 0x%lx bytes for initrd ...\n\r", 13062306a36Sopenharmony_ci initrd_size); 13162306a36Sopenharmony_ci initrd_addr = (unsigned long)malloc(initrd_size); 13262306a36Sopenharmony_ci if (! initrd_addr) 13362306a36Sopenharmony_ci fatal("Can't allocate memory for initial " 13462306a36Sopenharmony_ci "ramdisk !\n\r"); 13562306a36Sopenharmony_ci printf("Relocating initrd 0x%lx <- 0x%p (0x%lx bytes)\n\r", 13662306a36Sopenharmony_ci initrd_addr, old_addr, initrd_size); 13762306a36Sopenharmony_ci memmove((void *)initrd_addr, old_addr, initrd_size); 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci printf("initrd head: 0x%lx\n\r", *((unsigned long *)initrd_addr)); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* Tell the kernel initrd address via device tree */ 14362306a36Sopenharmony_ci setprop_val(chosen, "linux,initrd-start", (u32)(initrd_addr)); 14462306a36Sopenharmony_ci setprop_val(chosen, "linux,initrd-end", (u32)(initrd_addr+initrd_size)); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci return (struct addr_range){(void *)initrd_addr, initrd_size}; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci#ifdef __powerpc64__ 15062306a36Sopenharmony_cistatic void prep_esm_blob(struct addr_range vmlinux, void *chosen) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci unsigned long esm_blob_addr, esm_blob_size; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* Do we have an ESM (Enter Secure Mode) blob? */ 15562306a36Sopenharmony_ci if (&_esm_blob_end <= &_esm_blob_start) 15662306a36Sopenharmony_ci return; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci printf("Attached ESM blob at 0x%p-0x%p\n\r", 15962306a36Sopenharmony_ci _esm_blob_start, _esm_blob_end); 16062306a36Sopenharmony_ci esm_blob_addr = (unsigned long)_esm_blob_start; 16162306a36Sopenharmony_ci esm_blob_size = _esm_blob_end - _esm_blob_start; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* 16462306a36Sopenharmony_ci * If the ESM blob is too low it will be clobbered when the 16562306a36Sopenharmony_ci * kernel relocates to its final location. In this case, 16662306a36Sopenharmony_ci * allocate a safer place and move it. 16762306a36Sopenharmony_ci */ 16862306a36Sopenharmony_ci if (esm_blob_addr < vmlinux.size) { 16962306a36Sopenharmony_ci void *old_addr = (void *)esm_blob_addr; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci printf("Allocating 0x%lx bytes for esm_blob ...\n\r", 17262306a36Sopenharmony_ci esm_blob_size); 17362306a36Sopenharmony_ci esm_blob_addr = (unsigned long)malloc(esm_blob_size); 17462306a36Sopenharmony_ci if (!esm_blob_addr) 17562306a36Sopenharmony_ci fatal("Can't allocate memory for ESM blob !\n\r"); 17662306a36Sopenharmony_ci printf("Relocating ESM blob 0x%lx <- 0x%p (0x%lx bytes)\n\r", 17762306a36Sopenharmony_ci esm_blob_addr, old_addr, esm_blob_size); 17862306a36Sopenharmony_ci memmove((void *)esm_blob_addr, old_addr, esm_blob_size); 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* Tell the kernel ESM blob address via device tree. */ 18262306a36Sopenharmony_ci setprop_val(chosen, "linux,esm-blob-start", (u32)(esm_blob_addr)); 18362306a36Sopenharmony_ci setprop_val(chosen, "linux,esm-blob-end", (u32)(esm_blob_addr + esm_blob_size)); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci#else 18662306a36Sopenharmony_cistatic inline void prep_esm_blob(struct addr_range vmlinux, void *chosen) { } 18762306a36Sopenharmony_ci#endif 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci/* A buffer that may be edited by tools operating on a zImage binary so as to 19062306a36Sopenharmony_ci * edit the command line passed to vmlinux (by setting /chosen/bootargs). 19162306a36Sopenharmony_ci * The buffer is put in it's own section so that tools may locate it easier. 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_cistatic char cmdline[BOOT_COMMAND_LINE_SIZE] 19462306a36Sopenharmony_ci __attribute__((__section__("__builtin_cmdline"))); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic void prep_cmdline(void *chosen) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci unsigned int getline_timeout = 5000; 19962306a36Sopenharmony_ci int v; 20062306a36Sopenharmony_ci int n; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci /* Wait-for-input time */ 20362306a36Sopenharmony_ci n = getprop(chosen, "linux,cmdline-timeout", &v, sizeof(v)); 20462306a36Sopenharmony_ci if (n == sizeof(v)) 20562306a36Sopenharmony_ci getline_timeout = v; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (cmdline[0] == '\0') 20862306a36Sopenharmony_ci getprop(chosen, "bootargs", cmdline, BOOT_COMMAND_LINE_SIZE-1); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci printf("\n\rLinux/PowerPC load: %s", cmdline); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* If possible, edit the command line */ 21362306a36Sopenharmony_ci if (console_ops.edit_cmdline && getline_timeout) 21462306a36Sopenharmony_ci console_ops.edit_cmdline(cmdline, BOOT_COMMAND_LINE_SIZE, getline_timeout); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci printf("\n\r"); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci /* Put the command line back into the devtree for the kernel */ 21962306a36Sopenharmony_ci setprop_str(chosen, "bootargs", cmdline); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistruct platform_ops platform_ops; 22362306a36Sopenharmony_cistruct dt_ops dt_ops; 22462306a36Sopenharmony_cistruct console_ops console_ops; 22562306a36Sopenharmony_cistruct loader_info loader_info; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_civoid start(void) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci struct addr_range vmlinux, initrd; 23062306a36Sopenharmony_ci kernel_entry_t kentry; 23162306a36Sopenharmony_ci unsigned long ft_addr = 0; 23262306a36Sopenharmony_ci void *chosen; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* Do this first, because malloc() could clobber the loader's 23562306a36Sopenharmony_ci * command line. Only use the loader command line if a 23662306a36Sopenharmony_ci * built-in command line wasn't set by an external tool */ 23762306a36Sopenharmony_ci if ((loader_info.cmdline_len > 0) && (cmdline[0] == '\0')) 23862306a36Sopenharmony_ci memmove(cmdline, loader_info.cmdline, 23962306a36Sopenharmony_ci min(loader_info.cmdline_len, BOOT_COMMAND_LINE_SIZE-1)); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (console_ops.open && (console_ops.open() < 0)) 24262306a36Sopenharmony_ci exit(); 24362306a36Sopenharmony_ci if (platform_ops.fixups) 24462306a36Sopenharmony_ci platform_ops.fixups(); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", 24762306a36Sopenharmony_ci _start, get_sp()); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci /* Ensure that the device tree has a /chosen node */ 25062306a36Sopenharmony_ci chosen = finddevice("/chosen"); 25162306a36Sopenharmony_ci if (!chosen) 25262306a36Sopenharmony_ci chosen = create_node(NULL, "chosen"); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci vmlinux = prep_kernel(); 25562306a36Sopenharmony_ci initrd = prep_initrd(vmlinux, chosen, 25662306a36Sopenharmony_ci loader_info.initrd_addr, loader_info.initrd_size); 25762306a36Sopenharmony_ci prep_esm_blob(vmlinux, chosen); 25862306a36Sopenharmony_ci prep_cmdline(chosen); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci printf("Finalizing device tree..."); 26162306a36Sopenharmony_ci if (dt_ops.finalize) 26262306a36Sopenharmony_ci ft_addr = dt_ops.finalize(); 26362306a36Sopenharmony_ci if (ft_addr) 26462306a36Sopenharmony_ci printf(" flat tree at 0x%lx\n\r", ft_addr); 26562306a36Sopenharmony_ci else 26662306a36Sopenharmony_ci printf(" using OF tree (promptr=%p)\n\r", loader_info.promptr); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (console_ops.close) 26962306a36Sopenharmony_ci console_ops.close(); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci kentry = (kernel_entry_t) vmlinux.addr; 27262306a36Sopenharmony_ci if (ft_addr) { 27362306a36Sopenharmony_ci if(platform_ops.kentry) 27462306a36Sopenharmony_ci platform_ops.kentry(ft_addr, vmlinux.addr); 27562306a36Sopenharmony_ci else 27662306a36Sopenharmony_ci kentry(ft_addr, 0, NULL); 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci else 27962306a36Sopenharmony_ci kentry((unsigned long)initrd.addr, initrd.size, 28062306a36Sopenharmony_ci loader_info.promptr); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* console closed so printf in fatal below may not work */ 28362306a36Sopenharmony_ci fatal("Error: Linux kernel returned to zImage boot wrapper!\n\r"); 28462306a36Sopenharmony_ci} 285