第二阶段入口地址为: start_armboot 在lib_arm/board.c 中定义。如下图所示
http://blog.chinaunix.net/uid-29589379-id-5572663.html
1.U-BOOT 环境变量实现
(基于smdkc210)
1.相关文件
common/env_common.c
供u-boot 调用的通用函数接口,它们隐藏了env 的不同实现方式,比如dataflash, epprom, flash 等
common/env_dataflash.c
env 存储在dataflash 中的实现
common/env_eepprom.c
env 存储在epprom 中的实现
common/env_flash.c
env 存储在flash 中的实现
common/env_nand.c
env 存储在nand 中的实现
common/env_nvram.c
实现u-boot 对环境变量的操作命令
common/env_common.c
环境变量以及一些宏定义
env 如果存储在Flash 中还需要Flash 的支持。
2.数据结构
env 在 u-boot 中通常有两种存在方式,在永久性存储介质中( Flash NVRAM 等 )在SDRAM,可以
配置不使用 env 的永久存储方式,但这不常用。u-boot 在启动的时候会将存储在永久性存储介质中的
env 重新定位到 RAM 中,这样可以快速访问,同时可以通过saveenv 将 RAM 中的 env 保存到永久
性存储介质中。
在include/environment.h 中定义了表示env 的数据结构
typedef struct environment_s
{
unsigned long crc; /* CRC32 over data bytes */
#ifdef CFG_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
关于以上结构的说明:
crc 是u-boot 在保存env 的时候加上去的校验头,在第一次启动时一般 crc 校验会出错,这很正常,因
为这时 Flash 中的数据无效。
data 字段保存实际的环境变量。u-boot 的 env 按 name=value”\0”的方式存储,在所有env 的最后
以”\0\0”表示整个 env 的结束。新的name=value 对总是被添加到 env 数据块的末尾,当删除一个
name=value 对时,后面的环境变量将前移,对一个已经存在的环境变量的修改实际上先删除再插入。
env 可以保存在 u-boot 的 TEXT 段中,这样 env 就可以同 u-boot 一同加载入RAM 中,这种方法
没有测试过。
上文提到u-boot 会将 env 从 flash 等存储设备重定位到 RAM 中,在 env 的不同实现版本
( env_xxx.c )中定义了 env_ptr, 它指向 env 在RAM 中的位置。u-boot 在重定位 env 后对环境
变量的操作都是针对 env_ptr。
env_t 中除了数据之外还包含校验头,u-boot 把env_t 的数据指针有保存在了另外一个地方,这就
是 gd_t 结构( 不同平台有不同的 gd_t 结构 ),这里以ARM 为例仅列出和 env 相关的部分
typedef struct global_data
{
…
unsigned long env_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct ??? */
unsigned long env_valid /* Checksum of Environment valid */
…
} gd_t;
< include/asm-arm/Global_data.h>
gd_t.env_addr 即指向 env_ptr->data。
3.ENV 的初始化
start_armboot : ( lib_arm/board.c )
*env_init : env_xxx.c( xxx = nand | flash | epprom … )
env_relocate : env_common.c
*env_relocate_spec : env_xxx.c( xxx=nand | flash | eporom… )
3.1env_init
实现 env 的第一次初始化,对于nand env (非embedded 方式):
Env_nand.c : env_init
gd->env_addr = (ulong)&default_environment[0]; //先使gd->env_addr 指向默认的环境变量
gd->env_valid = 1;// env 有效位置1
3.2 env_relocate
#ifdefine ENV_IS_EMBEDDED
…(略)
#else
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
#endif
if( gd->env_valid == 0) // 在 Env_annd.c : env_init 中已经将 gd->env_valid 置1
{
…
}
else
env_relocate_spec ();// 调用具体的 env_relocate_spec 函数
gd->env_addr = (ulong)&(env_ptr->data);// 最终完成将环境变量搬移到内存
这里涉及到两个和环境变量有关的宏
ENV_IS_EMBEDDED : env 是否存在于 u-boot TEXT 段中
CFG_ENV_SIZE : env 块的大小
实际上还需要几个宏来控制u-boot 对环境变量的处理
CFG_ENV_IS_IN_NAND : env 块是否存在于Nand Flash 中
CFG_ENV_OFFSET : env 块在 Flash 中偏移地址
3.3*env_relocate_spec
这里仅分析 Nand Flash 的 env_relocate_spec 实现
如果未设置 CFG_ENV_OFFSET_REDUND,env_relocate_spec 的实现如下 :
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
ulong total;
int ret;
total = CFG_ENV_SIZE;
ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return use_default();
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
上面的代码很清楚的表明了 env_relocate_spec 的意图, 调用 nand_read 将环境变量从
CFG_ENV_OFFSET 处读出,环境变量的大小为 CFG_ENV_SIZE 注意 CFG_ENV_OFFSET 和
CFG_ENV_SIZE 要和 Nand Flash 的块/页边界对齐。读出数据后再调用crc32 对env_ptr->data 进
行校验并与保存在 env_ptr->crc 的校验码对比,看数据是否出错,从这里也可以看出在系统第一次启动
时,Nand Flash 里面没有存储任何环境变量,crc 校验肯定回出错,当我们保存环境变量后,接下来再启
动板子u-boot 就不会再报crc32 出错了。
4. ENV 的保存
由上问的论述得知, env 将从永久性存储介质中搬到RAM 里面,以后对env 的操作,比如修改环境变量
的值,删除环境变量的值都是对这个 env 在RAM 中的拷贝进行操作,由于RAM 的特性,下次启动时所
做的修改将全部消失,u-boot 提供了将env 写回 永久性存储介质的命令支持 : saveenv,不同版本的
env ( nand flash, flash … ) 实现方式不同, 以Nand Flash 的实现( 未定义
CFG_ENV_OFFSET_REDUND)为例
Env_nand.c : saveenv
int saveenv(void)
{
ulong total;
int ret = 0;
puts ("Erasing Nand...");
if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE))
return 1;
puts ("Writing to Nand... ");
total = CFG_ENV_SIZE;
ret = nand_write(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return 1;
puts ("done\n");
return ret;
}
Nand Flash 的 saveenv 命令实现很简单,调用nand_erase 和nand_write 进行Nand Flash 的
erase, write。nand_write/erase 使用的是u-boot 的nand 驱动框架,我在做开发的过程中使用的是
nand_legacy 驱动, 所以可以把nand_erase 和nand_write 改成nand_legacy_erase 和
nand_legacy_rw 就可实现nand_legacy 驱动的保存环境变量版本。
arm_cortexa9 架构的CPU 在完成基本的初始化后(ARM 汇编代码),就进入它的C 语言代
码,而C 语言代码的入口就是start_armboot, start_armboot 在lib_arm/board.c 中。start_armboot
将完成以下工作。
1.全局数据结构的初始化
比如gd_t 结构的初始化:
251 gd = (gd_t*)(_armboot_start – CFG_MALLOC_LEN – sizeof(gd_t));
_armboot_start 是u-boot 在RAM 中的开始地址(对于u-boot 最终搬移到RAM 中运行的情
况),CFG_MALLOC_LEN 在include/configs/.h 中定义。
bd_t 结构的初始化:
272 gd->bd = (bd_t*)((char*)gd-sizeof(bd_t));
u-boot 把bd_t 结构紧接着gd_t 结构存放。
内存分配的初始化
316 mem_malloc_init(_armboot_start-CFG_MALLOC_LEN);
经过以上的初始化后,u-boot 在内存中的布局为(在底端为低地址)
-----------------------------
BSS
-----------------------------
U-BOOT TEXT/DATA
-----------------------------
CFG_MALLOC_LEN
-----------------------------
gd_t
-----------------------------
bd_t
-----------------------------
STACK
-----------------------------
2.调用通用初始化函数
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
init_sequence[]是init_fnc_t 函数指针数组,这个数组包含了众多初始化函数,比如cpu_init,
board_init 等。
//init_fnc_t *init_sequence[] = {
// cpu_init, /* basic cpu dependent setup */
// board_init, /* basic board dependent setup */
// interrupt_init, /* set up exceptions */
// env_init, /* initialize environment */
// init_baudrate, /* initialze baudrate settings */
// serial_init, /* serial communications setup */
// display_banner,
// dram_init, /* configure available RAM banks */
// display_dram_config,
// NULL,
// };
3.初始化具体设备
这一部分包括对Flash,LCD,网络的初始化等,例如
318 #if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
367 devices_init();
386 #ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif
4.初始化环境变量
环境变量在通用初始化函数里面,已经初始化一次(env_init),这里调用env_relocate 对环
境变量进行重新定位。在我的另一篇文章”U-BOOT ENV 实现”中有对环境变量实现的讨论。
5.进入主循环
当然start_armboot 除了以上工作外,还完成其它的初始化工作,具体参考lib_arm/board.c,
在一切准备就绪之后,就进入u-boot 的主循环:
416 for (;;) {
main_loop ();
}
main_loop 的代码比较长,基本是就是执行用户的输入命令。