u-boot通过环境变量为用户提供一定程度的可配置性,如波特率,启动参数等。环境变量固化在非易失性存储介质中,通过saveenv来保存。可配置性意味着环境变量是可以添加、删除和修改的,也就是说环境变量的内容可能会频繁变化,为了不让这种变化对u-boot的代码和数据造成破坏,通常的选择是在FLASH中预留一个专门用来存储环境变量的块。
U-boot环境变量大致分为四个部分:环境变量的结构,初始化,环境变量的保存,环境变量的读取。根据这个思路,依次学习各个部分。
本例中环境变量存储在NAND FLASH上,且环境变量没有嵌入到u-boot中,即ENV_IS_EMBEDDED宏状态是undefined。NAND FLASH擦除以块为单位,一块的大小时128K,下图是本例u-boot和环境变量在NAND FLASH上的存储, 单独为环境变量分配了一个块的存储空间。
一、环境变量的结构
环境变量是一个结构体
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保存的是整个环境变量值做CRC运算的结果,用于校验环境变量是否合法。
元素data[ENV_SIZE] 保存了环境变量的值,下面是默认的环境变量的值,可以看出环境变量其实是一个字符串,由”\0”将每个变量分开,环境变量的末尾是”\0\0”。
uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#ifdef CONFIG_RAMBOOTCOMMAND
"ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
#endif
#ifdef CONFIG_NFSBOOTCOMMAND
"nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
"baudrate=" MK_STR(CONFIG_BAUDRATE) "\0"
#endif
#ifdef CONFIG_LOADS_ECHO
"loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0"
#endif
#ifdef CONFIG_ETHADDR
"ethaddr=" MK_STR(CONFIG_ETHADDR) "\0"
#endif
#ifdef CONFIG_ETH1ADDR
"eth1addr=" MK_STR(CONFIG_ETH1ADDR) "\0"
#endif
#ifdef CONFIG_ETH2ADDR
"eth2addr=" MK_STR(CONFIG_ETH2ADDR) "\0"
#endif
#ifdef CONFIG_ETH3ADDR
"eth3addr=" MK_STR(CONFIG_ETH3ADDR) "\0"
#endif
#ifdef CONFIG_IPADDR
"ipaddr=" MK_STR(CONFIG_IPADDR) "\0"
#endif
#ifdef CONFIG_SERVERIP
"serverip=" MK_STR(CONFIG_SERVERIP) "\0"
#endif
#ifdef CFG_AUTOLOAD
"autoload=" CFG_AUTOLOAD "\0"
#endif
#ifdef CONFIG_PREBOOT
"preboot=" CONFIG_PREBOOT "\0"
#endif
#ifdef CONFIG_ROOTPATH
"rootpath=" MK_STR(CONFIG_ROOTPATH) "\0"
#endif
#ifdef CONFIG_GATEWAYIP
"gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0"
#endif
#ifdef CONFIG_NETMASK
"netmask=" MK_STR(CONFIG_NETMASK) "\0"
#endif
#ifdef CONFIG_HOSTNAME
"hostname=" MK_STR(CONFIG_HOSTNAME) "\0"
#endif
#ifdef CONFIG_BOOTFILE
"bootfile=" MK_STR(CONFIG_BOOTFILE) "\0"
#endif
#ifdef CONFIG_LOADADDR
"loadaddr=" MK_STR(CONFIG_LOADADDR) "\0"
#endif
#ifdef CONFIG_CLOCKS_IN_MHZ
"clocks_in_mhz=1\0"
#endif
#if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
"pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY) "\0"
#endif
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
};
二、环境变量的初始化
u-boot首先调用common/env_nand.c下的int env_init(void)函数。由于ENV_IS_EMBEDDED是未定义的,故初始化函数执行了两条语句,这里暂时认为CRC是正确的,在环境变量拷贝到SDRAM上之后再做校验。
int env_init(void)
{
#if defined(ENV_IS_EMBEDDED)
ulong total;
int crc1_ok = 0, crc2_ok = 0;
env_t *tmp_env1, *tmp_env2;
total = CFG_ENV_SIZE;
tmp_env1 = env_ptr;
tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);
crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);
if (!crc1_ok && !crc2_ok)
gd->env_valid = 0;
else if(crc1_ok && !crc2_ok)
gd->env_valid = 1;
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 == 1)
env_ptr = tmp_env1;
else if (gd->env_valid == 2)
env_ptr = tmp_env2;
#else /* ENV_IS_EMBEDDED */
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED */
return (0);
}
环境变量原本是存放在NAND FLASH上的,由于环境变量经常被用到,且NAND FLASH的擦写速度太慢,需要将环境变量从FLASH上拷贝到SDRAM上,u-boot通过调用env_relocate()完成这部分的操作。
void env_relocate (void)
{
DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
gd->reloc_off);
#ifdef ENV_IS_EMBEDDED
/*
* The environment buffer is embedded with the text segment,
* just relocate the environment pointer
*/
env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);
DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#else
/*
* We must allocate a buffer for the environment
*/
//在malloc区分配大小为CFG_ENV_SIZE的空间,并将env_ptr指向该空间的起始地址。
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif
/*
* After relocation to RAM, we can always use the "memory" functions
*/
env_get_char = env_get_char_memory;
// gd->env_valid的值为1
if (gd->env_valid == 0) {
#if defined(CONFIG_GTH) || defined(CFG_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 (-1);
#endif
if (sizeof(default_environment) > ENV_SIZE)
{
puts ("*** Error - default environment is too large\n\n");
return;
}
memset (env_ptr, 0, sizeof(env_t));
memcpy (env_ptr->data,
default_environment,
sizeof(default_environment));
#ifdef CFG_REDUNDAND_ENVIRONMENT
env_ptr->flags = 0xFF;
#endif
env_crc_update ();
gd->env_valid = 1;
}
else {
// 从FLASH中读取环境变量的内容到SDRAM上。
env_relocate_spec ();
}
// gd->env_addr指向环境变量的第一个参数。
gd->env_addr = (ulong)&(env_ptr->data);
}
通过env_relocate_spec ()读取环境变量到SDRAM上。请注意,刚烧写的板子上仅仅给环境变量预留了一定大小的空间,并没有环境变量的内容。根据重定位函数,我们知道板子第一次开机时,读出来的环境变量进行CRC校验时肯定是失败的,会使用预存的默认环境变量。
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
ulong total = CFG_ENV_SIZE;
int ret;
// 读取FLASH上环境变量到env_ptr所指向的地址,若读取失败,则使用默认的环境变量。
ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return use_default();
// 校验CRC,若失败,则使用默认的环境变量。
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
static void use_default()
{
puts ("*** Warning - bad CRC or NAND, using default environment\n\n");
if (default_environment_size > CFG_ENV_SIZE){
puts ("*** Error - default environment is too large\n\n");
return;
}
memset (env_ptr, 0, sizeof(env_t));
memcpy (env_ptr->data,
default_environment,
default_environment_size);
// 计算CRC,并赋给SDRAM上的env_ptr->crc
env_ptr->crc = crc32(0, env_ptr->data, ENV_SIZE);
gd->env_valid = 1;
}
至此,环境变量的初始化就结束了,环境变量的值被拷贝到SDRAM,全局变量
gd->env_addr = (ulong)&(env_ptr->data); 指向了环境变量的第一个参数。
gd->env_valid = 1;
三、环境变量的保存
int saveenv(void)用来保存环境变量的。由于NAND FLASH的每个位只能从1->0,而不能相反,所以我们需要先对其擦除,然后再进行写操作。
int saveenv(void)
{
ulong total;
int ret = 0;
puts ("Erasing Nand...");
// 擦除FLASH,&nand_info[0]是NAND的起始地址,擦除操作从CFG_ENV_OFFSET开始,大小为CFG_ENV_SIZE。
if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE))
return 1;
puts ("Writing to Nand... ");
total = CFG_ENV_SIZE;
// 写FLASH,&nand_info[0]是NAND的起始地址,将env_ptr所指向的内容写到CFG_ENV_OFFSET开始的位置,大小为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的块擦除的大小为128K (0x20000),下面的宏定义保证了环境变量保存在单独的一个NAND 块上。
#define CFG_ENV_OFFSET 0x40000
#define CFG_ENV_SIZE 0x20000 /* Total Size of Environment Sector */
四、环境变量的读取
在u-boot运行时,我们可以使用U-boot命令配置环境变量而不是修改代码来控制U-boot的行为,如我们可以修改环境变量来配置开发板的IP地址。u-boot配置环境变量就是配置存储器上的某个地址的内容,读取这个地址的内容再进行接下来的操作。u-boot中读取环境变量的函数是common/cmd_nvedit.c下的
int getenv_r (char *name, char *buf, unsigned len)。
@para1: 需要读取的环境变量名
@para2: 若找到了该环境变量,将值存放到buf。
@para3: buf的长度
@return value: 若有该环境变量,返回值为环境变量参数的长度。否则返回-1
int getenv_r (char *name, char *buf, unsigned len)
{
int i, nxt;
for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
int val, n;
for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
if (nxt >= CFG_ENV_SIZE) {
return (-1);
}
}
if ((val=envmatch((uchar *)name, i)) < 0)
continue;
/* found; copy out */
n = 0;
while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')
;
if (len == n)
*buf = '\0';
return (n);
}
return (-1);
}
其中 env_get_char指向函数env_get_char_memory。
功能是获取环境变量中,索引为index处的字符。
uchar env_get_char_memory (int index)
{
if (gd->env_valid) {
// gd->env_addr是环境变量的首地址。
return ( *((uchar *)(gd->env_addr + index)) );
} else {
return ( default_environment[index] );
}
}