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)都是对RAM中env的操作,只有在执行saveenv时才会将RAM中的env重新写入静态存储器中。

这种设计逻辑可以加快对env的读写速度。

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

那我们就来看一下uboot中env的数据结构 初始化 操作如何实现的。

一 env数据结构

在include/environment.h中定义了env_t,如下:

  1. #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
  2. # define ENV_HEADER_SIZE    (sizeof(uint32_t) + 1)
  3. # define ACTIVE_FLAG   1
  4. # define OBSOLETE_FLAG 0
  5. #else
  6. # define ENV_HEADER_SIZE    (sizeof(uint32_t))
  7. #endif
  8. #define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
  9. typedef struct environment_s {
  10. uint32_t    crc;        /* CRC32 over data bytes    */
  11. #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
  12. unsigned char   flags;      /* active/obsolete flags    */
  13. #endif
  14. unsigned char   data[ENV_SIZE]; /* Environment data     */
  15. } env_t;

CONFIG_ENV_SIZE是我们需要在配置文件中配置的环境变量的总长度。

这里我们使用的nand作为静态存储器,nand的一个block是128K,因此选用一个block来存储env,CONFIG_ENV_SIZE为128K。

Env_t结构体头4个bytes是对data的crc校验码,没有定义CONFIG_SYS_REDUNDAND_ENVIRONMENT,所以后面紧跟data数组,数组大小是ENV_SIZE.

ENV_SIZE是CONFIG_ENV_SIZE减掉ENV_HEADER_SIZE,也就是4bytes,

所以env_t这个结构体就包含了整个我们规定的长度为CONFIG_ENV_SIZE的存储区域。

头4bytes是crc校验码,后面剩余的空间全部用来存储环境变量。

需要说明的一点,crc校验码是uboot中在saveenv时计算出来,然后写入nand,所以在第一次启动uboot时crc校验会出错,

因为uboot从nand上读入的一个block数据是随机的,没有意义的,执行saveenv后重启uboot,crc校验就正确了。

data 字段保存实际的环境变量。u-boot  的 env  按 name=value”\0”的方式存储,在所有env 的最后以”\0\0”表示整个 env  的结束。

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

  1. typedef struct global_data
  2. {
  3. unsigned long env_off;        /* Relocation Offset */
  4. unsigned long env_addr;       /* Address of Environment struct ??? */
  5. unsigned long env_valid       /* Checksum of Environment valid */
  6. } gd_t;

二 env的初始化

uboot中env的整个架构可以分为3层:

(1) 命令层,如saveenv,setenv editenv这些命令的实现,还有如启动时调用的env_relocate函数。

(2) 中间封装层,利用不同静态存储器特性封装出命令层需要使用的一些通用函数,如env_init,env_relocate_spec,saveenv这些函数。实现文件在common/env_xxx.c

(3) 驱动层,实现不同静态存储器的读写擦等操作,这些是uboot下不同子系统都必须的。

按照执行流顺序,首先分析一下uboot启动的env初始化过程。

首先在board_init_f中调用init_sequence的env_init,这个函数是不同存储器实现的函数,nand中的实现如下:

  1. <span style="font-size:14px;">/*
  2. * This is called before nand_init() so we can't read NAND to
  3. * validate env data.
  4. *
  5. * Mark it OK for now. env_relocate() in env_common.c will call our
  6. * relocate function which does the real validation.
  7. *
  8. * When using a NAND boot image (like sequoia_nand), the environment
  9. * can be embedded or attached to the U-Boot image in NAND flash.
  10. * This way the SPL loads not only the U-Boot image from NAND but
  11. * also the environment.
  12. */
  13. int env_init(void)
  14. {
  15. gd->env_addr    = (ulong)&default_environment[0];
  16. gd->env_valid   = 1;
  17. return 0;
  18. }</span>

从注释就基本可以看出这个函数的作用,因为env_init要早于静态存储器的初始化,所以无法进行env的读写,这里将gd中的env相关变量进行配置,

默认设置env为valid。方便后面env_relocate函数进行真正的env从nand到ram的relocate。

继续执行,在board_init_r中,如下:

  1. /* initialize environment */
  2. if (should_load_env())
  3. env_relocate();
  4. else
  5. set_default_env(NULL);

