From 5ac8f14ccb078d136db17716eff3a85685e55f9b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 27 Jan 2026 18:09:10 +0100 Subject: [PATCH] kernel: mtdsplit: create executable prolog splitter The problem is the following: we have three fixed partitions in a RedBoot partition for kernel, initrd and rootfs. On the surface this looks good. But we have little flash and want to use it efficiently. We want to use the OpenWrt "firmware" partition scheme where the kernel, initramfs and sqashfs+jffs2 rootfs is appended, leaving maximum space for a writeable rootfs. To do this we will override the existing RedBoot partition table with one that merges the three separate partitions into one "firmware" partition. RedBoot is still booting the system. It still needs to read the first two parts "as if" these were the kernel and initrd. This works fine, because the kernel still comes first. We already have hacks in place to merge the two kernel and initrd into one binary image and execute it. This is done by prepending a "prolog" to the kernel that does the necessary copying in memory and then jumps to execute the kernel. Since this "prolog" copying routine is just 92 bytes but has 512 bytes allocated, we can trivially create a firmware format that can be used for splitting the image into kernel and rootfs using a tagging scheme that can be done directly by scripting so we don't need any special binary programs. This splitter implements that idea. This will be used on the Gemini platform and was tested on the Raidsonic IB-4220-B. Link: https://github.com/openwrt/openwrt/pull/21820 Signed-off-by: Linus Walleij --- .../files/drivers/mtd/mtdsplit/Kconfig | 5 + .../files/drivers/mtd/mtdsplit/Makefile | 1 + .../mtd/mtdsplit/mtdsplit_owrt_prolog.c | 134 ++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_owrt_prolog.c diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig index 396becf160..268eaf163e 100644 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig @@ -115,3 +115,8 @@ config MTD_SPLIT_MSTC_BOOT bool "MSTC bootnum-based parser" depends on MTD_SPLIT_SUPPORT select MTD_SPLIT + +config MTD_SPLIT_OPENWRT_PROLOG + bool "OpenWrt executable prolog-based parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile index b85ce5d21f..435b475d59 100644 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o obj-$(CONFIG_MTD_SPLIT_ELF_FW) += mtdsplit_elf.o obj-$(CONFIG_MTD_SPLIT_H3C_VFS) += mtdsplit_h3c_vfs.o obj-$(CONFIG_MTD_SPLIT_MSTC_BOOT) += mtdsplit_mstc_boot.o +obj-$(CONFIG_MTD_SPLIT_OPENWRT_PROLOG) += mtdsplit_owrt_prolog.o diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_owrt_prolog.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_owrt_prolog.c new file mode 100644 index 0000000000..78c9c4dae0 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_owrt_prolog.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2026 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * The idea about the "executable prolog" is simple: this is an assembly + * prolog that OpenWrt adds in front of the kernel to move it around in + * memory. Since it is 512 bytes, we can also store the kernel file size + * in the prolog and use it for splitting the partition. + * + * This format has been designed to be easy to create an executable prolog + * with a parseable size from bash scripts: + * + * cat executable_prolog > image.bin + * echo "OPENWRT-PROLOG-512" >> image.bin + * stat -c %s zImage >> image.image + * dd if=image.bin of=image.new bs=512 + * mv image.new image.bin + * + * will create a 512 bytes executable prolog with the needed tag somewhere + * in the header area. (The executable_prolog needs to be small.) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define OWRT_PROLOG_SIZE 512 +#define OWRT_PROLOG_MAX_OVERHEAD 32 +#define OWRT_PROLOG_MAGIC "OPENWRT-PROLOG-512" +#define OWRT_PROLOG_NR_PARTS 2 + +static int mtdsplit_parse_owrt_prolog(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + char buf[OWRT_PROLOG_SIZE]; + size_t retlen; + unsigned long kernel_size, rootfs_offset; + int ret; + int i; + + ret = mtd_read(master, 0, sizeof(buf), &retlen, buf); + if (ret) + return ret; + + if (retlen != sizeof(buf)) + return -EIO; + + for (i = 0; i < (OWRT_PROLOG_SIZE - OWRT_PROLOG_MAX_OVERHEAD); i++) { + if (!strncmp(OWRT_PROLOG_MAGIC, buf + i, strlen(OWRT_PROLOG_MAGIC))) + break; + } + if (i == (OWRT_PROLOG_SIZE - OWRT_PROLOG_MAX_OVERHEAD)) { + pr_err("%s: no OpenWrt prolog found\n", master->name); + return -EINVAL; + } + + pr_debug("%s: OpenWrt prolog found at offset %d\n", master->name, i); + + i += strlen(OWRT_PROLOG_MAGIC); + i++; /* Skip linefeed after the magic */ + + ret = kstrtol(buf + i, 10, &kernel_size); + if (ret) + return ret; + + /* + * From the MTD point of view, the prolog is part of + * the kernel. + */ + kernel_size += OWRT_PROLOG_SIZE; + pr_debug("%s: OpenWrt prolog kernel size %08lx\n", + master->name, kernel_size); + + /* rootfs starts at the next 0x20000 (128k) boundary: */ + rootfs_offset = round_up(kernel_size, 0x20000); + + pr_debug("%s: OpenWrt prolog rootfs offset %08lx\n", + master->name, rootfs_offset); + + if (rootfs_offset >= master->size) + return -EINVAL; + + ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (ret) + return ret; + + parts = kzalloc(OWRT_PROLOG_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return OWRT_PROLOG_NR_PARTS; +} + +static const struct of_device_id mtdsplit_owrt_prolog_of_match_table[] = { + { .compatible = "openwrt,executable-prolog" }, + {}, +}; + +static struct mtd_part_parser mtdsplit_owrt_prolog_parser = { + .owner = THIS_MODULE, + .name = "executable-prolog", + .of_match_table = mtdsplit_owrt_prolog_of_match_table, + .parse_fn = mtdsplit_parse_owrt_prolog, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_owrt_prolog_init(void) +{ + register_mtd_parser(&mtdsplit_owrt_prolog_parser); + + return 0; +} + +subsys_initcall(mtdsplit_owrt_prolog_init);