U-Boot源码之环境变量

it2023-06-08  75

U-Boot通过环境变量(env)为用户提供一定程度的可配置性,这些环境变量包括串口终端所使用的波特率(baudrate)、启动操作系统内核的参数(bootargs)、本地IP地址(ipaddr)、网卡MAC地址(ethaddr)等等。环境变量可以固化到非易失性存储介质中,使用相关命令来查看、设置及保存环境变量。

一、环境变量的命令操作

1.查看环境变量

使用 printenv 命令即可查看 U-Boot 的所有环境变量:

MX28 U-Boot > printenv bootcmd=run nand_boot bootdelay=0 baudrate=115200 ipaddr=192.168.1.87 netmask=255.255.255.0 bootfile="uImage" loadaddr=0x41600000 „„省略„„ serverip=192.168.1.94 mem=64M stdin=serial stdout=serial stderr=serial ver=U-Boot 2009.08 ( 3 月  11 2015 - 18:40:57) Environment size: 1656/131068 bytes

当要查看具体哪个环境变量的值时,只需要在 printenv 命令加上环境变量名参数即可。例如,若要查看 serverip 环境变量的值,方法如下: MX28 U-Boot > printenv serverip serverip=192.168.1.94

2.使用环境变量

环境变量的作用是字符串替换。若已经定义了 serverip 环境变量,那么引用该环境的方法为: $(serverip)

例如,若要在 U-Boot 下探测网络上是否有 192.168.1.94 的 IP 时,可以使用如下命令: MX28 U-Boot > ping 192.168.1.94        若设置了 serverip 环境变量的值为 192.168.1.94,那么 ping 命令可改成: MX28 U-Boot > ping $(serverip) 

3.设置环境变量

使用 setenv 命令可以设置环境变量。setenv 命令的格式如下: MX28 U-Boot > setenv  环境变量名  环境变量值

当使用 setenv 命令时,若环境变量名不存在,该命令会添加这个新的环境变量;若环境变量已经存在,该命令则修改环境变量的值。

例如,设置 serverip 环境变量的值为 192.168.1.94 的方法为: MX28 U-Boot > setenv   serverip 192.168.1.94

注意 setenv 命令设置的环境变量仅保存在内存中,当 U-Boot 重新启动时,设置内容将丢失。若需要保存所设置的环境变量,可使用 saveenv 命令:

MX28 U-Boot > saveenv Saving Environment to NAND... Erasing Nand... NAND Erasing at 0x0000000000100000 -- 100% complete. Writing to Nand... done        saveenv 命令会把所有的环境变量都一起保存在非易失性储存器中。

4.特殊环境变量

U-Boot 有一些特殊的环境在使用中经常用到,如表1所示。

                                                                                             表1 特殊环境变量

二、环境变量的实现

下面以u-boot-2009.08版本介绍环境变量的实现。

1、相关文件

(1)common/env_common.c

供u-boot调用的通用函数接口,它们隐藏了env的不同实现方式,比如dataflash, epprom, flash等。

(2)common/env_dataflash.c、env_epprom.c、env_flash.c 、env_nand.c、env_mgdisk.c、env_mcc.c、env_nvram等

      初始化环境变量、读写环境变量、保存环境变量、重定位环境变量在dataflash、epprom、nor-flash、nand-flash、磁盘、SD卡、NV-ram中的具体实现。

(3)include/environment.h

       环境变量结构体及一些宏定义。

(4)include/configs目录下对应开发板的xxx.h

该文件定义了配置环境变量相关的宏定义,比如:CONFIG_ENV_IS_IN_NAND、CONFIG_ENV_OFFSET、CONFIG_ENV_SIZE等。

2、环境变量的配置

(1)CONFIG_ENV_IS_IN_xxx

       配置环境变量的存储介质:

CONFIG_ENV_IS_IN_EEPROM 

CONFIG_ENV_IS_IN_FLASH

CONFIG_ENV_IS_IN_DATAFLASH

CONFIG_ENV_IS_IN_MG_DISK

CONFIG_ENV_IS_IN_NAND

CONFIG_ENV_IS_IN_NVRAM

CONFIG_ENV_IS_IN_ONENAND

CONFIG_ENV_IS_IN_SPI_FLASH

CONFIG_ENV_IS_IN_MMC

CONFIG_ENV_IS_NOWHERE