这是在所有存储器初始化完成后执行的。

首先调用should_load_env,如下:

  1. /*
  2. * Tell if it's OK to load the environment early in boot.
  3. *
  4. * If CONFIG_OF_CONFIG is defined, we'll check with the FDT to see
  5. * if this is OK (defaulting to saying it's not OK).
  6. *
  7. * NOTE: Loading the environment early can be a bad idea if security is
  8. *       important, since no verification is done on the environment.
  9. *
  10. * @return 0 if environment should not be loaded, !=0 if it is ok to load
  11. */
  12. static int should_load_env(void)
  13. {
  14. #ifdef CONFIG_OF_CONTROL
  15. return fdtdec_get_config_int(gd->fdt_blob, "load-environment", 1);
  16. #elif defined CONFIG_DELAY_ENVIRONMENT
  17. return 0;
  18. #else
  19. return 1;
  20. #endif
  21. }

从注释可以看出,CONFIG_OF_CONTROL没有定义,鉴于考虑安全性问题,如果我们想要推迟env的load,可以定义CONFIG_DELAY_ENVIRONMENT,这里返回0,就调用set_default_env使用默认的env,默认env是在配置文件中CONFIG_EXTRA_ENV_SETTINGS设置的。

我们可以在之后的某个地方在调用env_relocate来load env。这里我们选择在这里直接load env。所以没有定义CONFIG_DELAY_ENVIRONMENT,返回1。调用env_relocate。

在common/env_common.c中:

  1. void env_relocate(void)
  2. {
  3. #if defined(CONFIG_NEEDS_MANUAL_RELOC)
  4. env_reloc();
  5. env_htab.change_ok += gd->reloc_off;
  6. #endif
  7. if (gd->env_valid == 0) {
  8. #if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)
  9. /* Environment not changable */
  10. set_default_env(NULL);
  11. #else
  12. bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);
  13. set_default_env("!bad CRC");
  14. #endif
  15. } else {
  16. env_relocate_spec();
  17. }
  18. }
 

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

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

  1. void env_relocate_spec(void)
  2. {
  3. int ret;
  4. ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
  5. ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
  6. if (ret) {
  7. set_default_env("!readenv() failed");
  8. return;
  9. }
  10. env_import(buf, 1);
  11. }

首先定义一个长度为CONFIG_ENV_SIZE的buf,然后调用readenv,

CONFIG_ENV_OFFSET是在配置文件中定义的env在nand中偏移位置。我们这里定义的是在4M的位置。

Readenv也在env_nand.c中,如下:

  1. int readenv(size_t offset, u_char *buf)
  2. {
  3. size_t end = offset + CONFIG_ENV_RANGE;
  4. size_t amount_loaded = 0;
  5. size_t blocksize, len;
  6. u_char *char_ptr;
  7. blocksize = nand_info[0].erasesize;
  8. if (!blocksize)
  9. return 1;
  10. len = min(blocksize, CONFIG_ENV_SIZE);
  11. while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
  12. if (nand_block_isbad(&nand_info[0], offset)) {
  13. offset += blocksize;
  14. } else {
  15. char_ptr = &buf[amount_loaded];
  16. if (nand_read_skip_bad(&nand_info[0], offset,
  17. &len, NULL,
  18. nand_info[0].size, char_ptr))
  19. return 1;
  20. offset += blocksize;
  21. amount_loaded += len;
  22. }
  23. }
  24. if (amount_loaded != CONFIG_ENV_SIZE)
  25. return 1;
  26. return 0;
  27. }

Readenv函数利用nand_info[0]对nand进行读操作,读出指定位置,指定长度的数据到buf中。Nand_info[0]是一个全局变量,来表征第一个nand device,这里在nand_init时会初始化这个变量。Nand_init必须在env_relocate之前。

回到env_relocate_spec中,buf读回后调用env_import,如下:

  1. /*
  2. * Check if CRC is valid and (if yes) import the environment.
  3. * Note that "buf" may or may not be aligned.
  4. */
  5. int env_import(const char *buf, int check)
  6. {
  7. env_t *ep = (env_t *)buf;
  8. if (check) {
  9. uint32_t crc;
  10. memcpy(&crc, &ep->crc, sizeof(crc));
  11. if (crc32(0, ep->data, ENV_SIZE) != crc) {
  12. set_default_env("!bad CRC");
  13. return 0;
  14. }
  15. }
  16. if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0,
  17. 0, NULL)) {
  18. gd->flags |= GD_FLG_ENV_READY;
  19. return 1;
  20. }
  21. error("Cannot import environment: errno = %d\n", errno);
  22. set_default_env("!import failed");
  23. return 0;
  24. }

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

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

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

