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] );
}
}