blob: fd7e42b2d3fad34027420706c8888096cd771d3f [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "boot-shim.h"
#include <lib/ddk/platform-defs.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <zircon/boot/driver-config.h>
#include "debug.h"
#include "devicetree.h"
#include "util.h"
#include "zbi.h"
// used in boot-shim-config.h and in this file below
static void append_boot_item(zbi_header_t* container, uint32_t type, uint32_t extra,
const void* payload, uint32_t length) {
zbi_result_t result =
zbi_create_entry_with_payload(container, SIZE_MAX, type, extra, 0, payload, length);
if (result != ZBI_RESULT_OK) {
fail("zbi_append_section failed\n");
}
}
// defined in boot-shim-config.h
static void append_board_boot_item(zbi_header_t* container);
#if USE_DEVICE_TREE_CPU_COUNT
static void set_cpu_count(uint32_t cpu_count);
#endif
// Include board specific definitions
#include "boot-shim-config.h"
// behavior switches that may be overridden by boot-shim-config.h
// uncomment to dump device tree at boot
#ifndef PRINT_DEVICE_TREE
#define PRINT_DEVICE_TREE 0
#endif
// Uncomment to list ZBI items.
#ifndef PRINT_ZBI
#define PRINT_ZBI 0
#endif
// When copying the kernel out of the ZBI as it was placed by previous loaders,
// remove the kernel ZBI section to reclaim some memory.
#ifndef REMOVE_KERNEL_FROM_ZBI
#define REMOVE_KERNEL_FROM_ZBI 1
#endif
#if HAS_DEVICE_TREE
typedef enum {
NODE_NONE,
NODE_CHOSEN,
NODE_MEMORY,
NODE_CPU,
NODE_INTC,
} node_t;
typedef struct {
dt_slice_t devicetree;
node_t node;
uintptr_t initrd_start;
size_t memory_base;
size_t memory_size;
char* cmdline;
size_t cmdline_length;
uint32_t cpu_count;
int gic_version;
} device_tree_context_t;
static int node_callback(int depth, const char* name, void* cookie) {
#if PRINT_DEVICE_TREE
uart_puts("node: ");
uart_puts(name);
uart_puts("\n");
#endif
device_tree_context_t* ctx = cookie;
if (!strcmp(name, "chosen")) {
ctx->node = NODE_CHOSEN;
} else if (!strcmp(name, "memory") || !strncmp(name, "memory@", 7)) {
ctx->node = NODE_MEMORY;
} else if (!strncmp(name, "cpu@", 4)) {
ctx->node = NODE_CPU;
ctx->cpu_count++;
} else if (!strcmp(name, "intc") || !strncmp(name, "intc@", 5)) {
ctx->node = NODE_INTC;
} else {
ctx->node = NODE_NONE;
}
return 0;
}
static int prop_callback(const char* name, uint8_t* data, uint32_t size, void* cookie) {
#if PRINT_DEVICE_TREE
uart_puts(" prop: ");
uart_puts(name);
uart_puts(" size: ");
uart_print_hex(size);
#endif
device_tree_context_t* ctx = cookie;
switch (ctx->node) {
case NODE_CHOSEN:
if (!strcmp(name, "linux,initrd-start")) {
if (size == sizeof(uint32_t)) {
ctx->initrd_start = dt_rd32(data);
} else if (size == sizeof(uint64_t)) {
uint64_t most = dt_rd32(data);
uint64_t least = dt_rd32(data + 4);
ctx->initrd_start = (most << 32) | least;
} else {
fail("bad size for linux,initrd-start in device tree\n");
}
} else if (!strcmp(name, "bootargs")) {
ctx->cmdline = (char*)data;
ctx->cmdline_length = size;
}
break;
case NODE_MEMORY:
if (!strcmp(name, "reg") && size == 16) {
// memory size is big endian uint64_t at offset 0
uint64_t most = dt_rd32(data + 0);
uint64_t least = dt_rd32(data + 4);
ctx->memory_base = (most << 32) | least;
// memory size is big endian uint64_t at offset 8
most = dt_rd32(data + 8);
least = dt_rd32(data + 12);
ctx->memory_size = (most << 32) | least;
}
break;
case NODE_INTC:
if (!strcmp(name, "compatible")) {
if (!strncmp((const char*)data, "arm,gic-v3", size)) {
ctx->gic_version = 3;
} else if (!strncmp((const char*)data, "arm,cortex-a15-gic", size)) {
ctx->gic_version = 2;
}
#if PRINT_DEVICE_TREE
uart_puts(" gic version ");
uart_print_hex(ctx->gic_version);
#endif
}
break;
default:;
}
#if PRINT_DEVICE_TREE
uart_puts("\n");
#endif
return 0;
}
// Parse the device tree to find our ZBI, kernel command line, and RAM size.
static void* read_device_tree(void* device_tree, device_tree_context_t* ctx) {
ctx->node = NODE_NONE;
ctx->initrd_start = 0;
ctx->memory_base = 0;
ctx->memory_size = 0;
ctx->cmdline = NULL;
ctx->cpu_count = 0;
ctx->gic_version = -1;
devicetree_t dt;
dt.error = uart_puts;
int ret = dt_init(&dt, device_tree, 0xffffffff);
if (ret) {
fail("dt_init failed\n");
}
ctx->devicetree.data = device_tree;
ctx->devicetree.size = dt.hdr.size;
dt_walk(&dt, node_callback, prop_callback, ctx);
#if USE_DEVICE_TREE_CPU_COUNT
set_cpu_count(ctx->cpu_count);
#endif
#if USE_DEVICE_TREE_GIC_VERSION
set_gic_version(ctx->gic_version);
#endif
#if USE_DEVICE_TREE_TOP_OF_RAM
set_top_of_ram(ctx->memory_base + ctx->memory_size);
#endif
// Use the device tree initrd as the ZBI.
return (void*)ctx->initrd_start;
}
static void append_from_device_tree(zbi_header_t* zbi, device_tree_context_t* ctx) {
// append kernel command line
if (ctx->cmdline && ctx->cmdline_length) {
const uint32_t length = (uint32_t)ctx->cmdline_length;
append_boot_item(zbi, ZBI_TYPE_CMDLINE, 0, ctx->cmdline, length);
}
append_boot_item(zbi, ZBI_TYPE_DEVICETREE, 0, ctx->devicetree.data, ctx->devicetree.size);
}
static bool device_tree_memory_range(device_tree_context_t* ctx, zbi_mem_range_t* range) {
if (!ctx->memory_size) {
uart_puts("RAM size not found in device tree\n");
return false;
}
uart_puts("Setting RAM base and size device tree value: ");
uart_print_hex(ctx->memory_base);
uart_puts(" ");
uart_print_hex(ctx->memory_size);
uart_puts("\n");
range->paddr = ctx->memory_base;
range->length = ctx->memory_size;
range->type = ZBI_MEM_RANGE_RAM;
return true;
}
#else
typedef struct {
} device_tree_context_t;
static void* read_device_tree(void* device_tree, device_tree_context_t* ctx) { return NULL; }
static void append_from_device_tree(zbi_header_t* zbi, device_tree_context_t* ctx) {}
static bool device_tree_memory_range(device_tree_context_t* ctx, zbi_mem_range_t* range) {
return false;
}
#endif // HAS_DEVICE_TREE
// Append board memory information to the ZBI.
//
// The memory information mostly consists of a number of static entries with
// (optionally) an additional single entry from the device tree at the end.
static void append_board_memory_info(zbi_header_t* zbi, device_tree_context_t* ctx) {
// Fetch the device tree memory entry if present.
zbi_mem_range_t device_tree_range;
bool has_extra_range = device_tree_memory_range(ctx, &device_tree_range);
// Allocate space in the ZBI for the memory ranges.
char* output_buffer;
zbi_result_t result =
zbi_create_entry(zbi, SIZE_MAX, ZBI_TYPE_MEM_CONFIG, /*extra=*/0, /*flags=*/0,
sizeof(mem_config) + (has_extra_range ? sizeof(device_tree_range) : 0),
(void**)&output_buffer);
if (result != ZBI_RESULT_OK) {
fail("zbi_create_entry failed\n");
}
// Copy over the static entries.
memcpy(output_buffer, mem_config, sizeof(mem_config));
output_buffer += sizeof(mem_config);
// Copy over the additional entry if required.
if (has_extra_range) {
memcpy(output_buffer, &device_tree_range, sizeof(device_tree_range));
}
}
__attribute__((unused)) static void dump_words(const char* what, const void* data) {
uart_puts(what);
const uint64_t* words = data;
for (int i = 0; i < 8; ++i) {
uart_puts(i == 4 ? "\n " : " ");
uart_print_hex(words[i]);
}
uart_puts("\n");
}
static zbi_result_t list_zbi_cb(zbi_header_t* item, void* payload, void* ctx) {
uart_print_hex((uintptr_t)item);
uart_puts(": length=0x");
uart_print_hex(item->length);
uart_puts(" type=0x");
uart_print_hex(item->type);
uart_puts(" (");
uart_putc(item->type & 0xff);
uart_putc((item->type >> 8) & 0xff);
uart_putc((item->type >> 16) & 0xff);
// The cast below is needed to make GCC with -Wconversion happy.
uart_putc((char)(item->type >> 24) & 0xff);
uart_puts(") extra=0x");
uart_print_hex(item->extra);
uart_puts("\n");
return ZBI_RESULT_OK;
}
static void list_zbi(zbi_header_t* zbi) {
uart_puts("ZBI container length 0x");
uart_print_hex(zbi->length);
uart_puts("\n");
zbi_for_each(zbi, &list_zbi_cb, NULL);
uart_puts("ZBI container ends 0x");
uart_print_hex((uintptr_t)(zbi + 1) + zbi->length);
uart_puts("\n");
}
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define ARM64_READ_SYSREG(reg) \
({ \
uint64_t _val; \
__asm__ volatile("mrs %0," TOSTRING(reg) : "=r"(_val)); \
_val; \
})
#define dump_arm_reg(reg) \
({ \
uart_puts(#reg " = "); \
uart_print_hex(ARM64_READ_SYSREG(reg)); \
uart_puts("\n"); \
})
boot_shim_return_t boot_shim(void* device_tree) {
uart_puts("boot_shim: hi there!\n");
zircon_kernel_t* kernel = NULL;
// Check the ZBI from device tree.
device_tree_context_t ctx;
zbi_header_t* zbi = read_device_tree(device_tree, &ctx);
if (zbi != NULL) {
zbi_header_t* bad_hdr;
zbi_result_t check = zbi_check(zbi, &bad_hdr);
if (check == ZBI_RESULT_OK && zbi->length > sizeof(zbi_header_t) &&
zbi[1].type == ZBI_TYPE_KERNEL_ARM64) {
kernel = (zircon_kernel_t*)zbi;
} else {
// No valid ZBI in device tree.
// We will look in embedded_zbi instead.
zbi = NULL;
}
}
// If there is a complete ZBI from device tree, ignore whatever might
// have been appended to the shim image. If not, the kernel is appended.
if (kernel == NULL) {
zbi_header_t* bad_hdr;
zbi_result_t check = zbi_check(&embedded_zbi, &bad_hdr);
if (check != ZBI_RESULT_OK) {
fail("no ZBI from device tree and no valid ZBI embedded\n");
}
if (embedded_zbi.hdr_file.length > sizeof(zbi_header_t) &&
embedded_zbi.hdr_kernel.type == ZBI_TYPE_KERNEL_ARM64) {
kernel = &embedded_zbi;
} else {
fail("no ARM64 kernel in ZBI from device tree or embedded ZBI\n");
}
}
// If there was no ZBI at all from device tree then use the embedded ZBI
// along with the embedded kernel. Otherwise always use the ZBI from
// device tree, whether the kernel is in that ZBI or was embedded.
if (zbi == NULL) {
zbi = &kernel->hdr_file;
}
// Add board-specific ZBI items.
append_board_boot_item(zbi);
// Add memory information.
append_board_memory_info(zbi, &ctx);
// Append other items from device tree.
append_from_device_tree(zbi, &ctx);
uint8_t* const kernel_end = (uint8_t*)&kernel->data_kernel + kernel->hdr_kernel.length +
kernel->data_kernel.reserve_memory_size;
uart_puts("Kernel at ");
uart_print_hex((uintptr_t)kernel);
uart_puts(" to ");
uart_print_hex((uintptr_t)kernel_end);
uart_puts(" reserved ");
uart_print_hex(kernel->data_kernel.reserve_memory_size);
uart_puts("\nZBI at ");
uart_print_hex((uintptr_t)zbi);
uart_puts(" to ");
uart_print_hex((uintptr_t)(zbi + 1) + zbi->length);
uart_puts("\n");
if ((uint8_t*)zbi < kernel_end && zbi != &kernel->hdr_file) {
fail("expected kernel to be loaded lower in memory than initrd\n");
}
if (PRINT_ZBI) {
list_zbi(zbi);
}
if (zbi == &kernel->hdr_file || (uintptr_t)zbi % 4096 != 0) {
// The ZBI needs to be page-aligned, so move it up.
// If it's a complete ZBI, splice out the kernel and move it higher.
zbi_header_t* old = zbi;
zbi = (void*)(((uintptr_t)old + 4095) & -(uintptr_t)4096);
if (old == &kernel->hdr_file) {
// Length of the kernel item payload, without header.
uint32_t kernel_len = kernel->hdr_kernel.length;
// Length of the ZBI container, including header, without kernel.
uint32_t zbi_len = kernel->hdr_file.length - kernel_len;
uart_puts("Splitting kernel len ");
uart_print_hex(kernel_len);
uart_puts(" from ZBI len ");
uart_print_hex(zbi_len);
// First move the kernel up out of the way.
uintptr_t zbi_end = (uintptr_t)(old + 1) + old->length;
if (zbi_end < (uintptr_t)zbi + zbi_len) {
zbi_end = (uintptr_t)zbi + zbi_len;
}
#if RELOCATE_KERNEL
// relocate the kernel to a new hard coded spot
kernel = (void*)RELOCATE_KERNEL_ADDRESS;
#else
kernel = (void*)((zbi_end + KERNEL_ALIGN - 1) & -(uintptr_t)KERNEL_ALIGN);
#endif
uart_puts("\nKernel to ");
uart_print_hex((uintptr_t)kernel);
memcpy(kernel, old, (2 * sizeof(*zbi)) + kernel_len);
// Fix up the kernel's solo container size.
kernel->hdr_file.length = sizeof(*zbi) + kernel_len;
#if REMOVE_KERNEL_FROM_ZBI
// Now move the ZBI into its aligned place and fix up the
// container header to exclude the kernel. We can conditionally
// disable this to avoid a fairly expensive memmove() with the
// cpu cache disabled.
uart_puts("\nZBI to ");
uart_print_hex((uintptr_t)zbi);
zbi_header_t header = *old;
header.length -= kernel->hdr_file.length;
void* payload = (uint8_t*)(old + 1) + kernel->hdr_file.length;
memmove(zbi + 1, payload, header.length);
*zbi = header;
#else
// Just mark the original kernel item as to be ignored.
((zircon_kernel_t*)zbi)->hdr_kernel.type = ZBI_TYPE_DISCARD;
#endif
#if RELOCATE_KERNEL
// move the final ZBI far away as well
void* target = (void*)RELOCATE_ZBI_ADDRESS;
memmove(target, zbi, zbi->length);
zbi = target;
#endif
uart_puts("\nKernel container length ");
uart_print_hex(kernel->hdr_file.length);
uart_puts(" ZBI container length ");
uart_print_hex(zbi->length);
uart_puts("\n");
} else {
uart_puts("Relocating whole ZBI for alignment\n");
memmove(zbi, old, sizeof(*old) + old->length);
}
}
if ((uintptr_t)kernel % KERNEL_ALIGN != 0) {
// The kernel has to be relocated for alignment.
uart_puts("Relocating kernel for alignment\n");
zbi_header_t* old = &kernel->hdr_file;
kernel =
(void*)(((uintptr_t)(zbi + 1) + zbi->length + KERNEL_ALIGN - 1) & -(uintptr_t)KERNEL_ALIGN);
memmove(kernel, old, sizeof(*old) + old->length);
}
boot_shim_return_t result = {
.zbi = zbi,
.entry = (uintptr_t)kernel + kernel->data_kernel.entry,
};
uart_puts("Entering kernel at ");
uart_print_hex(result.entry);
uart_puts(" with ZBI at ");
uart_print_hex((uintptr_t)result.zbi);
uart_puts("\n");
return result;
}