1 概述
uboot在命令行界面可以查看、更改flash中相关的环境变量,这些环境变量可以用于uboot本身进行相关设置,也可以被kernel识别,用于完成特定的配置。
本小节主要讲述如何通过配置将环境变量保存到Nor Flash中。
1.1 环境变量的概念
可以理解为用户对软件的全局配置信息,这部分信息应该可以从永久性存储器上读取,能被查询,能被修改。
启动过程中,应该首先把环境变量读取到合适的内存区域,然后利用环境变量初始化硬件、启动操作系统等等。
1.2 启动过程中环境变量初始化过程涉及的问题
这里涉及到两个问题:
环境变量在哪个地方存着(从哪个地方取)
将环境变量存储到哪里(放到哪)
(1)环境变量位于存储器(norflash、nandflash )
“CFG_ENV_IS_IN_XXX“定义了则这种情况有效,以在flash上为例。
CONFIG_ENV_IS_NOWHERE 系统中没有存储器存放环境变量
CONFIG_ENV_IS_IN_EEPROM 环境变量存放在EEPROM
CONFIG_ENV_IS_IN_FAT 环境变量存放在FAT文件系统
CONFIG_ENV_IS_IN_EXT4 环境变量存放在EXT4文件系统
CONFIG_ENV_IS_IN_FLASH
CONFIG_ENV_IS_IN_MMC 环境变量存放在EMMC
CONFIG_ENV_IS_IN_NAND 环境变量存放在NAND FLASH
CONFIG_ENV_IS_IN_NVRAM 环境变量存放在NVRAM
CONFIG_ENV_IS_IN_ONENAND 环境变量存放在NAND
CONFIG_ENV_IS_IN_REMOTE 环境变量存放在远程环境
CONFIG_ENV_IS_IN_SPI_FLASH 环境变量存放在SPI FLASH
CONFIG_ENV_IS_IN_FLASH 和 CONFIG_ENV_IS_IN_SPI_FLASH 有什么区别????
定义ENV_IS_EMBEDDED
这种情况的环境变量在flash上存着(但是占了flash一个扇区),并且随着代码段(因为环境变量区嵌在代码段内)在start.s重定位时一同载入内存。在环境变量初始化时候,如果这部分能通过校验,就不需要先在堆区开辟空间然后搬移的工作,而是直接使用这部分环境变量(省了搬移工作)。倘若不能通过校验,则使用默认环境变量放到重定位时环境变量所占的空间中。
未定义ENV_IS_EMBEDDED
这种情况会在堆区为环境变量区开辟空间,如果flash上存储的是有效的(能通过校验)环境变量,则需要把flash上的数据搬运到堆区指定的位置;如果flash上的存储是错误的环境变量,那么使用默认的环境变量(default_environment)放到堆区。
(2)没有存储器上存储有环境变量
“CFG_ENV_IS_NOWHERE”定义了则选择这种模式,使用common/env_nowhere.c文件而不是用env_flash.c、env_nvram.c等等文件。
这种情况下,使用默认的环境变量(default_environment)。先在堆区为环境变量开辟空间,然后启动搬运工作。
2 功能开启
2.1 相关设置
功能配置宏
#
# Environment
#
CONFIG_ENV_IS_IN_SPI_FLASH=y #支持将环境变量保存到nor flash上
CONFIG_ENV_OVERWRITE=y #允许修改环境变量
CONFIG_ENV_SIZE=0x1000 #实际环境变量使用的内存大小,这个变量决定了实际从nor上拷贝到内存的数据大小。
CONFIG_ENV_OFFSET=0x140000 #env在nor flash上的便宜,相对于0x0(nor flash默认以0x0开始)
#CONFIG_ENV_ADDR=CONFIG_ENV_OFFSET #环境变量起始地址的另外一种表达方式
CONFIG_ENV_SECT_SIZE=0x10000 #环境变量占用nor flash区域的大小
2.2 基本调用关系
在最新的uboot实现中,与环境变量相关的源码实现已经放到了uboot/env目录下
目前我所使用的Nor Flash通过sf命令进行probe,在CONFIG_ENV_IS_IN_SPI_FLASH使能之后会使用到env/sf.c
在初始化函数中,uboot会自动判断环境变量是否在flash中,如果不存在会使用默认的环境变量
static int initr_env(void)
>>> env_relocate();
>>>
>>> env_import_fdt();
2.3 环境变量初始化流程
以环境变量位于NorFlash上,并且没有使能“ENV_IS_EMBEDDED”功能为例,进行以下内容的分析。其他情况本文不讨论。
2.3.1 校验
直接在NorFlash上校验环境变量,实际上这一步是确定环境变量的源。如果NorFlash上存储的是有效的环境变量的话,那么就从NorFlash上读取数据。倘若NorFlash上存储的是无效的环境变量,那么使用默认的环境变量作为源。
2.3.2 重定位
将从存储环境变量的存储区加载到系统指定的位置。
2.3.3 涉及到的函数
(1)env_init
完成校验,并决定环境变量的源
此函数在start_armboot函数的开始阶段,会依次执行“init_sequence”中的每一个函数,其中一个就是env_init。
_start // start.S
>>>bl _main // arch/arm/lib/crt0_64.S
>>> bl board_init_f // common/board_f.c
>>>initcall_run_list(init_sequence_f)
>>> b board_init_r // common/board_r.c
>>> initcall_run_list(init_sequence_r)
static const init_fnc_t init_sequence_f[] = {
。。。
env_init, /* initialize environment */
。。。
}
(2)env_relocate
首先,在堆区为环境变量开辟存储缓冲区。另外,当NorFlash上的环境变量区是无效的时候,选择默认的环境变量区进行定位。当NorFlash上的环境变量区是有效的时候,调用env_relocate_spec进行重定位。
此函数是在start_armboot函数的中途阶段,在NorFlash和NandFlash都初始化后,会调用这个函数。
(3)env_relocate_spec
此函数是被env_relocate所调用。
3 保存环境变量(saveenv命令)的函数调用过程
文件:cmd/nvedit.c
#if defined(CONFIG_ENV_IS_IN_EEPROM) || \
defined(CONFIG_ENV_IS_IN_FLASH) || \
defined(CONFIG_ENV_IS_IN_MMC) || \
defined(CONFIG_ENV_IS_IN_FAT) || \
defined(CONFIG_ENV_IS_IN_EXT4) || \
defined(CONFIG_ENV_IS_IN_NAND) || \
defined(CONFIG_ENV_IS_IN_NVRAM) || \
defined(CONFIG_ENV_IS_IN_ONENAND) || \
defined(CONFIG_ENV_IS_IN_SATA) || \
defined(CONFIG_ENV_IS_IN_SPI_FLASH) || \
defined(CONFIG_ENV_IS_IN_REMOTE) || \
defined(CONFIG_ENV_IS_IN_UBI)
#define ENV_IS_IN_DEVICE //定义了此变量
#endif
当执行saveenv时会执行do_env_save
#if defined(CONFIG_CMD_SAVEENV) && defined(ENV_IS_IN_DEVICE)
static int do_env_save(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
return env_save() ? 1 : 0;
}
U_BOOT_CMD(
saveenv, 1, 0, do_env_save,
"save environment variables to persistent storage",
""
);
再来看下env_save函数
文件:env/env.c
int env_save(void)
{
struct env_driver *drv;
drv = env_driver_lookup(ENVOP_SAVE, gd->env_load_prio);
if (drv) {
int ret;
printf("Saving Environment to %s... ", drv->name);
if (!drv->save) {
printf("not possible\n");
return -ENODEV;
}
if (!env_has_inited(drv->location)) {
printf("not initialized\n");
return -ENODEV;
}
ret = drv->save(); //调用回掉
if (ret)
printf("Failed (%d)\n", ret);
else
printf("OK\n");
if (!ret)
return 0;
}
return -ENODEV;
}
env_save函数会调用具体的save回掉函数来实现的。那么这个回掉函数在哪儿实现的呢?
通过env_driver_lookup函数的实现可知,struct env_driver类似于U_BOOT_DRIVER,通过编译时静态初始化结构体来实现。
然后通过搜索ENVL_SPI_FLASH最终找到了env_driver的实现方式
#define U_BOOT_ENV_LOCATION(__name) \
ll_entry_declare(struct env_driver, __name, env_driver)
文件:env/sf.c
U_BOOT_ENV_LOCATION(sf) = {
.location = ENVL_SPI_FLASH,
ENV_NAME("SPIFlash")
.load = env_sf_load,
.save = ENV_SAVE_PTR(env_sf_save), //从这儿我们找到了save的具体实现函数
.erase = ENV_ERASE_PTR(env_sf_erase),
.init = env_sf_init,
};
我们再回过头来看下env/sf.c函数有没有编译
env/Makefile:30:obj-$(CONFIG_$(SPL_TPL_)ENV_IS_IN_SPI_FLASH) += sf.o
从上面的Makefile中可以确认sf.c文件依赖与CONFIG_ENV_IS_IN_SPI_FLASH这个变量已经被打开。
下面我们看下env_sf_save函数的实现
static int env_sf_save(void)
>>> ret = setup_flash_device(&env_flash); //找到用sf probe的spi nor flash
>>> ret = spi_flash_probe_bus_cs(CONFIG_ENV_SPI_BUS, CONFIG_ENV_SPI_CS,
CONFIG_ENV_SPI_MAX_HZ, CONFIG_ENV_SPI_MODE,
&new);
>>> *env_flash = dev_get_uclass_priv(new);
>>> (1)先把nor flash中的参数区域没有使用的部分(CONFIG_ENV_SIZE != CONFIG_ENV_SECT_SIZE)读到动态分布的内存中
if (sect_size > CONFIG_ENV_SIZE)
saved_size = sect_size - CONFIG_ENV_SIZE;
saved_offset = CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE;
saved_buffer = malloc(saved_size);
ret = spi_flash_read(env_flash, saved_offset,
saved_size, saved_buffer);
>>> (2)从hash表中获取环境变量,这个环境变量是在初始化过程中导入到hash表中的,后面详解
ret = env_export(&env_new);
>>> (3)擦除要写的nor flash内存空间
sector = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size);
ret = spi_flash_erase(env_flash, CONFIG_ENV_OFFSET,
sector * sect_size);
>>> (4)将环境变量写入到nor flash 内存空间区域
ret = spi_flash_write(env_flash, CONFIG_ENV_OFFSET,
CONFIG_ENV_SIZE, &env_new);
>>> (5)把之前读出来的剩余的nor flash参数区域中的数据再写回去
ret = spi_flash_write(env_flash, saved_offset,
saved_size, saved_buffer);
还有一个问题,环境变量何时导入到hash表中
文件:common/board_r.c
static int initr_env(void)
>>> env_relocate();
>>> env_set_default(NULL, 0);
>>> himport_r(&env_htab, (char *)default_environment,
sizeof(default_environment), '\0', flags, 0,
0, NULL) == 0 //将default_environment导入到env_htab