# [Record] An observation on the implementation of the redundant environment feature in bootloader ###### tags: `BSP`, `flash corruption`, `saveenv`, `setenv`, `env save`, `env set`, `fw_env`, `fw_setenv`,`CONFIG_SYS_REDUNDAND_ENVIRONMENT`, `CONFIG_ENV_OFFSET_REDUND`, `CONFIG_ENV_ADDR_REDUND` [toc] ## Introduction Generally, in uboot, an ENV saving operation "saveenv" consists of two transactions: 1. Erase 2. Write For example, to set an environment variable in uboot you will do: ```shell MyUboot# setenv test good MyUboot# saveenv Saving Environment to SPI Flash... Erasing SPI flash...II: Erasing 65536 bytes from 000e0000... 100% Writing to SPI flash...II: Writting 65536 bytes to 000e0000... 100% ``` Sometimes, it takes much time to complete this saving operation depending on how large the partition is you are working on. **The more time it takes, the more risk you will take.** What is this risk? It's ++corruption++. Corruption happens usually only because you  have  ***<font color="red">a  power  loss  or reset  or  crash when  writing the new environment into flash.</font>*** In order to multigate this risk, redundant environment implements something like an atomic transaction. ## Redundant Environment Enabling 1. Establish your own partition layout like below. Assume we have two partitions to store environment variables, which are ENV_A and ENV_B, respectively. <div style="text-align: center"><img src="https://i.imgur.com/Kiu6qPO.png"/></div></br> :::warning (1) It is worthy noticing that each partition's boundary should align with the block erasing size. Here is 64 KB (0x10000). (2) The size of ENV_A and ENV_B must be the same. ::: 2. Let's modify defconfig in **./.config** under your uboot folder, enable it by ```shell Tomas # make menuconfig ... ... Tomas # vi .config CONFIG_ENV_ADDR_REDUND=0x0e0000 CONFIG_SYS_REDUNDAND_ENVIRONMENT=y or Tomas # vi .config CONFIG_ENV_OFFSET_REDUND=0x0e0000 CONFIG_SYS_REDUNDAND_ENVIRONMENT=y ``` 3. Build and insall u-boot env tools fw_env You can build this packages in buildroot as shown below. ```kconfig Symbol: BR2_PACKAGE_UBOOT_TOOLS_FWPRINTENV [=y] Type : bool Prompt: fw_printenv Location: -> Target packages -> Hardware handling (8) -> u-boot tools (BR2_PACKAGE_UBOOT_TOOLS [=y]) Defined at package/uboot-tools/Config.in:69 Depends on: BR2_PACKAGE_UBOOT_TOOLS [=y] Selected by [n]: - BR2_PACKAGE_MENDER [=n] && BR2_PACKAGE_HOST_GO_ARCH_SUPPORTS [=n] && BR2_PACKAGE_HOST_GO_CGO_LINKING_SUPPORTS [=n] && BR2_TOOLCHAIN_HAS_THREADS [=y] ``` 4. Edit configuration file ***/etc/fw_env.config*** for fw_env tool ```shell= Tomas# cat /proc/mtd dev: size erasesize name mtd0: 000d0000 00010000 "U-Boot" mtd1: 00010000 00010000 "ENV_A" mtd2: 00010000 00010000 "ENV_B" mtd3: 00010000 00010000 "Backup" ... Tomas# vi /etc/fw_env.config # MTD device name Device offset Env. size Flash sector size Number of sectors /dev/mtd1 0x00000 0x010000 0x010000 /dev/mtd2 0x00000 0x010000 0x010000 ``` 5. Get/Set environment variables in Linux shell ```shell Tomas # fw_printenv bootdelay=1 baudrate=115200 autoload=no console=console=ttyO2,115200n8 loadaddr=0x81000000 Tomas # fw_setenv test isgood Tomas # fw_printenv bootdelay=1 baudrate=115200 autoload=no console=console=ttyO2,115200n8 loadaddr=0x81000000 test=isgood ``` 6. Set environment variables in uboot ```shell MyUboot# saveenv Saving Environment to SPI Flash... Erasing SPI flash...II: Erasing 65536 bytes from 000e0000... 100% Writing to SPI flash...II: Writting 65536 bytes to 000e0000... 100% II: Writting 1 bytes to 000d0004... 100% Valid environment: 2 MyUboot# saveenv Saving Environment to SPI Flash... Erasing SPI flash...II: Erasing 65536 bytes from 000d0000... 100% Writing to SPI flash...II: Writting 65536 bytes to 000d0000... 100% II: Writting 1 bytes to 000e0004... 100% Valid environment: 1 ``` 7. Generate a U-Boot environment binary image ```shell Tomas# mkenvimage -b -r -s 0x10000 -o uboot_envA.bin env_content.txt Tomas# mkenvimage -b -r -s 0x10000 -o uboot_envB.bin env_content.txt Tomas# mkenvimage [-h] [-r] [-b] [-p <byte>] -s <environment partition size> -o <output> <input file> This tool takes a key=value input file (same as would a `printenv' show) and generates the corresponding environment image, ready to be flashed. The input file is in format: key1=value1 key2=value2 ... Empty lines are skipped, and lines with a # in the first column are treated as comments (also skipped). -r : the environment has multiple copies in flash -b : the target is big endian (default is little endian) -p <byte> : fill the image with <byte> bytes instead of 0xff bytes -V : print version information and exit If the input file is "-", data is read from standard input ``` ## Trouble shooting ### 1. An error message "Redundant environments have unequal size" in fw_printenv ``` Tomas# fw_printenv Redundant environments have unequal size# ``` The solution is to check */etc/fw_env.config* and make the two partition the same size. ``` Tomas# vi /etc/fw_env.config # NOR example # MTD device name Device offset Env. size Flash sector size Number of sectors /dev/mtd1 0x00000 0x10000 0x10000 /dev/mtd2 0x00000 0x10000 0x10000 # error occurred in below line #/dev/mtd2 0x00000 0x01000 0x10000 ``` ### 2. Error messages in dmesg and in fw_setenv These error messages is as shown below log. ```shell Tomas# dmesg ... ... XXX_SPI_FLASH_MIO driver is used Probe: SPI CS1 Flash Type MX25L25635F 0x000000000000-0x0000000d0000 : "LOADER" 0x0000000d0000-0x0000000d1000 : "ENV_A" mtd: partition "ENV_A" doesn't end on an erase block -- force read-only 0x0000000e0000-0x000000f00000 : "ENV_B" 0x0000000f0000-0x000000100000 : "Backup" 0x000000100000-0x000000300000 : "KIMAGE" 0x000000300000-0x000000b00000 : "RIMAGE" 0x000000b00000-0x000000d00000 : "KIMAGE2" 0x000000d00000-0x000001500000 : "RIMAGE2" 0x000001500000-0x000002000000 : "JFFS2_CFG" 0x000000100000-0x000000b00000 : "FW1" 0x000000b00000-0x000001500000 : "FW2" ... ... Tomas# fw_setenv test isbad Can't open /dev/mtd1: Permission denied Error: can't write fw_env to flash mtd: partition "ENV_A" doesn't end on an erase block -- force read-only ``` The solution is to make the size of "ENV_A" partition to be 0x10000 (64KB) as "ENV_B" partition. ## Code review Here is a piece of sample code from env/sf.c. First, we have to know a data structure in uboot which is env_t. It is a data structure to store uboot environments. :::info env_t = header + data header = CRC (4 Bytes) + Flag (1 Byte) data is the partition size like ENV_A = 64 KB. ::: ```c= #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT # define ENV_HEADER_SIZE (sizeof(uint32_t) + 1) #else # define ENV_HEADER_SIZE (sizeof(uint32_t)) #endif #define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE) typedef struct environment_s { uint32_t crc; /* CRC32 over data bytes */ #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT unsigned char flags; /* active/obsolete flags ENVF_REDUND_ */ #endif unsigned char data[ENV_SIZE]; /* Environment data */ } env_t; ``` Second, we have to understand the init sequence. In general, env data will be loaded from flash to memory. If the redundant environment feature is defined, uboot needs to decide which partition should be loaded to memory. ```c= #if defined(CONFIG_ENV_SPI_EARLY) /* * early load environment from SPI flash (before relocation) * and check if it is valid. */ static int env_sf_init_early(void) { int ret; int read1_fail; int read2_fail; int crc1_ok; env_t *tmp_env2 = NULL; env_t *tmp_env1; /* * if malloc is not ready yet, we cannot use * this part yet. */ if (!gd->malloc_limit) return -ENOENT; tmp_env1 = (env_t *)memalign(ARCH_DMA_MINALIGN, CONFIG_ENV_SIZE); if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT)) tmp_env2 = (env_t *)memalign(ARCH_DMA_MINALIGN, CONFIG_ENV_SIZE); if (!tmp_env1 || !tmp_env2) goto out; ret = setup_flash_device(); if (ret) goto out; read1_fail = spi_flash_read(env_flash, CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, tmp_env1); if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT)) { read2_fail = spi_flash_read(env_flash, CONFIG_ENV_OFFSET_REDUND, CONFIG_ENV_SIZE, tmp_env2); ret = env_check_redund((char *)tmp_env1, read1_fail, (char *)tmp_env2, read2_fail); if (ret == -EIO || ret == -ENOMSG) goto err_read; if (gd->env_valid == ENV_VALID) gd->env_addr = (unsigned long)&tmp_env1->data; else gd->env_addr = (unsigned long)&tmp_env2->data; } else { if (read1_fail) goto err_read; crc1_ok = crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc; if (!crc1_ok) goto err_read; /* if valid -> this is our env */ gd->env_valid = ENV_VALID; gd->env_addr = (unsigned long)&tmp_env1->data; } return 0; err_read: spi_flash_free(env_flash); env_flash = NULL; free(tmp_env1); if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT)) free(tmp_env2); out: /* env is not valid. always return 0 */ gd->env_valid = ENV_INVALID; return 0; } #endif ``` Finally, when env is saved, uboot needs to decide where should these env data be wrote to. ```C= #if defined(CONFIG_ENV_OFFSET_REDUND) static int env_sf_save(void) { env_t env_new; char *saved_buffer = NULL, flag = ENV_REDUND_OBSOLETE; u32 saved_size, saved_offset, sector; int ret; ret = setup_flash_device(); if (ret) return ret; ret = env_export(&env_new); if (ret) return -EIO; env_new.flags = ENV_REDUND_ACTIVE; if (gd->env_valid == ENV_VALID) { env_new_offset = CONFIG_ENV_OFFSET_REDUND; env_offset = CONFIG_ENV_OFFSET; } else { env_new_offset = CONFIG_ENV_OFFSET; env_offset = CONFIG_ENV_OFFSET_REDUND; } /* Is the sector larger than the env (i.e. embedded) */ if (CONFIG_ENV_SECT_SIZE > CONFIG_ENV_SIZE) { saved_size = CONFIG_ENV_SECT_SIZE - CONFIG_ENV_SIZE; saved_offset = env_new_offset + CONFIG_ENV_SIZE; saved_buffer = memalign(ARCH_DMA_MINALIGN, saved_size); if (!saved_buffer) { ret = -ENOMEM; goto done; } ret = spi_flash_read(env_flash, saved_offset, saved_size, saved_buffer); if (ret) goto done; } sector = DIV_ROUND_UP(CONFIG_ENV_SIZE, CONFIG_ENV_SECT_SIZE); puts("Erasing SPI flash..."); ret = spi_flash_erase(env_flash, env_new_offset, sector * CONFIG_ENV_SECT_SIZE); if (ret) goto done; puts("Writing to SPI flash..."); ret = spi_flash_write(env_flash, env_new_offset, CONFIG_ENV_SIZE, &env_new); if (ret) goto done; if (CONFIG_ENV_SECT_SIZE > CONFIG_ENV_SIZE) { ret = spi_flash_write(env_flash, saved_offset, saved_size, saved_buffer); if (ret) goto done; } ret = spi_flash_write(env_flash, env_offset + offsetof(env_t, flags), sizeof(env_new.flags), &flag); if (ret) goto done; puts("done\n"); gd->env_valid = gd->env_valid == ENV_REDUND ? ENV_VALID : ENV_REDUND; printf("Valid environment: %d\n", (int)gd->env_valid); done: if (saved_buffer) free(saved_buffer); return ret; } ``` ## Reference https://elixir.bootlin.com/u-boot/latest/source/include/env.h https://elixir.bootlin.com/u-boot/latest/source/env/sf.c#L138 https://github.com/ARM-software/u-boot/tree/master/tools/env https://u-boot.denx.narkive.com/YlvQwql9/u-boot-users-redundant-environment-block https://lists.denx.de/pipermail/u-boot/2006-April/014610.html http://shyuanliang.blogspot.com/2013/12/uboot-fwprintenv-fwsetenv.html https://bootlin.com/blog/mkenvimage-uboot-binary-env-generator/