MISC-TN-032: Stand alone boot using UBI volumes on NAND

From DAVE Developer's Wiki
Jump to: navigation, search
Info Box


History
Issue Date Notes
2025/03/04 First release for DESK-MX6UL-L_4.2.1


Introduction[edit | edit source]

Installing and configuring the internal storage is the final step for Deploying the software in the field.

This Technical Note shows how to configure on AXEL ULite SOM, to install the boot binary artifacts the internal NAND flash for a safe-redundant system.

MTD parts[edit | edit source]

The NAND partitioning is defined using the command line mtdparts parameters.

The default partitioning in DESK-MX6UL-L is the following one:

mtdparts=gpmi-nand:2M(nand-SPL),6M(nand-uboot),1M(nand-env1),1M(nand-env2),1M(nand-fdt),1M(nand-spare),8M(nand-kernel),4M(nand-splash),-(nand-ubi)

The first eight parts allow to store - as raw data - the binary artifacts for u-boot, kernel, device tree and splash image. This choice has made it easy to test a complete stand-alone boot as described here. This parts are not suggested to be a final choice for a Deployment strategy due to poor NAND reliability over the years with static NAND-cell management.

For a safe or more robust application, it is suggested to use UBI layer (over mtd partitioning) with different volumes for a proper NAND wear-leveling management.

In this example, the mtdparts U-Boot variable is the following one:

=> print mtdparts
mtdparts=mtdparts=gpmi-nand:2M(nand-SPL),6M(nand-uboot),1M(nand-env1),1M(nand-env2),1M(nand-fdt),1M(nand-spare),8M(nand-kernel),4M(nand-splash),64M(nand-boot),-(nand-rootfs)
=>

A redundant platform[edit | edit source]

A/B system updates allow for a safe system update, avoiding loosing the equipment during a partial or overall update. Moreover, it allows to have coherent installed images in both boot and root file system partitions. This feature allows to use two sets of partitions normally called A and B. The system runs from the current slot while the partitions in the unused slot can be updated.

The idea is to have two mtd partitions, UBI formatted and - on each one - to have two UBI volumes containing the redundant or the new memory copy for the binary artifacts. See here below a simple scheme:

    ┌─────────────────┐
    │ MTD PArtition   │
    ├─────────────────┤                         ┌───────────────┐
mtdX│ NAND - UBI      │────────────────────────>│  UBI volumes  │
    ├─────────────────┤    ┌───────────────┐    ├───────────────┤
mtdY│ NAND - UBI      │───>│  UBI volumes  │    │ bootA         │
    └─────────────────┘    ├───────────────┤    ├───────────────┤
                           │ rootfsA       │    │ bootB         │
                           ├───────────────┤    └───────────────┘
                           │ rootfsB       │ 
                           └───────────────┘

Two approaches can be used:

  • A/B scheme: two full copies of the root filesystem, alternating between active/inactive
  • Rescue system: one full copy of the system, and one smaller rescue system used to do the update

Please find more information in the Bootlin presentation - Implementing A/B System Updates

Linux Kernel MTD parts[edit | edit source]

The Linux kernel automatically detects the NAND internal storage in the DESK-MX6UL-L_4.0.1.


Warning-icon.png This technical note uses the modified NAND partitioning for evaluation purposes. A dedicated partitioning must be selected for the real use-case. Warning-icon.png

With cat /proc/mtd it is possible to see the mtd parts and then which parts will be UBI formatted:

root@desk-mx6ul-axelulite:~# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00200000 00020000 "nand-SPL"
mtd1: 00600000 00020000 "nand-uboot"
mtd2: 00100000 00020000 "nand-env1"
mtd3: 00100000 00020000 "nand-env2"
mtd4: 00100000 00020000 "nand-fdt"
mtd5: 00100000 00020000 "nand-spare"
mtd6: 00800000 00020000 "nand-kernel"
mtd7: 00400000 00020000 "nand-splash"
mtd8: 04000000 00020000 "nand-boot"
mtd9: 3a800000 00020000 "nand-rootfs"
mtd10: 00010000 00001000 "spi-SPL"
mtd11: 000f0000 00001000 "spi-uboot"
mtd12: 00040000 00001000 "spi-env1"
mtd13: 00040000 00001000 "spi-env2"
mtd14: 00080000 00001000 "spi-dtb"
mtd15: 00800000 00001000 "spi-kernel"
mtd16: 00400000 00001000 "spi-splash"
mtd17: 00200000 00001000 "spi-free"
root@desk-mx6ul-axelulite:~#

