blob: d6994fd1b888b41123d520518a0d158360706ab9 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include "arch/arm/boot.h"
#include "base/io.h"
#include "base/lz4/lz4.h"
#include "base/lzma/lzma.h"
#include "base/physmem.h"
#include "base/ranges.h"
#include "base/timestamp.h"
#include "boot/boot.h"
static const size_t MaxKernelSize = 64 * MiB;
typedef struct {
uint32_t code0;
uint32_t code1;
uint64_t text_offset;
uint64_t image_size;
uint64_t flags;
uint64_t res2;
uint64_t res3;
uint64_t res4;
uint32_t magic;
uint32_t res5;
} Arm64KernelHeader;
struct {
union {
Arm64KernelHeader header;
uint8_t raw[sizeof(Arm64KernelHeader) + 0x100];
};
uint32_t canary;
} scratch;
static void *get_kernel_reloc_addr(uint32_t load_offset)
{
E820MemRanges *e820 = get_e820_mem_ranges();
if (!e820)
return NULL;
for (int i = 0; i < e820->num_ranges; i++) {
E820MemRange *range = &e820->ranges[i];
if (range->type != E820MemRange_Ram)
continue;
uint64_t start = range->base;
uint64_t end = range->base + range->size;
uint64_t kstart = ALIGN_UP(start, 2 * MiB) + load_offset;
uint64_t kend = kstart + MaxKernelSize;
if (kend > CONFIG_BASE_ADDRESS || kend > CONFIG_KERNEL_START ||
kend > CONFIG_KERNEL_FIT_FDT_ADDR) {
printf("ERROR: Kernel might overlap depthcharge!\n");
return 0;
}
if (kend <= end)
return (void *)kstart;
// Should be avoided in practice, that memory might be wasted.
printf("WARNING: Skipping low memory range [%p:%p]!\n",
(void *)start, (void *)end);
}
printf("ERROR: Cannot find enough continuous memory for kernel!\n");
return NULL;
}
int boot_arm_linux(void *fdt, FitImageNode *kernel)
{
static const uint32_t KernelHeaderMagic = 0x644d5241;
static const uint32_t ScratchCanaryValue = 0xdeadbeef;
// Partially decompress to get text_offset. Can't check for errors.
scratch.canary = ScratchCanaryValue;
switch (kernel->compression) {
case CompressionNone:
memcpy(scratch.raw, kernel->data, sizeof(scratch.raw));
break;
case CompressionLzma:
ulzman(kernel->data, kernel->size,
scratch.raw, sizeof(scratch.raw));
break;
case CompressionLz4:
ulz4fn(kernel->data, kernel->size,
scratch.raw, sizeof(scratch.raw));
break;
default:
printf("ERROR: Unsupported compression algorithm!\n");
return 1;
}
// Should never happen, but if it does we'll want to know.
if (scratch.canary != ScratchCanaryValue) {
printf("ERROR: Partial decompression ran over scratchbuf!\n");
return 1;
}
if (scratch.header.magic != KernelHeaderMagic) {
printf("ERROR: Invalid kernel magic: %#.8x\n != %#.8x\n",
scratch.header.magic, KernelHeaderMagic);
return 1;
}
void *reloc_addr = get_kernel_reloc_addr(scratch.header.text_offset);
if (!reloc_addr)
return 1;
size_t true_size = kernel->size;
switch (kernel->compression) {
case CompressionNone:
if (kernel->size > MaxKernelSize) {
printf("ERROR: Cannot relocate a kernel this large!\n");
return 1;
}
printf("Relocating kernel to %p\n", reloc_addr);
memmove(reloc_addr, kernel->data, kernel->size);
break;
case CompressionLzma:
printf("Decompressing LZMA kernel to %p\n", reloc_addr);
true_size = ulzman(kernel->data, kernel->size,
reloc_addr, MaxKernelSize);
if (!true_size) {
printf("ERROR: LZMA decompression failed!\n");
return 1;
}
break;
case CompressionLz4:
printf("Decompressing LZ4 kernel to %p\n", reloc_addr);
true_size = ulz4fn(kernel->data, kernel->size,
reloc_addr, MaxKernelSize);
if (!true_size) {
printf("ERROR: LZ4 decompression failed!\n");
return 1;
}
break;
default:
return 1;
}
printf("jumping to kernel\n");
timestamp_add_now(TS_START_KERNEL);
/* Flush dcache and icache to make loaded code visible. */
arch_program_segment_loaded(reloc_addr, true_size);
tlb_invalidate_all();
boot_arm_linux_jump(fdt, reloc_addr);
return 0;
}