uboot的环境变量

注:本文是学习朱老师课程整理的笔记,基于uboot-1.3.4和s5pc11x分析。  

环境变量的作用

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

环境变量的优先级

如果环境变量为空则使用代码中的值;如果环境变量不为空则优先使用环境变量对应的值。

譬如machid(机器码)。uboot中在x210_sd.h中定义了一个机器码2456,如果要修改uboot中配置的机器码,可以修改x210_sd.h中的机器码,但是修改源代码后需要重新编译烧录,很麻烦;比较简单的方法就是使用环境变量machid。如:set machid 0x998,有了machid环境变量后,系统启动时会优先使用machid对应的环境变量,这就是优先级问题。

环境变量的工作方式

默认环境变量,在uboot/common/env_common.c中default_environment:

uchar default_environment[CFG_ENV_SIZE] = {
    "bootargs=" CONFIG_BOOTARGS         "\0"
    "bootcmd="  CONFIG_BOOTCOMMAND      "\0"
    "mtdpart="  CONFIG_MTDPARTITION     "\0"
    "nfsboot="  CONFIG_NFSBOOTCOMMAND       "\0"
    "bootdelay="    MK_STR(CONFIG_BOOTDELAY)    "\0"
    "baudrate=" MK_STR(CONFIG_BAUDRATE)     "\0"
    "ethaddr="  MK_STR(CONFIG_ETHADDR)      "\0"
    "ipaddr="   MK_STR(CONFIG_IPADDR)       "\0"
    "serverip=" MK_STR(CONFIG_SERVERIP)     "\0"
    "gatewayip="    MK_STR(CONFIG_GATEWAYIP)    "\0"
    "netmask="  MK_STR(CONFIG_NETMASK)      "\0"
    "\0"
};

环境变量在内存中的存储大体如下:
这里写图片描述
这东西本质是一个字符数组,大小为CFG_ENV_SIZE(16kb),里面内容就是很多个环境变量连续分布组成的,每个环境变量最末端以’\0’结束。

SD卡中环境变量分区,在uboot的raw分区中。存储时是把DDR中的环境变量整体的写入SD卡中分区里。所以当我们saveenv时其实整个所有的环境变量都被保存了一遍,而不是只保存更改了的。

DDR中环境变量就是default_environment字符数组,在uboot中其实是一个全局变量,链接时在数据段,重定位时default_environment就被重定位到DDR中一个内存地址处了。

刚烧录的SD卡中环境变量分区是空白的,uboot第一次运行时加载的是uboot代码中自带的一份环境变量,叫默认环境变量。我们在saveenv时DDR中的环境变量会被更新到SD卡中的环境变量中,就可以被保存下来,下次开机会将环境变量从SD卡中relocate到DDR中去。

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

环境变量相关命令源码解析

  • printenv
int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    int i, j, k, nxt;
    int rcode = 0;

    if (argc == 1) {        /* Print all env variables  */
        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');

            if (ctrlc()) {
                puts ("\n ** Abort\n");
                return 1;
            }
        }

        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;

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

找到printenv命令所对应的函数。通过printenv的help可以看出,这个命令有2种使用方法。第一种直接使用不加参数则打印所有的环境变量;第二种是printenv name则只打印出name这个环境变量的值。

do_printenv函数首先判断argc是否等于1,若argc=1那么就循环打印所有的环境变量出来;如果argc不等于1,则后面的参数就是要打印的环境变量,给哪个环境变量就打印哪个。

argc=1时用双重for循环来依次打印所有的环境变量。第一重for循环就是处理各个环境变量。所以有多少个环境变量则第一重就执行循环多少圈。

env_get_char函数中又调用了 env_get_char_memory:

uchar env_get_char_memory (int index)
{
    if (gd->env_valid) {
        return ( *((uchar *)(gd->env_addr + index)) );
    } else {
        return ( default_environment[index] );
    }
}

上面两条return的语句其实可以相等。
在env_init函数中可以看出:

gd->env_addr  = (ulong)&default_environment[0];
gd->env_valid = 1;

总结:这个函数要看懂,首先要明白整个环境变量在内存中如何存储的。

  • setenv
    命令定义对应的函数在uboot/common/cmd_nvedit.c中,对应的函数为do_setenv。