The mtd8 and mtd9 will be used for storing the boot binary artifacts (mtd8) and the root file system (mtd9). In each UBI part, two logical volumes will be created for a A/B scheme with redudancy with the following commands:

  • mtd8: nand-boot
ubiformat /dev/mtd8
ubiattach /dev/ubi_ctrl -m 8
ubimkvol /dev/ubi0 -N bootA -s 12MiB
ubimkvol /dev/ubi0 -N bootB -s 12MiB
  • mtd9: nand-rootfs
ubiformat /dev/mtd9
ubiattach /dev/ubi_ctrl -m 9
ubimkvol /dev/ubi1 -N rootfsA -s 128MiB
ubimkvol /dev/ubi1 -N rootfsB -s 128MiB

Binary artifacts programming[edit | edit source]

For programming the binaries in the proper UBI volumes, firstly attach both UBI mtd:

root@desk-mx6ul-axelulite:~# ubiattach /dev/ubi_ctrl -m 8
root@desk-mx6ul-axelulite:~# ubiattach /dev/ubi_ctrl -m 9

boot binaries UBI volume[edit | edit source]

To manage boot binaries (kernel and device tree/s), a useful way is to create a FIT image. This allows having a single binary image to be flashed in a UBI volume and readable directly by U-Boot.

The FIT image can be created with the (easy) following steps:

  • create a FIT image source file descriptor fitImage.dts
/dts-v1/;
/{
description = "Simple image with single Linux kernel and FDT blob";
#address-cells = <1>;
images {
    kernel@1 {
        description = "Linux kernel";
        data = /incbin/("uImage");
        type = "kernel";
        arch = "arm";
        os = "linux";
        compression = "none";
        load = <0x80008000>;
        entry = <0x80008000>;
        hash@1 {
            algo = "crc32";
        };
        hash@2 {
            algo = "sha1";
        };
    };
    fdt@0000003a {
        description = "Flattened Device Tree blob CB003A";
        data = /incbin/("imx6ul-axelulite-cb003a.dtb");
        type = "flat_dt";
        arch = "arm";
        compression = "none";
        load = <0x83000000>;
        hash@1 {
            algo = "crc32";
        };
        hash@2 {
            algo = "sha1";
        };
    };
};
configurations {
    default = "conf@0000003a";
    conf@0000003a {
        description = "Boot Linux kernel with FDT blob - CB003A";
        kernel = "kernel@1";
        fdt = "fdt@0000003a";
    };
};
};
  • build the file with mkimage for creating the fitImage.itb blob file
mkimage -f fitImage.dts fitImage.itb
  • then, it is possible to program the FIT image in the UBI volume
root@desk-mx6ul-axelulite:~# ubiupdatevol /dev/ubi0_0 fitImage.itb

root file system UBI volume[edit | edit source]

On the rootfsA UBI volume, the root file system can be extracted:

root@desk-mx6ul-axelulite:~# mount -t ubifs /dev/ubi1_0 /mnt
root@desk-mx6ul-axelulite:~# tar jxpf rootfs.tar.bz2 -C /mnt
root@desk-mx6ul-axelulite:~# umount /mnt

Finally detach both UBI mtd:

root@desk-mx6ul-axelulite:~# ubidetach -m 8
root@desk-mx6ul-axelulite:~# ubidetach -m 9

U-Boot environment[edit | edit source]

If the U-Boot has already been programmed in NOR, it is enough to properly configure the U-Boot environment for using the UBI volumes already programmed:

  • set the kernel parameters for retreiving the root file system in the proper UBI/mtd part and volume
