十一.linux开发之uboot移植(十一)——uboot源码分析5-uboot的环境变量

版权声明:本文为博主原创文章,允许转载请注明。谢谢! https://blog.csdn.net/wangweijundeqq/article/details/81700014

老规矩有道云地址:http://note.youdao.com/noteshare?id=3645af9d084b3aad1d13858fe5614528&sub=FC582BCEB2994BF09BB3291BF0DE40FA

一.uboot的环境变量简介

u-boot的缺省情况下会有一些基本的环境变量,当执行saveenv时,环境变量会保存到flash存储设备中。

  • 环境变量的优先级

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

默认环境变量在uboot源码中common/Env_common.c文件中。

  • 实现代码
  • 这里写图片描述

所有的环境变量存储在一个16KB大小的一维数组中,每个环境变量以”\0”结束。

uboot在启动时,在遍历调用执行init_sequence函数指针数组中的env_init函数时已经对环境变量进行校验,通过调用env_relocate函数,将环境变量从Flash启动设备重定位到SDRAM中。uboot启动时环境变量的初始化如下:

(具体参考八.linux开发之uboot移植(八)——uboot源码分析2-启动第二阶段之start_armboot函数分析)

这里写图片描述

环境变量有2份,一份在Flash中,另一份在DDR中。uboot开机时一次性从Flash中读取全部环境变量到DDR中作为环境变量的初始化值,然后使用过程中都是用DDR中这一份

bootdelay 执行自动启动(bootcmd中的命令)的等候秒数
baudrate 串口控制台的波特率
netmask 以太网的网络掩码
ethaddr 以太网的MAC地址
bootfile 默认的下载文件名
bootargs 传递给Linux内核的启动参数
bootcmd 自动启动时执行命令
serverip TFTP服务器端的IP地址
ipaddr 本地的IP地址
stdin 标准输入设备,一般是串口
stdout 标准输出,一般是串口,也可是LCD(VGA)
stderr 标准出错,一般是串口,也可是LCD(VGA)

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

1、printenv环境变量对应函数(打印出环境变量的功能)

这里写图片描述

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

(2)分析do_printenv函数。

这里写图片描述

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

int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int i, j, k, nxt;
int rcode = 0;
if (argc == 1) {//printenv命令没有参数时打印出所有变量信息
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()) { //打印中使用ctrl+c中止过程,则打印如下信息
puts ("\n ** Abort\n");
return 1;
}
}
printf("\nEnvironment size: %d/%ld bytes\n",
i, (ulong)ENV_SIZE);
return 0;
}
//argc不等于1,则后面的参数就是要打印的环境变量,遍历打印单个或多个变量的信息,即print  xx  xx  则只打印出xx  xx这个环境变量的值。
for (i=1; i<argc; ++i) {
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;
}

(4)总结:do_printenv函数首先区分argc=1还是不等于1的情况,若argc=1那么就循环打印所有的环境变量出来;如果argc不等于1,则后面的参数就是要打印的环境变量,给哪个就打印哪个。

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

1、setenv环境变量对应函数(改变或增加环境变量的功能

这里写图片描述

(1)命令定义和对应的函数在uboot/common/cmd_nvedit.c中,对应的函数为do_setenv。分析里面**,最重要的是int
_do_setenv函数**

这里写图片描述

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

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

这里写图片描述

第2步:擦除原来的环境变量,259-265行

这里写图片描述

第3步:写入新的环境变量,266-273行。

这里写图片描述

(3)本来setenv做完上面的就完了,但是还要考虑一些附加的问题。

问题一:环境变量太多超出DDR中的字符数组,溢出的解决方法。

问题二:有些环境变量如baudrate、ipaddr等,在gd中有对应的全局变量。这种环境变量在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全局变量不一致的情况。

2、saveenv环境变量(保存更改命令后的功能)

(1)在uboot/common/cmd_nvedit.c中,对应函数为do_saveenv函数

这里写图片描述

(2)从uboot实际执行saveenv命令的输出,和x210_sd.h中的配置(#define
CFG_ENV_IS_IN_AUTO)可以分析出:我们实际使用的是env_auto.c中相关的内容。没有一种芯片叫auto的,env_auto.c中是使用宏定义的方式去条件编译了各种常见的flash芯片(如movinand、norflash、nand等)。然后在程序中读取INF_REG(OMpin内部对应的寄存器)从而知道我们的启动介质,然后调用这种启动介质对应的操作函数来操作。

(3)do_saveenv内部调用env_auto.c中的saveenv函数来执行实际的环境变量保存操作。

这里写图片描述

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

**\#define INF_REG3_REG \__REG(INF_REG_BASE+INF_REG3_OFFSET)**

**\#define INF_REG_BASE0xE010F**

**\#define INF_REG3_OFFSET0x0c**

这里写图片描述

这里写图片描述

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

这里写图片描述

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

这里写图片描述

四.uboot内部获取环境变量——getenv

1、getenv环境变量对应函数(功 能: 从环境中取字符串,获取环境变量的值

这里写图片描述

(1)应该是不可重入的。

(2)实现方式就是去遍历default_environment数组,挨个拿出所有的环境变量比对name,找到相等的直接返回这个环境变量的首地址即可。

(3)getenv()用来取得参数name环境变量的内容。参数name为环境变量的名称,如果该变量存在则会返回指向该内容的指针。环境变量的格式为name=value。getenv函数的返回值存储在一个全局二维数组里,当你再次使用getenv函数时不用担心会覆盖上次的调用结果。

一旦查找到匹配的字符串*name,则返回*name的地址。

2、getenv_r

这里写图片描述

(1)可重入版本。(可自行搜索补充可重入函数的概念)

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

所以差别就是:getenv中返回的地址只能读不能随便乱写,而getenv_r中返回的环境变量是在自己提供的buf中,是可以随便改写加工的。

3、字符串的解析

(转载:http://blog.51cto.com/9291927/1792952)

uboot中对环境变量的操作充分利用了对环境变量字符串的解析。本部分将字符串的解析提取出来进行分析。

环境变量存储在default_environment[CFG_ENV_SIZE]数组中,每个环境变量以”\0”结束。通过对环境变量数default_environment[CFG_ENV_SIZE]进行解析,将环境变量分别打印出来。

实验源码:

#include <stdio.h>
unsigned char environment[] = 
{
    "bootdelay="    "10"    "\0"
    "ipaddr="   "192.168.1.210" "\0"
    "serverip=" "192.168.1.200" "\0"
    "bootcmd="  "tftp 0x20008000 uImage;bootm 0x20008000"   "\0"
    "baudrate=" "115200"    "\0"
};
int main(int argc, char **argv)
{
    int i, s,k;
    for(i = 0; *(environment+i) != '\0'; i = s + 1) //按照变量遍历环境变量数组
    {   //
        for(s = i; *(environment + s) != '\0'; s++)//计算出变量的字符数量
            ;   
        for(k = i; k < s; k++)//打印出变量的信息
            putc( *(environment + k), stdout);
        putc('\n', stdout);
    }   
    return 0;
}

这里写图片描述

编译运行结果:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/wangweijundeqq/article/details/81700014