uboot的环境变量

来源:互联网 发布:网络光端机价格 编辑:程序博客网 时间:2024/05/21 07:12
注:本文是学习朱老师课程整理的笔记,基于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 0
原创粉丝点击