]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[lkrn] Add basic support for the RISC-V Linux kernel image format
authorMichael Brown <mcb30@ipxe.org>
Mon, 19 May 2025 23:26:08 +0000 (00:26 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 20 May 2025 12:08:38 +0000 (13:08 +0100)
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 <mcb30@ipxe.org>
src/arch/arm64/include/bits/lkrn.h [new file with mode: 0644]
src/arch/riscv/include/bits/lkrn.h [new file with mode: 0644]
src/config/config_pcbios.c
src/config/config_sbi.c
src/config/defaults/sbi.h
src/config/general.h
src/image/lkrn.c [new file with mode: 0644]
src/include/bits/lkrn.h [new file with mode: 0644]
src/include/ipxe/errfile.h
src/include/ipxe/lkrn.h [new file with mode: 0644]

diff --git a/src/arch/arm64/include/bits/lkrn.h b/src/arch/arm64/include/bits/lkrn.h
new file mode 100644 (file)
index 0000000..943464e
--- /dev/null
@@ -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 (file)
index 0000000..d261086
--- /dev/null
@@ -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 <ipxe/hart.h>
+
+/** 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 */
index 698c68a8d115952adda0ecd2dd7b3df4d7206d9c..c8a7e708b5c6ad2a6ea5de871d0dad045920d7f4 100644 (file)
@@ -22,6 +22,7 @@
 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <config/console.h>
+#include <config/general.h>
 
 /** @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
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..901f5d3ab71ff40b2077cd7fde358d77b236caad 100644 (file)
@@ -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 <config/general.h>
+
+/** @file
+ *
+ * SBI-specific configuration options
+ *
+ */
+
+PROVIDE_REQUIRING_SYMBOL();
+
+/*
+ * Drag in all requested image types
+ *
+ */
+
+#ifdef IMAGE_LKRN
+REQUIRE_OBJECT ( lkrn );
+#endif
index 4edc0ab015e0e543780a1870f8394b20df0e9063..900d27be448b4e4df1fda10334fe089180cdddc2 100644 (file)
@@ -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
index 1c690328d29ec79cd05a63f7e521683a7d50fb45..683c02ffbe44746fc9d27260998dd54a348e25f1 100644 (file)
@@ -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 (file)
index 0000000..37e1485
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * 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/io.h>
+#include <ipxe/fdt.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 );
+
+       /* 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 ( &region, 0 ) {
+               memmap_dump ( &region );
+               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 (file)
index 0000000..58aee73
--- /dev/null
@@ -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 */
index 37e36c720d9485cdd5b304b4bb65f18474f9f9d9..22215699b3fc30b5b36f766394369c57bcb24c19 100644 (file)
@@ -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 (file)
index 0000000..c3a781b
--- /dev/null
@@ -0,0 +1,69 @@
+#ifndef _IPXE_LKRN_H
+#define _IPXE_LKRN_H
+
+/** @file
+ *
+ * Linux kernel images
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+
+/** 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 <bits/lkrn.h>
+
+/**
+ * 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 */