summaryrefslogtreecommitdiff
path: root/semi_loader.c
diff options
context:
space:
mode:
Diffstat (limited to 'semi_loader.c')
-rw-r--r--semi_loader.c536
1 files changed, 536 insertions, 0 deletions
diff --git a/semi_loader.c b/semi_loader.c
new file mode 100644
index 0000000..9039445
--- /dev/null
+++ b/semi_loader.c
@@ -0,0 +1,536 @@
+/*
+ * Copyright (c) 2012 Linaro Limited
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Linaro Limited nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ */
+
+#include <string.h>
+#include "libfdt.h"
+#include "semihosting.h"
+#include "semi_loader.h"
+
+static void _print_info(char const **strings)
+{
+ char const *string;
+
+ semi_write0("[bootwrapper] ");
+ while((string = *strings++))
+ semi_write0(string);
+}
+
+#define info(strings...) do { \
+ char const *__info_strings[] = { strings, NULL }; \
+ \
+ _print_info(__info_strings); \
+} while(0)
+
+#define warn(strings...) info("WARNING: ", strings)
+#define error(strings...) info("ERROR: ", strings)
+
+#define fatal(strings...) do { \
+ error(strings); \
+ semi_fatal("[bootwrapper] BOOT FAILED\n"); \
+} while(0)
+
+#define CMDLINE_KERNEL "--kernel"
+#define CMDLINE_INITRD "--initrd"
+#define CMDLINE_NOINITRD "--no-initrd"
+#define CMDLINE_DTB "--dtb"
+#define CMDLINE_FDT "--fdt" /* deprecated */
+#define CMDLINE_REST "-- "
+
+static void _usage_fatal(void)
+{
+ info("Usage: [" CMDLINE_KERNEL " <kernel filename>] ["
+ CMDLINE_NOINITRD "|" CMDLINE_INITRD " <initrd filename>] ["
+ CMDLINE_DTB " <dtb filename>] ["
+ CMDLINE_REST "<kernel boot arguments>]\n");
+ fatal("Incorrect bootwrapper command-line.\n");
+}
+
+#define usage_fatal(strings...) do { \
+ error(strings); \
+ _usage_fatal(); \
+} while(0)
+
+static void atag_append(void **dest, unsigned tag, void const *data, unsigned size)
+{
+ char *d = *dest;
+ unsigned padded_size = ALIGN_INT(size, 4) + 8;
+ struct atag_header header = {
+ padded_size >> 2,
+ tag
+ };
+
+ if(tag == ATAG_NONE)
+ header.size = 0;
+
+ memcpy(d, &header, sizeof header);
+ memcpy(d + 8, data, size);
+
+ if(padded_size > size + 8)
+ memset(d + 8 + size, 0, padded_size - (size + 8));
+
+ *dest = d + padded_size;
+}
+
+static int _fdt_make_node(void *fdt, int parentoffset, const char *name)
+{
+ int e;
+
+ e = fdt_subnode_offset(fdt, parentoffset, name);
+ if(e != -FDT_ERR_NOTFOUND)
+ return e;
+
+ return fdt_add_subnode(fdt, parentoffset, name);
+}
+
+static void update_fdt(void **dest, struct loader_info *info)
+{
+ int e;
+ int _chosen;
+ void *fdt;
+ uint32_t const *p;
+
+ if(!info->fdt_start)
+ return;
+
+ fdt = ALIGN(*dest, 4);
+ if((e = fdt_open_into((void *)info->fdt_start, fdt, FDT_SIZE_MAX)) < 0)
+ goto libfdt_error;
+
+ /*
+ * Sanity-check address sizes, since addresses and sizes which do
+ * not take up exactly 4 bytes are not supported.
+ */
+ {
+ if(!(p = fdt_getprop(fdt, 0, "#address-cells", &e)))
+ goto libfdt_error;
+ else if(e != 4 || fdt32_to_cpu(*p) != 1)
+ goto size_error;
+
+ if(!(p = fdt_getprop(fdt, 0, "#size-cells", &e)))
+ goto libfdt_error;
+ else if(e != 4 || fdt32_to_cpu(*p) != 1)
+ goto size_error;
+ }
+
+ /*
+ * Add a memory node, but only if there isn't one already. If
+ * there is already a memory node with non-zero size, it was put
+ * in the DT on purpose and should take precedence over our
+ * guesses. Otherwise, make a memory node with the appropriate
+ * parameters.
+ */
+ {
+ int offset, depth = 0;
+ int _memory;
+ uint32_t reg[2];
+
+ for(offset = fdt_next_node(fdt, 0, &depth); offset >= 0;
+ offset = fdt_next_node(fdt, offset, &depth)) {
+ char const *name;
+
+ if(depth != 1)
+ continue;
+
+ name = fdt_get_name(fdt, offset, (void *)0);
+ if(!strcmp(name, "memory") ||
+ !strncmp(name, "memory@", 7)) {
+ p = fdt_getprop(fdt, offset, "reg", &e);
+ if(e < 0)
+ goto libfdt_error;
+
+ if(fdt32_to_cpu(p[1]) != 0)
+ goto no_add_memory;
+ }
+ }
+
+ if((e = _fdt_make_node(fdt, 0, "memory")) < 0)
+ goto libfdt_error;
+ _memory = e;
+
+ reg[0] = cpu_to_fdt32(PHYS_OFFSET);
+ reg[1] = cpu_to_fdt32(PHYS_SIZE);
+ if((e = fdt_setprop(fdt, _memory, "reg", &reg, sizeof reg)) < 0)
+ goto libfdt_error;
+
+ if((e = fdt_setprop_string(fdt, _memory, "device_type",
+ "memory")) < 0)
+ goto libfdt_error;
+ }
+
+no_add_memory:
+ /* populate the "chosen" node */
+
+ if((e = _fdt_make_node(fdt, 0, "chosen")) < 0)
+ goto libfdt_error;
+ _chosen = e;
+
+ e = fdt_setprop_string(fdt, _chosen, "bootargs",
+ (char const *)info->cmdline_start);
+ if(e < 0)
+ goto libfdt_error;
+
+ if(info->initrd_start) {
+ uint32_t initrd_end = info->initrd_start + info->initrd_size;
+
+ if((e = fdt_setprop_cell(fdt, _chosen, "linux,initrd-start",
+ info->initrd_start)) < 0)
+ goto libfdt_error;
+
+ if((e = fdt_setprop_cell(fdt, _chosen, "linux,initrd-end",
+ initrd_end)) < 0)
+ goto libfdt_error;
+ }
+
+ /* success */
+
+ /* clean up */
+ fdt_pack(fdt);
+ info->fdt_start = (unsigned)fdt;
+ info->fdt_size = fdt_totalsize(fdt);
+ info("FDT updated.\n");
+
+ return;
+
+libfdt_error:
+ fatal("libfdt: ", fdt_strerror(e), ", while updating device tree\n");
+
+size_error:
+ fatal("Unexpected/invalid #address-cells/#size-cells in device tree\n");
+}
+
+static int is_space(char c)
+{
+ return c == ' ';
+}
+
+static void skip_space(char **s)
+{
+ char *t = *s;
+
+ for(t = *s; is_space(*t); t++);
+ *s = t;
+}
+
+static void find_space(char **s)
+{
+ char *t = *s;
+
+ for(t = *s; *t && !is_space(*t); t++);
+ *s = t;
+}
+
+static int match_word(char **s, char const *string)
+{
+ unsigned l;
+
+ l = strlen(string);
+ if(strncmp(*s, string, l))
+ return 0;
+
+ *s += l;
+ skip_space(s);
+ return 1;
+}
+
+/*
+ * Match an option with a mandatory argument.
+ * On success, a pointer to the argument is returned, with leading and
+ * trailing whitespace stripped.
+ */
+static char *match_option(char **s, char const *option_string)
+{
+ char *arg;
+
+ if(!match_word(s, option_string))
+ return (void *)0;
+
+ if(!**s)
+ usage_fatal("Option requires as argument: ",
+ option_string, "\n");
+
+ /* otherwise, *s now points to the argument */
+ arg = *s;
+
+ find_space(s); /* find the end of the argument */
+ if(**s) {
+ *(*s)++ = '\0'; /* null-terminate if necessary */
+ skip_space(s); /* skip any remaining space */
+ }
+
+ return arg;
+}
+
+static void load_file_essential(void **dest, char const *filename,
+ unsigned *size, char const *failmsg)
+{
+ if(semi_load_file(dest, size, filename))
+ fatal(failmsg, ": \"", filename, "\"\n");
+}
+
+/* Move the kernel if necessary, based on the image type: */
+static void correct_kernel_location(struct loader_info *info)
+{
+ char *const text_start = (char *)(PHYS_OFFSET + TEXT_OFFSET);
+ char *const text_end = text_start + info->kernel_size;
+ char *const uImage_payload = text_start + UIMAGE_HEADER_SIZE;
+ unsigned long *const zImage_magic_p = (unsigned long *)(
+ uImage_payload + ZIMAGE_MAGIC_OFFSET);
+
+ /*
+ * If the image is not a uImage, then it is a raw Image or zImage,
+ * and no action is necessary:
+ */
+ if(info->kernel_size <= UIMAGE_HEADER_SIZE)
+ return;
+
+ if(memcmp(text_start, uImage_magic, sizeof uImage_magic))
+ return;
+
+ warn("Ignoring uImage meta-data\n");
+
+ /*
+ * If the uImage payload is a zImage, the position-independent
+ * nature of the zImage header means that no relocation is
+ * needed. Instead, just enter at the start of the loaded
+ * zImage header:
+ */
+ if(text_end >= (char *)&zImage_magic_p[1]
+ && *zImage_magic_p == ZIMAGE_MAGIC) {
+ info->kernel_entry += UIMAGE_HEADER_SIZE;
+ return;
+ }
+
+ /*
+ * Otherwise, move the payload to replace the uImage header, and
+ * leave the entry point unmodified.
+ */
+ memmove(text_start, uImage_payload,
+ info->kernel_size - UIMAGE_HEADER_SIZE);
+}
+
+static char semi_cmdline[SEMI_CMDLINE_MAX];
+
+static char *kernel_arg = (void *)0;
+static char *initrd_arg = (void *)0;
+static char *fdt_arg = (void *)0;
+static char *dtb_arg = (void *)0;
+static char *cmdline_arg = (void *)0;
+static char *noinitrd_arg = (void *)0;
+
+static const struct {
+ char const *option_string;
+ char **argp;
+ enum { OPT_ARG, OPT_BOOL, OPT_REST } action;
+} options[] = {
+ { CMDLINE_KERNEL, &kernel_arg, OPT_ARG },
+ { CMDLINE_INITRD, &initrd_arg, OPT_ARG },
+ { CMDLINE_NOINITRD, &noinitrd_arg, OPT_BOOL },
+ { CMDLINE_FDT, &fdt_arg, OPT_ARG },
+ { CMDLINE_DTB, &dtb_arg, OPT_ARG },
+ { CMDLINE_REST, &cmdline_arg, OPT_REST },
+};
+
+void load_kernel(struct loader_info *info)
+{
+ unsigned i;
+ char *cmdline = semi_cmdline;
+ int cmdline_length;
+ void *phys = (char *)(PHYS_OFFSET + TEXT_OFFSET);
+ void *atagp = (char *)(PHYS_OFFSET + ATAGS_OFFSET);
+
+ union {
+ struct atag_core core;
+ struct atag_mem mem;
+ struct atag_initrd2 initrd;
+ } atag;
+
+ /* Fetch the command line: */
+
+ if(semi_get_cmdline(semi_cmdline, sizeof semi_cmdline,
+ &cmdline_length) ||
+ cmdline_length >= sizeof semi_cmdline) {
+ warn("Failed to get semihosting command line, using built-in defaults\n");
+ cmdline_length = 0;
+ }
+ cmdline[cmdline_length] = '\0';
+
+ /* Parse the arguments (if any): */
+
+ skip_space(&cmdline);
+
+ while(*cmdline) {
+ for(i = 0; i < sizeof options / sizeof *options; i++) {
+ char *arg;
+
+ switch(options[i].action) {
+ case OPT_BOOL:
+ if(!match_word(&cmdline,
+ options[i].option_string))
+ continue;
+
+ *options[i].argp = cmdline; /* non-NULL */
+ goto next_arg;
+
+ case OPT_REST:
+ if(!match_word(&cmdline,
+ options[i].option_string))
+ continue;
+
+ *options[i].argp = cmdline;
+ goto args_done;
+
+ case OPT_ARG:
+ arg = match_option(&cmdline,
+ options[i].option_string);
+ if(!arg)
+ continue;
+
+ if(*options[i].argp)
+ usage_fatal("Duplicate option ",
+ options[i].option_string);
+
+ /* otherwise, option was parsed successfully: */
+ *options[i].argp = arg;
+ goto next_arg;
+ }
+ } /* for(i) */
+
+ /* Failed to match any expected option: */
+ usage_fatal("Invalid option(s): ", cmdline);
+
+ next_arg: ;
+ } /* while(*cmdline) */
+
+args_done:
+ if(initrd_arg && noinitrd_arg)
+ usage_fatal("Option --initrd conflicts with --no-initrd.\n");
+
+ if(fdt_arg) {
+ warn("--fdt is deprecated. Please use --dtb instead.\n");
+
+ if(dtb_arg)
+ usage_fatal("--fdt conflicts with --dtb.\n");
+ else
+ dtb_arg = fdt_arg;
+ }
+
+ /*
+ * Now, proceed to load images and set up ATAGs.
+ * For simplicity, ATAGs are generated even if there is a DTB
+ */
+
+ /* built-in FDT not supported, for now */
+ info->fdt_start = info->fdt_size = 0;
+
+ info->atags_start = (unsigned)atagp;
+
+ memset(&atag.core, 0, sizeof atag.core);
+ atag_append(&atagp, ATAG_CORE, &atag.core, sizeof atag.core);
+
+ /* create the essential ATAGs */
+
+ atag.mem.start = PHYS_OFFSET;
+ atag.mem.size = PHYS_SIZE;
+ atag_append(&atagp, ATAG_MEM, &atag.mem, sizeof atag.mem);
+
+ /* load the kernel */
+
+ info->kernel_entry = (unsigned)phys;
+
+ if(kernel_arg) {
+ load_file_essential(&phys, kernel_arg, &info->kernel_size,
+ "Failed to load kernel image");
+ info("Loaded kernel: ", kernel_arg, "\n");
+ } else if(info->kernel_size) {
+ info("Using built-in kernel\n");
+ phys += info->kernel_size;
+ } else
+ usage_fatal("Expected " CMDLINE_KERNEL "\n");
+
+ /* move the kernel to the correct place, if necessary */
+
+ correct_kernel_location(info);
+
+ phys = (char *)(PHYS_OFFSET + INITRD_OFFSET);
+
+ /* load the initrd */
+
+ atag.initrd.size = 0;
+
+ if(initrd_arg) {
+ unsigned start = (unsigned)phys;
+
+ load_file_essential(&phys, initrd_arg, NULL,
+ "Failed to load initrd image");
+ info("Loaded initrd: ", initrd_arg, "\n");
+
+ info->initrd_start = atag.initrd.start = start;
+ info->initrd_size = atag.initrd.size = (unsigned)phys - start;
+ } else if(info->initrd_size) {
+ if(noinitrd_arg) {
+ info->initrd_size = 0;
+ info("Built-in initrd discarded, as requested\n");
+ } else {
+ info("Using built-in initrd\n");
+
+ atag.initrd.start = info->initrd_start;
+ atag.initrd.size = info->initrd_size;
+ }
+ } else
+ info->initrd_size = 0;
+
+ if(atag.initrd.size)
+ atag_append(&atagp, ATAG_INITRD2, &atag.initrd, sizeof atag.initrd);
+
+ /* load the FDT, if specified */
+
+ if(fdt_arg) {
+ phys = ALIGN(phys, 4);
+ info->fdt_start = (unsigned)phys;
+
+ load_file_essential(&phys, fdt_arg, NULL,
+ "Failed to load device tree blob");
+ info("Loaded FDT: ", fdt_arg, "\n");
+
+ info->fdt_size = (unsigned)phys - info->fdt_start;
+ }
+
+ /*
+ * The FDT will get modified to reflect bootargs, initrd and memory
+ * configuration later.
+ */
+
+ /* set the command line */
+
+ if(cmdline_arg) {
+ info->cmdline_start = (unsigned)cmdline_arg;
+ info->cmdline_size = strlen(cmdline_arg) + 1;
+ } else if(info->cmdline_size)
+ info("Using built-in kernel bootargs\n");
+
+ /* cmdline_size is presumed to include a NUL terminator: */
+ if(info->cmdline_size) {
+ atag_append(&atagp, ATAG_CMDLINE,
+ (char *)info->cmdline_start, info->cmdline_size);
+ info("Kernel bootargs: ", (char *)info->cmdline_start, "\n");
+ }
+
+ atag_append(&atagp, ATAG_NONE, 0, 0);
+
+ update_fdt(&phys, info);
+}