(2)CONFIG_CMD_ENV

        配置是否支持环境变量相关命令

(3)CONFIG_ENV_OVERWRITE     

      配置某些环境变量(比如:serial#、ethaddr)是否允许更改。

(4)CONFIG_ENV_ADDR

       环境变量的主存储区首地址。

(5)CONFIG_ENV_OFFSET

       环境变量的主存储区的地址偏移量(相对于存储器的基地址)。

(6)CONFIG_ENV_SIZE / CONFIG_ENV_SECT_SIZE

       环境变量的大小(字节数)。

       存储环境变量的Flash扇区大小。     

(7)CONFIG_ENV_ADDR_REDUND 

         环境变量的备份存储区首地址。     

(8)CONFIG_ENV_OFFSET_REDUND

       环境变量的备份存储区的地址偏移量(相对于存储器的基地址)。

(9)CONFIG_ENV_SIZE_REDUND

        同CONFIG_ENV_SIZE。

(10)CONFIG_ENV_IS_EMBEDDED

       配置环境变量是否嵌入至U-boot映像。

(11)CONFIG_SYS_MONITOR_BASE / CONFIG_SYS_MONITOR_LEN

(12)/common/env_common.c文件中的default_environment数组里的缺省环境变量值

       CONFIG_BOOTARGS

       CONFIG_BOOTCOMMAND

       ...

       CONFIG_EXTRA_ENV_SETTINGS      

3、数据结构

       在include/environment.h 中定义了表示环境变量env的数据结构。

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;

       crc是u-boot在保存env的时候加上去的校验头(CRC32),在第一次启动时一般 crc 校验会出错,这很正常,因为这时 Flash 中的数据无效。

      环境变量可以同时存储在永久性存储介质中的两个独立区域,一个是主存储区,另一个是备份存储区。当两个存储区的环境变量都有效的时候,需要根据flags决定使用哪个存储区的环境变量。

       data成员保存实际的环境变量。u-boot 的 env 按 name=value”\0”的方式存储,在所有env的最后以”\0\0”表示整个 env  的结束。新的name=value对总是被添加到 env 数据块的末尾,当删除一个name=value对时,后面的环境变量将前移,对一个已经存在的环境变量的修改实际上先删除再插入。

       ENV_SIZE宏定义位于include/environment.h:

#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)

CONFIG_ENV_SIZE定义在include/configs目录下对应开发板的xxx.h文件中。

       ENV_HEADER_SIZE宏定义位于include/environment.h:

#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT # define ENV_HEADER_SIZE (sizeof(uint32_t) + 1) #else # define ENV_HEADER_SIZE (sizeof(uint32_t)) #endif

当环境变量无备份存储区时,ENV_HEADER_SIZE是CRC32值的大小,为4个字节。当环境变量有备份存储区时,ENV_HEADER_SIZE是CRC32值的大小和一个字节的flags大小总和。

       CONFIG_SYS_REDUNDAND_ENVIRONMENT宏定义位于include/environment.h:

# ifdef CONFIG_ENV_OFFSET_REDUND # define CONFIG_SYS_REDUNDAND_ENVIRONMENT # endif

根据有无定义CONFIG_ENV_OFFSET_REDUND来定义CONFIG_SYS_REDUNDAND_ENVIRONMENT的存在。CONFIG_ENV_OFFSET_REDUND定义在include/configs目录下对应开发板的xxx.h文件中。

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 reloc_off; /* Relocation Offset */ unsigned long env_addr; /* Address of Environment struct ??? */ unsigned long env_valid /* Checksum of Environment valid */ … } gd_t;

env_addr成员指向 env_ptr->data。env_valid成员指示环境变量crc校验是否通过。env_off成员表示环境变量重定位到RAM中的地址偏移量(相对RAM基地址)。

4、环境变量的初始化

        初始化环境变量要完成的工作主要是从 flash 等存储设备读取环境变量,更新env_ptr各个成员的值,更新gd_t 结构中环境变量部分的成员值。

环境变量的初始化流程图如图1所示:

                                                        图1 环境变量的初始化流程图

上面流程图中的各个函数所处的位置如下:

start_armboot : lib_arm/board.c

env_init : common/env_xxx.c( xxx = nand | flash | epprom … )

env_relocate : common/env_common.c

