blob: 64ee54f5eeba7cd164096f2613b71b269cff083a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "boot-shim.h"
#include "debug.h"
#include "devicetree.h"
#include "util.h"
#include <stdbool.h>
#include <stddef.h>
#include <ddk/protocol/platform-defs.h>
#include <zircon/boot/bootdata.h>
#include <zircon/boot/driver-config.h>
// uncomment to dump device tree at boot
// #define PRINT_DEVICE_TREE
// used in boot-shim-config.h and in this file below
static void append_bootdata(bootdata_t* container, uint32_t type, uint32_t extra,
const void* payload, uint32_t length);
// defined in boot-shim-config.h
static void append_board_bootdata(bootdata_t* container);
// Include board specific definitions
#include "boot-shim-config.h"
#define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
#if HAS_DEVICE_TREE
typedef enum {
NODE_NONE,
NODE_CHOSEN,
NODE_MEMORY,
} node_t;
typedef struct {
node_t node;
uintptr_t initrd_start;
size_t memory;
char* cmdline;
size_t cmdline_length;
} device_tree_context_t;
static int node_callback(int depth, const char *name, void *cookie) {
#ifdef 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") || !strcmp(name, "memory@00000000")) {
ctx->node = NODE_MEMORY;
} else {
ctx->node = NODE_NONE;
}
return 0;
}
static int prop_callback(const char *name, uint8_t *data, uint32_t size, void *cookie) {
#ifdef PRINT_DEVICE_TREE
uart_puts(" prop: ");
uart_puts(name);
uart_puts(" size: ");
uart_print_hex(size);
uart_puts("\n");
#endif
device_tree_context_t* ctx = cookie;
if (ctx->node == 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;
}
} else if (ctx->node == NODE_MEMORY) {
if (!strcmp(name, "reg") && size == 16) {
// memory size is big endian uint64_t at offset 8
uint64_t most = dt_rd32(data + 8);
uint64_t least = dt_rd32(data + 12);
ctx->memory = (most << 32) | least;
}
}
return 0;
}
static void read_device_tree(void* device_tree, device_tree_context_t* ctx) {
devicetree_t dt;
dt.error = uart_puts;
int ret = dt_init(&dt, device_tree, 0xffffffff);
if (ret) {
fail("dt_init failed\n");
}
dt_walk(&dt, node_callback, prop_callback, ctx);
}
#endif // HAS_DEVICE_TREE
static void append_bootdata(bootdata_t* container, uint32_t type, uint32_t extra,
const void* payload, uint32_t length) {
bootdata_t* dest = (bootdata_t*)((uintptr_t)container + container->length + sizeof(bootdata_t));
dest->type = type;
dest->length = length;
dest->extra = extra;
dest->flags = 0;
dest->reserved0 = 0;
dest->reserved1 = 0;
dest->magic = BOOTITEM_MAGIC;
dest->crc32 = BOOTITEM_NO_CRC32;
if (length) {
memcpy(dest + 1, payload, length);
}
length = BOOTDATA_ALIGN(length + sizeof(bootdata_t));
container->length += length;
}
uint64_t boot_shim(void* device_tree, zircon_kernel_t* kernel) {
uart_puts("boot_shim: hi there!\n");
// sanity check the bootdata headers
// it must start with a container record followed by a kernel record
if (kernel->hdr_file.type != BOOTDATA_CONTAINER ||
kernel->hdr_file.extra != BOOTDATA_MAGIC ||
kernel->hdr_file.magic != BOOTITEM_MAGIC ||
kernel->hdr_kernel.type != BOOTDATA_KERNEL ||
kernel->hdr_kernel.magic != BOOTITEM_MAGIC) {
fail("zircon_kernel_t sanity check failed\n");
}
uint32_t bootdata_size = kernel->hdr_file.length + sizeof(bootdata_t);
uint32_t kernel_size = kernel->hdr_kernel.length + 2 * sizeof(bootdata_t);
// If we have bootdata following the kernel, then the kernel is the beginning of our bootdata.
// Otherwise we will need to look for the bootdata in the device tree
bootdata_t* bootdata = (bootdata_size > kernel_size ? &kernel->hdr_file : NULL);
// If we have more bootdata following the kernel we must relocate the kernel
// past the end of the bootdata so the kernel bss does not collide with it.
// We will do this relocation after we are done appending new bootdata items.
bool relocate_kernel = (bootdata != NULL);
#if HAS_DEVICE_TREE
// Parse the Linux device tree to find our bootdata, kernel command line and RAM size
device_tree_context_t ctx;
ctx.node = NODE_NONE;
ctx.initrd_start = 0;
ctx.memory = 0;
ctx.cmdline = NULL;
read_device_tree(device_tree, &ctx);
// find our bootdata first
if (!bootdata) {
if (ctx.initrd_start) {
bootdata = (bootdata_t*)ctx.initrd_start;
if (bootdata->type != BOOTDATA_CONTAINER || bootdata->extra != BOOTDATA_MAGIC ||
bootdata->magic != BOOTITEM_MAGIC) {
fail("bad magic for bootdata in device tree\n");
}
} else {
fail("could not find bootdata in device tree\n");
}
}
#endif // HAS_DEVICE_TREE
// add board specific bootdata
append_board_bootdata(bootdata);
#if HAS_DEVICE_TREE
// look for optional RAM size in device tree
// do this last so device tree can override value in boot-shim-config.h
if (ctx.memory) {
bootdata_mem_range_t mem_range;
mem_range.paddr = 0;
mem_range.length = ctx.memory;
mem_range.type = BOOTDATA_MEM_RANGE_RAM;
uart_puts("Setting RAM size device tree value: ");
uart_print_hex(ctx.memory);
uart_puts("\n");
append_bootdata(bootdata, BOOTDATA_MEM_CONFIG, 0, &mem_range, sizeof(mem_range));
} else {
uart_puts("RAM size not found in device tree\n");
}
// append kernel command line
if (ctx.cmdline && ctx.cmdline_length) {
append_bootdata(bootdata, BOOTDATA_CMDLINE, 0, ctx.cmdline, ctx.cmdline_length);
}
#endif // HAS_DEVICE_TREE
uintptr_t kernel_base;
if (relocate_kernel) {
// recalculate bootdata_size after appending more bootdata records
bootdata_size = kernel->hdr_file.length + sizeof(bootdata_t);
// round up to align new kernel location
bootdata_size = ROUNDUP(bootdata_size, KERNEL_ALIGN);
kernel_base = (uintptr_t)kernel + bootdata_size;
memcpy((void *)kernel_base, kernel, BOOTDATA_ALIGN(kernel_size));
} else {
kernel_base = (uintptr_t)kernel;
}
// return pointer to bootdata in bootdata_return
bootdata_return = bootdata;
// return kernel entry point address
return kernel_base + kernel->data_kernel.entry64;
}