setenv ubiargs 'setenv bootargs ubi.mtd=9 root=ubi0:rootfsA rootfstype=ubifs rw quiet'
  • let U-Boot to retreive the FIT image from the proper UBI/mtd part and volume
setenv ubiloadfit 'ubi part nand-boot; ubi read ${loadaddr} bootA ${fitsize}'
setenv fitsize 800000
  • set the boot command for booting from NAND UBI volumes
setenv bootcmd 'run ${normalboot}'
setenv normalboot ubi_ubi
setenv ubi_ubi 'run ubiloadfit ubiargs addcons addmisc; if run configid_fixupfdt; then bootm ${loadaddr} - ${fdtaddr}; fi'
  • once started, the ubi_ubi command reads the FIT image and then starts the kernel (N.B the quiet parameter avoid to report the overall kernel messages)
=> run ubi_ubi
ubi0: attaching mtd9
ubi0: scanning is finished
ubi0: attached mtd9 (name "nand-boot", size 64 MiB)
ubi0: PEB size: 131072 bytes (128 KiB), LEB size: 126976 bytes
ubi0: min./max. I/O unit sizes: 2048/2048, sub-page size 2048
ubi0: VID header offset: 2048 (aligned 2048), data offset: 4096
ubi0: good PEBs: 512, bad PEBs: 0, corrupted PEBs: 0
ubi0: user volume: 2, internal volumes: 1, max. volumes count: 128
ubi0: max/mean erase counter: 8/4, WL threshold: 4096, image sequence number: 828929081
ubi0: available PEBs: 148, total reserved PEBs: 364, PEBs reserved for bad PEB handling: 160
Read 8388608 bytes from volume bootA to 80800000
## Loading kernel from FIT Image at 80800000 ...
   Using 'conf@0000003a' configuration
   Trying 'kernel@1' kernel subimage
     Description:  Linux kernel
     Type:         Kernel Image
     Compression:  uncompressed
     Data Start:   0x808000e4
     Data Size:    8100488 Bytes = 7.7 MiB
     Architecture: ARM
     OS:           Linux
     Load Address: 0x80008000
     Entry Point:  0x80008000
     Hash algo:    crc32
     Hash value:   0cff992f
     Hash algo:    sha1
     Hash value:   5bd239ed4186ebcf183bd9f937c224457fe16779
   Verifying Hash Integrity ... crc32+ sha1+ OK
## Loading fdt from FIT Image at 80800000 ...
   Using 'conf@0000003a' configuration
   Trying 'fdt@0000003a' fdt subimage
     Description:  Flattened Device Tree blob CB003A
     Type:         Flat Device Tree
     Compression:  uncompressed
     Data Start:   0x80fb9ca4
     Data Size:    32848 Bytes = 32.1 KiB
     Architecture: ARM
     Load Address: 0x83000000
     Hash algo:    crc32
     Hash value:   b22ac650
     Hash algo:    sha1
     Hash value:   f09f296c7dd77fb020015115e3b8d2b2431ecc97
   Verifying Hash Integrity ... crc32+ sha1+ OK
   Loading fdt from 0x80fb9ca4 to 0x83000000
   Booting using the fdt blob at 0x83000000
   Loading Kernel Image
   Using Device Tree in place at 83000000, end 8300b04f

Starting kernel ...

[    6.904196] systemd[156]: /usr/lib/systemd/system-generators/systemd-gpt-auto-generator failed with exit status 1.

NXP i.MX Release Distro 5.15-kirkstone desk-mx6ul-axelulite ttymxc0

desk-mx6ul-axelulite login:

U-Boot FIT image[edit | edit source]

In U-Boot it is required to enable the FIT image support: the following configuration has then to be enabled

CONFIG_FIT=y
CONFIG_FIT_VERBOSE=y
CONFIG_OF_BOARD_SETUP=y

and a proper ECC layout should be configured as per kernel configuration:

CONFIG_NAND_MXS_USE_MINIMUM_ECC=y

Moreover, a proper ft_board_setup function should be added to the board file for a proper configid management:

