从零开始的UBOOT的学习--环境变量

从零开始的UBOOT的学习--环境变量

参考朱有鹏UBOOT全集

1、环境变量的作用

让我们可以不用修改UBOOT的源代码,而是通过修改环境变量来影响UBOOT运行的一些数据和特性,比如通过修改bootdelay环境变量就可以更改系统开机自动启动的倒数的秒数。

2、环境变量的优先级

(1)uboot代码中当中有一个值,环境变量中也有一个值。
其实UBOOT代码中的值是存放在内存当中的,环境变量的值是存放在硬盘当中的,所以就算是掉电的话,也不会把环境变量的值改变。

(2)比如machid机器码,UBOOT中在x210_sd.h中定义了一个机器码2456,写死在程序中的不能进行修改,但是如果需要修改这个UBOOT中的机器码的话,可以有两种方式:
1、更改源代码,修改机器码的全局变量
2、或者是直接修改环境变量的值,下次掉电启动的时候就可以生效。
设置环境变量的方法。
set machid 0x998类似这样,有了machid环境变量后,系统启动后会优先使用machid对应的环境变量,这就是优先级的问题。

3、环境变量的存储方式

(1)默认环境变量,在UBOOT/common/env_common.c中的default_environment,这东西本质上是一个字符数组,大小为CFG_ENV_SIZE(16KB)里面内容很多个环境变量连续分布组成的。每个环境变量都是以最末端'\0'结束。

(2)SD卡中的环境变量分区,在UBOOT的raw分区中,SD卡中其实就是给了一个分区,什么是分区,(分区好像是很牛逼的名词,但是实际上并不是很牛逼的名词,就是划定一块区域地址给你放置环境变量)

(3)DDR中的环境变量,在default_environment实际是字符数组,在UBOOT中其实就是一个全局变量的数组。

总结:刚烧录的系统中环境变量分区是空白的,UBOOT的第一次运行时加载的是UBOOT中DDR中自带的环境变量,叫做默认环境变量,我们在saveenv时DDR中的环境变量会被更新到SD卡中的环境变量中,就可以被保存下来,下次开机会在环境变量重定位的时候会把SD卡中的环境变量会被加载到DDR中去。

为什么我们每次执行都会从UBOOT的环境变量中读取数据,而不是直接从全局数组中读取数值?

虽然这个字符数组内容会被UBOOT源代码初始化为一定的值,(这个值就是我们默认的环境变量)但是在UBOOT启动的第二阶段,env_relocate时代码会去判断SD卡中的ENV分区的CRC是否通过。如果CRC校验通过说明SD卡中有正确的环境变量存储。
则relocate函数会从SD卡中读取环境变量来覆盖这个字符数组。从而每次开机都可以保持上一次开机更改过的环境变量的值。

环境变量在内存中的存储方法:代码小节

