18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) Paul Mackerras 1997. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Updates for PPC64 by Todd Inglett, Dave Engebretsen & Peter Bergner. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include <stdarg.h> 88c2ecf20Sopenharmony_ci#include <stddef.h> 98c2ecf20Sopenharmony_ci#include "elf.h" 108c2ecf20Sopenharmony_ci#include "page.h" 118c2ecf20Sopenharmony_ci#include "string.h" 128c2ecf20Sopenharmony_ci#include "stdio.h" 138c2ecf20Sopenharmony_ci#include "ops.h" 148c2ecf20Sopenharmony_ci#include "reg.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistruct addr_range { 178c2ecf20Sopenharmony_ci void *addr; 188c2ecf20Sopenharmony_ci unsigned long size; 198c2ecf20Sopenharmony_ci}; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#undef DEBUG 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic struct addr_range prep_kernel(void) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci char elfheader[256]; 268c2ecf20Sopenharmony_ci unsigned char *vmlinuz_addr = (unsigned char *)_vmlinux_start; 278c2ecf20Sopenharmony_ci unsigned long vmlinuz_size = _vmlinux_end - _vmlinux_start; 288c2ecf20Sopenharmony_ci void *addr = 0; 298c2ecf20Sopenharmony_ci struct elf_info ei; 308c2ecf20Sopenharmony_ci long len; 318c2ecf20Sopenharmony_ci int uncompressed_image = 0; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci len = partial_decompress(vmlinuz_addr, vmlinuz_size, 348c2ecf20Sopenharmony_ci elfheader, sizeof(elfheader), 0); 358c2ecf20Sopenharmony_ci /* assume uncompressed data if -1 is returned */ 368c2ecf20Sopenharmony_ci if (len == -1) { 378c2ecf20Sopenharmony_ci uncompressed_image = 1; 388c2ecf20Sopenharmony_ci memcpy(elfheader, vmlinuz_addr, sizeof(elfheader)); 398c2ecf20Sopenharmony_ci printf("No valid compressed data found, assume uncompressed data\n\r"); 408c2ecf20Sopenharmony_ci } 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci if (!parse_elf64(elfheader, &ei) && !parse_elf32(elfheader, &ei)) 438c2ecf20Sopenharmony_ci fatal("Error: not a valid PPC32 or PPC64 ELF file!\n\r"); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if (platform_ops.image_hdr) 468c2ecf20Sopenharmony_ci platform_ops.image_hdr(elfheader); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci /* We need to alloc the memsize: gzip will expand the kernel 498c2ecf20Sopenharmony_ci * text/data, then possible rubbish we don't care about. But 508c2ecf20Sopenharmony_ci * the kernel bss must be claimed (it will be zero'd by the 518c2ecf20Sopenharmony_ci * kernel itself) 528c2ecf20Sopenharmony_ci */ 538c2ecf20Sopenharmony_ci printf("Allocating 0x%lx bytes for kernel...\n\r", ei.memsize); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci if (platform_ops.vmlinux_alloc) { 568c2ecf20Sopenharmony_ci addr = platform_ops.vmlinux_alloc(ei.memsize); 578c2ecf20Sopenharmony_ci } else { 588c2ecf20Sopenharmony_ci /* 598c2ecf20Sopenharmony_ci * Check if the kernel image (without bss) would overwrite the 608c2ecf20Sopenharmony_ci * bootwrapper. The device tree has been moved in fdt_init() 618c2ecf20Sopenharmony_ci * to an area allocated with malloc() (somewhere past _end). 628c2ecf20Sopenharmony_ci */ 638c2ecf20Sopenharmony_ci if ((unsigned long)_start < ei.loadsize) 648c2ecf20Sopenharmony_ci fatal("Insufficient memory for kernel at address 0!" 658c2ecf20Sopenharmony_ci " (_start=%p, uncompressed size=%08lx)\n\r", 668c2ecf20Sopenharmony_ci _start, ei.loadsize); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci if ((unsigned long)_end < ei.memsize) 698c2ecf20Sopenharmony_ci fatal("The final kernel image would overwrite the " 708c2ecf20Sopenharmony_ci "device tree\n\r"); 718c2ecf20Sopenharmony_ci } 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (uncompressed_image) { 748c2ecf20Sopenharmony_ci memcpy(addr, vmlinuz_addr + ei.elfoffset, ei.loadsize); 758c2ecf20Sopenharmony_ci printf("0x%lx bytes of uncompressed data copied\n\r", 768c2ecf20Sopenharmony_ci ei.loadsize); 778c2ecf20Sopenharmony_ci goto out; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* Finally, decompress the kernel */ 818c2ecf20Sopenharmony_ci printf("Decompressing (0x%p <- 0x%p:0x%p)...\n\r", addr, 828c2ecf20Sopenharmony_ci vmlinuz_addr, vmlinuz_addr+vmlinuz_size); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci len = partial_decompress(vmlinuz_addr, vmlinuz_size, 858c2ecf20Sopenharmony_ci addr, ei.loadsize, ei.elfoffset); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (len < 0) 888c2ecf20Sopenharmony_ci fatal("Decompression failed with error code %ld\n\r", len); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (len != ei.loadsize) 918c2ecf20Sopenharmony_ci fatal("Decompression error: got 0x%lx bytes, expected 0x%lx.\n\r", 928c2ecf20Sopenharmony_ci len, ei.loadsize); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci printf("Done! Decompressed 0x%lx bytes\n\r", len); 958c2ecf20Sopenharmony_ciout: 968c2ecf20Sopenharmony_ci flush_cache(addr, ei.loadsize); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci return (struct addr_range){addr, ei.memsize}; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic struct addr_range prep_initrd(struct addr_range vmlinux, void *chosen, 1028c2ecf20Sopenharmony_ci unsigned long initrd_addr, 1038c2ecf20Sopenharmony_ci unsigned long initrd_size) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci /* If we have an image attached to us, it overrides anything 1068c2ecf20Sopenharmony_ci * supplied by the loader. */ 1078c2ecf20Sopenharmony_ci if (&_initrd_end > &_initrd_start) { 1088c2ecf20Sopenharmony_ci printf("Attached initrd image at 0x%p-0x%p\n\r", 1098c2ecf20Sopenharmony_ci _initrd_start, _initrd_end); 1108c2ecf20Sopenharmony_ci initrd_addr = (unsigned long)_initrd_start; 1118c2ecf20Sopenharmony_ci initrd_size = _initrd_end - _initrd_start; 1128c2ecf20Sopenharmony_ci } else if (initrd_size > 0) { 1138c2ecf20Sopenharmony_ci printf("Using loader supplied ramdisk at 0x%lx-0x%lx\n\r", 1148c2ecf20Sopenharmony_ci initrd_addr, initrd_addr + initrd_size); 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* If there's no initrd at all, we're done */ 1188c2ecf20Sopenharmony_ci if (! initrd_size) 1198c2ecf20Sopenharmony_ci return (struct addr_range){0, 0}; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci /* 1228c2ecf20Sopenharmony_ci * If the initrd is too low it will be clobbered when the 1238c2ecf20Sopenharmony_ci * kernel relocates to its final location. In this case, 1248c2ecf20Sopenharmony_ci * allocate a safer place and move it. 1258c2ecf20Sopenharmony_ci */ 1268c2ecf20Sopenharmony_ci if (initrd_addr < vmlinux.size) { 1278c2ecf20Sopenharmony_ci void *old_addr = (void *)initrd_addr; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci printf("Allocating 0x%lx bytes for initrd ...\n\r", 1308c2ecf20Sopenharmony_ci initrd_size); 1318c2ecf20Sopenharmony_ci initrd_addr = (unsigned long)malloc(initrd_size); 1328c2ecf20Sopenharmony_ci if (! initrd_addr) 1338c2ecf20Sopenharmony_ci fatal("Can't allocate memory for initial " 1348c2ecf20Sopenharmony_ci "ramdisk !\n\r"); 1358c2ecf20Sopenharmony_ci printf("Relocating initrd 0x%lx <- 0x%p (0x%lx bytes)\n\r", 1368c2ecf20Sopenharmony_ci initrd_addr, old_addr, initrd_size); 1378c2ecf20Sopenharmony_ci memmove((void *)initrd_addr, old_addr, initrd_size); 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci printf("initrd head: 0x%lx\n\r", *((unsigned long *)initrd_addr)); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* Tell the kernel initrd address via device tree */ 1438c2ecf20Sopenharmony_ci setprop_val(chosen, "linux,initrd-start", (u32)(initrd_addr)); 1448c2ecf20Sopenharmony_ci setprop_val(chosen, "linux,initrd-end", (u32)(initrd_addr+initrd_size)); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return (struct addr_range){(void *)initrd_addr, initrd_size}; 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci#ifdef __powerpc64__ 1508c2ecf20Sopenharmony_cistatic void prep_esm_blob(struct addr_range vmlinux, void *chosen) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci unsigned long esm_blob_addr, esm_blob_size; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci /* Do we have an ESM (Enter Secure Mode) blob? */ 1558c2ecf20Sopenharmony_ci if (&_esm_blob_end <= &_esm_blob_start) 1568c2ecf20Sopenharmony_ci return; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci printf("Attached ESM blob at 0x%p-0x%p\n\r", 1598c2ecf20Sopenharmony_ci _esm_blob_start, _esm_blob_end); 1608c2ecf20Sopenharmony_ci esm_blob_addr = (unsigned long)_esm_blob_start; 1618c2ecf20Sopenharmony_ci esm_blob_size = _esm_blob_end - _esm_blob_start; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci /* 1648c2ecf20Sopenharmony_ci * If the ESM blob is too low it will be clobbered when the 1658c2ecf20Sopenharmony_ci * kernel relocates to its final location. In this case, 1668c2ecf20Sopenharmony_ci * allocate a safer place and move it. 1678c2ecf20Sopenharmony_ci */ 1688c2ecf20Sopenharmony_ci if (esm_blob_addr < vmlinux.size) { 1698c2ecf20Sopenharmony_ci void *old_addr = (void *)esm_blob_addr; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci printf("Allocating 0x%lx bytes for esm_blob ...\n\r", 1728c2ecf20Sopenharmony_ci esm_blob_size); 1738c2ecf20Sopenharmony_ci esm_blob_addr = (unsigned long)malloc(esm_blob_size); 1748c2ecf20Sopenharmony_ci if (!esm_blob_addr) 1758c2ecf20Sopenharmony_ci fatal("Can't allocate memory for ESM blob !\n\r"); 1768c2ecf20Sopenharmony_ci printf("Relocating ESM blob 0x%lx <- 0x%p (0x%lx bytes)\n\r", 1778c2ecf20Sopenharmony_ci esm_blob_addr, old_addr, esm_blob_size); 1788c2ecf20Sopenharmony_ci memmove((void *)esm_blob_addr, old_addr, esm_blob_size); 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci /* Tell the kernel ESM blob address via device tree. */ 1828c2ecf20Sopenharmony_ci setprop_val(chosen, "linux,esm-blob-start", (u32)(esm_blob_addr)); 1838c2ecf20Sopenharmony_ci setprop_val(chosen, "linux,esm-blob-end", (u32)(esm_blob_addr + esm_blob_size)); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci#else 1868c2ecf20Sopenharmony_cistatic inline void prep_esm_blob(struct addr_range vmlinux, void *chosen) { } 1878c2ecf20Sopenharmony_ci#endif 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci/* A buffer that may be edited by tools operating on a zImage binary so as to 1908c2ecf20Sopenharmony_ci * edit the command line passed to vmlinux (by setting /chosen/bootargs). 1918c2ecf20Sopenharmony_ci * The buffer is put in it's own section so that tools may locate it easier. 1928c2ecf20Sopenharmony_ci */ 1938c2ecf20Sopenharmony_cistatic char cmdline[BOOT_COMMAND_LINE_SIZE] 1948c2ecf20Sopenharmony_ci __attribute__((__section__("__builtin_cmdline"))); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic void prep_cmdline(void *chosen) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci unsigned int getline_timeout = 5000; 1998c2ecf20Sopenharmony_ci int v; 2008c2ecf20Sopenharmony_ci int n; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* Wait-for-input time */ 2038c2ecf20Sopenharmony_ci n = getprop(chosen, "linux,cmdline-timeout", &v, sizeof(v)); 2048c2ecf20Sopenharmony_ci if (n == sizeof(v)) 2058c2ecf20Sopenharmony_ci getline_timeout = v; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci if (cmdline[0] == '\0') 2088c2ecf20Sopenharmony_ci getprop(chosen, "bootargs", cmdline, BOOT_COMMAND_LINE_SIZE-1); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci printf("\n\rLinux/PowerPC load: %s", cmdline); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci /* If possible, edit the command line */ 2138c2ecf20Sopenharmony_ci if (console_ops.edit_cmdline && getline_timeout) 2148c2ecf20Sopenharmony_ci console_ops.edit_cmdline(cmdline, BOOT_COMMAND_LINE_SIZE, getline_timeout); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci printf("\n\r"); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci /* Put the command line back into the devtree for the kernel */ 2198c2ecf20Sopenharmony_ci setprop_str(chosen, "bootargs", cmdline); 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistruct platform_ops platform_ops; 2238c2ecf20Sopenharmony_cistruct dt_ops dt_ops; 2248c2ecf20Sopenharmony_cistruct console_ops console_ops; 2258c2ecf20Sopenharmony_cistruct loader_info loader_info; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_civoid start(void) 2288c2ecf20Sopenharmony_ci{ 2298c2ecf20Sopenharmony_ci struct addr_range vmlinux, initrd; 2308c2ecf20Sopenharmony_ci kernel_entry_t kentry; 2318c2ecf20Sopenharmony_ci unsigned long ft_addr = 0; 2328c2ecf20Sopenharmony_ci void *chosen; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* Do this first, because malloc() could clobber the loader's 2358c2ecf20Sopenharmony_ci * command line. Only use the loader command line if a 2368c2ecf20Sopenharmony_ci * built-in command line wasn't set by an external tool */ 2378c2ecf20Sopenharmony_ci if ((loader_info.cmdline_len > 0) && (cmdline[0] == '\0')) 2388c2ecf20Sopenharmony_ci memmove(cmdline, loader_info.cmdline, 2398c2ecf20Sopenharmony_ci min(loader_info.cmdline_len, BOOT_COMMAND_LINE_SIZE-1)); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci if (console_ops.open && (console_ops.open() < 0)) 2428c2ecf20Sopenharmony_ci exit(); 2438c2ecf20Sopenharmony_ci if (platform_ops.fixups) 2448c2ecf20Sopenharmony_ci platform_ops.fixups(); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", 2478c2ecf20Sopenharmony_ci _start, get_sp()); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* Ensure that the device tree has a /chosen node */ 2508c2ecf20Sopenharmony_ci chosen = finddevice("/chosen"); 2518c2ecf20Sopenharmony_ci if (!chosen) 2528c2ecf20Sopenharmony_ci chosen = create_node(NULL, "chosen"); 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci vmlinux = prep_kernel(); 2558c2ecf20Sopenharmony_ci initrd = prep_initrd(vmlinux, chosen, 2568c2ecf20Sopenharmony_ci loader_info.initrd_addr, loader_info.initrd_size); 2578c2ecf20Sopenharmony_ci prep_esm_blob(vmlinux, chosen); 2588c2ecf20Sopenharmony_ci prep_cmdline(chosen); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci printf("Finalizing device tree..."); 2618c2ecf20Sopenharmony_ci if (dt_ops.finalize) 2628c2ecf20Sopenharmony_ci ft_addr = dt_ops.finalize(); 2638c2ecf20Sopenharmony_ci if (ft_addr) 2648c2ecf20Sopenharmony_ci printf(" flat tree at 0x%lx\n\r", ft_addr); 2658c2ecf20Sopenharmony_ci else 2668c2ecf20Sopenharmony_ci printf(" using OF tree (promptr=%p)\n\r", loader_info.promptr); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci if (console_ops.close) 2698c2ecf20Sopenharmony_ci console_ops.close(); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci kentry = (kernel_entry_t) vmlinux.addr; 2728c2ecf20Sopenharmony_ci if (ft_addr) { 2738c2ecf20Sopenharmony_ci if(platform_ops.kentry) 2748c2ecf20Sopenharmony_ci platform_ops.kentry(ft_addr, vmlinux.addr); 2758c2ecf20Sopenharmony_ci else 2768c2ecf20Sopenharmony_ci kentry(ft_addr, 0, NULL); 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci else 2798c2ecf20Sopenharmony_ci kentry((unsigned long)initrd.addr, initrd.size, 2808c2ecf20Sopenharmony_ci loader_info.promptr); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci /* console closed so printf in fatal below may not work */ 2838c2ecf20Sopenharmony_ci fatal("Error: Linux kernel returned to zImage boot wrapper!\n\r"); 2848c2ecf20Sopenharmony_ci} 285