uboot环境变量实现分析_loongembedded的博客-程序员秘密

技术标签: Android Bootloader  

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)都是对RAMenv的操作,只有在执行saveenv时才会将RAM中的env重新写入静态存储器中。

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

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

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

一 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;  
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
# define ENV_HEADER_SIZE    (sizeof(uint32_t) + 1)
# define ACTIVE_FLAG   1
# define OBSOLETE_FLAG 0
#else
# define ENV_HEADER_SIZE    (sizeof(uint32_t))
#endif
#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
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;

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

这里我们使用的nand作为静态存储器,nand的一个block128K,因此选用一个block来存储envCONFIG_ENV_SIZE128K

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

ENV_SIZECONFIG_ENV_SIZE减掉ENV_HEADER_SIZE,也就是4bytes

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

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

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

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

data 字段保存实际的环境变量。u-boot  的 env  按 name=value”\0”的方式存储,在所有env 的最后以”\0\0”表示整个 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;   
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; 


二 env的初始化

ubootenv的整个架构可以分为3层:

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

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

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

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

首先在board_init_f中调用init_sequenceenv_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>  
<span style="font-size:14px;">/*
 * This is called before nand_init() so we can't read NAND to
 * validate env data.
 *
 * Mark it OK for now. env_relocate() in env_common.c will call our
 * relocate function which does the real validation.
 *
 * When using a NAND boot image (like sequoia_nand), the environment
 * can be embedded or attached to the U-Boot image in NAND flash.
 * This way the SPL loads not only the U-Boot image from NAND but
 * also the environment.
 */
int env_init(void)
{
    gd->env_addr    = (ulong)&default_environment[0];
    gd->env_valid   = 1;
    return 0;
}</span>

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

默认设置envvalid。方便后面env_relocate函数进行真正的envnandramrelocate

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

  1. /* initialize environment */  
  2.     if (should_load_env())  
  3.         env_relocate();  
  4.     else  
  5.         set_default_env(NULL);  
/* initialize environment */
    if (should_load_env())
        env_relocate();
    else
        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. }  
/*
 * Tell if it's OK to load the environment early in boot.
 *
 * If CONFIG_OF_CONFIG is defined, we'll check with the FDT to see
 * if this is OK (defaulting to saying it's not OK).
 *
 * NOTE: Loading the environment early can be a bad idea if security is
 *       important, since no verification is done on the environment.
 *
 * @return 0 if environment should not be loaded, !=0 if it is ok to load
 */
static int should_load_env(void)
{
#ifdef CONFIG_OF_CONTROL
    return fdtdec_get_config_int(gd->fdt_blob, "load-environment", 1);
#elif defined CONFIG_DELAY_ENVIRONMENT
    return 0;
#else
    return 1;
#endif
}

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

我们可以在之后的某个地方在调用env_relocateload 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. }  
void env_relocate(void)
{
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
    env_reloc();
    env_htab.change_ok += gd->reloc_off;
#endif
    if (gd->env_valid == 0) {
#if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)
        /* Environment not changable */
        set_default_env(NULL);
#else
        bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);
        set_default_env("!bad CRC");
#endif
    } else {
        env_relocate_spec();
    }
}
  1.   