int do_setenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    if (argc < 2) {
        printf ("Usage:\n%s\n", cmdtp->usage);
        return 1;
    }

    return _do_setenv (flag, argc, argv);
}

do_setenv中又调用了_do_setenv,_do_setenv的思路就是:先去DDR中的环境变量处寻找原来有没有这个环境变量,如果原来就有则需要覆盖原来的环境变量,如果原来没有则在最后新增一个环境变量即可。

第1步:遍历DDR中环境变量的数组,找到原来就有的那个环境变量对应的地址。

/*
     * 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;
    }

第2步:擦除原来的环境变量
第3步:写入新的环境变量

if (*++nxt == '\0') {     /* 擦除原来的环境变量 */
            if (env > env_data) {
                env--;
            } else {
                *env = '\0';
            }
        } else {      
            for (;;) {   /* 写入新的环境变量 */
                *env = *nxt++;
                if ((*env == '\0') && (*nxt == '\0'))
                    break;
                ++env;
            }
        }
        *++env = '\0';
    }

本来setenv做完上面的就完了,但是还要考虑一些附加的问题。
问题一:环境变量太多超出DDR中的字符数组,溢出的解决方法。
问题二:有些环境变量如baudrate、ipaddr等,在gd中有对应的全局变量。这种环境变量在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全局变量不一致的情况。

  • saveenv

在uboot/common/cmd_nvedit.c中,对应函数为do_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);
}

从uboot实际执行saveenv命令的输出,可以知道env_name_spec的定义在env_auto.c中。接着saveenv()就是定义在env_auto.c中:

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

    else
        printf("Unknown boot device\n");

    return 0;
}

使用宏定义的方式去条件编译了各种常见的flash芯片(如movinand、norflash、nand等)。然后在程序中读取INF_REG(OMpin内部对应的寄存器)从而知道我们的启动介质,然后调用这种启动介质对应的函数来操作。这里我们的INF_REG3_REG =3,它的赋值在start.s中:

/* SD/MMC BOOT */
    cmp     r2, #0xc
    moveq   r3, #BOOT_MMCSD  /* #define BOOT_MMCSD      0x3 */
    ……

    ldr r0, =INF_REG_BASE
    str r3, [r0, #INF_REG3_OFFSET]     

INF_REG3_REG 寄存器地址:E010F000+0C=E010_F00C,在芯片数据手册中查到该寄存器是用户自定义数据。我们在start.S中判断启动介质后将#BOOT_MMCSD(就是3,定义在x210_sd.h)写入了这个寄存器,所以这里读出的肯定是3,经过判断就是movinand。所以实际执行的函数是:saveenv_movinand。

int saveenv_movinand(void)
{
        movi_write_env(virt_to_phys((ulong)env_ptr));
        puts("done\n");

        return 1;
}

真正执行保存环境变量操作的是:cpu/s5pc11x/movi.c中的movi_write_env函数,这个函数肯定是写sd卡,将DDR中的环境变量数组(其实就是default_environment这个数组,大小16kb,刚好32个扇区)写入iNand中的ENV分区中。

void movi_write_env(ulong addr)
{
    movi_write(raw_area_control.image[2].start_blk,
           raw_area_control.image[2].used_blk, addr);
}

raw_area_control是uboot中规划iNnad/SD卡的原始分区表,这个里面记录了我们对iNand的分区,env分区也在这里,下标是2。追到这一层就够了,再里面就是调用驱动部分的写SD卡/iNand的底层函数了。

  • getenv和getenv_r

getenv是不可重入函数(关于函数的可重入性分析见函数的可重入性理解)。实现方式就是去遍历default_environment数组,挨个拿出所有的环境变量比对name,找到相等的直接返回这个环境变量的首地址即可。

getenv_r是可重入函数。getenv函数是直接返回这个找到的环境变量在DDR中环境变量处的地址,而getenv_r函数的做法是找到了DDR中环境变量地址后,将这个环境变量复制一份到提供的buf中,而不动原来DDR中环境变量。

所以差别就是:getenv中返回的地址只能读不能随便乱写,而getenv_r中返回的环境变量是在自己提供的buf中,是可以随便改写加工的。两者功能是一样的,但是可重入版本会比较安全一些,建议使用。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值