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 <linusw@kernel.org>
This commit is contained in:
Linus Walleij 2026-01-27 18:09:10 +01:00
parent 3f0de6a28d
commit 5ac8f14ccb
3 changed files with 140 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,134 @@
/*
* Copyright (C) 2026 Linus Walleij <linusw@kernel.org>
*
* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#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);