</pre><p style="font-size: 14px;">Gd->env_valid<span style="font-family: 宋体;">在之前的</span><span style="font-family: Verdana;">env_init</span><span style="font-family: 宋体;">中设置为</span><span style="font-family: Verdana;">1</span><span style="font-family: 宋体;">,所以这里调用</span><span style="font-family: Verdana;">env_relocate_spec</span><span style="font-family: 宋体;">,</span></p><p style="font-size: 14px;"><span style="font-family: 宋体;">这个函数也是不同存储器的中间封装层提供的函数,对于</span><span style="font-family: Verdana;">nand</span><span style="font-family: 宋体;">在</span><span style="font-family: Verdana;">common/env_nand.c</span><span style="font-family: 宋体;">中,如下:</span></p><div class="dp-highlighter bg_cpp"><div class="bar"><div class="tools"><strong>[cpp]</strong> <a target=_blank title="view plain" class="ViewSource" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">view plain</a><a target=_blank title="copy" class="CopyToClipboard" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">copy</a><a target=_blank title="print" class="PrintSource" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">print</a><a target=_blank title="?" class="About" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">?</a><a target=_blank title="在CODE上查看代码片" style="text-indent: 0px;" href="https://code.csdn.net/snippets/462607" target="_blank"><img width="12" height="12" style="left: 2px; top: 1px; position: relative;" alt="在CODE上查看代码片" src="https://code.csdn.net/assets/CODE_ico.png" /></a><a target=_blank title="派生到我的代码片" style="text-indent: 0px;" href="https://code.csdn.net/snippets/462607/fork" target="_blank"><img width="12" height="12" style="left: 2px; top: 2px; position: relative;" alt="派生到我的代码片" src="https://code.csdn.net/assets/ico_fork.svg" /></a></div></div><ol class="dp-cpp"><li class="alt"><span><span class="keyword">void</span><span> env_relocate_spec(</span><span class="keyword">void</span><span>)  </span></span></li><li><span>{  </span></li><li class="alt"><span>   <span class="datatypes">int</span><span> ret;  </span></span></li><li><span>    ALLOC_CACHE_ALIGN_BUFFER(<span class="datatypes">char</span><span>, buf, CONFIG_ENV_SIZE);  </span></span></li><li class="alt"><span>    ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);  </span></li><li><span>    <span class="keyword">if</span><span> (ret) {  </span></span></li><li class="alt"><span>        set_default_env(<span class="string">"!readenv() failed"</span><span>);  </span></span></li><li><span>        <span class="keyword">return</span><span>;  </span></span></li><li class="alt"><span>    }  </span></li><li><span>    env_import(buf, 1);  </span></li><li class="alt"><span>}   </span></li></ol></div><pre class="cpp" style="font-size: 14px; display: none;" name="code" code_snippet_id="462607" snippet_file_name="blog_20140902_8_4316261">void env_relocate_spec(void)
{
   int ret;
    ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
    ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
    if (ret) {
        set_default_env("!readenv() failed");
        return;
    }
    env_import(buf, 1);
} 

首先定义一个长度为CONFIG_ENV_SIZEbuf,然后调用readenv

CONFIG_ENV_OFFSET是在配置文件中定义的envnand中偏移位置。我们这里定义的是在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. }  
int readenv(size_t offset, u_char *buf)
{
    size_t end = offset + CONFIG_ENV_RANGE;
    size_t amount_loaded = 0;
    size_t blocksize, len;
    u_char *char_ptr;
    blocksize = nand_info[0].erasesize;
    if (!blocksize)
        return 1;
    len = min(blocksize, CONFIG_ENV_SIZE);
    while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
        if (nand_block_isbad(&nand_info[0], offset)) {
            offset += blocksize;
        } else {
            char_ptr = &buf[amount_loaded];
            if (nand_read_skip_bad(&nand_info[0], offset,
                           &len, NULL,
                           nand_info[0].size, char_ptr))
                return 1;
            offset += blocksize;
            amount_loaded += len;
        }
    }

    if (amount_loaded != CONFIG_ENV_SIZE)
        return 1;

    return 0;
}

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

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

  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', 0,  
  21.             0, NULL)) {  
  22.         gd->flags |= GD_FLG_ENV_READY;  
  23.         return 1;  
  24.     }  
  25.   
  26.     error("Cannot import environment: errno = %d\n", errno);  
  27.   
  28.     set_default_env("!import failed");  
  29.   
  30.     return 0;  
  31. }  
/*
 * Check if CRC is valid and (if yes) import the environment.
 * Note that "buf" may or may not be aligned.
 */
