zoukankan      html  css  js  c++  java
  • 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)都是对RAMenv的操作。仅仅有在运行saveenv时才会将RAM中的env又一次写入静态存储器中。

    这样的设计逻辑能够加快对env的读写速度。

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

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

    一 env数据结构

    include/environment.h中定义了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””的方式存储,在全部env 的最后以””表示整个 env  的结束。

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

    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中的实现例如以下:

    <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中,例如以下:

    /* initialize environment */
        if (should_load_env())
            env_relocate();
        else
            set_default_env(NULL);

    这是在全部存储器初始化完毕后运行的。

    首先调用should_load_env。例如以下:

    /*
     * 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中:

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

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

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

    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中,例如以下:

    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,例如以下:

    /*
     * 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, NULL)) {
            gd->flags |= GD_FLG_ENV_READY;
            return 1;
        }
    
        error("Cannot import environment: errno = %d
    ", errno);
    
        set_default_env("!import failed");
    
        return 0;
    }

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

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

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

    Env_relocate运行完毕。env的初始化就完毕了。


    三 env的操作实现

    Ubootenv的操作命令实如今common/cmd_nvedit.c中。

    对于setenv printenv editenv3个命令。看事实上现代码,都是对relocateRAM中的env_htab的操作。这里就不再具体分析了,重点来看一下savenv实现。

    static int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc,
                   char * const argv[])
    {
        printf("Saving Environment to %s...
    ", 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中,例如以下:

    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, &res, ENV_SIZE, 0, NULL);
        if (len < 0) {
            error("Cannot export environment: errno = %d
    ", 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!
    ");
    #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中。这里就不细致分析这些函数了。


  • 相关阅读:
    从零开始学VUE3.X-常用模版语法
    从零开始学3.X-生命周期函数
    从零开始学TypeScript-readonly
    从零开始学Typescript-基础类型
    从零开始学Typescript-webpack打包
    探索 .NET Core 依赖注入的 IServiceProvider
    在.NET Core 中使用 FluentValidation 进行规则验证
    盘点大厂的那些开源项目
    探索 .NET Core 依赖注入的 IServiceCollection
    使用 Benchmark.NET 测试代码性能
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/5201997.html
Copyright © 2011-2022 走看看