diff --git a/board/dave/lynx/lynx.c b/board/dave/lynx/lynx.c
index c0fb9c1e9b4..e5e9e31622a 100644
--- a/board/dave/lynx/lynx.c
+++ b/board/dave/lynx/lynx.c
@@ -1358,3 +1358,87 @@ int checkboard(void)
 
 	return 0;
 }*/
+
+#ifdef CONFIG_OF_BOARD_SETUP
+#define SOM_PREFIX	"dave,som"
+#define CB_PREFIX	"dave,cb"
+#define MAX(a, b)             ((a) > (b) ? (a) : (b))
+int ft_board_setup(void *fdt, struct bd_info *bd)
+{
+	/* defines variable */
+	int ret;
+	int som_offset;
+	int cb_offset;
+	char* som_configid_property;
+	char* som_uniqueid_property;
+	char* cb_configid_property;
+	char* cb_uniqueid_property;
+	char compatible[MAX(sizeof(SOM_PREFIX), sizeof(CB_PREFIX)) + sizeof(int)*2 + 1]; /* max(prefix) + configid (ascii) + \0 */
+
+
+	/* Find node som */
+	som_offset = fdt_path_offset(fdt, "/som");
+	if (som_offset < 0) {
+		printf("ERROR: couldn't find /som device tree node\n");
+		return som_offset;
+	}
+
+	/* Find node cb */
+	cb_offset = fdt_path_offset(fdt, "/cb");
+	if (cb_offset < 0) {
+		printf("ERROR: couldn't find /cb device tree node\n");
+		return cb_offset;
+	}
+
+	/* save the information taken from u-boot into these variables. */
+	som_configid_property = env_get("som_configid#");
+	som_uniqueid_property = env_get("som_uniqueid#");
+	cb_configid_property = env_get("cb_configid#");
+	cb_uniqueid_property = env_get("cb_uniqueid#");
+	if (som_configid_property==NULL ||
+		som_uniqueid_property==NULL ||
+		cb_configid_property==NULL ||
+		cb_uniqueid_property==NULL) {
+		printf("ERROR: missing some SOM/CB ConfigID/UniqueID environment variables");
+		return -FDT_ERR_BADVALUE;
+	}
+
+	/* check that the compatible of the som for the target is correct*/
+	snprintf(compatible, sizeof(compatible), "%s%s", SOM_PREFIX, som_configid_property);
+	ret = fdt_node_offset_by_compatible(fdt, -1, compatible);
+	if (ret < 0) {
+		printf("not match SOM compatible node while looking for %s\n", compatible);
+		return ret;
+	}
+
+	/* check that the compatible of the cb for the target is correct*/
+	snprintf(compatible, sizeof(compatible), "%s%s", CB_PREFIX, cb_configid_property);
+	ret = fdt_node_offset_by_compatible(fdt, -1, compatible);
+	if (ret < 0) {
+		printf("not match CB compatible node while looking for %s\n", compatible);
+		return ret;
+	}
+
+	/* overwrite the som configid */
+	ret = fdt_setprop(fdt, som_offset, "configid", som_configid_property, strlen(som_configid_property)+1);
+	if (ret < 0)
+		printf("WARNING: cannot update SOM ConfigID\n");
+
+	/* and set the som uniqueid */
+	ret = fdt_setprop(fdt, som_offset, "uniqueid", som_uniqueid_property, strlen(som_uniqueid_property)+1);
+	if (ret < 0)
+		printf("WARNING: cannot update SOM UniqueID\n");
+
+	/* overwrite the cb configid */
+	ret = fdt_setprop(fdt, cb_offset, "configid", cb_configid_property, strlen(cb_configid_property)+1);
+	if (ret < 0)
+		printf("WARNING: cannot update CB ConfigID\n");
+
+	/* and set the cb uniqueid */
+	ret  = fdt_setprop(fdt, cb_offset, "uniqueid", cb_uniqueid_property, strlen(cb_uniqueid_property)+1);
+	if (ret < 0)
+		printf("WARNING: cannot update CB UniqueID\n");
+
+	return 0;
+}
+#endif