summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/console.c191
-rw-r--r--common/image.c66
-rw-r--r--common/load.c70
-rw-r--r--common/main.c164
4 files changed, 491 insertions, 0 deletions
diff --git a/common/console.c b/common/console.c
new file mode 100644
index 0000000..2e9bfa6
--- /dev/null
+++ b/common/console.c
@@ -0,0 +1,191 @@
+/*
+ * console.c - the console dprintf writes to. the dtb picks the uart,
+ * chosen/stdout-path first, any arm,pl011 node as fallback.
+ *
+ * Copyright (c) 2008 Travis Geiselbrecht
+ * Copyright (c) 2026 Bradley Morgan <brads@mainlining.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <libfdt.h>
+#include <printf.h>
+#include <debug.h>
+#include <reg.h>
+#include <drivers/serial/pl011.h>
+
+static uintptr_t tb_uart_base;
+static uint32 tb_uart_clock;
+static bool tb_uart_ready;
+
+/*
+ * read one reg pair out of a node. cells are u32s in the blob, address
+ * and size widths come from the parent #address-cells/#size-cells,
+ * 2/1 is what every board we care about uses, qemu virt included.
+ */
+static int tb_read_reg(const void *blob, int node, int index,
+ uintptr_t *base, uint32 *size)
+{
+ const fdt32_t *prop;
+ int len;
+ int offset;
+
+ prop = fdt_getprop(blob, node, "reg", &len);
+ if (!prop)
+ return -1;
+
+ /*
+ * a reg pair here is <addr_hi addr_lo size>, 3 cells, 2 for the
+ * address and 1 for the size. index 0 is the pair we want.
+ */
+ offset = index * 3;
+ if (offset + 3 > len / 4)
+ return -1;
+
+ *base = ((uintptr_t)fdt32_to_cpu(prop[offset]) << 32) |
+ fdt32_to_cpu(prop[offset + 1]);
+ *size = fdt32_to_cpu(prop[offset + 2]);
+ return 0;
+}
+
+static int tb_console_from_stdout_path(const void *blob)
+{
+ const char *path;
+ const char *p;
+ char buf[32];
+ int node;
+ int chosen;
+ int len;
+
+ chosen = fdt_path_offset(blob, "/chosen");
+ if (chosen < 0)
+ return -1;
+
+ path = fdt_getprop(blob, chosen, "stdout-path", &len);
+ if (!path || len <= 0)
+ return -1;
+
+ /* the path can carry :baud after the node, cut it */
+ p = strchr(path, ':');
+ len = p ? (int)(p - path) : strlen(path);
+ if (len >= (int)sizeof(buf))
+ len = sizeof(buf) - 1;
+ memcpy(buf, path, len);
+ buf[len] = 0;
+
+ /* a leading / is a full path, otherwise it's an alias */
+ if (buf[0] == '/')
+ node = fdt_path_offset(blob, buf);
+ else
+ node = fdt_path_offset(blob, fdt_get_alias(blob, buf));
+ if (node < 0)
+ return -1;
+
+ return node;
+}
+
+static int tb_console_scan(const void *blob)
+{
+ const char *compat;
+ int node;
+ int len;
+
+ fdt_for_each_subnode(node, blob, 0) {
+ compat = fdt_getprop(blob, node, "compatible", &len);
+ if (!compat)
+ continue;
+ if (fdt_stringlist_contains(compat, len, "arm,pl011"))
+ return node;
+ }
+ return -1;
+}
+
+int tb_console_init(const void *dtb)
+{
+ const fdt32_t *clk;
+ int node;
+ uint32 size;
+ uint32 baud;
+ int len;
+
+ node = tb_console_from_stdout_path(dtb);
+ if (node < 0)
+ node = tb_console_scan(dtb);
+ if (node < 0)
+ return -1;
+
+ if (tb_read_reg(dtb, node, 0, &tb_uart_base, &size))
+ return -1;
+
+ clk = fdt_getprop(dtb, node, "clock-frequency", &len);
+ tb_uart_clock = (clk && len == 4) ? fdt32_to_cpu(*clk) : 24000000;
+
+ {
+ const fdt32_t *cs;
+ int cslen;
+
+ cs = fdt_getprop(dtb, node, "current-speed", &cslen);
+ baud = (cs && cslen == 4) ? fdt32_to_cpu(*cs) : 115200;
+ }
+
+ pl011_init(tb_uart_base, tb_uart_clock, baud);
+ tb_uart_ready = true;
+ return 0;
+}
+
+static int tb_console_out(char c, void *state)
+{
+ if (c == '\n')
+ pl011_putc(tb_uart_base, '\r');
+ pl011_putc(tb_uart_base, c);
+ return 1;
+}
+
+int _dprintf(const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ if (!tb_uart_ready)
+ return 0;
+
+ va_start(ap, fmt);
+ ret = _dvprintf(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+int _dvprintf(const char *fmt, va_list ap)
+{
+ if (!tb_uart_ready)
+ return 0;
+ return _printf_engine(tb_console_out, NULL, fmt, ap);
+}
+
+void platform_halt(void)
+{
+ for (;;)
+ __asm__ volatile("wfe");
+}
diff --git a/common/image.c b/common/image.c
new file mode 100644
index 0000000..d114c6a
--- /dev/null
+++ b/common/image.c
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * image.c - arm64 kernel Image validation and placement.
+ *
+ * The relocation rules are the ones from the kernel boot protocol,
+ * including the pre a2c1d73b94ed fallback where image_size is 0 and
+ * the offset was fixed. qemu virt loads us at the bottom of RAM, same
+ * place the kernel wants to be, so the common case is a no move.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <boot.h>
+#include <endian.h>
+
+#define SZ_2M 0x200000UL
+#define SZ_16M 0x1000000UL
+#define IMAGE_OFFSET_FIXUP 0x80000ULL
+
+int tb_image_setup(uintptr_t image, uintptr_t mem_base, uint32 mem_size,
+ struct tb_image *img)
+{
+ struct Image_header *ih = (struct Image_header *)image;
+ uint64_t image_size;
+ uint64_t text_offset;
+ uintptr_t dst;
+
+ if (le32_to_cpu(ih->magic) != LINUX_ARM64_IMAGE_MAGIC)
+ return -1;
+
+ if (le64_to_cpu(ih->image_size) == 0) {
+ /* ancient image, no size field, assume the old defaults */
+ image_size = SZ_16M;
+ text_offset = IMAGE_OFFSET_FIXUP;
+ } else {
+ image_size = le64_to_cpu(ih->image_size);
+ text_offset = le64_to_cpu(ih->text_offset);
+ }
+
+ if (image_size > mem_size)
+ return -1;
+
+ /*
+ * flag bit 3 says the image can live anywhere, honour where it
+ * already is. otherwise the base must be 2MB aligned, the
+ * physical offset from there is text_offset.
+ */
+ if (le64_to_cpu(ih->flags) & (1ULL << 3))
+ dst = image - text_offset;
+ else
+ dst = mem_base;
+
+ /* the whole image, header included, sits here, and code0 at its
+ * start is the first instruction executed */
+ img->load = ((dst + SZ_2M - 1) & ~(uintptr_t)(SZ_2M - 1)) + text_offset;
+ img->ep = img->load;
+ img->size = image_size;
+
+ if (dst + image_size > mem_base + mem_size)
+ return -1;
+
+ return 0;
+}
diff --git a/common/load.c b/common/load.c
new file mode 100644
index 0000000..4669c0b
--- /dev/null
+++ b/common/load.c
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * load.c - pull the payload into RAM. semihosting is the qemu path,
+ * flash and block devices follow the same shape once they exist.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <semihosting.h>
+#include <boot.h>
+
+/*
+ * load fname into mem_base. the header decides the real placement, so
+ * we read the whole file first and let tb_image_setup place it.
+ */
+int tb_load_semihosting(const char *fname, uintptr_t load_addr,
+ uintptr_t mem_base, uint32 mem_size,
+ struct tb_image *img)
+{
+ struct Image_header *ih = (struct Image_header *)load_addr;
+ long fd;
+ long len;
+ long ret;
+
+ fd = smh_open(fname, MODE_READ | MODE_BINARY);
+ if (fd < 0)
+ return fd;
+
+ len = smh_flen(fd);
+ if (len < 0) {
+ smh_close(fd);
+ return len;
+ }
+
+ if ((uint32)len > mem_size) {
+ smh_close(fd);
+ return -2;
+ }
+
+ /* header first so placement is known before the big copy */
+ ret = smh_read(fd, (void *)load_addr, sizeof(*ih));
+ if (ret != sizeof(*ih)) {
+ smh_close(fd);
+ return -3;
+ }
+
+ if (tb_image_setup(load_addr, mem_base, mem_size, img)) {
+ smh_close(fd);
+ return -4;
+ }
+
+ if (img->load != load_addr) {
+ /* the image wants to sit elsewhere, copy it there */
+ memmove((void *)img->load, (void *)load_addr,
+ sizeof(*ih));
+ }
+
+ ret = smh_read(fd, (void *)(img->load + sizeof(*ih)),
+ len - sizeof(*ih));
+ if (ret != len - sizeof(*ih)) {
+ smh_close(fd);
+ return -5;
+ }
+
+ smh_close(fd);
+ return 0;
+}
diff --git a/common/main.c b/common/main.c
new file mode 100644
index 0000000..94f7195
--- /dev/null
+++ b/common/main.c
@@ -0,0 +1,164 @@
+/*
+ * main.c - the C entry. console up first, then the board from the dtb,
+ * then find a payload and jump. called from start.S with x0 = dtb.
+ *
+ * Copyright (c) 2026 Bradley Morgan <brads@mainlining.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <libfdt.h>
+#include <debug.h>
+#include <semihosting.h>
+#include <boot.h>
+
+#define TB_VERSION "0.1"
+
+extern int tb_console_init(const void *dtb);
+extern void __NO_RETURN tb_boot_linux(uintptr_t ep, const void *dtb);
+
+/*
+ * staging area for the payload. past the bootloader at the bottom of
+ * RAM, the image header decides its final resting place.
+ */
+#define TB_LOAD_ADDR 0x40200000
+
+static int tb_read_memory(const void *dtb, uintptr_t *base, uint32 *size)
+{
+ const fdt32_t *prop;
+ int len;
+ int node;
+
+ node = fdt_path_offset(dtb, "/memory@40000000");
+ if (node < 0)
+ node = fdt_path_offset(dtb, "/memory");
+ if (node < 0)
+ return -1;
+
+ prop = fdt_getprop(dtb, node, "reg", &len);
+ if (!prop || len < 12)
+ return -1;
+
+ *base = ((uintptr_t)fdt32_to_cpu(prop[0]) << 32) |
+ fdt32_to_cpu(prop[1]);
+ *size = fdt32_to_cpu(prop[2]);
+ return 0;
+}
+
+/*
+ * bootargs from chosen. passed straight through, tashaboot does not
+ * rewrite the kernel command line.
+ */
+static const char *tb_get_cmdline(const void *dtb)
+{
+ const char *cmdline;
+ int len;
+ int node;
+
+ node = fdt_path_offset(dtb, "/chosen");
+ if (node < 0)
+ return NULL;
+
+ cmdline = fdt_getprop(dtb, node, "bootargs", &len);
+ if (!cmdline || len <= 0)
+ return NULL;
+ return cmdline;
+}
+
+/*
+ * tashaboot,bootfile in chosen overrides the default image name. the
+ * host file that semihosting serves is what gets loaded.
+ */
+static const char *tb_get_bootfile(const void *dtb)
+{
+ const char *bootfile;
+ int len;
+ int node;
+
+ node = fdt_path_offset(dtb, "/chosen");
+ if (node < 0)
+ return NULL;
+
+ bootfile = fdt_getprop(dtb, node, "tashaboot,bootfile", &len);
+ if (!bootfile || len <= 0)
+ return NULL;
+ return bootfile;
+}
+
+void tashaboot_main(const void *dtb)
+{
+ struct tb_image img;
+ uintptr_t mem_base = 0;
+ uint32 mem_size = 0;
+ const char *cmdline;
+ const char *bootfile;
+ int ret;
+
+ if (tb_console_init(dtb))
+ return;
+
+ dprintf(ALWAYS, "tashaboot " TB_VERSION "\n");
+ dprintf(ALWAYS, "dtb at %p\n", dtb);
+
+ if (fdt_check_header(dtb)) {
+ dprintf(ALWAYS, "bad dtb header, halting\n");
+ platform_halt();
+ }
+
+ if (tb_read_memory(dtb, &mem_base, &mem_size)) {
+ dprintf(ALWAYS, "no usable memory node, halting\n");
+ platform_halt();
+ }
+ dprintf(ALWAYS, "memory %lx-%lx\n", mem_base, mem_base + mem_size);
+
+ cmdline = tb_get_cmdline(dtb);
+ if (cmdline)
+ dprintf(ALWAYS, "cmdline: %s\n", cmdline);
+
+ /*
+ * semihosting first, it is the qemu development path. a real board
+ * boots from flash or block storage, those loaders come later and
+ * slot in right here.
+ */
+ if (!smh_probe()) {
+ dprintf(ALWAYS, "no semihosting host, halting\n");
+ platform_halt();
+ }
+
+ bootfile = tb_get_bootfile(dtb);
+ if (!bootfile)
+ bootfile = "Image";
+
+ ret = tb_load_semihosting(bootfile, TB_LOAD_ADDR, mem_base, mem_size, &img);
+ if (ret) {
+ dprintf(ALWAYS, "load failed (%d), halting\n", ret);
+ platform_halt();
+ }
+
+ dprintf(ALWAYS, "loaded %llu bytes at %lx, entry %lx\n",
+ (unsigned long long)img.size, img.load, img.ep);
+ dprintf(ALWAYS, "jumping\n");
+
+ tb_boot_linux(img.ep, dtb);
+}