env_relocate_spec : common/env_xxx.c( xxx = nand | flash | epprom … )

(1)env_init

       env_init函数被start_armboot函数调用:

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } }

init_sequence数组存放着env_init函数指针。

       环境变量在Flash等非易失性存储器中存储的方式有三种:第一种,环境变量存储在一个专门的Flash区域,该区域与u-boot映像存储区域相互独立。第二种,环境变量存储在两个专门的Flash区域,其中一个区域称为主存储区,另一个区域称为备份存储区,这两个区域与u-boot映像存储区域相互独立。第三种,环境变量存储在一个专门的Flash区域,该区域位于u-boot映像存储区域,即环境变量嵌入在u-boot映像中。第四种,环境变量存储在两个专门的Flash区域,其中一个区域称为主存储区,另一个区域称为备份存储区,这两个区域都位于u-boot映像存储区域,即环境变量嵌入在u-boot映像中。

对于第一种和第二种存储方式,由于env_init函数运行之前还未初始化Flash等非易失性存储器,所以此时还不能从Flash等非易失性存储器中把环境变量读取出来(要等在env_relocate函数完成环境变量的读取和校验)。env_init函数只是把gd_t 结构体中的env_addr成员值设置为缺省环境变量default_environment的指针,并且把gd_t 结构体中的env_valid成员值设置为1,即标记环境变量为有效。

对于第三种存储方式,由于在上电复位加载u-boot映像至RAM时,已经把环境变量读取至RAM了,所以只需对读取至RAM中的环境变量进行校验检查。

对于第四种存储方式,类似第三种存储方式的处理,只是要根据两份环境变量的校验检查结果和flags值决定使用哪一份环境变量。

环境变量嵌入至u-boot映像的存储方式的实现细节参考博文https://www.cnblogs.com/amanlikethis/p/3449347.html。

(2)env_relocate

对于第一种和第二种存储方式,env_relocate函数分配一块CONFIG_ENV_SIZE大小的内存用于存放从Flash等读取的环境变量。如果环境变量校验失败,则使用缺省环境变量。

从Flash等读取环境变量的实现由env_relocate_spec函数实现。

对于第三种和第四种存储方式,只需调整下env_ptr的值。

(3)env_relocate_spec

不同的非易失性存储器,env_relocate_spec有着不同的实现,这里仅分析Nand Flash的env_relocate_spec实现。

对于第一种存储方式,env_relocate_spec函数从nand flash偏移CONFIG_ENV_OFFSET处读取环境变量至RAM中,并对环境变量进行校验,如果环境变量读取失败或校验失败,则使用缺省环境变量。

对于第二种存储方式,env_relocate_spec函数分配两块CONFIG_ENV_SIZE大小的内存用于存放从Flash等读取的环境变量。如果两个存储区域的环境变量都读取成功,则根据校验结果和flags值来决定使用哪份环境变量。

对于第三种和第四种存储方式,env_relocate_spec函数内容为空。

5、环境变量的保存

环境变量将从永久性存储介质中搬到RAM里面,以后对env 的操作,比如修改环境变量的值,删除环境变量的值都是对这个 env 在RAM中的拷贝进行操作,由于RAM的特性,下次启动时所做的修改将全部消失,u-boot提供了将env 写回永久性存储介质的命令支持 :  saveenv,不同版本的saveenv( nand  flash,  flash  … ) 实 现 方 式 不 同 , 这里以Nand Flash的实现为例。

对于不包含备份存储区的情况,saveenv函数先调用nand_erase_opts函数擦除Flash,然后调用writeenv函数把环境变量写入Flash。

对于包含备份存储区的情况,首先要根据gd->env_valid的值确定环境变量在Flash中的存储地址,即主存储区还是备份存储区。然后再调用nand_erase_opts函数擦除Flash,最后调用writeenv函数把环境变量写入Flash。

6、环境变量的查看

       使用 printenv 命令即可查看 U-Boot 的所有环境变量。当要查看具体哪个环境变量的值时,只需要在 printenv 命令加上环境变量名参数即可。例如,若要查看 serverip 环境变量的值,方法如下: MX28 U-Boot > printenv serverip serverip=192.168.1.94       

printenv 命令是由common/cmd_nvedit.c文件中的do_printenv函数实现的,do_printenv函数调用printenv函数具体实现。

7、环境变量的设置

