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 <musashino.open@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/18976
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
			
			
This commit is contained in:
		 INAGAKI Hiroshi
					INAGAKI Hiroshi
				
			
				
					committed by
					
						 Hauke Mehrtens
						Hauke Mehrtens
					
				
			
			
				
	
			
			
			 Hauke Mehrtens
						Hauke Mehrtens
					
				
			
						parent
						
							c08c2d6eb3
						
					
				
				
					commit
					ddf7d63e94
				
			| @@ -3915,6 +3915,7 @@ CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" | |||||||
| # CONFIG_MTD_SPLIT_JIMAGE_FW is not set | # CONFIG_MTD_SPLIT_JIMAGE_FW is not set | ||||||
| # CONFIG_MTD_SPLIT_LZMA_FW is not set | # CONFIG_MTD_SPLIT_LZMA_FW is not set | ||||||
| # CONFIG_MTD_SPLIT_MINOR_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_SEAMA_FW is not set | ||||||
| # CONFIG_MTD_SPLIT_SEIL_FW is not set | # CONFIG_MTD_SPLIT_SEIL_FW is not set | ||||||
| CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y | CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y | ||||||
|   | |||||||
| @@ -3803,6 +3803,7 @@ CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" | |||||||
| # CONFIG_MTD_SPLIT_JIMAGE_FW is not set | # CONFIG_MTD_SPLIT_JIMAGE_FW is not set | ||||||
| # CONFIG_MTD_SPLIT_LZMA_FW is not set | # CONFIG_MTD_SPLIT_LZMA_FW is not set | ||||||
| # CONFIG_MTD_SPLIT_MINOR_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_SEAMA_FW is not set | ||||||
| # CONFIG_MTD_SPLIT_SEIL_FW is not set | # CONFIG_MTD_SPLIT_SEIL_FW is not set | ||||||
| CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y | CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y | ||||||
|   | |||||||
| @@ -110,3 +110,8 @@ config MTD_SPLIT_SEIL_FW | |||||||
| 	bool "IIJ SEIL firmware parser" | 	bool "IIJ SEIL firmware parser" | ||||||
| 	depends on MTD_SPLIT_SUPPORT | 	depends on MTD_SPLIT_SUPPORT | ||||||
| 	select MTD_SPLIT | 	select MTD_SPLIT | ||||||
|  |  | ||||||
|  | config MTD_SPLIT_MSTC_BOOT | ||||||
|  | 	bool "MSTC bootnum-based parser" | ||||||
|  | 	depends on MTD_SPLIT_SUPPORT | ||||||
|  | 	select MTD_SPLIT | ||||||
|   | |||||||
| @@ -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_JIMAGE_FW) += mtdsplit_jimage.o | ||||||
| obj-$(CONFIG_MTD_SPLIT_ELF_FW) += mtdsplit_elf.o | obj-$(CONFIG_MTD_SPLIT_ELF_FW) += mtdsplit_elf.o | ||||||
| obj-$(CONFIG_MTD_SPLIT_H3C_VFS) += mtdsplit_h3c_vfs.o | obj-$(CONFIG_MTD_SPLIT_H3C_VFS) += mtdsplit_h3c_vfs.o | ||||||
|  | obj-$(CONFIG_MTD_SPLIT_MSTC_BOOT) += mtdsplit_mstc_boot.o | ||||||
|   | |||||||
| @@ -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 <linux/module.h> | ||||||
|  | #include <linux/mtd/mtd.h> | ||||||
|  | #include <linux/mtd/partitions.h> | ||||||
|  | #include <linux/types.h> | ||||||
|  | #include <linux/byteorder/generic.h> | ||||||
|  | #include <linux/slab.h> | ||||||
|  | #include <linux/libfdt.h> | ||||||
|  | #include <linux/of_fdt.h> | ||||||
|  | #include <dt-bindings/mtd/partitions/uimage.h> | ||||||
|  |  | ||||||
|  | #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) | ||||||
		Reference in New Issue
	
	Block a user