From: INAGAKI Hiroshi Date: Mon, 28 Apr 2025 09:44:29 +0000 (+0900) Subject: generic: add mstc-boot mtdsplit parser X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ddf7d63e946a65d510c4ec0338aea49dc45a976c;p=thirdparty%2Fopenwrt.git generic: add mstc-boot mtdsplit parser Add new mtdsplit parser "mstc-boot" for the devices manufactured by MSTC (Mitra Star Technology Corp.). This is necessary to handle dual-boot on those devices. This parser splits kernel+rootfs or only rootfs(or UBI) based on the image in the firmware partition or pre-defined partitions in dts, and "bootnum" value in the "persist" (or "working") partition. Note: "bootnum" is used for switching active firmware partitions on the devices manufactured by MSTC and '1' or '2' are used on most devices. But some devices use '0' or '1'. (example: I-O DATA WN-DEAX1800GR) Sequence: 1. obtain "bootnum" value 2. child nodes exsist (regardless of bootnum) -> fixed partitions (active parts : without bootnum (ex.: "kernel", "rootfs") inactive parts: with bootnum (ex.: "kernel2", "rootfs2")) 3. current partition is active (dt bootnum == mtd bootnum) -> image-based partitions Device Tree: - common - mstc,bootnum : "bootnum" value for the mtd partition (0/1/2) - mstc,persist : phandle of "persist" partition containing "bootnum" value - fixed partitions - #address-cells: indicate cell count of address of child nodes (1) - #size-cells : indicate cell count of size of child nodes (1) - (child nodes) : define the child partitions - reg : define the offset and size - label-base : define the base name of the partition - (example) : base:"kernel"->"kernel"(active)/"kernel2"(inactive) example: partition@3c0000 { compatible = "mstc,boot"; reg = <0x3c0000 0x3240000>; label = "firmware1"; mstc,bootnum = <1>; mstc,persist = <&mtd_persist>; #address-cells = <1>; #size-cells = <1>; partition@0 { reg = <0x0 0x800000>; label-base = "kernel"; }; partition@800000 { reg = <0x800000 0x2a40000>; label-base = "ubi"; }; }; - image-based partitions (no additional properties) example: partition@5a0000 { compatible = "mstc,boot"; label = "firmware1"; reg = <0x5a0000 0x3200000>; mstc,bootnum = <1>; mstc,persist = <&mtd_persist>; }; Signed-off-by: INAGAKI Hiroshi Link: https://github.com/openwrt/openwrt/pull/18976 Signed-off-by: Hauke Mehrtens --- diff --git a/target/linux/generic/config-6.12 b/target/linux/generic/config-6.12 index dbf0d5b3aa6..53d799bc19b 100644 --- a/target/linux/generic/config-6.12 +++ b/target/linux/generic/config-6.12 @@ -3915,6 +3915,7 @@ CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" # CONFIG_MTD_SPLIT_JIMAGE_FW is not set # CONFIG_MTD_SPLIT_LZMA_FW is not set # CONFIG_MTD_SPLIT_MINOR_FW is not set +# CONFIG_MTD_SPLIT_MSTC_BOOT is not set # CONFIG_MTD_SPLIT_SEAMA_FW is not set # CONFIG_MTD_SPLIT_SEIL_FW is not set CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y diff --git a/target/linux/generic/config-6.6 b/target/linux/generic/config-6.6 index cb1fd5b2722..8ede1d244da 100644 --- a/target/linux/generic/config-6.6 +++ b/target/linux/generic/config-6.6 @@ -3803,6 +3803,7 @@ CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" # CONFIG_MTD_SPLIT_JIMAGE_FW is not set # CONFIG_MTD_SPLIT_LZMA_FW is not set # CONFIG_MTD_SPLIT_MINOR_FW is not set +# CONFIG_MTD_SPLIT_MSTC_BOOT is not set # CONFIG_MTD_SPLIT_SEAMA_FW is not set # CONFIG_MTD_SPLIT_SEIL_FW is not set CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig index 17d590890da..396becf160c 100644 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig @@ -110,3 +110,8 @@ config MTD_SPLIT_SEIL_FW bool "IIJ SEIL firmware parser" depends on MTD_SPLIT_SUPPORT select MTD_SPLIT + +config MTD_SPLIT_MSTC_BOOT + bool "MSTC bootnum-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 e9d63c83325..b85ce5d21f3 100644 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o 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 diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_mstc_boot.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_mstc_boot.c new file mode 100644 index 00000000000..6d7980792ae --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_mstc_boot.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * a mtdsplit parser using "bootnum" value in the "persist" partition + * for the devices manufactured by MSTC (MitraStar Technology Corp.) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define PERSIST_BOOTNUM_OFFSET 0x4 +#define NR_PARTS_MAX 2 + +/* + * Legacy format image header, + * all data in network byte order (aka natural aka bigendian). + */ + struct uimage_header { + uint32_t ih_magic; /* Image Header Magic Number */ + uint32_t ih_hcrc; /* Image Header CRC Checksum */ + uint32_t ih_time; /* Image Creation Timestamp */ + uint32_t ih_size; /* Image Data Size */ + uint32_t ih_load; /* Data Load Address */ + uint32_t ih_ep; /* Entry Point Address */ + uint32_t ih_dcrc; /* Image Data CRC Checksum */ + uint8_t ih_os; /* Operating System */ + uint8_t ih_arch; /* CPU architecture */ + uint8_t ih_type; /* Image Type */ + uint8_t ih_comp; /* Compression Type */ + uint8_t ih_name[IH_NMLEN]; /* Image Name */ +}; + +/* check whether the current mtd device is active or not */ +static int +mstcboot_is_active(struct mtd_info *mtd, u32 *bootnum_dt) +{ + struct device_node *np = mtd_get_of_node(mtd); + struct device_node *persist_np; + size_t retlen; + u32 persist_offset; + u_char bootnum; + int ret; + + ret = of_property_read_u32(np, "mstc,bootnum", bootnum_dt); + if (ret) + return ret; + + persist_np = of_parse_phandle(np, "mstc,persist", 0); + if (!persist_np) + return -ENODATA; + /* is "persist" under the same node? */ + if (persist_np->parent != np->parent) { + of_node_put(persist_np); + return -EINVAL; + } + + ret = of_property_read_u32(persist_np, "reg", &persist_offset); + of_node_put(persist_np); + if (ret) + return ret; + ret = mtd_read(mtd->parent, persist_offset + PERSIST_BOOTNUM_OFFSET, + 1, &retlen, &bootnum); + if (ret) + return ret; + if (retlen != 1) + return -EIO; + + return (bootnum == *bootnum_dt) ? 1 : 0; +} + +/* + * mainly for NOR devices that uses raw kernel and squashfs + * + * example: + * + * partition@5a0000 { + * compatible = "mstc,boot"; + * label = "firmware1"; + * reg = <0x5a0000 0x3200000>; + * mstc,bootnum = <1>; + * mstc,persist = <&mtd_persist>; + * }; + */ +static int +mstcboot_parse_image_parts(struct mtd_info *mtd, + const struct mtd_partition **pparts) +{ + struct mtd_partition *parts; + size_t retlen, kern_len = 0; + size_t rootfs_offset; + enum mtdsplit_part_type type; + u_char buf[0x40]; + int ret, nr_parts = 1, index = 0; + + ret = mtd_read(mtd, 0, sizeof(struct uimage_header), &retlen, buf); + if (ret) + return ret; + if (retlen != sizeof(struct uimage_header)) + return -EIO; + + if (be32_to_cpu(*(u32 *)buf) == OF_DT_HEADER) { + /* Flattened Image Tree (FIT) */ + struct fdt_header *fdthdr = (void *)buf; + kern_len = be32_to_cpu(fdthdr->totalsize); + } else if (be32_to_cpu(*(u32 *)buf) == IH_MAGIC) { + /* Legacy uImage */ + struct uimage_header *uimghdr = (void *)buf; + kern_len = sizeof(*uimghdr) + be32_to_cpu(uimghdr->ih_size); + } + + ret = mtd_find_rootfs_from(mtd, kern_len, mtd->size, &rootfs_offset, &type); + if (ret) { + pr_debug("no rootfs in \"%s\"\n", mtd->name); + return ret; + } + + if (kern_len > 0) + nr_parts++; + + parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + if (kern_len) { + parts[index].name = KERNEL_PART_NAME; + parts[index].offset = 0; + parts[index++].size = rootfs_offset; + } + + parts[index].name = (type == MTDSPLIT_PART_TYPE_UBI) + ? UBI_PART_NAME : ROOTFS_PART_NAME; + parts[index].offset = rootfs_offset; + parts[index].size = mtd->size - rootfs_offset; + + *pparts = parts; + return nr_parts; +} + +/* + * mainly for NAND devices that uses raw-kernel and UBI and needs + * splitted kernel/ubi partitions when sysupgrade + * + * example: + * + * partition@3c0000 { + * compatible = "mstc,boot"; + * reg = <0x3c0000 0x3240000>; + * label = "firmware1"; + * mstc,bootnum = <1>; + * mstc,persist = <&mtd_persist>; + * #address-cells = <1>; + * #size-cells = <1>; + * + * partition@0 { + * reg = <0x0 0x800000>; + * label-base = "kernel"; + * }; + * + * partition@800000 { + * reg = <0x800000 0x2a40000>; + * label-base = "ubi"; + * }; +}; + */ +static int +mstcboot_parse_fixed_parts(struct mtd_info *mtd, + const struct mtd_partition **pparts, + int active, u32 bootnum_dt) +{ + struct device_node *np = mtd_get_of_node(mtd); + struct device_node *child; + struct mtd_partition *parts; + int ret, nr_parts, index = 0; + + nr_parts = of_get_child_count(np); + if (nr_parts > NR_PARTS_MAX) { + pr_err("too many partitions found!\n"); + return -EINVAL; + } + + parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + for_each_child_of_node(np, child) { + u32 reg[2]; + if (of_n_addr_cells(child) != 1 || + of_n_size_cells(child) != 1) + { + ret = -EINVAL; + break; + } + + ret = of_property_read_u32_array(child, "reg", reg, 2); + if (ret) + break; + ret = of_property_read_string(child, "label-base", + &parts[index].name); + if (ret) + break; + + if (!active) { + parts[index].name = devm_kasprintf(&mtd->dev, GFP_KERNEL, + "%s%u", + parts[index].name, bootnum_dt); + if (!parts[index].name) { + ret = -ENOMEM; + break; + } + } + parts[index].offset = reg[0]; + parts[index].size = reg[1]; + index++; + } + of_node_put(child); + + if (ret) + kfree(parts); + else + *pparts = parts; + return ret ? ret : nr_parts; +} + +static int +mtdsplit_mstcboot_parse(struct mtd_info *mtd, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct device_node *np = mtd_get_of_node(mtd); + u32 bootnum_dt; + int ret; + + ret = mstcboot_is_active(mtd, &bootnum_dt); + if (ret < 0) + goto exit; + + if (of_get_child_count(np)) + ret = mstcboot_parse_fixed_parts(mtd, pparts, ret, bootnum_dt); + else if (ret != 0) + ret = mstcboot_parse_image_parts(mtd, pparts); + +exit: + /* + * return 0 when ret=-ENODEV, to prevent deletion of + * parent mtd partitions on Linux 6.7 and later + */ + return ret == -ENODEV ? 0 : ret; +} + +static const struct of_device_id mtdsplit_mstcboot_of_match_table[] = { + { .compatible = "mstc,boot" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtdsplit_mstcboot_of_match_table); + +static struct mtd_part_parser mtdsplit_mstcboot_parser = { + .owner = THIS_MODULE, + .name = "mstc-boot", + .of_match_table = mtdsplit_mstcboot_of_match_table, + .parse_fn = mtdsplit_mstcboot_parse, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; +module_mtd_part_parser(mtdsplit_mstcboot_parser)