summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMichael Brown2025-05-20 01:26:08 +0200
committerMichael Brown2025-05-20 14:08:38 +0200
commitecac4a34c7be8d1d81d21fa662460bf162d6a434 (patch)
treea4aa0d13bb2a580515896debda75b83ae780ef47 /src
parent[bios] Use generic external heap based on the system memory map (diff)
downloadipxe-ecac4a34c7be8d1d81d21fa662460bf162d6a434.tar.gz
ipxe-ecac4a34c7be8d1d81d21fa662460bf162d6a434.tar.xz
ipxe-ecac4a34c7be8d1d81d21fa662460bf162d6a434.zip
[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 <mcb30@ipxe.org>
Diffstat (limited to 'src')
-rw-r--r--src/arch/arm64/include/bits/lkrn.h30
-rw-r--r--src/arch/riscv/include/bits/lkrn.h34
-rw-r--r--src/config/config_pcbios.c11
-rw-r--r--src/config/config_sbi.c41
-rw-r--r--src/config/defaults/sbi.h2
-rw-r--r--src/config/general.h2
-rw-r--r--src/image/lkrn.c254
-rw-r--r--src/include/bits/lkrn.h17
-rw-r--r--src/include/ipxe/errfile.h1
-rw-r--r--src/include/ipxe/lkrn.h69
10 files changed, 460 insertions, 1 deletions
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 <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 */
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 <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
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 <config/general.h>
+
+/** @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 <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
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 <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 */