<think>
我们之前的分区表中已经有一个env_backup分区作为备份分区。现在需要修改代码,使得在保存环境变量时同时写入主环境分区和备份环境分区(env_backup)。同时,在启动时能够自动检查主分区是否有效,如果无效则使用备份分区。
修改思路:
1. 在配置文件中定义两个分区的偏移和大小。
2. 修改环境变量驱动(通常是nand_env.c或env_nand.c),实现双分区的读取和写入。
3. 在读取环境变量时,先尝试读取主分区,如果主分区无效(CRC错误或读取失败)则尝试读取备份分区。
4. 在保存环境变量时,同时写入两个分区(先写备份分区,再写主分区,这样如果写主分区时掉电,备份分区还是旧的但完整的数据,可以保证下次启动有环境变量可用)。
具体修改如下:
步骤1:修改板级配置文件(include/configs/your_board.h)
步骤2:修改环境变量驱动(这里假设使用NAND,驱动文件为drivers/env/nand.c)
步骤3:修改设备树(dts)文件,确保分区表包含env和env_backup分区,并且大小一致。
注意:由于分区表已经存在env_backup分区,我们不需要删除它,只需要在代码中使用它。
下面给出具体代码修改:
</think>
### 保留env_backup分区的双备份方案实现
以下是修改后的完整方案,保留了设备树中的env_backup分区定义,同时实现可靠的环境变量双备份功能:
#### 1. 修改板级配置文件 (`include/configs/your_board.h`)
```c
/* 环境变量配置 - 保持原有分区定义 */
#define CONFIG_ENV_OFFSET 0x1200000 // env分区偏移
#define CONFIG_ENV_SIZE 0x100000 // 环境分区大小
#define CONFIG_ENV_OFFSET_REDUND 0x1300000 // env_backup分区偏移
#define CONFIG_ENV_SIZE_REDUND 0x100000 // 备份分区大小
/* 启用双备份支持 */
#define CONFIG_SYS_REDUNDAND_ENVIRONMENT
#define CONFIG_ENV_OVERWRITE
/* 定义分区名称用于诊断 */
#define CONFIG_ENV_PRIMARY_NAME "env"
#define CONFIG_ENV_BACKUP_NAME "env_backup"
```
#### 2. 修改环境驱动文件 (`drivers/env/nand.c`)
```c
// 添加分区状态结构体
struct env_partition {
uint32_t offset;
uint32_t size;
const char *name;
int valid;
};
// 修改env_relocate_spec函数
void env_relocate_spec(void)
{
struct env_partition partitions[2] = {
{CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, CONFIG_ENV_PRIMARY_NAME, 0},
{CONFIG_ENV_OFFSET_REDUND, CONFIG_ENV_SIZE_REDUND, CONFIG_ENV_BACKUP_NAME, 0}
};
env_t *env_ptr = NULL;
char *buf = malloc(max(CONFIG_ENV_SIZE, CONFIG_ENV_SIZE_REDUND));
// 检查两个分区
for (int i = 0; i < 2; i++) {
struct env_partition *p = &partitions[i];
if (readenv(p->offset, (u_char *)buf, p->size) == 0) {
p->valid = env_crc_ok(buf);
if (p->valid && !env_ptr) {
env_ptr = (env_t *)buf; // 首个有效分区
printf("Using %s partition at 0x%08x\n", p->name, p->offset);
}
}
}
// 处理分区状态
if (env_ptr) {
env_import((char *)env_ptr, 1);
} else {
printf("## Both %s and %s corrupted! Using default env\n",
partitions[0].name, partitions[1].name);
set_default_env(NULL);
}
free(buf);
}
// 修改saveenv函数
int saveenv(void)
{
env_t env_new;
char *buf = (char *)&env_new;
// 准备环境数据
memcpy(env_new.data, env_ptr->data, ENV_SIZE);
env_new.crc = env_crc;
// 先写入env_backup分区
int ret = writeenv(CONFIG_ENV_OFFSET_REDUND, (u_char *)buf, CONFIG_ENV_SIZE_REDUND);
if (ret) {
printf("Error writing %s partition!\n", CONFIG_ENV_BACKUP_NAME);
return 1;
}
// 再写入主env分区
ret = writeenv(CONFIG_ENV_OFFSET, (u_char *)buf, CONFIG_ENV_SIZE);
if (ret) {
printf("Error writing %s partition! Backup is intact\n", CONFIG_ENV_PRIMARY_NAME);
// 标记主分区损坏
env_mark_bad(CONFIG_ENV_OFFSET);
return 1;
}
return 0;
}
// 添加坏块标记函数
static void env_mark_bad(uint32_t offset)
{
struct mtd_info *mtd = get_nand_dev_by_index(0);
if (mtd) {
printf("Marking block @0x%08x as bad\n", offset);
int block = offset / mtd->erasesize;
nand_block_markbad(mtd, block);
}
}
```
#### 3. 修改设备树文件 (`arch/arm/dts/your_board.dts`)
```dts
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "boot";
reg = <0 0x1200000>;
};
// 保留env分区定义
partition@1 {
label = "env";
reg = <0x1200000 0x100000>;
};
// 保留env_backup分区定义
partition@2 {
label = "env_backup";
reg = <0x1300000 0x100000>;
};
// 其他分区
partition@3 {
label = "dgs";
reg = <0x1400000 0x80000>;
};
// ...无需调整后续分区
};
```
#### 4. 添加分区健康检查命令 (`common/cmd_nvedit.c`)
```c
// 添加env_check命令
static int do_env_check(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
struct env_partition partitions[] = {
{CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, CONFIG_ENV_PRIMARY_NAME, 0},
{CONFIG_ENV_OFFSET_REDUND, CONFIG_ENV_SIZE_REDUND, CONFIG_ENV_BACKUP_NAME, 0}
};
puts("Environment partitions health check:\n");
for (int i = 0; i < 2; i++) {
char *buf = malloc(partitions[i].size);
if (!buf) continue;
// 读取分区数据
int ret = readenv(partitions[i].offset, (u_char *)buf, partitions[i].size);
if (ret) {
printf(" %s @0x%08x: READ ERROR\n",
partitions[i].name, partitions[i].offset);
} else {
int valid = env_crc_ok(buf);
printf(" %s @0x%08x: %s\n",
partitions[i].name, partitions[i].offset,
valid ? "VALID" : "CRC ERROR");
}
free(buf);
}
return 0;
}
// 注册命令
U_BOOT_CMD(
env_check, 1, 1, do_env_check,
"Check environment partitions health",
""
);
```
#### 5. 修改NAND读写辅助函数 (`env/nand.c`)
```c
// 扩展readenv函数支持不同分区大小
int readenv(unsigned long offset, u_char *buf, size_t size)
{
struct mtd_info *mtd = get_nand_dev_by_index(0);
size_t retlen;
int ret;
// 坏块检查
if (nand_block_isbad(mtd, offset)) {
printf("Bad block at 0x%08lx\n", offset);
return -EIO;
}
ret = nand_read_skip_bad(mtd, offset, &retlen, buf, size);
if (ret || retlen != size) {
return -EIO;
}
return 0;
}
// 添加分区同步函数
int env_sync(void)
{
char *buf = malloc(CONFIG_ENV_SIZE);
int ret;
// 读取主分区
ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf, CONFIG_ENV_SIZE);
if (ret) {
free(buf);
return ret;
}
// 写入备份分区
ret = writeenv(CONFIG_ENV_OFFSET_REDUND, (u_char *)buf, CONFIG_ENV_SIZE_REDUND);
free(buf);
return ret;
}
```
### 分区健康监控方案
```c
/* 添加启动时自动检查 */
__attribute__((constructor))
static void env_init_check(void)
{
struct env_partition primary = {
CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, CONFIG_ENV_PRIMARY_NAME, 0
};
// 检查主分区
char *buf = malloc(primary.size);
if (readenv(primary.offset, (u_char *)buf, primary.size) == 0) {
primary.valid = env_crc_ok(buf);
}
if (!primary.valid) {
printf("WARNING: Primary env partition invalid! ");
printf("Switching to backup on next save\n");
env_set("env_active", "backup");
}
free(buf);
}
/* 定期后台检查 */
void env_bg_check(void)
{
static ulong next_check = 0;
if (get_timer(0) < next_check)
return;
next_check = get_timer(0) + 60000; // 每60秒检查一次
// 执行同步检查
if (env_get("env_active")) {
if (strcmp(env_get("env_active"), "backup") == 0) {
env_sync(); // 确保备份分区最新
}
}
}
```
### 验证与测试流程
1. **分区状态检查**:
```bash
# U-Boot命令行
env_check
# 预期输出示例
Environment partitions health check:
env @0x01200000: VALID
env_backup @0x01300000: VALID
```
2. **模拟分区故障**:
```bash
# 在U-Boot中标记主分区为坏块
nand markbad 0x1200000
# 重启后验证自动切换
reset
env_check # 应显示主分区READ ERROR,使用备份分区
```
3. **分区同步测试**:
```bash
# 修改环境变量
setenv test_value sync_test
# 手动同步分区
env_sync
# 比较分区内容
nand read 0x82000000 env 0x100000
nand read 0x83000000 env_backup 0x100000
cmp.b 0x82000000 0x83000000 0x100000
```
### 关键优势与特点
1. **分区独立性**:
- 保持env和env_backup两个独立分区
- 分区大小可独立配置
- 分区名称显示在诊断信息中
2. **增强的可靠性**:
- 写入顺序:先备份分区再主分区
- 自动坏块检测和标记
- 后台定期同步检查
3. **诊断工具**:
- 新增env_check命令
- 详细的启动日志
- 自动健康监控
4. **无缝集成**:
- 兼容现有分区表定义
- 保持原有分区偏移
- 最小化对其他分区的影响