| /* | |
| * 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 ); | |
| FILE_SECBOOT ( FORBIDDEN ); | |
| #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, | |
| }; |