Env_relocate执行完成,env的初始化就完成了。

三 env的操作实现

Uboot对env的操作命令实现在common/cmd_nvedit.c中。

对于setenv printenv editenv这3个命令,看其实现代码,都是对relocate到RAM中的env_htab的操作,这里就不再详细分析了,重点来看一下savenv实现。

  1. static int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc,
  2. char * const argv[])
  3. {
  4. printf("Saving Environment to %s...\n", env_name_spec);
  5. return saveenv() ? 1 : 0;
  6. }
  7. U_BOOT_CMD(
  8. saveenv, 1, 0,  do_env_save,
  9. "save environment variables to persistent storage",
  10. ""
  11. );

在do_env_save调用saveenv,这个函数是不同存储器实现的封装层函数。对于nand,在common/env_nand.c中,如下:

  1. int saveenv(void)
  2. {
  3. int ret = 0;
  4. ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
  5. ssize_t len;
  6. char    *res;
  7. int env_idx = 0;
  8. static const struct env_location location[] = {
  9. {
  10. .name = "NAND",
  11. .erase_opts = {
  12. .length = CONFIG_ENV_RANGE,
  13. .offset = CONFIG_ENV_OFFSET,
  14. },
  15. },
  16. #ifdef CONFIG_ENV_OFFSET_REDUND
  17. {
  18. .name = "redundant NAND",
  19. .erase_opts = {
  20. .length = CONFIG_ENV_RANGE,
  21. .offset = CONFIG_ENV_OFFSET_REDUND,
  22. },
  23. },
  24. #endif
  25. };
  26. if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
  27. return 1;
  28. res = (char *)&env_new->data;
  29. len = hexport_r(&env_htab, '\0', 0, &res, ENV_SIZE, 0, NULL);
  30. if (len < 0) {
  31. error("Cannot export environment: errno = %d\n", errno);
  32. return 1;
  33. }
  34. env_new->crc   = crc32(0, env_new->data, ENV_SIZE);
  35. #ifdef CONFIG_ENV_OFFSET_REDUND
  36. env_new->flags = ++env_flags; /* increase the serial */
  37. env_idx = (gd->env_valid == 1);
  38. #endif
  39. ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
  40. #ifdef CONFIG_ENV_OFFSET_REDUND
  41. if (!ret) {
  42. /* preset other copy for next write */
  43. gd->env_valid = gd->env_valid == 2 ? 1 : 2;
  44. return ret;
  45. }
  46. env_idx = (env_idx + 1) & 1;
  47. ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
  48. if (!ret)
  49. printf("Warning: primary env write failed,"
  50. " redundancy is lost!\n");
  51. #endif
  52. return ret;
  53. }

定义env_t类型的变量env_new,准备来存储env。

利用函数hexport_r对env_htab操作,读取env内容到env_new->data,

校验data,获取校验码env_new->crc。

最后调用erase_and_write_env将env_new先擦后写入由location定义的偏移量和长度的nand区域中。

这样就完成了env写入nand的操作。

在savenv readenv函数以及printenv setenv的实现函数中涉及到的函数himport_r hexport_r hdelete_r hmatch_r都是对env_htab哈希表的一些基本操作函数。

