uboot 环境变量实现简析
----------基于u-boot-2010.03
u-boot的环境变量是使用u-boot的关键,它可以由你自己定义的,但是其中有一些也是大家经常使用,约定熟成的,有一些是u-boot自己定义的,更改这些名字会出现错误,下面的表中我们列出了一些常用的环境变量:
bootdelay 执行自动启动的等候秒数
baudrate 串口控制台的波特率
netmask 以太网接口的掩码
ethaddr 以太网卡的网卡物理地址
bootfile 缺省的下载文件
bootargs 传递给内核的启动参数
bootcmd 自动启动时执行的命令
serverip 服务器端的ip地址
ipaddr 本地ip 地址
stdin 标准输入设备
stdout 标准输出设备
stderr 标准出错设备
上面只是一些最基本的环境变量,请注意,板子里原本是没有环境变量的,u-boot的缺省情况下会有一些基本的环境变量,在你执行了saveenv之后,环境变量会第一次保存到flash或者eeprom中,之后你对环境变量的修改,保存都是基于保存在flash中的环境变量的操作。
环境变量可以通过printenv命令查看环境变量的设置描述,通过setenv 命令进行重新设置,设置完成后可以通过saveenv将新的设置保存在非易失的存储设备中(nor flash 、nand flash 、eeprom)。例如:
setenv bootcmd "nand read 0x30008000 0x80000 0x500000;bootm 0x30008000"
saveenv
通过这两条命令就完成了环境变量bootcmd的重新设置,并讲其保存在固态存储器中。
下面简单分析下uboot中环境变量的实现流程。
uboot启动后,执行玩start.S中的汇编程序,将跳入board.c 中定义的start_arm_boot()函数中,在该函数中,uboot讲完成板子外设和相关系统环境的初始化,然后进入main_loop循环中进行系统启动或者等待与用户交互,这其中就包括环境变量的初始化和重定位。主要代码如下:
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
.....................................
/* Pointer is writable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
gd->flags |= GD_FLG_RELOC;
monitor_flash_len = _bss_start - _armboot_start;
(1):
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
{
if ((*init_fnc_ptr)() != 0)
{
hang ();
}
}
/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
CONFIG_SYS_MALLOC_LEN);
(2)
/* initialize environment */
env_relocate ();
for (;;) {
main_loop ();
}
/* NOTREACHED - no way out of command loop except booting */
}
在代码段(1)中,会通过for循环调用init_sequence中的系统初始化函数,其中一个就是环境变量的初始化函数env_init(),uboot在编译的时候,会根据配置文件比如(mini2440.h)中的定义环境变量存储设备类型,编译对应存储设备的环境变量存储驱动文件,具体可参考u-boot/common/Makefile。比如在mini2440.h中定义了#define CONFIG_ENV_IS_IN_NAND 1 ,表明系统中环境变量存储在nand flash中,uboot在编译的时候会编译env_nand.c,在该文件中,env_ini()定义和实现如下:
/* this is called before nand_init()
* so we can't read Nand to validate env data.
* Mark it OK for now. env_relocate() in env_common.c
* will call our relocate function which does the real
* validation.
*
* When using a NAND boot image (like sequoia_nand), the environment
* can be embedded or attached to the U-Boot image in NAND flash. This way
* the SPL loads not only the U-Boot image from NAND but also the
* environment.
*/
int env_init(void)
{
#if defined(ENV_IS_EMBEDDED) || defined(CONFIG_NAND_ENV_DST)
int crc1_ok = 0, crc2_ok = 0;
env_t *tmp_env1;
#ifdef CONFIG_ENV_OFFSET_REDUND
env_t *tmp_env2;
tmp_env2 = (env_t *)((ulong)env_ptr + CONFIG_ENV_SIZE);
crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);
#endif
tmp_env1 = env_ptr;
crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
if (!crc1_ok && !crc2_ok)
{
gd->env_addr = 0;
gd->env_valid = 0;
return 0;
}
else if (crc1_ok && !crc2_ok) {
gd->env_valid = 1;
}
#ifdef CONFIG_ENV_OFFSET_REDUND
else if (!crc1_ok && crc2_ok) {
gd->env_valid = 2;
} else {
/* both ok - check serial */
if(tmp_env1->flags == 255 && tmp_env2->flags == 0)
gd->env_valid = 2;
else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)
gd->env_valid = 1;
else if(tmp_env1->flags > tmp_env2->flags)
gd->env_valid = 1;
else if(tmp_env2->flags > tmp_env1->flags)
gd->env_valid = 2;
else /* flags are equal - almost impossible */
gd->env_valid = 1;
}
if (gd->env_valid == 2)
env_ptr = tmp_env2;
else
#endif
if (gd->env_valid == 1)
env_ptr = tmp_env1;
gd->env_addr = (ulong)env_ptr->data;
#else /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
return (0);
}
其实这段代码,只看注释就能明白个大概,注释中说,这段代码将在nand_init()之前调用,所以在这段函数中是无法从nand里面读取到存储到的环境变量的,这里先假设环境变量是可用的,设置gd—>env_valid =1;然后在env_relocate ()会进行真正的判断。这也说明在拜读别人的代码的时候,仔细阅读下别人的注释也是多么的重要啊。
在看代码段(2),在这里调用了env_relocate()函数。该函数主要代码如下:
void env_relocate (void)
{
...............................................
#ifdef ENV_IS_EMBEDDED
/*
* The environment buffer is embedded with the text segment,
* just relocate the environment pointer
*/
#ifndef CONFIG_RELOC_FIXUP_WORKS
env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);
#endif
DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#else
(1): /*
* We must allocate a buffer for the environment
*/
env_ptr = (env_t *)malloc (CONFIG_ENV_SIZE);
DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif
(2):
if (gd->env_valid == 0)
{
#if defined(CONFIG_GTH) || defined(CONFIG_ENV_IS_NOWHERE) /* Environment not changable */
puts ("Using default environment\n\n");
#else
puts ("*** Warning - bad CRC, using default environment\n\n");
show_boot_progress (-60);
#endif
set_default_env();
}
else
{
env_relocate_spec ();
}
gd->env_addr = (ulong)&(env_ptr->data);
............................................................
}
这段代码先在代码段(1)中从内存中为环境变量分配空间,然后在代码段(2)中,犹豫我们在env_init()中,已经强制将gd->env_valid设置为1,所以这里将调用env_relocate_spec()函数。在env_nand.c中,evn_relocate_spec()实现如下:
/*
* The legacy NAND code saved the environment in the first NAND device i.e.,
* nand_dev_desc + 0. This is also the behaviour using the new NAND code.
*/
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
int ret;
ret = readenv(CONFIG_ENV_OFFSET, (u_char *) env_ptr);
if (ret)
return use_default();
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
这段代码用意很明确,先从nand中evn变量的存储区将env参数读取到内存中为env变量分配的区域,然后对这些env数据进行crc校验以验证数据的有效性。如果读取失败,或者crc校验失败,就会调用use_default()函数,使用系统默认的环境变量,这时候我们将在串口等终端上看到“*** Warning - bad CRC or NAND, using default environment”
这里再说下env_ptr指向的环境变量的数据结构,相关定义如下:
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
env_t *env_ptr=0;
在用户执行setenv命令的时候, 会调用env_crc_update ()函数,更新env数据中对应的crc,然后用户执行saveenv命令的时候,env数据和crc都被存储到固态存储设备中,下一次从固态设备中读取env数据的时候,根据读取到的数据计算出对应的crc校验和,然后与读取到的crc 数据比较,就知道获取的数据是否合法。