# [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/