diff options
Diffstat (limited to 'src/image/lkrn.c')
| -rw-r--r-- | src/image/lkrn.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/src/image/lkrn.c b/src/image/lkrn.c new file mode 100644 index 000000000..a2044cb82 --- /dev/null +++ b/src/image/lkrn.c @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/image.h> +#include <ipxe/memmap.h> +#include <ipxe/uaccess.h> +#include <ipxe/segment.h> +#include <ipxe/initrd.h> +#include <ipxe/io.h> +#include <ipxe/fdt.h> +#include <ipxe/init.h> +#include <ipxe/lkrn.h> + +/** @file + * + * Linux kernel image format + * + */ + +/** + * Parse kernel image + * + * @v image Kernel image + * @v ctx Kernel image context + * @ret rc Return status code + */ +static int lkrn_parse ( struct image *image, struct lkrn_context *ctx ) { + const struct lkrn_header *hdr; + + /* Initialise context */ + memset ( ctx, 0, sizeof ( *ctx ) ); + + /* Read image header */ + if ( image->len < sizeof ( *hdr ) ) { + DBGC ( image, "LKRN %s too short for header\n", image->name ); + return -ENOEXEC; + } + hdr = image->data; + + /* Check magic value */ + if ( hdr->magic != cpu_to_le32 ( LKRN_MAGIC_ARCH ) ) { + DBGC ( image, "LKRN %s bad magic value %#08x\n", + image->name, le32_to_cpu ( hdr->magic ) ); + return -ENOEXEC; + } + + /* Record load offset */ + ctx->offset = le64_to_cpu ( hdr->text_offset ); + if ( ctx->offset & ( ctx->offset - 1 ) ) { + DBGC ( image, "LKRN %s offset %#zx is not a power of two\n", + image->name, ctx->offset ); + return -ENOEXEC; + } + + /* Record and check image size */ + ctx->filesz = image->len; + ctx->memsz = le64_to_cpu ( hdr->image_size ); + if ( ctx->filesz > ctx->memsz ) { + DBGC ( image, "LKRN %s invalid image size %#zx/%#zx\n", + image->name, ctx->filesz, ctx->memsz ); + return -ENOEXEC; + } + + return 0; +} + +/** + * Locate start of RAM + * + * @v image Kernel image + * @v ctx Kernel image context + * @ret rc Return status code + */ +static int lkrn_ram ( struct image *image, struct lkrn_context *ctx ) { + struct memmap_region region; + + /* Locate start of RAM */ + for_each_memmap ( ®ion, 0 ) { + DBGC_MEMMAP ( image, ®ion ); + if ( ! ( region.flags & MEMMAP_FL_MEMORY ) ) + continue; + ctx->ram = region.min; + DBGC ( image, "LKRN %s RAM starts at %#08lx\n", + image->name, ctx->ram ); + return 0; + } + + DBGC ( image, "LKRN %s found no RAM\n", image->name ); + return -ENOTSUP; +} + +/** + * Execute kernel image + * + * @v image Kernel image + * @ret rc Return status code + */ +static int lkrn_exec ( struct image *image ) { + static struct image fdtimg = { + .refcnt = REF_INIT ( free_image ), + .name = "<FDT>", + .flags = ( IMAGE_STATIC | IMAGE_STATIC_NAME ), + }; + struct lkrn_context ctx; + struct memmap_region region; + struct fdt_header *fdt; + size_t initrdsz; + size_t totalsz; + void *dest; + int rc; + + /* Parse header */ + if ( ( rc = lkrn_parse ( image, &ctx ) ) != 0 ) + goto err_parse; + + /* Locate start of RAM */ + if ( ( rc = lkrn_ram ( image, &ctx ) ) != 0 ) + goto err_ram; + + /* Place kernel at specified address from start of RAM */ + ctx.entry = ( ctx.ram + ctx.offset ); + DBGC ( image, "LKRN %s loading to [%#08lx,%#08lx,%#08lx)\n", + image->name, ctx.entry, ( ctx.entry + ctx.filesz ), + ( ctx.entry + ctx.memsz ) ); + + /* Place initrd after kernel, aligned to the kernel's image offset */ + ctx.initrd = ( ctx.ram + initrd_align ( ctx.offset + ctx.memsz ) ); + ctx.initrd = ( ( ctx.initrd + ctx.offset - 1 ) & ~( ctx.offset - 1 ) ); + initrdsz = initrd_len(); + if ( initrdsz ) { + DBGC ( image, "LKRN %s initrd at [%#08lx,%#08lx)\n", + image->name, ctx.initrd, ( ctx.initrd + initrdsz ) ); + } + + /* Place device tree after initrd */ + ctx.fdt = ( ctx.initrd + initrd_align ( initrdsz ) ); + + /* Construct device tree and post-initrd image */ + if ( ( rc = fdt_create ( &fdt, image->cmdline, ctx.initrd, + initrdsz ) ) != 0 ) { + goto err_fdt; + } + fdtimg.data = fdt; + fdtimg.len = be32_to_cpu ( fdt->totalsize ); + list_add_tail ( &fdtimg.list, &images ); + DBGC ( image, "LKRN %s FDT at [%08lx,%08lx)\n", + image->name, ctx.fdt, ( ctx.fdt + fdtimg.len ) ); + + /* Find post-reshuffle region */ + if ( ( rc = initrd_region ( initrdsz, ®ion ) ) != 0 ) { + DBGC ( image, "LKRN %s no available region: %s\n", + image->name, strerror ( rc ) ); + goto err_region; + } + + /* Check that everything can be placed at its target addresses */ + totalsz = ( ctx.fdt + fdtimg.len - ctx.ram ); + if ( ( ctx.entry >= region.min ) && + ( ( ctx.offset + totalsz ) <= memmap_size ( ®ion ) ) ) { + /* Target addresses are within the reshuffle region */ + DBGC ( image, "LKRN %s fits within reshuffle region\n", + image->name ); + } else { + /* Target addresses are outside the reshuffle region */ + if ( ( rc = prep_segment ( phys_to_virt ( ctx.entry ), + totalsz, totalsz ) ) != 0 ) { + DBGC ( image, "LKRN %s could not prepare segment: " + "%s\n", image->name, strerror ( rc ) ); + goto err_segment; + } + } + + /* This is the point of no return: we are about to reshuffle + * and thereby destroy the external heap. No errors are + * allowed to occur after this point. + */ + + /* Shut down ready for boot */ + shutdown_boot(); + + /* Prepend kernel to reshuffle list, reshuffle, and remove kernel */ + list_add ( &image->list, &images ); + initrd_reshuffle(); + list_del ( &image->list ); + + /* Load kernel to entry point and zero bss */ + dest = phys_to_virt ( ctx.entry ); + memmove ( dest, image->data, ctx.filesz ); + memset ( ( dest + ctx.filesz ), 0, ( ctx.memsz - ctx.filesz ) ); + + /* Load initrds and device tree */ + dest = phys_to_virt ( ctx.initrd ); + initrd_load_all ( dest ); + + /* Jump to kernel entry point */ + DBGC ( image, "LKRN %s jumping to kernel at %#08lx\n", + image->name, ctx.entry ); + lkrn_jump ( ctx.entry, ctx.fdt ); + + /* There is no way for the image to return, since we provide + * no return address. + */ + assert ( 0 ); + + return -ECANCELED; /* -EIMPOSSIBLE */ + + err_segment: + err_region: + list_del ( &fdtimg.list ); + fdt_remove ( fdt ); + err_fdt: + err_ram: + err_parse: + return rc; +} + +/** + * Probe kernel image + * + * @v image Kernel image + * @ret rc Return status code + */ +static int lkrn_probe ( struct image *image ) { + struct lkrn_context ctx; + int rc; + + /* Parse header */ + if ( ( rc = lkrn_parse ( image, &ctx ) ) != 0 ) + return rc; + + DBGC ( image, "LKRN %s is a Linux kernel\n", image->name ); + return 0; +} + +/** Linux kernel image type */ +struct image_type lkrn_image_type __image_type ( PROBE_NORMAL ) = { + .name = "lkrn", + .probe = lkrn_probe, + .exec = lkrn_exec, +}; + +/** + * Parse compressed kernel image + * + * @v image Compressed kernel image + * @v zctx Compressed kernel image context + * @ret rc Return status code + */ +static int zimg_parse ( struct image *image, struct zimg_context *zctx ) { + const struct zimg_header *zhdr; + + /* Initialise context */ + memset ( zctx, 0, sizeof ( *zctx ) ); + + /* Parse header */ + if ( image->len < sizeof ( *zhdr ) ) { + DBGC ( image, "ZIMG %s too short for header\n", + image->name ); + return -ENOEXEC; + } + zhdr = image->data; + + /* Check magic value */ + if ( zhdr->magic != cpu_to_le32 ( ZIMG_MAGIC ) ) { + DBGC ( image, "ZIMG %s bad magic value %#08x\n", + image->name, le32_to_cpu ( zhdr->magic ) ); + return -ENOEXEC; + } + + /* Record and check offset and length */ + zctx->offset = le32_to_cpu ( zhdr->offset ); + zctx->len = le32_to_cpu ( zhdr->len ); + if ( ( zctx->offset > image->len ) || + ( zctx->len > ( image->len - zctx->offset ) ) ) { + DBGC ( image, "ZIMG %s bad range [+%#zx,+%#zx)/%#zx\n", + image->name, zctx->offset, + (zctx->offset + zctx->len ), image->len ); + return -ENOEXEC; + } + + /* Record compression type */ + zctx->type.raw = zhdr->type; + + return 0; +} + +/** + * Extract compresed kernel image + * + * @v image Compressed kernel image + * @v extracted Extracted image + * @ret rc Return status code + */ +static int zimg_extract ( struct image *image, struct image *extracted ) { + struct zimg_context zctx; + const void *payload; + int rc; + + /* Parse header */ + if ( ( rc = zimg_parse ( image, &zctx ) ) != 0 ) + return rc; + DBGC ( image, "ZIMG %s has %s-compressed payload at [+%#zx,+%#zx)\n", + image->name, zctx.type.string, zctx.offset, + ( zctx.offset + zctx.len ) ); + + /* Extract compressed payload */ + payload = ( image->data + zctx.offset ); + if ( ( rc = image_set_data ( extracted, payload, zctx.len ) ) != 0 ) { + DBGC ( image, "ZIMG %s could not extract: %s\n", + image->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Probe compressed kernel image + * + * @v image Compressed kernel image + * @ret rc Return status code + */ +static int zimg_probe ( struct image *image ) { + struct zimg_context zctx; + int rc; + + /* Parse header */ + if ( ( rc = zimg_parse ( image, &zctx ) ) != 0 ) + return rc; + + DBGC ( image, "ZIMG %s is a %s-compressed Linux kernel\n", + image->name, zctx.type.string ); + return 0; +} + +/** Linux kernel compressed image type */ +struct image_type zimg_image_type __image_type ( PROBE_NORMAL ) = { + .name = "zimg", + .probe = zimg_probe, + .extract = zimg_extract, + .exec = image_extract_exec, +}; |
