From 5dbc82a9ee3705ef1133dea8811f7456c644103e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 6 Feb 2021 16:35:24 +0900 Subject: [PATCH] payload: add support for booting in-line payloads Usage: $ cat m1n1.macho Image.gz apple-j274.dtb initramfs.gz \ > m1n1-payload.macho That's it. Signed-off-by: Hector Martin --- Makefile | 4 +- src/kboot.h | 14 ++++ src/main.c | 7 ++ src/payload.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/payload.h | 8 +++ 5 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/payload.c create mode 100644 src/payload.h diff --git a/Makefile b/Makefile index e037f9b4..9ebd5920 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,8 @@ LIBFDT_OBJECTS := $(patsubst %,libfdt/%, \ fdt_wip.o fdt.o) OBJECTS := adt.o bootlogo_128.o bootlogo_256.o chickens.o exception.o exception_asm.o fb.o \ - heapblock.o kboot.o main.o memory.o memory_asm.o proxy.o smp.o start.o startup.o string.o \ - uart.o uartproxy.o utils.o utils_asm.o vsprintf.o wdt.o $(MINILZLIB_OBJECTS) \ + heapblock.o kboot.o main.o memory.o memory_asm.o payload.o proxy.o smp.o start.o startup.o \ + string.o uart.o uartproxy.o utils.o utils_asm.o vsprintf.o wdt.o $(MINILZLIB_OBJECTS) \ $(TINF_OBJECTS) $(DLMALLOC_OBJECTS) $(LIBFDT_OBJECTS) DTS := apple-j274.dts diff --git a/src/kboot.h b/src/kboot.h index 62ee460e..744fe751 100644 --- a/src/kboot.h +++ b/src/kboot.h @@ -3,6 +3,20 @@ #ifndef KBOOT_H #define KBOOT_H +#include "types.h" + +struct kernel_header { + u32 code[2]; /* Executable code */ + u64 text_offset; /* Image load offset, little endian */ + u64 image_size; /* Effective Image size, little endian */ + u64 flags; /* kernel flags, little endian */ + u64 res2; /* reserved */ + u64 res3; /* reserved */ + u64 res4; /* reserved */ + u32 magic; /* Magic number, little endian, "ARM\x64" */ + u32 res5; /* reserved (used for PE COFF offset) */ +}; + void kboot_set_initrd(void *start, size_t size); void kboot_set_bootargs(const char *ba); int kboot_prepare_dt(void *fdt); diff --git a/src/main.c b/src/main.c index 8841b993..04934968 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include "fb.h" #include "heapblock.h" #include "memory.h" +#include "payload.h" #include "smp.h" #include "string.h" #include "uart.h" @@ -58,6 +59,12 @@ void m1n1_main(void) print_info(); wdt_disable(); + printf("Checking for payloads...\n"); + + payload_run(); + + printf("No valid payload found\n"); + printf("Running proxy...\n"); uartproxy_run(); diff --git a/src/payload.c b/src/payload.c new file mode 100644 index 00000000..2dd3096f --- /dev/null +++ b/src/payload.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: MIT */ + +#include + +#include "payload.h" +#include "heapblock.h" +#include "kboot.h" +#include "smp.h" +#include "utils.h" + +#include "libfdt/libfdt.h" +#include "minilzlib/minlzma.h" +#include "tinf/tinf.h" + +// Kernels must be 2MB aligned +#define KERNEL_ALIGN (2 << 20) + +const u8 gz_magic[] = {0x1f, 0x8b}; +const u8 xz_magic[] = {0xfd, '7', 'z', 'X', 'Z', 0x00}; +const u8 fdt_magic[] = {0xd0, 0x0d, 0xfe, 0xed}; +const u8 kernel_magic[] = {'A', 'R', 'M', 0x64}; // at 0x38 +const u8 cpio_magic[] = {'0', '7', '0', '7', '0'}; // '1' or '2' next +const u8 empty[] = {0, 0, 0, 0}; + +struct kernel_header *kernel = NULL; +void *fdt = NULL; + +static void *load_one_payload(void *start, size_t size); + +static void finalize_uncompression(void *dest, size_t dest_len) +{ + // Actually reserve the space. malloc is safe after this, but... + assert(dest == heapblock_alloc_aligned(dest_len, KERNEL_ALIGN)); + + void *end = ((u8 *)dest) + dest_len; + void *next = load_one_payload(dest, dest_len); + assert(!next || next >= dest); + + // If the payload needs padding, we need to reserve more, so it better have not used + // malloc either. + if (next > end) { + // Explicitly *un*aligned or it'll fail this assert, since 64b alignment is the default + assert(end == heapblock_alloc_aligned((u8 *)next - (u8 *)end, 1)); + } +} + +static void *decompress_gz(void *p, size_t size) +{ + unsigned int source_len = size, dest_len = 1 << 30; // 1 GiB should be enough hopefully + + // Start at the end of the heap area, no allocation yet. The following code must not use + // malloc or heapblock, until finalize_uncompression is called. + void *dest = heapblock_alloc_aligned(0, KERNEL_ALIGN); + + printf("Uncompressing... "); + int ret = tinf_gzip_uncompress(dest, &dest_len, p, &source_len); + + if (ret != TINF_OK) { + printf("Error %d\n", ret); + return NULL; + } + + printf("%d bytes uncompressed to %d bytes\n", source_len, dest_len); + + finalize_uncompression(dest, dest_len); + + return ((u8 *)p) + source_len; +} + +static void *decompress_xz(void *p, size_t size) +{ + uint32_t source_len = size, dest_len = 1 << 30; // 1 GiB should be enough hopefully + + // Start at the end of the heap area, no allocation yet. The following code must not use + // malloc or heapblock, until finalize_uncompression is called. + void *dest = heapblock_alloc_aligned(0, KERNEL_ALIGN); + + printf("Uncompressing... "); + int ret = XzDecode(p, &source_len, dest, &dest_len); + + if (!ret) { + printf("XZ decode failed\n"); + return NULL; + } + + printf("%d bytes uncompressed to %d bytes\n", source_len, dest_len); + + finalize_uncompression(dest, dest_len); + + return ((u8 *)p) + source_len; +} + +static void *load_fdt(void *p, size_t size) +{ + fdt = p; + assert(!size || size == fdt_totalsize(fdt)); + return ((u8 *)p) + fdt_totalsize(fdt); +} + +static void *load_cpio(void *p, size_t size) +{ + if (!size) { + // We could handle this, but who uses uncompressed initramfs? + printf("Uncompressed cpio archives not supported\n"); + return NULL; + } + + kboot_set_initrd(p, size); + return ((u8 *)p) + size; +} + +static void *load_kernel(void *p, size_t size) +{ + kernel = p; + + assert(size <= kernel->image_size); + + // If this is an in-line kernel, it's probably not aligned, so we need to make a copy + if (((u64)kernel) & (KERNEL_ALIGN - 1)) { + void *new_addr = heapblock_alloc_aligned(kernel->image_size, KERNEL_ALIGN); + memcpy(new_addr, kernel, size ? size : kernel->image_size); + kernel = new_addr; + } + + /* + * Kernel blobs unfortunately do not have an accurate file size header, so + * this will fail for in-line payloads. However, conversely, this is required for + * compressed payloads, in order to allocate padding that the kernel needs, which will be + * beyond the end of the compressed data. So if we know the input size, tell the caller + * about the true image size; otherwise don't. + */ + if (size) { + return ((u8 *)p) + kernel->image_size; + } else { + return NULL; + } +} + +static void *load_one_payload(void *start, size_t size) +{ + u8 *p = start; + + if (!start) + return NULL; + + if (!memcmp(p, gz_magic, sizeof gz_magic)) { + printf("Found a gzip compressed payload at %p\n", p); + return decompress_gz(p, size); + } else if (!memcmp(p, xz_magic, sizeof xz_magic)) { + printf("Found an XZ compressed payload at %p\n", p); + return decompress_xz(p, size); + } else if (!memcmp(p, fdt_magic, sizeof fdt_magic)) { + printf("Found a devicetree at %p\n", p); + return load_fdt(p, size); + } else if (!memcmp(p, cpio_magic, sizeof cpio_magic)) { + printf("Found a cpio initramfs at %p\n", p); + return load_cpio(p, size); + } else if (!memcmp(p + 0x38, kernel_magic, sizeof kernel_magic)) { + printf("Found a kernel at %p\n", p); + return load_kernel(p, size); + } else if (!memcmp(p, empty, sizeof empty)) { + printf("No more payloads at %p\n", p); + return NULL; + } else { + printf("Unknown payload at %p (magic: %02x%02x%02x%02x)\n", p, p[0], p[1], p[2], p[3]); + return NULL; + } +} + +void payload_run(void) +{ + void *p = _payload_start; + + while (p) + p = load_one_payload(p, 0); + + if (kernel && fdt) { + smp_start_secondaries(); + + if (kboot_prepare_dt(fdt)) { + printf("Failed to prepare FDT!"); + return; + } + + kboot_boot(kernel); + printf("Failed to boot kernel!"); + } +} diff --git a/src/payload.h b/src/payload.h new file mode 100644 index 00000000..4781046f --- /dev/null +++ b/src/payload.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef __PAYLOAD_H__ +#define __PAYLOAD_H__ + +void payload_run(void); + +#endif