• uboot环境变量实现分析


    u-boot的环境变量用来存储一些经常使用的参数变量,uboot希望将环境变量存储在静态存储器中(如nand nor eeprom mmc)。

    其中有一些也是大家经常使用,有一些是使用人员自己定义的,更改这些名字会出现错误,下面的表中我们列出了一些常用的环境变量:

         bootdelay    执行自动启动的等候秒数
         baudrate     串口控制台的波特率
         netmask     以太网接口的掩码
         ethaddr       以太网卡的网卡物理地址
         bootfile        缺省的下载文件
         bootargs     传递给内核的启动参数
         bootcmd     自动启动时执行的命令
         serverip       服务器端的ip地址
         ipaddr         本地ip 地址
         stdin           标准输入设备
         stdout        标准输出设备
         stderr         标准出错设备

    上面这些是uboot默认存在的环境变量,uboot本身会使用这些环境变量来进行配置。我们可以自己定义一些环境变量来供我们自己uboot驱动来使用。

    Uboot环境变量的设计逻辑是在启动过程中将env从静态存储器中读出放到RAM中,之后在uboot下对env的操作(如printenv editenv setenv)都是对RAM中env的操作,只有在执行saveenv时才会将RAM中的env重新写入静态存储器中。

    这种设计逻辑可以加快对env的读写速度。

    基于这种设计逻辑,2014.4版本uboot实现了saveenv这个保存env到静态存储器的命令,而没有实现读取env到RAM的命令。

    那我们就来看一下uboot中env的数据结构 初始化 操作如何实现的。

    一 env数据结构

    在include/environment.h中定义了env_t,如下:

     
    1. #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT  
    2. # define ENV_HEADER_SIZE    (sizeof(uint32_t) + 1)  
    3. # define ACTIVE_FLAG   1  
    4. # define OBSOLETE_FLAG 0  
    5. #else  
    6. # define ENV_HEADER_SIZE    (sizeof(uint32_t))  
    7. #endif  
    8. #define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)  
    9. typedef struct environment_s {  
    10.     uint32_t    crc;        /* CRC32 over data bytes    */  
    11. #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT  
    12.     unsigned char   flags;      /* active/obsolete flags    */  
    13. #endif  
    14.     unsigned char   data[ENV_SIZE]; /* Environment data     */  
    15. } env_t;  

    CONFIG_ENV_SIZE是我们需要在配置文件中配置的环境变量的总长度。

    这里我们使用的nand作为静态存储器,nand的一个block是128K,因此选用一个block来存储env,CONFIG_ENV_SIZE为128K。

    Env_t结构体头4个bytes是对data的crc校验码,没有定义CONFIG_SYS_REDUNDAND_ENVIRONMENT,所以后面紧跟data数组,数组大小是ENV_SIZE.

    ENV_SIZE是CONFIG_ENV_SIZE减掉ENV_HEADER_SIZE,也就是4bytes,

    所以env_t这个结构体就包含了整个我们规定的长度为CONFIG_ENV_SIZE的存储区域。

    头4bytes是crc校验码,后面剩余的空间全部用来存储环境变量。

    需要说明的一点,crc校验码是uboot中在saveenv时计算出来,然后写入nand,所以在第一次启动uboot时crc校验会出错,

    因为uboot从nand上读入的一个block数据是随机的,没有意义的,执行saveenv后重启uboot,crc校验就正确了。

    data 字段保存实际的环境变量。u-boot  的 env  按 name=value””的方式存储,在所有env 的最后以””表示整个 env  的结束。

    新的name=value 对总是被添加到 env  数据块的末尾,当删除一个 name=value 对时,后面的环境变量将前移,对一个已经存在的环境变量的修改实际上先删除再插入。 
    u-boot 把env_t  的数据指针还保存在了另外一个地方,这就 
    是 gd_t  结构(不同平台有不同的 gd_t  结构 ),这里以ARM 为例仅列出和 env  相关的部分 

     
    1. typedef struct global_data   
    2. {   
    3.      …   
    4.      unsigned long env_off;        /* Relocation Offset */   
    5.      unsigned long env_addr;       /* Address of Environment struct ??? */   
    6.      unsigned long env_valid       /* Checksum of Environment valid */   
    7.      …   
    8. } gd_t;   

    二 env的初始化

    uboot中env的整个架构可以分为3层:

    (1) 命令层,如saveenv,setenv editenv这些命令的实现,还有如启动时调用的env_relocate函数。

    (2) 中间封装层,利用不同静态存储器特性封装出命令层需要使用的一些通用函数,如env_init,env_relocate_spec,saveenv这些函数。实现文件在common/env_xxx.c

    (3) 驱动层,实现不同静态存储器的读写擦等操作,这些是uboot下不同子系统都必须的。

    按照执行流顺序,首先分析一下uboot启动的env初始化过程。

    首先在board_init_f中调用init_sequence的env_init,这个函数是不同存储器实现的函数,nand中的实现如下:

     
    1. <span style="font-size:14px;">/* 
    2.  * This is called before nand_init() so we can't read NAND to 
    3.  * validate env data. 
    4.  * 
    5.  * Mark it OK for now. env_relocate() in env_common.c will call our 
    6.  * relocate function which does the real validation. 
    7.  * 
    8.  * When using a NAND boot image (like sequoia_nand), the environment 
    9.  * can be embedded or attached to the U-Boot image in NAND flash. 
    10.  * This way the SPL loads not only the U-Boot image from NAND but 
    11.  * also the environment. 
    12.  */  
    13. int env_init(void)  
    14. {  
    15.     gd->env_addr    = (ulong)&default_environment[0];  
    16.     gd->env_valid   = 1;  
    17.     return 0;  
    18. }</span>  

    从注释就基本可以看出这个函数的作用,因为env_init要早于静态存储器的初始化,所以无法进行env的读写,这里将gd中的env相关变量进行配置,

    默认设置env为valid。方便后面env_relocate函数进行真正的env从nand到ram的relocate。

    继续执行,在board_init_r中,如下:

     
    1. /* initialize environment */  
    2.     if (should_load_env())  
    3.         env_relocate();  
    4.     else  
    5.         set_default_env(NULL);  

    这是在所有存储器初始化完成后执行的。

    首先调用should_load_env,如下:

     
    1. /* 
    2.  * Tell if it's OK to load the environment early in boot. 
    3.  * 
    4.  * If CONFIG_OF_CONFIG is defined, we'll check with the FDT to see 
    5.  * if this is OK (defaulting to saying it's not OK). 
    6.  * 
    7.  * NOTE: Loading the environment early can be a bad idea if security is 
    8.  *       important, since no verification is done on the environment. 
    9.  * 
    10.  * @return 0 if environment should not be loaded, !=0 if it is ok to load 
    11.  */  
    12. static int should_load_env(void)  
    13. {  
    14. #ifdef CONFIG_OF_CONTROL  
    15.     return fdtdec_get_config_int(gd->fdt_blob, "load-environment", 1);  
    16. #elif defined CONFIG_DELAY_ENVIRONMENT  
    17.     return 0;  
    18. #else  
    19.     return 1;  
    20. #endif  
    21. }  

    从注释可以看出,CONFIG_OF_CONTROL没有定义,鉴于考虑安全性问题,如果我们想要推迟env的load,可以定义CONFIG_DELAY_ENVIRONMENT,这里返回0,就调用set_default_env使用默认的env,默认env是在配置文件中CONFIG_EXTRA_ENV_SETTINGS设置的。

    我们可以在之后的某个地方在调用env_relocate来load env。这里我们选择在这里直接load env。所以没有定义CONFIG_DELAY_ENVIRONMENT,返回1。调用env_relocate。

    在common/env_common.c中:

     
    1. void env_relocate(void)  
    2. {  
    3. #if defined(CONFIG_NEEDS_MANUAL_RELOC)  
    4.     env_reloc();  
    5.     env_htab.change_ok += gd->reloc_off;  
    6. #endif  
    7.     if (gd->env_valid == 0) {  
    8. #if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)  
    9.         /* Environment not changable */  
    10.         set_default_env(NULL);  
    11. #else  
    12.         bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);  
    13.         set_default_env("!bad CRC");  
    14. #endif  
    15.     } else {  
    16.         env_relocate_spec();  
    17.     }  
    18. }  
     

    Gd->env_valid在之前的env_init中设置为1,所以这里调用env_relocate_spec,

    这个函数也是不同存储器的中间封装层提供的函数,对于nand在common/env_nand.c中,如下:

     
    1. void env_relocate_spec(void)  
    2. {  
    3.    int ret;  
    4.     ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);  
    5.     ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);  
    6.     if (ret) {  
    7.         set_default_env("!readenv() failed");  
    8.         return;  
    9.     }  
    10.     env_import(buf, 1);  
    11. }   

    首先定义一个长度为CONFIG_ENV_SIZE的buf,然后调用readenv,

    CONFIG_ENV_OFFSET是在配置文件中定义的env在nand中偏移位置。我们这里定义的是在4M的位置。

    Readenv也在env_nand.c中,如下:

     
    1. int readenv(size_t offset, u_char *buf)  
    2. {  
    3.     size_t end = offset + CONFIG_ENV_RANGE;  
    4.     size_t amount_loaded = 0;  
    5.     size_t blocksize, len;  
    6.     u_char *char_ptr;  
    7.     blocksize = nand_info[0].erasesize;  
    8.     if (!blocksize)  
    9.         return 1;  
    10.     len = min(blocksize, CONFIG_ENV_SIZE);  
    11.     while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {  
    12.         if (nand_block_isbad(&nand_info[0], offset)) {  
    13.             offset += blocksize;  
    14.         } else {  
    15.             char_ptr = &buf[amount_loaded];  
    16.             if (nand_read_skip_bad(&nand_info[0], offset,  
    17.                            &len, NULL,  
    18.                            nand_info[0].size, char_ptr))  
    19.                 return 1;  
    20.             offset += blocksize;  
    21.             amount_loaded += len;  
    22.         }  
    23.     }  
    24.   
    25.     if (amount_loaded != CONFIG_ENV_SIZE)  
    26.         return 1;  
    27.   
    28.     return 0;  
    29. }  

    Readenv函数利用nand_info[0]对nand进行读操作,读出指定位置,指定长度的数据到buf中。Nand_info[0]是一个全局变量,来表征第一个nand device,这里在nand_init时会初始化这个变量。Nand_init必须在env_relocate之前。

    回到env_relocate_spec中,buf读回后调用env_import,如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /* 
    2.  * Check if CRC is valid and (if yes) import the environment. 
    3.  * Note that "buf" may or may not be aligned. 
    4.  */  
    5. int env_import(const char *buf, int check)  
    6. {  
    7.     env_t *ep = (env_t *)buf;  
    8.   
    9.     if (check) {  
    10.         uint32_t crc;  
    11.   
    12.         memcpy(&crc, &ep->crc, sizeof(crc));  
    13.   
    14.         if (crc32(0, ep->data, ENV_SIZE) != crc) {  
    15.             set_default_env("!bad CRC");  
    16.             return 0;  
    17.         }  
    18.     }  
    19.   
    20.     if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '', 0,  
    21.             0, NULL)) {  
    22.         gd->flags |= GD_FLG_ENV_READY;  
    23.         return 1;  
    24.     }  
    25.   
    26.     error("Cannot import environment: errno = %d ", errno);  
    27.   
    28.     set_default_env("!import failed");  
    29.   
    30.     return 0;  
    31. }  

    首先将buf强制转换为env_t类型,然后对data进行crc校验,跟buf中原有的crc对比,不一致则使用默认env。

    最后调用himport_r,该函数将给出的data按照‘’分割填入env_htab的哈希表中。

    之后对于env的操作,如printenv setenv editenv,都是对该哈希表的操作。

    Env_relocate执行完成,env的初始化就完成了。

    三 env的操作实现

    Uboot对env的操作命令实现在common/cmd_nvedit.c中。

    对于setenv printenv editenv这3个命令,看其实现代码,都是对relocate到RAM中的env_htab的操作,这里就不再详细分析了,重点来看一下savenv实现。

     
    1. static int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc,  
    2.                char * const argv[])  
    3. {  
    4.     printf("Saving Environment to %s... ", env_name_spec);  
    5.   
    6.     return saveenv() ? 1 : 0;  
    7. }  
    8.   
    9. U_BOOT_CMD(  
    10.     saveenv, 1, 0,  do_env_save,  
    11.     "save environment variables to persistent storage",  
    12.     ""  
    13. );  

    在do_env_save调用saveenv,这个函数是不同存储器实现的封装层函数。对于nand,在common/env_nand.c中,如下:

     
    1. int saveenv(void)  
    2. {  
    3.     int ret = 0;  
    4.     ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);  
    5.     ssize_t len;  
    6.     char    *res;  
    7.     int env_idx = 0;  
    8.     static const struct env_location location[] = {  
    9.         {  
    10.             .name = "NAND",  
    11.             .erase_opts = {  
    12.                 .length = CONFIG_ENV_RANGE,  
    13.                 .offset = CONFIG_ENV_OFFSET,  
    14.             },  
    15.         },  
    16. #ifdef CONFIG_ENV_OFFSET_REDUND  
    17.         {  
    18.             .name = "redundant NAND",  
    19.             .erase_opts = {  
    20.                 .length = CONFIG_ENV_RANGE,  
    21.                 .offset = CONFIG_ENV_OFFSET_REDUND,  
    22.             },  
    23.         },  
    24. #endif  
    25.     };  
    26.   
    27.     if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)  
    28.         return 1;  
    29.   
    30.     res = (char *)&env_new->data;  
    31.     len = hexport_r(&env_htab, '', 0, &res, ENV_SIZE, 0, NULL);  
    32.     if (len < 0) {  
    33.         error("Cannot export environment: errno = %d ", errno);  
    34.         return 1;  
    35.     }  
    36.     env_new->crc   = crc32(0, env_new->data, ENV_SIZE);  
    37. #ifdef CONFIG_ENV_OFFSET_REDUND  
    38.     env_new->flags = ++env_flags; /* increase the serial */  
    39.     env_idx = (gd->env_valid == 1);  
    40. #endif  
    41.   
    42.     ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);  
    43. #ifdef CONFIG_ENV_OFFSET_REDUND  
    44.     if (!ret) {  
    45.         /* preset other copy for next write */  
    46.         gd->env_valid = gd->env_valid == 2 ? 1 : 2;  
    47.         return ret;  
    48.     }  
    49.   
    50.     env_idx = (env_idx + 1) & 1;  
    51.     ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);  
    52.     if (!ret)  
    53.         printf("Warning: primary env write failed,"  
    54.                 " redundancy is lost! ");  
    55. #endif  
    56.   
    57.     return ret;  
    58. }  

    定义env_t类型的变量env_new,准备来存储env。

    利用函数hexport_r对env_htab操作,读取env内容到env_new->data,

    校验data,获取校验码env_new->crc。

    最后调用erase_and_write_env将env_new先擦后写入由location定义的偏移量和长度的nand区域中。

    这样就完成了env写入nand的操作。

    在savenv readenv函数以及printenv setenv的实现函数中涉及到的函数himport_r hexport_r hdelete_r hmatch_r都是对env_htab哈希表的一些基本操作函数。

  • 相关阅读:
    【ZJOI2007】矩阵游戏
    【洛谷1402】酒店之王
    【洛谷2756】飞行员配对方案问题
    【BZOJ2125】最短路
    【SDOI2018】战略游戏
    【APIO2018】铁人两项
    【FJOI2014】最短路径树问题
    【GXOI/GZOI2019】旅行者
    【Cerc2012】Farm and factory
    【CERC2017】Gambling Guide
  • 原文地址:https://www.cnblogs.com/yangv/p/5731907.html
Copyright © 2020-2023  润新知