| /* |
| * 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, |
| }; |