uboot环境变量实现分析的更多相关文章

  1. MPC8313ERDB在Linux从NAND FLASH读取UBoot环境变量的代码分析

    MPC8313ERDB在Linux从NAND FLASH读取UBoot环境变量的代码分析 Yao.GUET@2014-05-19 一.故事起因 由于文件系统的增大,已经大大的超出了8MB的NOR FL ...

  2. I.MX6 Linux U-boot 环境变量解析

    /********************************************************************************** * I.MX6 Linux U- ...

  3. Linux系统——访问U-BOOT环境变量

    Linux系统下访问U-BOOT环境变量 移植过U-BOOT的人,都知道:在U-BOOT中存有ENV.但U-BOOT在引导内核启动之后,U-BOOT的生命周期就结束了.那么启动LINUX内核之后,U- ...

  4. 在Linux里读取UBOOT环境变量

    转载:http://falloutmx.blog.163.com/blog/static/39236020201211145010154/ 可以通过mtd方式读取,也可以用ioremap方式.不过这些 ...

  5. u-boot 环境变量参数设置

    今天本来是烧写内核,结果一不小心把uboot也整不能用了,无奈之下只好重新烧个uboot,等都弄好以后,发现系统还是启动不了,原来是启动参数设置不对,于是找到了这篇文章,//是我添加的内容. 原文地址 ...

  6. OK335xS U-boot 环境变量解析

    /************************************************************************************************** ...

  7. openwrt设置uboot环境变量在flash上的存储地址

    1.分析如下 ubootenv_add_app_config ubootenv_add_uci_config "/dev/mtd1" "0x40000" &qu ...

  8. mac攻略(七) -- 环境变量PATH分析

      一.首先需要了解 1>mac 一般使用bash作为默认shell 2>Mac系统的环境变量,加载顺序为: 1.系统级别的 /etc/profile /etc/bashrc /etc/p ...

  9. uboot 环境变量

    从bootm 命令讲起 1 找到linux的内核入口 Bootm命令通过读取uImage的头部0×40字节的信息,将uImage定位到正确的地址,同时找到linux的内核入口地址. 这个地方就涉及到u ...

随机推荐

  1. Virtualbox中不能为虚拟机打开一个新任务的原因及解决方法

    VirtualBox新建虚拟机时报错,不能为虚拟机打开一个新任务的原因 解决办法如下 1.保证bios里的virtualization technology的选项开启,不同电脑BIOS设置可能会不一样 ...

  2. pandas应用之分组因子暴露和分位数分析

    pandas应用之分组因子暴露和分位数分析 首先感谢原书作者Mes McKinney和batteryhp网友的博文, 俺在此基础上继续探索python的神奇功能. 用A股的实际数据, 以书里的代码为蓝 ...

  3. weblogic.security.SecurityInitializationException: Authentication for user weblogic denied(详见下面具体报错信息)

    在使用nodemanager启动受管服务器时,报错 <Dec 25, 2016 5:54:31 PM CST> <Error> <NodeManager> < ...

  4. Failed: error processing document #281: unexpected EOF,往MongoDB当中插入json文件时出现的错误。

    往MongoDB当中插入json文件时提示的错误(我的操作系统是win10): 当时的执行命令是:mongoimport -d test -c restaurants d://primer-datas ...

  5. Opentaps安装小记

    这周了解了个MES项目需求,于是乎找了些开源项目了解下,有php+mysql写的weberp,配置起来相当方便,下一版webERP_v4.13,往F:\Apache\htdocs\一放,启动F:\Ap ...

  6. sphinx全文检索 安装配置和使用

    公司项目刚刚导入大量产品数据,然后发现网站的产品搜索很卡,原本是原生sql的like来做模糊搜索,数据量20W的时候还可以接受,但是上百万就很卡了,所以需要做优化. 经过考虑,打算采用全文检索 sph ...

  7. Python正则式的基本用法

    Python正则式的基本用法 1.1基本规则 1.2重复 1.2.1最小匹配与精确匹配 1.3前向界定与后向界定 1.4组的基本知识 2.re模块的基本函数 2.1使用compile加速 2.2 ma ...

  8. 结合个人经历总结的前端入门方法 (转自https://github.com/qiu-deqing/FE-learning)

    结合个人经历总结的前端入门方法 (https://github.com/qiu-deqing/FE-learning),里面有很详细的介绍. 之前一直想学习前端的,都不知道怎么下手都一年了啥也没学到, ...

  9. C#基础

    .net/dotnet:一般指.NetFramework框架,一种平台,一种技术. c#(sharp):一种编程语言,可以开发基于.net平台的应用. Java:是一种技术,又是一门语言: .net应 ...

  10. Ajax_showHint() 函数

    showHint() 函数实现的功能是:当用户在输入框中键入字符时,网页如何与 web 服务器进行通信,完整的代码如下: <html><head><script type ...