/* * Copyright (C) 2009, 2013 Apple, Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ #include #include #include #include #include #include #include #include #include #include "Buffer.h" #include "DeviceTreePatcher.h" #include "Kernelcache.h" #include "LoadMachO.h" #include #include #define DEFAULT_SDRAM_BASE 0x00000000 #define DEFAULT_SDRAM_LEN (256 * 1024 * 1024) #define TARGET_MMU_BASE_ALIGN 16384 #define ARM_B_POS_0x40 0xea00000e // b pc+0x40 #define ARM_LDR_R0_PC 0xe59f0000 // ldr r0, [pc] (here+8) #define ARM_LDR_PC_PC 0xe59ff000 // ldr pc, [pc] (here+8) #define ARM64_BOARD_SECURITY_EPOCH 0 // An arbitrarily sized framebuffer. #define FRAMEBUFFER_WIDTH 640 #define FRAMEBUFFER_HEIGHT 960 #define FRAMEBUFFER_DEPTH_BITS 32 #define FRAMEBUFFER_STRIDE_BYTES \ (FRAMEBUFFER_WIDTH * FRAMEBUFFER_DEPTH_BITS / 8) #define FRAMEBUFFER_SIZE_BYTES \ (FRAMEBUFFER_STRIDE_BYTES * FRAMEBUFFER_HEIGHT * 3) #define PANIC_SIZE 16384 #define PACKED __attribute__((packed)) #define USE_L4 #ifdef USE_L4 #define MONITOR_SIZE (0x00800000) #endif /* Just to make sure we get the alignment right */ typedef uint64_t arm64_ptr_t __attribute__((aligned(8))); typedef uint64_t arm64_uint64_t __attribute__((aligned(8))); // lib/macho/boot.h // This isn't taken verbatim: changed host-varying types to exact width, // as they would otherwise not work on 64 bit hosts. /* * Video information.. */ #define BOOT_LINE_LENGTH 256 struct Boot_Video { uint32_t v_baseAddr; /* Base address of video memory */ uint32_t v_display; /* Display Code (if Applicable */ uint32_t v_rowBytes; /* Number of bytes per pixel row */ uint32_t v_width; /* Width */ uint32_t v_height; /* Height */ uint32_t v_depth; /* Pixel Depth */ } PACKED; struct Boot_Video_64 { arm64_ptr_t v_baseAddr;/* Base address of video memory */ arm64_ptr_t v_display; /* Display Code (if Applicable */ arm64_uint64_t v_rowBytes; /* Number of bytes per pixel row */ arm64_uint64_t v_width; /* Width */ arm64_uint64_t v_height; /* Height */ arm64_uint64_t v_depth; /* Pixel Depth */ } PACKED; /* Boot argument structure - passed into Mach kernel at boot time. */ #define kBootArgsRevision 1 #define kBootArgsVersion1 1 struct boot_args { uint16_t revision; /* Revision of boot_args structure */ uint16_t version; /* Version of boot_args structure */ uint32_t virtBase; /* Virtual base of memory */ uint32_t physBase; /* Physical base of memory */ uint32_t memSize; /* Size of memory */ uint32_t topOfKernelData; /* Highest physical address used in kernel data area */ Boot_Video video; /* Video Information */ uint32_t machineType; /* Machine Type */ uint32_t deviceTreeAddr; /* Base of flattened device tree */ uint32_t deviceTreeLength; /* Length of flattened tree */ char commandLine[BOOT_LINE_LENGTH]; /* Passed in command line */ } PACKED; typedef struct boot_args_64 { uint16_t revision; /* Revision of boot_args structure */ uint16_t version; /* Version of boot_args structure */ arm64_ptr_t virtBase; /* Virtual base of memory */ arm64_ptr_t physBase; /* Physical base of memory */ arm64_uint64_t memSize; /* Size of memory */ arm64_ptr_t topOfKernelData; /* Highest physical address used in kernel data area */ Boot_Video_64 video; /* Video Information */ uint32_t machineType; /* Machine Type */ arm64_ptr_t deviceTreeAddr; /* Base of flattened device tree */ uint32_t deviceTreeLength; /* Length of flattened tree */ char commandLine[BOOT_LINE_LENGTH]; /* Passed in command line */ } boot_args_64; #ifdef USE_L4 typedef struct monitor_boot_args { uint64_t version; /* structure version - this is version 1 */ uint64_t virtBase; /* virtual base of memory assigned to the monitor */ uint64_t physBase; /* physical address corresponding to the virtual base */ uint64_t memSize; /* size of memory assigned to the monitor */ uint64_t kernArgs; /* physical address of the kernel boot_args structure */ uint64_t kernEntry; /* kernel entrypoint */ } monitor_boot_args; #endif struct dt_patch_s { const char *node; const char *property; const char *value; bool str; }; typedef struct dt_patch_s dt_patch_t; struct Options { const char *kernelcache_file; const char *devicetree_file; const char *ramdisk_file; const char *output_file; std::string bootargs_string; uint64_t sdram_base; uint32_t sdram_len; uint64_t virtual_base; bool override_virtual_base; bool have_boot_partition_id; unsigned boot_partition_id; std::vector dt_patches; bool page_size_16kb; #ifdef USE_L4 const char *l4_path; #endif Options() : kernelcache_file(NULL), devicetree_file(NULL), ramdisk_file(NULL), output_file(NULL), bootargs_string(), sdram_base(DEFAULT_SDRAM_BASE), sdram_len(DEFAULT_SDRAM_LEN), virtual_base(0), override_virtual_base(false), have_boot_partition_id(false), boot_partition_id(), dt_patches(), page_size_16kb(false), #ifdef USE_L4 l4_path(NULL) #endif { } bool Parse(int argc, char **argv); }; static void Usage(const char *argv0); static uint64_t RoundToNextPage(const Options &options, uint64_t address); static uint64_t RoundToNextMmuBase(uint64_t address); static bool SetFrequenciesInDeviceTree(DeviceTreePatcher *device_tree_patcher); int main(int argc, char *argv[]) { Options options; if (!options.Parse(argc, argv)) { Usage(argv[0]); return 1; } FILE *output_str; if (!strcmp(options.output_file, "-")) { // Output to stdout. output_str = stdout; } else { // Output to a file. output_str = fopen(options.output_file, "wb"); if (output_str == NULL) { fprintf(stderr, "Couldn't open output file \"%s\"\n", options.output_file); return 1; } } // Load the kernelcache from an img3 file. Buffer kernelcache; if (!kernelcache.loadFromAuto(options.kernelcache_file, IMAGE_TYPE_KERNELCACHE)) { fprintf(stderr, "Couldn't load kernel cache %s\n", options.kernelcache_file); return 1; } // Decompress the kernel cache to a Mach-O binary. Buffer macho; if (!DecompressKernelcache(kernelcache, &macho)) { fprintf(stderr, "Couldn't decompress kernel cache\n"); return 1; } // Unpack the Mach-O kernel into the base of 'ram'. Buffer ram; ram.alloc(options.sdram_len); LoadMachO load_macho(macho, &ram); if (options.override_virtual_base) { load_macho.OverrideVirtualBase(options.virtual_base); } if (!load_macho.Load()) { fprintf(stderr, "Couldn't unpack Mach-O kernel image\n"); return 1; } uint64_t virtual_base; if (!load_macho.GetVirtualBase(&virtual_base)) { fprintf(stderr, "Couldn't infer virtual base of Mach-O kernel image\n"); return false; } uint64_t phys_base = options.sdram_base; uint64_t image_size = load_macho.LastAddress(); uint64_t entry_point_phys_offset = load_macho.EntryPointPhysOffset(); // Load the device tree from an img3 file. Buffer devicetree; if (!devicetree.loadFromAuto(options.devicetree_file, IMAGE_TYPE_DEVTREE)) { fprintf(stderr, "Couldn't load devicetree %s\n", options.devicetree_file); return 1; } // Load the ramdisk from an img3 file. Buffer ramdisk; if (options.ramdisk_file != NULL && !ramdisk.loadFromAuto(options.ramdisk_file, IMAGE_TYPE_RAMDISK)) { fprintf(stderr, "Couldn't load ramdisk %s\n", options.ramdisk_file); return 1; } fprintf(stderr, "kernelcache: %u\n" "macho: %u\n" "unpacked: %llu\n" "devicetree: %u\n" "ramdisk: %u\n", (unsigned) kernelcache.size(), (unsigned) macho.size(), image_size, (unsigned) devicetree.size(), (unsigned) ramdisk.size()); // Append the device tree to the unpacked Mach-O kernel. uint64_t devicetree_offset = RoundToNextPage(options, image_size); if (devicetree_offset + devicetree.size() > ram.size()) { fprintf(stderr, "Kernel + devicetree too large\n"); return 1; } memcpy((char *) ram.buf() + devicetree_offset, devicetree.buf(), devicetree.size()); // Append the ramdisk to the device tree. uint64_t ramdisk_offset = RoundToNextPage(options, devicetree_offset + devicetree.size()); memcpy((char *) ram.buf() + ramdisk_offset, ramdisk.buf(), ramdisk.size()); // Append bootargs to the device tree. uint64_t bootargs_offset = RoundToNextPage(options, ramdisk_offset + ramdisk.size()); // Framebuffer at the end of memory, followed by panic ram. uint64_t framebuffer_offset = options.sdram_len - PANIC_SIZE - FRAMEBUFFER_SIZE_BYTES; // Round down to a 1MB segment. MMU table initialization requires this. framebuffer_offset &= ~((1 << 20) - 1); uint64_t end_of_image = 0; uint64_t end_of_image_offset = 0; #ifdef USE_L4 monitor_boot_args mba; uint64_t mba_offset; if( options.l4_path ) { uint64_t monitor_offset = (framebuffer_offset - MONITOR_SIZE) & ~(0x200000ULL - 1); // 2MB-aligned mba_offset = monitor_offset + MONITOR_SIZE - sizeof(mba); mba.version = 1; mba.virtBase = 0; // L4 doesn't care mba.physBase = monitor_offset + phys_base; // L4 doesn't care mba.memSize = MONITOR_SIZE; mba.kernArgs = bootargs_offset + phys_base; mba.kernEntry = entry_point_phys_offset + phys_base; fprintf(stderr, "monitor at %llx\n", monitor_offset); end_of_image_offset = RoundToNextMmuBase(monitor_offset + MONITOR_SIZE); Buffer l4; if (!l4.loadFromAuto(options.l4_path, IMAGE_TYPE_MONITOR)) { fprintf(stderr, "Couldn't open monitor image\n"); return 1; } fprintf(stderr, "Opened monitor image\n"); LoadMachO load_l4(l4, &ram); fprintf(stderr, "loaded monitor image\n"); // L4 in 16KB mode is just a mini monitor with a different // link address. uint64_t l4_link_base = options.page_size_16kb ? 0x0000004000000000ULL : 0x0000004100000000ULL; load_l4.OverrideVirtualBase(l4_link_base - monitor_offset); if (options.override_virtual_base) { fprintf(stderr, "overriding virt base\n"); load_l4.OverrideVirtualBase(options.virtual_base); fprintf(stderr, "overrode virt base\n"); } fprintf(stderr, "about to load\n"); if (!load_l4.Load()) { fprintf(stderr, "Couldn't unpack Mach-O kernel image\n"); return 1; } /* re-set the entry point now to use L4's */ entry_point_phys_offset = load_l4.EntryPointPhysOffset(); fprintf(stderr, "Loaded l4: ept %llx\n", entry_point_phys_offset); } else #endif end_of_image_offset = RoundToNextMmuBase(bootargs_offset + sizeof(boot_args)); end_of_image = phys_base + end_of_image_offset; if (end_of_image_offset > framebuffer_offset) { fprintf(stderr, "Image size runs into framebuffer ram (%llx vs %llx)\n", end_of_image_offset, framebuffer_offset); return 1; } // Place a bootstrap to load r0 with the offset of the boot args, // and jump to the entry point. if (load_macho.Is64Bit()) { // Don't need to use exception vector to get started on ARM64 // [~] $ cat bootstrap.s // adr x0, Lboot_args_addr // adr x1, Lentry_addr // ldr x0, [x0] /* Load boot args address */ // ldr x1, [x1] /* Load entry point */ // br x1 /* Jump to entry point */ // .long 0 /* Padding */ // Lboot_args_addr: // .quad 0xfeedbeef // Lentry_addr: // .quad 0xabcdef11 // // [~] $ xcrun -sdk iphoneos clang -arch arm64 -c bootstrap.s // [~] $ otool -t bootstrap.o // bootstrap.o: // (__TEXT,__text) section // 00000000 100000c0 100000e1 f9400000 f9400021 // 00000010 d61f0020 00000000 feedbeef 00000000 // 00000020 abcdef11 00000000 // Validated by hand that "adr" instructions are encoded as desired. uint64_t entry_point_phys = phys_base + entry_point_phys_offset; uint64_t boot_args_phys = phys_base + bootargs_offset; uint32_t bootstrap[] = { 0xd5034fdf, // msr DAIFSet, #15 0xd5033fdf, // isb sy 0x100000c0, // Get location of boot args address: adr x0, Lboot_args_addr (pc + 24) 0x100000e1, // Get location of entry point: adr x1, Lentry_addr (pc + 32) 0xf9400000, // Load address of boot args: ldr x0, [x0] 0xf9400021, // Load entry point: ldr x1, [x1] 0xd61f0020, // Jump to entry point 0, // Padding for proper alignment (we reset with all memory as "device") (uint32_t) boot_args_phys, // Boot args address (low 32 bit) (uint32_t) (boot_args_phys >> 32), // Boot args address (high 32 bits) (uint32_t) entry_point_phys, // Entry address (low 32 bits) (uint32_t) (entry_point_phys >> 32), // Entry address (high 32 bits) 0 }; #ifdef USE_L4 if( options.l4_path ) { memcpy((char *)ram.buf() + (uintptr_t)mba_offset, &mba, sizeof(mba)); uint64_t mba_phys = phys_base + mba_offset; bootstrap[8] = (uint32_t) mba_phys; /* Pointer to monitor_boot_args */ bootstrap[9] = (uint32_t) (mba_phys >> 32); } #endif memcpy(ram.buf(), bootstrap, sizeof(bootstrap)); } else { uint32_t bootstrap[] = { ARM_B_POS_0x40, // 0x00: b 0x40 0, // undef exception 0, // swi exception 0, // pabt exception 0, // dabt exception 0, // reserved exception 0, // irq exception 0, // fiq exception 0, // 0x20: exception vector table 0, 0, 0, 0, 0, 0, 0, ARM_LDR_R0_PC, // 0x40: ldr r0, [pc] ; ldr r0, [0x48] ARM_LDR_PC_PC, // 0x44: ldr pc, [pc] ; ldr pc, [0x4c] (uint32_t) phys_base + (uint32_t) bootargs_offset,// 0x48: .word bootargs_offset (uint32_t) (phys_base + entry_point_phys_offset) // 0x4c: .word entry_point }; assert(phys_base == (uint32_t)phys_base); assert(bootargs_offset == (uint32_t)bootargs_offset); assert((phys_base + bootargs_offset) == (uint32_t)(phys_base + bootargs_offset)); memcpy(ram.buf(), bootstrap, sizeof(bootstrap)); } // Place the boot args into memory now that we have the location of // everything. if (load_macho.Is64Bit()) { printf("doing 64-bit boot-args.\n"); boot_args_64 bootArgs; memset(&bootArgs, 0, sizeof(bootArgs)); bootArgs.revision = kBootArgsRevision; bootArgs.version = kBootArgsVersion1 + ARM64_BOARD_SECURITY_EPOCH; bootArgs.virtBase = virtual_base; bootArgs.physBase = phys_base; bootArgs.memSize = framebuffer_offset; // Must be 1MB aligned. // Physical address of "video" ram. bootArgs.video.v_baseAddr = phys_base + framebuffer_offset; bootArgs.video.v_display = 1; // Display code is just 1 bootArgs.video.v_rowBytes = FRAMEBUFFER_STRIDE_BYTES; bootArgs.video.v_width = FRAMEBUFFER_WIDTH; bootArgs.video.v_height = FRAMEBUFFER_HEIGHT; bootArgs.video.v_depth = FRAMEBUFFER_DEPTH_BITS; // Physical address. MMU L1 tables are built here. #ifdef USE_L4 if( options.l4_path ) bootArgs.topOfKernelData = RoundToNextMmuBase(phys_base + bootargs_offset + sizeof(boot_args)); else #endif bootArgs.topOfKernelData = end_of_image; // Virtual address of device tree. bootArgs.deviceTreeAddr = virtual_base + devicetree_offset; bootArgs.deviceTreeLength = devicetree.size(); memcpy(bootArgs.commandLine, options.bootargs_string.data(), options.bootargs_string.size()); memcpy((char *) ram.buf() + bootargs_offset, &bootArgs, sizeof(bootArgs)); } else { boot_args bootArgs; memset(&bootArgs, 0, sizeof(bootArgs)); bootArgs.revision = kBootArgsRevision; bootArgs.version = kBootArgsVersion1; bootArgs.virtBase = (uint32_t)virtual_base; bootArgs.physBase = (uint32_t)phys_base; bootArgs.memSize = (uint32_t)framebuffer_offset; // Must be 1MB aligned. // Physical address of "video" ram. bootArgs.video.v_baseAddr = (uint32_t)(phys_base + framebuffer_offset); bootArgs.video.v_display = 1; // Display code is just 1 bootArgs.video.v_rowBytes = FRAMEBUFFER_STRIDE_BYTES; bootArgs.video.v_width = FRAMEBUFFER_WIDTH; bootArgs.video.v_height = FRAMEBUFFER_HEIGHT; bootArgs.video.v_depth = FRAMEBUFFER_DEPTH_BITS; // Physical address. MMU L1 tables are built here. bootArgs.topOfKernelData = (uint32_t)end_of_image; // Virtual address of device tree. bootArgs.deviceTreeAddr = (uint32_t)(virtual_base + devicetree_offset); bootArgs.deviceTreeLength = devicetree.size(); memcpy(bootArgs.commandLine, options.bootargs_string.data(), options.bootargs_string.size()); memcpy((char *) ram.buf() + bootargs_offset, &bootArgs, sizeof(bootArgs)); } // Patch the device tree with the location of loaded segments. DeviceTreePatcher device_tree_patcher((char *) ram.buf() + devicetree_offset, devicetree.size()); // There's a large number of properties not being set here, such as // 'firmware-version', 'pram', 'vram', but these are not necessary // yet. // Patch the location of the device tree itself. if (!device_tree_patcher.AllocateMemoryRange("DeviceTree", phys_base + devicetree_offset, devicetree.size())) { return 1; } // Patch the location of the ramdisk, if used. if (options.ramdisk_file != NULL && !device_tree_patcher.AllocateMemoryRange("RAMDisk", phys_base + ramdisk_offset, ramdisk.size())) { return 1; } // Patch the location of the bootargs. if (!device_tree_patcher.AllocateMemoryRange("BootArgs", phys_base + bootargs_offset, sizeof(boot_args))) { return 1; } // Patch KernelCache segments into the device tree. for (LoadMachO::SegmentListIterator it = load_macho.SegmentListBegin(); it != load_macho.SegmentListEnd(); ++it) { if (!device_tree_patcher.AllocateMemoryRange(it->name.c_str(), it->pmaddr, it->size)) { return 1; } // Also check that no segment overlaps the first 4KB page of // memory. That would mean the bootstrap overwrote something. if (it->pmaddr < 4096 && it->size != 0) { fprintf(stderr, "KernelCache segment \"%s\" overlaps first 4KB\n", it->name.c_str()); return 1; } } // Set /chosen:debug-enabled=1. if (!device_tree_patcher.SetPropertyInt("chosen", "debug-enabled", 1)) { fprintf(stderr, "Couldn't set /chosen:debug-enabled=1\n"); return 1; } // Set (arbitrary) core and peripheral frequencies in the device tree. if (!SetFrequenciesInDeviceTree(&device_tree_patcher)) { fprintf(stderr, "Couldn't set device frequencies\n"); return 1; } if (options.have_boot_partition_id) { // Set /chosen/root-matching=...boot_partition_id.... char s[256]; snprintf(s, sizeof(s), "" "IOProviderClass" "IOMedia" "IOPropertyMatch" "Partition ID%u" "", options.boot_partition_id); if (!device_tree_patcher.SetPropertyString("chosen", "root-matching", s)) { fprintf(stderr, "Couldn't set /chosen:root-matching=%s\n", s); return 1; } } // Set the location of panic ram. First word physical base, second word size. uint64_t panic_prop[2]; bool wide_panic_prop; uint64_t vram_prop[2]; switch (device_tree_patcher.GetNumAddressCells("")) { // Empty string -> root case 1: { wide_panic_prop = false; panic_prop[0] = ((uint64_t)PANIC_SIZE << 32) | (phys_base + options.sdram_len - PANIC_SIZE); if (!device_tree_patcher.SetPropertyInt("pram", "reg", panic_prop[0])) { fprintf(stderr, "Couldn't set /pram:reg=0x%016llx\n", panic_prop[0]); return 1; } vram_prop[0] = (((uint64_t)FRAMEBUFFER_SIZE_BYTES) << 32) | (phys_base + framebuffer_offset); if (!device_tree_patcher.SetPropertyInt("vram", "reg", vram_prop[0])) { fprintf(stderr, "Couldn't set /vram:reg=0x%016llx\n", vram_prop[0]); return 1; } break; } case 2: { wide_panic_prop = true; panic_prop[0] = phys_base + options.sdram_len - PANIC_SIZE; panic_prop[1] = ((uint64_t) PANIC_SIZE); if (!device_tree_patcher.SetPropertyTwoQuads("pram", "reg", panic_prop)) { fprintf(stderr, "Couldn't set /pram:reg=0x%016llx,0x%016llx\n", panic_prop[0], panic_prop[1]); return 1; } vram_prop[0] = phys_base + framebuffer_offset; vram_prop[1] = FRAMEBUFFER_SIZE_BYTES; if (!device_tree_patcher.SetPropertyTwoQuads("vram", "reg", vram_prop)) { fprintf(stderr, "Couldn't set /vram:reg=0x%016llx,0x%016llx\n", vram_prop[0], vram_prop[1]); return 1; } break; } default: { fprintf(stderr, "Can't set pram address because number of address cells is bad.\n"); return 1; } } std::vector::const_iterator patch_iterator; for (patch_iterator = options.dt_patches.begin(); patch_iterator != options.dt_patches.end(); ++patch_iterator) { dt_patch_t patch = *patch_iterator; if (patch.str) { fprintf(stderr, "Patching /%s:%s=%s\n", patch.node, patch.property, patch.value); if (!device_tree_patcher.SetPropertyString(patch.node, patch.property, patch.value)) { fprintf(stderr, "Couldn't set /%s:%s=%s\n", patch.node, patch.property, patch.value); return 1; } } else { uint64_t value = atoi(patch.value); fprintf(stderr, "Patching /%s:%s=0x%016llx\n", patch.node, patch.property, value); if (!device_tree_patcher.SetPropertyInt(patch.node, patch.property, value)) { fprintf(stderr, "Couldn't set /%s:%s=0x%016llx\n", patch.node, patch.property, value); return 1; } } } assert(end_of_image_offset == (size_t)end_of_image_offset); fwrite(ram.buf(), (size_t)end_of_image_offset, 1, output_str); fprintf(stderr, "KernelCache: %s\n" "DeviceTree: %s\n" "SDRAM size: %uMB\n" "SDRAM base: 0x%llx\n" "Virtual base: 0x%016llx\n" "Top of k. data: 0x%016llx\n" "Output image: %s\n" "Boot args: %s\n" "Ramdisk: %s at 0x%llx, %u\n" "Framebuffer: 0x%llx %dx%dx%dbpp (%uKB)\n" "Panic ram: 0x%llx size %lluKB\n" "Image size: %llu bytes\n" "Entry point: 0x%016llx\n" "Boot partition: %s\n", options.kernelcache_file, options.devicetree_file, options.sdram_len / 1024 / 1024, options.sdram_base, virtual_base, end_of_image, strcmp(options.output_file, "-") ? options.output_file : "", options.bootargs_string.c_str(), options.ramdisk_file ? options.ramdisk_file : "(none)", phys_base + ramdisk_offset, (unsigned) ramdisk.size(), phys_base + framebuffer_offset, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, FRAMEBUFFER_DEPTH_BITS, (unsigned) RoundToNextPage(options, FRAMEBUFFER_SIZE_BYTES) / 1024, wide_panic_prop ? panic_prop[0] : panic_prop[0] & 0xFFFFFFFF, wide_panic_prop ? panic_prop[1] / 1024 : (panic_prop[0] & 0xFFFFFFFF00000000ULL) >> 44, end_of_image - phys_base, phys_base + entry_point_phys_offset, options.have_boot_partition_id ? "Root filesystem" : "Ramdisk"); if (output_str != stdout) { fclose(output_str); } return 0; } static void Usage(const char *argv0) { fprintf(stderr, "Usage:\n" " %s \n" "\n" "Flags are: ('*' denotes mandatory)\n" "\n" "* -k \n" " Specify the kernelcache file to use. This is decompressed\n" " and the contained Mach-O binary unpacked at the start of\n" " the resulting image\n" "* -d \n" " Specify the devicetree file to use. This is extracted and\n" " appended to the kernelcache in the resulting image.\n" " -r \n" " Specify the ramdisk file to use. This is extracted and\n" " appended to the devicetree in the resulting image. Optional.\n" "* -o \n" " Specify the output file to use. This will contain the image\n" " to be loaded into memory starting at the base of SDRAM.\n" " You can use '-' to pipe to standard output.\n" " -m \n" " The target's memory size, in MB. This is inserted into the\n" " boot args structure, and bounds checked when creating the\n" " image. This does not change the image size.\n" " Default is %uMB.\n" " -v \n" " The target's kernel virtual memory base. This should match\n" " the layout of he input KernelCache or it will fail to\n" " translate to the base of SDRAM.\n" " Default inferred from the layout of the kernelcache.\n" " -s \n" " The target's SDRAM memory base. This is the location where\n" " the output image will be loaded on the target.\n" " Default is 0x%08x.\n" " -p \n" " Patch the devicetree property with the specified integer value.\n" " -P \n" " Patch the devicetree property with the specified string value.\n" " -K\n" " Tweak offsets for expected kernel layout with 16KB pages.\n" " -- [...]\n" " All remaining arguments after '--' are copied into the boot\n" " args structure as strings separated by spaces.\n" " Default is an empty string.\n" "", argv0, DEFAULT_SDRAM_LEN / 1024 / 1024, DEFAULT_SDRAM_BASE); } bool Options::Parse(int argc, char **argv) { for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i], "-k")) { if (i + 1 >= argc) { fprintf(stderr, "-k must be followed by the kernel cache file name\n"); return false; } kernelcache_file = argv[++i]; } else if (!strcmp(argv[i], "-d")) { if (i + 1 >= argc) { fprintf(stderr, "-d must be followed by the devicetree file name\n"); return false; } devicetree_file = argv[++i]; } else if (!strcmp(argv[i], "-r")) { if (i + 1 >= argc) { fprintf(stderr, "-r must be followed by the ramdisk file name\n"); return false; } ramdisk_file = argv[++i]; } else if (!strcmp(argv[i], "-m")) { if (i + 1 >= argc) { fprintf(stderr, "-m must be followed by the SDRAM size in MB\n"); return false; } sdram_len = strtoul(argv[++i], NULL, 0) * 1024 * 1024; } else if (!strcmp(argv[i], "-o")) { if (i + 1 >= argc) { fprintf(stderr, "-o must be followed by an output file name\n"); return false; } output_file = argv[++i]; } else if (!strcmp(argv[i], "-s")) { if (i + 1 >= argc) { fprintf(stderr, "-s must be followed by an address\n"); return false; } sdram_base = strtoull(argv[++i], NULL, 0); } else if (!strcmp(argv[i], "-v")) { if (i + 1 >= argc) { fprintf(stderr, "-v must be followed by an address\n"); return false; } virtual_base = strtoull(argv[++i], NULL, 0); override_virtual_base = true; } else if (!strcmp(argv[i], "-b")) { if (i + 1 >= argc) { fprintf(stderr, "-b must be followed by a partition id\n"); return false; } have_boot_partition_id = true; boot_partition_id = strtoul(argv[++i], NULL, 0); } else if (!strcmp(argv[i], "-p")) { if (i + 3 >= argc) { fprintf(stderr, "-p must be followed by a node, property, and value\n"); return false; } dt_patch_t patch = { argv[i+1], argv[i+2], argv[i+3], false }; dt_patches.push_back(patch); i += 3; } else if (!strcmp(argv[i], "-P")) { if (i + 3 >= argc) { fprintf(stderr, "-P must be followed by a node, property, and value\n"); return false; } dt_patch_t patch = { argv[i+1], argv[i+2], argv[i+3], true }; dt_patches.push_back(patch); i += 3; } else if (!strcmp(argv[i], "-K")) { page_size_16kb = true; #ifdef USE_L4 } else if (!strcmp(argv[i], "-l")) { fprintf(stderr, "got an l4!\n"); if (i + 1 >= argc) { fprintf(stderr, "-l must be followed by the L4 file name\n"); return false; } l4_path = argv[++i]; #endif } else if (!strcmp(argv[i], "--")) { // Consume the remaining arguments for bootargs. for (++i; i < argc; ++i) { if (!bootargs_string.empty()) bootargs_string += ' '; bootargs_string += argv[i]; } } else { fprintf(stderr, "Unrecognized option \"%s\"\n", argv[i]); return false; } } if (kernelcache_file == NULL || devicetree_file == NULL || output_file == NULL) { fprintf(stderr, "Must specify kernelcache, devicetree and output file\n"); return false; } if (bootargs_string.size() >= BOOT_LINE_LENGTH) { fprintf(stderr, "Boot args too long (%u >= %u)\n", (unsigned) bootargs_string.size(), (unsigned) BOOT_LINE_LENGTH); return false; } return true; } void _panic(const char *func, const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "\npanic in %s: ", func); vfprintf(stderr, fmt, args); fprintf(stderr, "\n\n"); va_end(args); exit(1); } static uint64_t RoundToNextPage(const Options &options, uint64_t address) { // Round an address up to the next page boundary if not aligned (4KB or 16KB) uint64_t page_size = options.page_size_16kb ? 16384 : 4096; address += page_size - 1; address &= ~((uint64_t) page_size - 1); return address; } static uint64_t RoundToNextMmuBase(uint64_t address) { // Round an address up to the next MMU base alignment (16KB). address += TARGET_MMU_BASE_ALIGN - 1; address &= ~((uint64_t) TARGET_MMU_BASE_ALIGN - 1); return address; } static bool SetFrequenciesInDeviceTree(DeviceTreePatcher *device_tree_patcher) { struct { const char *name; uint32_t mhz; } cpu_clocks[] = { { "clock-frequency", 1000 }, { "memory-frequency", 500 }, { "bus-frequency", 250 }, { "peripheral-frequency", 250 }, { "fixed-frequency", 24 }, { "timebase-frequency", 24 }, }; for (size_t i = 0; i < sizeof(cpu_clocks) / sizeof(cpu_clocks[0]); ++i) { assert(cpu_clocks[i].mhz <= UINT32_MAX / 1000000); if (!device_tree_patcher->SetPropertyInt("cpus/cpu0", cpu_clocks[i].name, cpu_clocks[i].mhz * 1000000)) { fprintf(stderr, "Couldn't set cpus/cpu0:%s = %uMHz\n", cpu_clocks[i].name, (unsigned) cpu_clocks[i].mhz); return false; } } return true; }