int env_import(const char *buf, int check)
{
    env_t *ep = (env_t *)buf;

    if (check) {
        uint32_t crc;

        memcpy(&crc, &ep->crc, sizeof(crc));

        if (crc32(0, ep->data, ENV_SIZE) != crc) {
            set_default_env("!bad CRC");
            return 0;
        }
    }

    if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0,
            0, NULL)) {
        gd->flags |= GD_FLG_ENV_READY;
        return 1;
    }

    error("Cannot import environment: errno = %d\n", errno);

    set_default_env("!import failed");

    return 0;
}

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

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

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

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


三 env的操作实现

Ubootenv的操作命令实现在common/cmd_nvedit.c中。

对于setenv printenv editenv3个命令,看其实现代码,都是对relocateRAM中的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...\n", 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. );  
static int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc,
               char * const argv[])
{
    printf("Saving Environment to %s...\n", env_name_spec);

    return saveenv() ? 1 : 0;
}

U_BOOT_CMD(
    saveenv, 1, 0,  do_env_save,
    "save environment variables to persistent storage",
    ""
);

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', 0, &res, ENV_SIZE, 0, NULL);  
  32.     if (len < 0) {  
  33.         error("Cannot export environment: errno = %d\n", 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!\n");  
  55. #endif  
  56.   
  57.     return ret;  
  58. }  
int saveenv(void)
{
    int ret = 0;
    ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
    ssize_t len;
    char    *res;
    int env_idx = 0;
    static const struct env_location location[] = {
        {
            .name = "NAND",
            .erase_opts = {
                .length = CONFIG_ENV_RANGE,
                .offset = CONFIG_ENV_OFFSET,
            },
        },
#ifdef CONFIG_ENV_OFFSET_REDUND
        {
            .name = "redundant NAND",
            .erase_opts = {
                .length = CONFIG_ENV_RANGE,
                .offset = CONFIG_ENV_OFFSET_REDUND,
            },
        },
#endif
    };

    if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
        return 1;

    res = (char *)&env_new->data;
    len = hexport_r(&env_htab, '\0', 0, &res, ENV_SIZE, 0, NULL);
    if (len < 0) {
        error("Cannot export environment: errno = %d\n", errno);
        return 1;
    }
    env_new->crc   = crc32(0, env_new->data, ENV_SIZE);
#ifdef CONFIG_ENV_OFFSET_REDUND
    env_new->flags = ++env_flags; /* increase the serial */
    env_idx = (gd->env_valid == 1);
#endif

    ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
#ifdef CONFIG_ENV_OFFSET_REDUND
    if (!ret) {
        /* preset other copy for next write */
        gd->env_valid = gd->env_valid == 2 ? 1 : 2;
        return ret;
    }

    env_idx = (env_idx + 1) & 1;
    ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
    if (!ret)
        printf("Warning: primary env write failed,"
                " redundancy is lost!\n");
#endif

    return ret;
}

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

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

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

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

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

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

这些函数都封装在uboot的lib/hashtable.c中,这里就不仔细分析这些函数了。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/LoongEmbedded/article/details/43635771

智能推荐

CSS3动画效果-animate.css_weixin_34237596的博客-程序员秘密

为什么80%的码农都做不了架构师?&gt;&gt;&gt; ...

IndexError: tuple index out of range_我又可以了的博客-程序员秘密

1.如果想把CUDA tensor格式的数据改成numpy时,需要先将其转换成cpu float-tensor随后再转到numpy格式。 numpy不能读取CUDA tensor 需要将它转化为 CPU tensor2. predicted.shape[0]通过安装导入numpy库,矩阵(ndarray)的shape属性可以获取矩阵的形状(例如二维数组的行列),获取的结果是一个元组,因此相关代码如下:import numpy as npx = np.array([[1,2,5],[2,3,5].

Django中的ORM及操作_django orm 或_tmccmt的博客-程序员秘密

1 ORM对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单说,ORM通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。ORM在业务逻辑层和数据库层之间充当了桥梁的作用。​2 Django中的ORM2.1 model​ 在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表

升级spring1.2.8.jar到 spring2.5.jar对应的bean.xml文件没做升级出现的错误_pcjavanet的博客-程序员秘密

[ INFO 2008-12-05 16:02:25 org.springframework.core.CollectionFactory - 66 - ] : JDK 1.4+ collections available[ INFO 2008-12-05 16:02:25 org.springframework.beans.factory.xml.XmlBeanDefinitionReader

分享:MSDN visual studio 2010简体中文旗舰版,专业版下载(内置正版密钥)_xiaoniba1024的博客-程序员秘密

Visual Studio是微软公司推出的开发环境,是一套完整的开发工具集,用于生成 ASP.NET Web 应用程序、XML Web Services、桌面应用程序和移动应用程序。是目前最流行的Windows 平台应用程序开发环境。伴随着office 2010的问世,Visual Studio 2010也正式发布,现分享微软MSDNVisual Studio 2010旗舰版,专业版下载,程序

从菜鸟到架构师(十八)_lynnlovemin的博客-程序员秘密

把这个社交APP成功接下来后,我们就开始了开发。        当时我和袁浩在项目上有些分歧,有点闹僵的感觉,我也对他有点失望,在公司里把任务分配给下面的员工后,悄悄的做这个项目。        没过多久,公司有一个员工给袁浩打小报告,说的就是我在公司里做私活。        袁浩找到我:“你做私活做的安逸哇。”        “我什么时候做过?”我并没有承认。        加之我之前招了一个很

随便推点

Centos 7服务器配置笔记-1安装及更新_weixin_30342209的博客-程序员秘密

Centos 7服务器配置笔记-1安装及更新  基于部门技术发展考虑,准备慢慢将服务器操作系统从windows迁移到linux平台,在查阅了网上一些资料后,选择centos为主要应用平台。刚好阿里云在搞全民上网活动,所以免费申请了两台服务器,折腾了一把centos7的安装配置。这份笔记是配置过程中的笔记的整理,大多数内容来源于网络。服务器的安装  因为使用的是阿里云的ECS,所...

截取输入图片的户型图部分(可用于其他图片中物体部分截取)_opencv户型图_唯有一颗慎独心的博客-程序员秘密

在输入的户型图中,一般含有其他无关部分,如下图:而我们在做户型图处理的时候,只需要取其中户型图相关部分:需要使用openCV来做截取:# step1:加载图片,转成灰度图import cv2import numpy as npimage = cv2.imread("./pic/ceshi4.jpg")gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#step2:用Sobel算子计算x,y方向上的梯度,之后在x方向上减去y方向上的梯度,通过这个

Java中发送Http请求之httpClient_java httpclient_韩_师兄的博客-程序员秘密

Java中发送Http请求之httpClient1 httpClient的简介2 httpClient的案列HTTP 协议是 Internet 上使用得最多、最重要的协议之一,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源, JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活.1 httpClient的简介HttpClient 是 Apache Jakarta Common 下的子

Python: 一文看懂pandas的透视表pivot_table_anshuai_aw1的博客-程序员秘密

本文转载自知乎《Pandas | 一文看懂透视表pivot_table》,在原文基础上略有增删改。感谢原作者非常生动的例子。目录一、概述1.1 什么是透视表?1.2 为什么要使用pivot_table?二、如何使用pivot_table2.1 读取数据2.2Index2.3Values2.4Aggfunc2.5Columns2.6pivot_t...

C语言实现-杨辉三角(详解解析-知识点总结)_杨辉三角c语言程序_Tech行者的博客-程序员秘密

​杨辉三角,是二项式系数在三角形中的一种几何排列,今天我们通过在屏幕上打印杨辉三角来复习和详解杨辉三角所涉及C语言的相关知识。​

推荐文章

热门文章

相关标签