From: Michael Brown Date: Mon, 19 May 2025 23:26:08 +0000 (+0100) Subject: [lkrn] Add basic support for the RISC-V Linux kernel image format X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ecac4a34c7be8d1d81d21fa662460bf162d6a434;p=thirdparty%2Fipxe.git [lkrn] Add basic support for the RISC-V Linux kernel image format The RISC-V and AArch64 bare-metal kernel images share a common header format, and require essentially the same execution environment: loaded close to the start of RAM, entered with paging disabled, and passed a pointer to a flattened device tree that describes the hardware and any boot arguments. Implement basic support for executing bare-metal RISC-V and AArch64 kernel images. The (trivial) AArch64-specific code path is untested since we do not yet have the ability to build for any bare-metal AArch64 platforms. Constructing and passing an initramfs image is not yet supported. Rename the IMAGE_BZIMAGE build configuration option to IMAGE_LKRN, since "bzImage" is specific to x86. To retain backwards compatibility with existing local build configurations, we leave IMAGE_BZIMAGE as the enabled option in config/default/pcbios.h and treat IMAGE_LKRN as a synonym for IMAGE_BZIMAGE when building for x86 BIOS. Signed-off-by: Michael Brown --- diff --git a/src/arch/arm64/include/bits/lkrn.h b/src/arch/arm64/include/bits/lkrn.h new file mode 100644 index 000000000..943464e9b --- /dev/null +++ b/src/arch/arm64/include/bits/lkrn.h @@ -0,0 +1,30 @@ +#ifndef _BITS_LKRN_H +#define _BITS_LKRN_H + +/** @file + * + * Linux kernel image invocation + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** Header magic value */ +#define LKRN_MAGIC_ARCH LKRN_MAGIC_AARCH64 + +/** + * Jump to kernel entry point + * + * @v entry Kernel entry point + * @v fdt Device tree + */ +static inline __attribute__ (( noreturn )) void +lkrn_jump ( physaddr_t entry, physaddr_t fdt ) { + register unsigned long x0 asm ( "x0" ) = fdt; + + __asm__ __volatile__ ( "br %1" + : : "r" ( x0 ), "r" ( entry ) ); + __builtin_unreachable(); +} + +#endif /* _BITS_LKRN_H */ diff --git a/src/arch/riscv/include/bits/lkrn.h b/src/arch/riscv/include/bits/lkrn.h new file mode 100644 index 000000000..d26108647 --- /dev/null +++ b/src/arch/riscv/include/bits/lkrn.h @@ -0,0 +1,34 @@ +#ifndef _BITS_LKRN_H +#define _BITS_LKRN_H + +/** @file + * + * Linux kernel image invocation + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +/** Header magic value */ +#define LKRN_MAGIC_ARCH LKRN_MAGIC_RISCV + +/** + * Jump to kernel entry point + * + * @v entry Kernel entry point + * @v fdt Device tree + */ +static inline __attribute__ (( noreturn )) void +lkrn_jump ( physaddr_t entry, physaddr_t fdt ) { + register unsigned long a0 asm ( "a0" ) = boot_hart; + register unsigned long a1 asm ( "a1" ) = fdt; + + __asm__ __volatile__ ( "call disable_paging\n\t" + "jr %2\n\t" + : : "r" ( a0 ), "r" ( a1 ), "r" ( entry ) ); + __builtin_unreachable(); +} + +#endif /* _BITS_LKRN_H */ diff --git a/src/config/config_pcbios.c b/src/config/config_pcbios.c index 698c68a8d..c8a7e708b 100644 --- a/src/config/config_pcbios.c +++ b/src/config/config_pcbios.c @@ -22,6 +22,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +#include /** @file * @@ -48,3 +49,13 @@ REQUIRE_OBJECT ( vesafb ); #ifdef CONSOLE_INT13 REQUIRE_OBJECT ( int13con ); #endif + +/* + * Drag in all requested image types + * + */ + +/* Allow IMAGE_LKRN to be a synonynm for IMAGE_BZIMAGE */ +#ifdef IMAGE_LKRN +REQUIRE_OBJECT ( bzimage ); +#endif diff --git a/src/config/config_sbi.c b/src/config/config_sbi.c index e69de29bb..901f5d3ab 100644 --- a/src/config/config_sbi.c +++ b/src/config/config_sbi.c @@ -0,0 +1,41 @@ +/* + * 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 + +/** @file + * + * SBI-specific configuration options + * + */ + +PROVIDE_REQUIRING_SYMBOL(); + +/* + * Drag in all requested image types + * + */ + +#ifdef IMAGE_LKRN +REQUIRE_OBJECT ( lkrn ); +#endif diff --git a/src/config/defaults/sbi.h b/src/config/defaults/sbi.h index 4edc0ab01..900d27be4 100644 --- a/src/config/defaults/sbi.h +++ b/src/config/defaults/sbi.h @@ -30,6 +30,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define TIME_NULL #define IMAGE_SCRIPT +#define IMAGE_LKRN +#define IMAGE_GZIP #define REBOOT_CMD #define POWEROFF_CMD diff --git a/src/config/general.h b/src/config/general.h index 1c690328d..683c02ffb 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -117,7 +117,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); //#define IMAGE_MULTIBOOT /* MultiBoot image support */ //#define IMAGE_PXE /* PXE image support */ //#define IMAGE_SCRIPT /* iPXE script image support */ -//#define IMAGE_BZIMAGE /* Linux bzImage image support */ +//#define IMAGE_LKRN /* Linux kernel image support */ //#define IMAGE_COMBOOT /* SYSLINUX COMBOOT image support */ //#define IMAGE_EFI /* EFI image support */ //#define IMAGE_SDI /* SDI image support */ diff --git a/src/image/lkrn.c b/src/image/lkrn.c new file mode 100644 index 000000000..37e1485d0 --- /dev/null +++ b/src/image/lkrn.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2025 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @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 ); + + /* 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 ) { + memmap_dump ( ®ion ); + if ( ! ( region.flags & MEMMAP_FL_MEMORY ) ) + continue; + ctx->ram = region.addr; + 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; +} + +/** + * Load kernel image + * + * @v image Kernel image + * @v ctx Kernel image context + * @ret rc Return status code + */ +static int lkrn_load ( struct image *image, struct lkrn_context *ctx ) { + void *dest; + int rc; + + /* Record entry point */ + ctx->entry = ( ctx->ram + ctx->offset ); + dest = phys_to_virt ( ctx->entry ); + DBGC ( image, "LKRN %s loading to [%#08lx,%#08lx,%#08lx)\n", + image->name, ctx->entry, ( ctx->entry + ctx->filesz ), + ( ctx->entry + ctx->memsz ) ); + + /* Prepare segment */ + if ( ( rc = prep_segment ( dest, ctx->filesz, ctx->memsz ) ) != 0 ) { + DBGC ( image, "LKRN %s could not prepare kernel " + "segment: %s\n", image->name, strerror ( rc ) ); + return rc; + } + + /* Copy to segment */ + memcpy ( dest, image->data, ctx->filesz ); + + return 0; +} + +/** + * Construct device tree + * + * @v image Kernel image + * @v ctx Kernel image context + * @ret rc Return status code + */ +static int lkrn_fdt ( struct image *image, struct lkrn_context *ctx ) { + struct fdt_header *fdt; + void *dest; + size_t len; + int rc; + + /* Build device tree (which may change system memory map) */ + if ( ( rc = fdt_create ( &fdt, image->cmdline ) ) != 0 ) + goto err_create; + len = be32_to_cpu ( fdt->totalsize ); + + /* Place device tree after kernel, rounded up to a page boundary */ + ctx->fdt = ( ( ctx->ram + ctx->offset + ctx->memsz + PAGE_SIZE - 1 ) & + ~( PAGE_SIZE - 1 ) ); + dest = phys_to_virt ( ctx->fdt ); + DBGC ( image, "LKRN %s FDT at [%#08lx,%#08lx)\n", + image->name, ctx->fdt, ( ctx->fdt + len ) ); + + /* + * No further allocations are permitted after this point, + * since we are about to start loading segments. + * + */ + + /* Prepare segment */ + if ( ( rc = prep_segment ( dest, len, len ) ) != 0 ) { + DBGC ( image, "LKRN %s could not prepare FDT segment: %s\n", + image->name, strerror ( rc ) ); + goto err_segment; + } + + /* Copy to segment */ + memcpy ( dest, fdt, len ); + + /* Success */ + rc = 0; + + err_segment: + fdt_remove ( fdt ); + err_create: + return rc; +} + +/** + * Execute kernel image + * + * @v image Kernel image + * @ret rc Return status code + */ +static int lkrn_exec ( struct image *image ) { + struct lkrn_context ctx; + int rc; + + /* Parse header */ + if ( ( rc = lkrn_parse ( image, &ctx ) ) != 0 ) + return rc; + + /* Locate start of RAM */ + if ( ( rc = lkrn_ram ( image, &ctx ) ) != 0 ) + return rc; + + /* Create device tree (which may change system memory map) */ + if ( ( rc = lkrn_fdt ( image, &ctx ) ) != 0 ) + return rc; + + /* Load kernel image (after all allocations are finished) */ + if ( ( rc = lkrn_load ( image, &ctx ) ) != 0 ) + return rc; + + /* 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 */ +} + +/** + * 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, +}; diff --git a/src/include/bits/lkrn.h b/src/include/bits/lkrn.h new file mode 100644 index 000000000..58aee73a2 --- /dev/null +++ b/src/include/bits/lkrn.h @@ -0,0 +1,17 @@ +#ifndef _BITS_LKRN_H +#define _BITS_LKRN_H + +/** @file + * + * Dummy architecture-specific Linux kernel image invocation + * + * This file is included only if the architecture does not provide its + * own version of this file. + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#define LKRN_MAGIC_ARCH 0 + +#endif /* _BITS_LKRN_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 37e36c720..22215699b 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -328,6 +328,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_zlib ( ERRFILE_IMAGE | 0x000b0000 ) #define ERRFILE_gzip ( ERRFILE_IMAGE | 0x000c0000 ) #define ERRFILE_efi_siglist ( ERRFILE_IMAGE | 0x000d0000 ) +#define ERRFILE_lkrn ( ERRFILE_IMAGE | 0x000e0000 ) #define ERRFILE_asn1 ( ERRFILE_OTHER | 0x00000000 ) #define ERRFILE_chap ( ERRFILE_OTHER | 0x00010000 ) diff --git a/src/include/ipxe/lkrn.h b/src/include/ipxe/lkrn.h new file mode 100644 index 000000000..c3a781b44 --- /dev/null +++ b/src/include/ipxe/lkrn.h @@ -0,0 +1,69 @@ +#ifndef _IPXE_LKRN_H +#define _IPXE_LKRN_H + +/** @file + * + * Linux kernel images + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +/** Kernel image header */ +struct lkrn_header { + /** Executable code */ + uint32_t code[2]; + /** Image load offset */ + uint64_t text_offset; + /** Image size */ + uint64_t image_size; + /** Flags */ + uint64_t flags; + /** Reserved */ + uint8_t reserved_a[24]; + /** Magic */ + uint32_t magic; + /** Reserved */ + uint8_t reserved_b[4]; +} __attribute__ (( packed )); + +/** Kernel magic value */ +#define LKRN_MAGIC( a, b, c, d ) \ + ( ( (a) << 0 ) | ( (b) << 8 ) | ( (c) << 16 ) | ( (d) << 24 ) ) + +/** Kernel magic value for AArch64 */ +#define LKRN_MAGIC_AARCH64 LKRN_MAGIC ( 'A', 'R', 'M', 0x64 ) + +/** Kernel magic value for RISC-V */ +#define LKRN_MAGIC_RISCV LKRN_MAGIC ( 'R', 'S', 'C', 0x05 ) + +/** Kernel image context */ +struct lkrn_context { + /** Load offset */ + size_t offset; + /** File size */ + size_t filesz; + /** Memory size */ + size_t memsz; + + /** Start of RAM */ + physaddr_t ram; + /** Entry point */ + physaddr_t entry; + /** Device tree */ + physaddr_t fdt; +}; + +#include + +/** + * Jump to kernel entry point + * + * @v entry Kernel entry point + * @v fdt Device tree + */ +void lkrn_jump ( physaddr_t entry, physaddr_t fdt ); + +#endif /* _IPXE_LKRN_H */