diff options
Diffstat (limited to 'common')
| -rw-r--r-- | common/console.c | 191 | ||||
| -rw-r--r-- | common/image.c | 66 | ||||
| -rw-r--r-- | common/load.c | 70 | ||||
| -rw-r--r-- | common/main.c | 164 |
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); +} |
