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 數據比較,就知道獲取的數據是否合法。