uchar default_environment[CFG_ENV_SIZE] = {
#else
uchar default_environment[] = {
#endif
#ifdef    CONFIG_BOOTARGS
    "bootargs="    CONFIG_BOOTARGS            "\0"
#endif
#ifdef    CONFIG_BOOTCOMMAND
    "bootcmd="    CONFIG_BOOTCOMMAND        "\0"
#endif
#if 0    /* for fast booting */
    "verify="        MK_STR(no)                    "\0"
#endif
#ifdef    CONFIG_MTDPARTITION
    "mtdpart="    CONFIG_MTDPARTITION        "\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

4、环境变量的有关命令和函数小结

(1)输出所有的环境变量:printenv
注册这个printenv这个函数
里面主要是调用这个函数do_printenv函数实现功能。

U_BOOT_CMD(
    printenv, CFG_MAXARGS, 1,    do_printenv,
    "printenv- print environment variables\n",
    "\n    - print values of all environment variables\n"
    "printenv name ...\n"
    "    - print value of environment variable 'name'\n"
);

参数1:cmd_tbl_t 结构体的参数
这个是命令结构体的参数:
首先第一个是命令的名字:
第二个是最大允许的参数的个数
第三个是是否接受可重复进行命令
第四个是命令执行体
第五个或者第六个是执行的命令的帮助信息。

struct cmd_tbl_s {
    char        *name;        /* Command Name            */
    int        maxargs;    /* maximum number of arguments    */
    int        repeatable;    /* autorepeat allowed?        */
                    /* Implementation function    */
    int        (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
    char        *usage;        /* Usage message    (short)    */
#ifdef    CFG_LONGHELP
    char        *help;        /* Help  message    (long)    */
#endif
};
   flag:这个还不知道是什么参数
argc:就是输入参数的个数,包含命令,也就是说命令也算一个参数
argv:就是命令输入的内容信息。
int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    int i, j, k, nxt;
    int rcode = 0;

    if (argc == 1) 
    {    
//以'\0'为单位,把每一个环境变量的值作为分界线,不断的循环遍历    
        for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
            for (nxt=i; env_get_char(nxt) != '\0'; ++nxt)
                ;
            for (k=i; k<nxt; ++k)
                putc(env_get_char(k));
            putc  ('\n');
        }

        printf("\nEnvironment size: %d/%ld bytes\n",
            i, (ulong)ENV_SIZE);

        return 0;
    }

    for (i=1; i<argc; ++i) {    /* print single env variables    */
        char *name = argv[i];

        k = -1;

//当env_get_char这个是得出字符数组中的字符,
        for (j=0; env_get_char(j) != '\0'; j=nxt+1) {

            for (nxt=j; env_get_char(nxt) != '\0'; ++nxt)
                ;
            k = envmatch((uchar *)name, j);
            if (k < 0) {
                continue;
            }
            puts (name);
            putc ('=');
            while (k < nxt)
//一个一个把字符打印出来
                putc(env_get_char(k++));
            putc ('\n');
            break;
        }
        if (k < 0) {
            printf ("## Error: \"%s\" not defined\n", name);
            rcode ++;
        }
    }
    return rcode;
}

(2)setenv,设置环境变量的函数,
(1)命令定义和对应函数在uboot/common/cmd_nvedit.c中对应的函数为do_setenv
(2)setenv的思路就是:先去DDR中的环境变量处寻找原来没有这个环境变量,如果原来有的话,就需要覆盖原来的环境变量,如果原来没有的话则在最后新增一个环境变量即可。

步奏:
1、遍历DDR中环境变量的数组,找到原来就有的那个环境变量对应的地址,168-174行。
2、檫除原来的环境变量,都是用'\0'表示。
3、写入新的环境变量

本来setenv已经完成了,但是还是需要考虑一些事情。
环境变量如果太大的话,会超出DDR中的字符数组,溢出的解决方法。
有些环境变量如baurate/ipaddr等,在gd中有对应的全局变量,这种环境变量在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全局变量不一致的情况。

(3)saveenv,保存环境变量的参数
在UBOOT/common/cmd_nvedit.c中对应函数为do_saveenv
从UBOOT实际执行saveenv命令的输出,和x210_sd.h中的配置,可以分析出,我们实际使用的env_auto.c中的相关的内容,没有一种芯片叫做auto的,env_auto.c中是使用宏定义的方式去条件编译了各种常见的flash芯片(movinand/norflash,nand等),然后在程序中读取INF_REG,从而知道我们的启动介质。

设置保存环境变量的参数
实际调用的函数就是saveenv()

int do_saveenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    extern char * env_name_spec;

    printf ("Saving Environment to %s...\n", env_name_spec);

    return (saveenv() ? 1 : 0);
}

//实际进行保存的函数:saveenv_movinand()
因为我们这个开发板是用INAND启动的,所以,具体的底层的驱动函数就先不说了。

int saveenv(void)
{
#if defined(CONFIG_S5PC100) || defined(CONFIG_S5PC110) || defined(CONFIG_S5P6442)
    if (INF_REG3_REG == 2)
        saveenv_nand();
    else if (INF_REG3_REG == 3)
        saveenv_movinand();
    else if (INF_REG3_REG == 1)
        saveenv_onenand();
    else if (INF_REG3_REG == 4)
        saveenv_nor();
#elif    defined(CONFIG_SMDK6440)
    if (INF_REG3_REG == 3)
        saveenv_nand();
    else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
        saveenv_nand_adv();
    else if (INF_REG3_REG == 0 || INF_REG3_REG == 1 || INF_REG3_REG == 7)
        saveenv_movinand();
#else   // others
    if (INF_REG3_REG == 2 || INF_REG3_REG == 3)
        saveenv_nand();
    else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
        saveenv_nand_adv();
    else if (INF_REG3_REG == 0 || INF_REG3_REG == 7)
        saveenv_movinand();
    else if (INF_REG3_REG == 1)
        saveenv_onenand();
#endif
    else
        printf("Unknown boot device\n");

    return 0;
}

(4)UBOOT获取内部的环境变量,这个不是命令,这是是中间的那些函数的中间函数。
2.9.5.1、getenv
(1)应该是不可重入的。
(2)实现方式就是去遍历default_environment数组,挨个拿出所有的环境变量比对name,找到相等的直接返回这个环境变量的首地址即可。

2.9.5.2、getenv_r
(1)可重入版本。(可自行搜索补充可重入函数的概念)
(2)getenv函数是直接返回这个找到的环境变量在DDR中环境变量处的地址,而getenv_r函数的做法是找到了DDR中环境变量地址后,将这个环境变量复制一份到提供的buf中,而不动原来DDR中环境变量。
所以差别就是:getenv中返回的地址只能读不能随便乱写,而getenv_r中返回的环境变量是在自己提供的buf中,是可以随便改写加工的。

通过名字,可以通过访问到env的名字,还有存储空间。
不断的循环遍历那个字符数组
并把得到的值存储到buf这个内存当中。

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

总结:
(1)功能是一样的,但是可重入版本会比较安全一些,建议使用。
(2)有关于环境变量的所有操作,主要理解了环境变量在DDR中的存储方法,理解了环境变量和gd全局变量的关联和优先级,理解了环境变量在存储介质中的存储方式(专用raw分区),整个环境变量相关的都清楚了。

猜你喜欢

转载自blog.csdn.net/dhauwd/article/details/80712055
今日推荐