如前所述,使用 setenv 命令可以设置环境变量。setenv 命令在common/cmd_nvedit.c文件中的setenv函数实现,而setenv函数调用_do_setenv函数具体实现。

理解_do_setenv函数的关键是要清楚环境变量在内存中的存放格式。环境变量在内存中的存放格式是:

                                name1=value1\0name2=value2\0name3=value3\0 ... namen=valuen\0\0

       上面的name1、=、value1、\0都是字符,name1是环境变量名,value1是环境变量值,每组环境变量之间用\0字符隔开,最后一组环境变量以两个\0字符结束,表示整个环境变量的结束符。

       环境变量在内存中的存放格式的范例是common/env_common.c中的default_environment数组。default_environment数组存放缺省环境变量。default_environment数组实现的关键是利用编译器编译时可以将多个字符串常量连接起来,例如。下列形式:

       "hello,"   "world"

等价于

       "hello, world"

       default_environment数组出现了宏定义MK_STR,宏定义MK_STR是使用#把宏参数变为一个字符串。

       _do_setenv函数的处理流程是,先查找setenv命令的环境变量是否已经存在,若存在则先删除掉(删除环境变量之前需要判断此环境变量是否允许删除),同时把后面的向前移动,覆盖已经删除的位置。即删除原有的,把要更改的那个环境变量放到环境变量区的最后面。若是新增加的环境变量,则不需要删除,直接在最后的环境变量后面添加即可。

       setenv命令是不需要写等号的  即:setenv bootdelay 10 ,对写等号的使用者会打印使用错误信息。

name = argv[1]; if (strchr(name, '=')) { printf ("## Error: illegal character '=' in variable name \"%s\"\n", name); return 1; }

查找setenv命令的环境变量是否已经存在,存在则oldval为正数。同时若存在则env最终指向那个环境变量的开头,nxt指向那个环境变量的结尾。若不存在,则env和nxt都指向环境变量区最后一个环境变量。整个过程中env_data是始终指向环境变量分区的首地址。

/* * search if variable with this name already exists */ oldval = -1; for (env=env_data; *env; env=nxt+1) { for (nxt=env; *nxt; ++nxt) ; if ((oldval = envmatch((uchar *)name, env-env_data)) >= 0) break; }

删除环境变量时,先判断待删除的环境变量是不是最后一个环境变量,如果是的话则不做任何处理。如果不是的话,则直接把后面的环境变量统一向前移动,同时最后一个环境变量后面要多增加一个'\0'.。

if (*++nxt == '\0') { // 最后一个环境变量直接删除 if (env > env_data) { // 环境变量区有多个环境变量的情况 env--; } else { // 环境变量区只有一个环境变量的情况 *env = '\0'; } } else { // 非最后一个,则直接把后面的环境变量统一向前移动,以把待删除的环境变量覆盖掉 for (;;) { *env = *nxt++; if ((*env == '\0') && (*nxt == '\0')) break; ++env; } } *++env = '\0'; // 最后一个环境变量后面要多增加一个'\0'. }

       如果setenv命令不带环境变量值,即setenv  envname,则表示是删除环境变量的命令。 

/* Delete only ? */ if ((argc < 3) || argv[2] == NULL) { env_crc_update (); return 0; }

       然后把env指向最后一个环境变量的后面。

/* * Append new definition at the end */ for (env=env_data; *env || *(env+1); ++env) ; if (env > env_data) ++env;

       接着判断新增加的环境变量是否会超过环境变量分区。

/* * Overflow when: * "name" + "=" + "val" +"\0\0" > ENV_SIZE - (env-env_data) */ len = strlen(name) + 2; /* add '=' for first arg, ' ' for all others */ for (i=2; i<argc; ++i) { len += strlen(argv[i]) + 1; } if (len > (&env_data[ENV_SIZE]-env)) { printf ("## Error: environment overflow, \"%s\" deleted\n", name); return 1; }

       经过前面的判断,开始真正的写环境变量。  写完后更新crc值。

while ((*env = *name++) != '\0') env++; for (i=2; i<argc; ++i) { char *val = argv[i]; *env = (i==2) ? '=' : ' '; while ((*++env = *val++) != '\0') ; } /* end is marked with double '\0' */ *++env = '\0'; /* Update CRC */ env_crc_update ();

       

最新回复(0)