/* * Copyright (c) 2009-2013, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Google, Inc. nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "boot.h" #include "debug.h" #include "utils.h" #include "bootimg.h" #define KEXEC_ARM_ATAGS_OFFSET 0x1000 #define KEXEC_ARM_ZIMAGE_OFFSET 0x8000 #define MEMORY_SIZE 0x0800000 #define START_ADDRESS 0x44000000 #define KERNEL_START (START_ADDRESS + KEXEC_ARM_ZIMAGE_OFFSET) #define ATAG_NONE_TYPE 0x00000000 #define ATAG_CORE_TYPE 0x54410001 #define ATAG_RAMDISK_TYPE 0x54410004 #define ATAG_INITRD2_TYPE 0x54420005 #define ATAG_CMDLINE_TYPE 0x54410009 #define MAX_ATAG_SIZE 0x4000 struct atag_info { unsigned size; unsigned type; }; struct atag_initrd2 { unsigned start; unsigned size; }; struct atag_cmdline { char cmdline[0]; }; struct atag { struct atag_info info; union { struct atag_initrd2 initrd2; struct atag_cmdline cmdline; } data; }; long kexec_load(unsigned int entry, unsigned long nr_segments, struct kexec_segment *segment, unsigned long flags) { return syscall(__NR_kexec_load, entry, nr_segments, segment, flags); } /* * Prepares arguments for kexec * Kernel address is not set into kernel_phys * Ramdisk is set to position relative to kernel */ int prepare_boot_linux(uintptr_t kernel_phys, void *kernel_addr, int kernel_size, uintptr_t ramdisk_phys, void *ramdisk_addr, int ramdisk_size, uintptr_t second_phys, void *second_addr, int second_size, uintptr_t atags_phys, void *atags_addr, int atags_size) { struct kexec_segment segment[4]; int segment_count = 2; unsigned entry = START_ADDRESS + KEXEC_ARM_ZIMAGE_OFFSET; int rv; int page_size = getpagesize(); segment[0].buf = kernel_addr; segment[0].bufsz = kernel_size; segment[0].mem = (void *) KERNEL_START; segment[0].memsz = ROUND_TO_PAGE(kernel_size, page_size); if (kernel_size > MEMORY_SIZE - KEXEC_ARM_ZIMAGE_OFFSET) { D(INFO, "Kernel image too big"); return -1; } segment[1].buf = atags_addr; segment[1].bufsz = atags_size; segment[1].mem = (void *) (START_ADDRESS + KEXEC_ARM_ATAGS_OFFSET); segment[1].memsz = ROUND_TO_PAGE(atags_size, page_size); D(INFO, "Ramdisk size is %d", ramdisk_size); if (ramdisk_size != 0) { segment[segment_count].buf = ramdisk_addr; segment[segment_count].bufsz = ramdisk_size; segment[segment_count].mem = (void *) (KERNEL_START + ramdisk_phys - kernel_phys); segment[segment_count].memsz = ROUND_TO_PAGE(ramdisk_phys, page_size); ++segment_count; } D(INFO, "Ramdisk size is %d", ramdisk_size); if (second_size != 0) { segment[segment_count].buf = second_addr; segment[segment_count].bufsz = second_size; segment[segment_count].mem = (void *) (KERNEL_START + second_phys - kernel_phys); segment[segment_count].memsz = ROUND_TO_PAGE(second_size, page_size); entry = second_phys; ++segment_count; } rv = kexec_load(entry, segment_count, segment, KEXEC_ARCH_DEFAULT); if (rv != 0) { D(INFO, "Kexec_load returned non-zero exit code: %s\n", strerror(errno)); return -1; } return 1; } unsigned *create_atags(unsigned *atags_position, int atag_size, const struct boot_img_hdr *hdr, int *size) { struct atag *current_tag = (struct atag *) atags_position; unsigned *current_tag_raw = atags_position; unsigned *new_atags = malloc(ROUND_TO_PAGE(atag_size + BOOT_ARGS_SIZE * sizeof(char), hdr->page_size)); //This pointer will point into the beggining of buffer free space unsigned *natags_raw_buff = new_atags; int new_atags_size = 0; int current_size; int cmdl_length; // copy tags from current atag file while (current_tag->info.type != ATAG_NONE_TYPE) { switch (current_tag->info.type) { case ATAG_CMDLINE_TYPE: case ATAG_RAMDISK_TYPE: case ATAG_INITRD2_TYPE: break; default: memcpy((void *)natags_raw_buff, (void *)current_tag_raw, current_tag->info.size * sizeof(unsigned)); natags_raw_buff += current_tag->info.size; new_atags_size += current_tag->info.size; } current_tag_raw += current_tag->info.size; current_tag = (struct atag *) current_tag_raw; if (current_tag_raw >= atags_position + atag_size) { D(ERR, "Critical error in atags"); return NULL; } } // set INITRD2 tag if (hdr->ramdisk_size > 0) { current_size = (sizeof(struct atag_info) + sizeof(struct atag_initrd2)) / sizeof(unsigned); *((struct atag *) natags_raw_buff) = (struct atag) { .info = { .size = current_size, .type = ATAG_INITRD2_TYPE }, .data = { .initrd2 = (struct atag_initrd2) { .start = hdr->ramdisk_addr, .size = hdr->ramdisk_size } } }; new_atags_size += current_size; natags_raw_buff += current_size; } // set ATAG_CMDLINE cmdl_length = strnlen((char *) hdr->cmdline, BOOT_ARGS_SIZE - 1); current_size = sizeof(struct atag_info) + (1 + cmdl_length); current_size = (current_size + sizeof(unsigned) - 1) / sizeof(unsigned); *((struct atag *) natags_raw_buff) = (struct atag) { .info = { .size = current_size, .type = ATAG_CMDLINE_TYPE }, }; //copy cmdline and ensure that there is null character memcpy(((struct atag *) natags_raw_buff)->data.cmdline.cmdline, (char *) hdr->cmdline, cmdl_length); ((struct atag *) natags_raw_buff)->data.cmdline.cmdline[cmdl_length] = '\0'; new_atags_size += current_size; natags_raw_buff += current_size; // set ATAG_NONE *((struct atag *) natags_raw_buff) = (struct atag) { .info = { .size = 0, .type = ATAG_NONE_TYPE }, }; new_atags_size += sizeof(struct atag_info) / sizeof(unsigned); natags_raw_buff += sizeof(struct atag_info) / sizeof(unsigned); *size = new_atags_size * sizeof(unsigned); return new_atags; } char *read_atags(const char * path, int *atags_sz) { int afd = -1; char *atags_ptr = NULL; afd = open(path, O_RDONLY); if (afd < 0) { D(ERR, "wrong atags file"); return 0; } atags_ptr = (char *) malloc(MAX_ATAG_SIZE); if (atags_ptr == NULL) { D(ERR, "insufficient memory"); return 0; } *atags_sz = read(afd, atags_ptr, MAX_ATAG_SIZE); close(afd); return atags_ptr; }