m1n1/src/payload.c
Hector Martin 7dfe24ee2c Rework kboot/chainload flow to shut down before calling the next stage
Next stage boots now exit back to main() after replying to the proxy
command, allowing shutdown functions to be called. Introduces a new
P_VECTOR proxy op, distinct from P_CALL. The Python side is reworked
to remain compatible with older versions that do not support this.

Signed-off-by: Hector Martin <marcan@marcan.st>
2021-04-17 18:12:59 +09:00

188 lines
5.5 KiB
C

/* SPDX-License-Identifier: MIT */
#include "payload.h"
#include "assert.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;
}
}
int 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 -1;
}
return kboot_boot(kernel);
}
return -1;
}