u-boot 分析之 默认环境变量配置
2018-02-13 09:48
381 查看
通过上节内容,我们大概了解了Uboot中DDR的配置相关知识,这节我们学习一下默认环境变量配置。在zynq开发的时候,在u-boot阶段,有的环境变量需要在烧录后再设置,在烧录之后都会有一些默认的环境变量比如bootarg等,如果在编译u-boot之前就把自己需要的变量设置好,烧录的时候直接就得到自己想要的变量,这在量产中是很方便的。在哪里配置呢?经过研习,终于找到了,在这里include/configs/<你的板子名称>.h,如include/configs/zynq_zturn.h,环境变量的部分配置如下:[cpp] view plain copy#define CONFIG_EXTRA_ENV_SETTINGS \
"qboot_addr=0x000000\0"\
"qbootenv_addr=0x080000\0"\
"qbootenv_size=0x020000\0"\
"qkernel_addr=0x500000\0"\
"qdevtree_addr=0x980000\0"\
"qramdisk_addr=0x990000\0"\
"kernel_size=0x480000\0" \
"devicetree_size=0x010000\0" \
"ramdisk_size=0x600000\0" \
"boot_size=0x080000\0" \
"ethaddr=00:0a:35:00:01:22\0" \
"kernel_image=uImage\0" \
"kernel_load_address=0x2080000\0"\
"ramdisk_image=uramdisk.image.gz\0" \
"ramdisk_load_address=0x4000000\0" \
"devicetree_image=devicetree.dtb\0" \
"devicetree_load_address=0x2000000\0" \
"bitstream_image=system.bit.bin\0" \
"boot_image=BOOT.bin\0" \
"loadbit_addr=0x100000\0" \
"loadbootenv_addr=0x2000000\0"\
"fdt_high=0x20000000\0" \
"initrd_high=0x20000000\0" \
"bootenv=uEnv.txt\0"\
"loadbootenv=fatloadmmc 0 ${loadbootenv_addr} ${bootenv}\0" \
"importbootenv=echoImporting environment from SD ...; " \
"envimport -t ${loadbootenv_addr} $filesize\0" \
"mmc_loadbit_fat=echoLoading bitstream from SD/MMC/eMMC to RAM.. && " \
"get_bitstream_name&& mmcinfo && " \
"fatloadmmc 0 ${loadbit_addr} ${bitstream_image} && " \
"fpgaloadb 0 ${loadbit_addr} ${filesize}\0" \
"norboot=echoCopying Linux from NOR flash to RAM... && " \
"cp.b0xE2100000 ${kernel_load_address} ${kernel_size} && " \
"cp.b0xE2600000 ${devicetree_load_address} ${devicetree_size} && " \
"echoCopying ramdisk... && " \
"cp.b0xE2620000 ${ramdisk_load_address} ${ramdisk_size} && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"uenvboot="\
"ifrun loadbootenv; then " \
"echoLoaded environment from ${bootenv}; " \
"runimportbootenv; " \
"fi;" \
"iftest -n $uenvcmd; then " \
"echoRunning uenvcmd ...; " \
"runuenvcmd; " \
"fi\0"\
"sdboot=ifmmcinfo; then " \
"runuenvboot; " \
"get_bitstream_name&& " \
"echo- load ${bitname} to PL... && " \
"fatloadmmc 0 0x200000 ${bitname} && " \
"fpgaloadb 0 0x200000 ${filesize} && " \
"echoCopying Linux from SD to RAM... && " \
"fatloadmmc 0 ${kernel_load_address} ${kernel_image} && " \
"fatloadmmc 0 ${devicetree_load_address} ${devicetree_image} && " \
"fatloadmmc 0 ${ramdisk_load_address} ${ramdisk_image} && " \
"bootm${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address};" \
"fi\0"\
"usbboot=ifusb start; then " \
"runuenvboot; " \
"echoCopying Linux from USB to RAM... && " \
"fatloadusb 0 ${kernel_load_address} ${kernel_image} && " \
"fatloadusb 0 ${devicetree_load_address} ${devicetree_image} && " \
"fatloadusb 0 ${ramdisk_load_address} ${ramdisk_image} && " \
"bootm${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address};" \
"fi\0"\
"nandboot=echoCopying Linux from NAND flash to RAM... && " \
"nandread ${kernel_load_address} 0x100000 ${kernel_size} && " \
"nandread ${devicetree_load_address} 0x600000 ${devicetree_size} && " \
"echoCopying ramdisk... && " \
"nandread ${ramdisk_load_address} 0x620000 ${ramdisk_size} && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"jtagboot=echoTFTPing Linux to RAM... && " \
"tftpboot${kernel_load_address} ${kernel_image} && " \
"tftpboot${devicetree_load_address} ${devicetree_image} && " \
"tftpboot${ramdisk_load_address} ${ramdisk_image} && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_norboot=echoCopying Image from NOR flash to RAM... && " \
"cp.b0xE2100000 0x100000 ${boot_size} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_nandboot=echoCopying Image from NAND flash to RAM... && " \
"nandread 0x100000 0x0 ${boot_size} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_qspiboot=echoCopying Image from QSPI flash to RAM... && " \
"sfprobe 0 0 0 && " \
"sfread 0x100000 0x0 ${boot_size} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_sdboot=echoCopying Image from SD to RAM... && " \
"fatloadmmc 0 0x100000 ${boot_image} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_jtagboot=echoTFTPing Image to RAM... && " \
"tftpboot0x100000 ${boot_image} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"qspiboot=echoCopying Linux from QSPI flash to RAM... && " \
"sfprobe 0 0 0 && " \
"qspi_get_bitsize0x0A0000 && " \
"sfread ${loadbit_addr} 0x0A0004 ${bitsize} && " \
"fpgaloadb 0 ${loadbit_addr} ${bitsize} && " \
"sfread ${kernel_load_address} ${qkernel_addr} ${kernel_size} && " \
"sfread ${devicetree_load_address} ${qdevtree_addr} ${devicetree_size} &&" \
"echoCopying ramdisk... && " \
"sfread ${ramdisk_load_address} ${qramdisk_addr} ${ramdisk_size} && "\
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"qspiupdate=echoUpdate qspi images from sd card... && " \
"echo- Init mmc... && mmc rescan && " \
"echo- Init qspi flash... && sf probe 0 0 0 && " \
"echo- Write boot.bin... && " \
"fatloadmmc 0 0x200000 boot.bin && " \
"sferase ${qboot_addr} ${boot_size} && " \
"sferase ${qbootenv_addr} ${qbootenv_size} && " \
"sfwrite 0x200000 0 ${filesize} && " \
"get_bitstream_name&& " \
"echo- Write ${bitstream_image}... && " \
"fatloadmmc 0 0x200000 ${bitstream_image} && " \
"sferase 0x0A0000 0x460000 && " \
"mw.l0x100000 ${filesize} && " \
"sfwrite 0x100000 0x0A0000 4 && " \
"sfwrite 0x200000 0x0A0004 ${filesize} && " \
"echo- Write uImage... && " \
"fatloadmmc 0 0x200000 uImage && " \
"sferase ${qkernel_addr} ${kernel_size} && " \
"sfwrite 0x200000 ${qkernel_addr} ${filesize} && " \
"echo- Write device tree... && " \
"fatloadmmc 0 0x200000 devicetree.dtb && " \
"sferase ${qdevtree_addr} ${devicetree_size} && " \
"sfwrite 0x200000 ${qdevtree_addr} ${filesize} && " \
"echo- Write Ramdisk... && " \
"fatloadmmc 0 0x200000 uramdisk.image.gz && " \
"sferase ${qramdisk_addr} ${ramdisk_size} && " \
"sfwrite 0x200000 ${qramdisk_addr} ${filesize} && " \
"echo- Done.\0"
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,如下:[cpp] view plain copy#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
# define ENV_HEADER_SIZE (sizeof(uint32_t) + 1)
# define ACTIVE_FLAG 1
# define OBSOLETE_FLAG 0
#else
# define ENV_HEADER_SIZE (sizeof(uint32_t))
#endif
#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} 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 相关的部分 [cpp] view plain copytypedef struct global_data
{
…
unsigned long env_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct ??? */
unsigned long env_valid /* Checksum of Environment valid */
…
} 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中的实现如下:[cpp] view plain copy/*
* This is called before nand_init() so we can't read NAND to
* validate env data.
*
* Mark it OK for now. env_relocate() in env_common.c will call our
* relocate function which does the real validation.
*
* When using a NAND boot image (like sequoia_nand), the environment
* can be embedded or attached to the U-Boot image in NAND flash.
* This way the SPL loads not only the U-Boot image from NAND but
* also the environment.
*/
int env_init(void)
{
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
return 0;
}
从注释就基本可以看出这个函数的作用,因为env_init要早于静态存储器的初始化,所以无法进行env的读写,这里将gd中的env相关变量进行配置,默认设置env为valid。方便后面env_relocate函数进行真正的env从nand到ram的relocate。继续执行,在board_init_r中,如下:[cpp] view plain copy/* initialize environment */
if (should_load_env())
env_relocate();
else
set_default_env(NULL);
这是在所有存储器初始化完成后执行的。首先调用should_load_env,如下:[cpp] view plain copy/*
* Tell if it's OK to load the environment early in boot.
*
* If CONFIG_OF_CONFIG is defined, we'll check with the FDT to see
* if this is OK (defaulting to saying it's not OK).
*
* NOTE: Loading the environment early can be a bad idea if security is
* important, since no verification is done on the environment.
*
* @return 0 if environment should not be loaded, !=0 if it is ok to load
*/
static int should_load_env(void)
{
#ifdef CONFIG_OF_CONTROL
return fdtdec_get_config_int(gd->fdt_blob, "load-environment", 1);
#elif defined CONFIG_DELAY_ENVIRONMENT
return 0;
#else
return 1;
#endif
}
从注释可以看出,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中:[cpp] view plain copyvoid env_relocate(void)
{
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
env_reloc();
env_htab.change_ok += gd->reloc_off;
#endif
if (gd->env_valid == 0) {
#if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)
/* Environment not changable */
set_default_env(NULL);
#else
bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);
set_default_env("!bad CRC");
#endif
} else {
env_relocate_spec();
}
}
Gd->env_valid在之前的env_init中设置为1,所以这里调用env_relocate_spec,这个函数也是不同存储器的中间封装层提供的函数,对于nand在common/env_nand.c中,如下:[cpp] view plain copyvoid env_relocate_spec(void)
{
int ret;
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
if (ret) {
set_default_env("!readenv() failed");
return;
}
env_import(buf, 1);
}
首先定义一个长度为CONFIG_ENV_SIZE的buf,然后调用readenv,CONFIG_ENV_OFFSET是在配置文件中定义的env在nand中偏移位置。我们这里定义的是在4M的位置。Readenv也在env_nand.c中,如下:[cpp] view plain copyint readenv(size_t offset, u_char *buf)
{
size_t end = offset + CONFIG_ENV_RANGE;
size_t amount_loaded = 0;
size_t blocksize, len;
u_char *char_ptr;
blocksize = nand_info[0].erasesize;
if (!blocksize)
return 1;
len = min(blocksize, CONFIG_ENV_SIZE);
while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
if (nand_block_isbad(&nand_info[0], offset)) {
offset += blocksize;
} else {
char_ptr = &buf[amount_loaded];
if (nand_read_skip_bad(&nand_info[0], offset,
&len, NULL,
nand_info[0].size, char_ptr))
return 1;
offset += blocksize;
amount_loaded += len;
}
}
if (amount_loaded != CONFIG_ENV_SIZE)
return 1;
return 0;
}
Readenv函数利用nand_info[0]对nand进行读操作,读出指定位置,指定长度的数据到buf中。Nand_info[0]是一个全局变量,来表征第一个nand device,这里在nand_init时会初始化这个变量。Nand_init必须在env_relocate之前。回到env_relocate_spec中,buf读回后调用env_import,如下:[cpp] view plain copy/*
* Check if CRC is valid and (if yes) import the environment.
* Note that "buf" may or may not be aligned.
*/
int env_import(const char *buf, int check)
{
env_t *ep = (env_t *)buf;
if (check) {
uint32_t crc;
memcpy(&crc, &ep->crc, sizeof(crc));
if (crc32(0, ep->data, ENV_SIZE) != crc) {
set_default_env("!bad CRC");
return 0;
}
}
if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0,
0, NULL)) {
gd->flags |= GD_FLG_ENV_READY;
return 1;
}
error("Cannot import environment: errno = %d\n", errno);
set_default_env("!import failed");
return 0;
}
首先将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实现。[cpp] view plain copystatic int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
printf("Saving Environment to %s...\n", env_name_spec);
return saveenv() ? 1 : 0;
}
U_BOOT_CMD(
saveenv, 1, 0, do_env_save,
"save environment variables to persistent storage",
""
);
在do_env_save调用saveenv,这个函数是不同存储器实现的封装层函数。对于nand,在common/env_nand.c中,如下:[cpp] view plain copyint saveenv(void)
{
int ret = 0;
ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
ssize_t len;
char *res;
int env_idx = 0;
static const struct env_location location[] = {
{
.name = "NAND",
.erase_opts = {
.length = CONFIG_ENV_RANGE,
.offset = CONFIG_ENV_OFFSET,
},
},
#ifdef CONFIG_ENV_OFFSET_REDUND
{
.name = "redundant NAND",
.erase_opts = {
.length = CONFIG_ENV_RANGE,
.offset = CONFIG_ENV_OFFSET_REDUND,
},
},
#endif
};
if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
return 1;
res = (char *)&env_new->data;
len = hexport_r(&env_htab, '\0', 0, &res, ENV_SIZE, 0, NULL);
if (len < 0) {
error("Cannot export environment: errno = %d\n", errno);
return 1;
}
env_new->crc = crc32(0, env_new->data, ENV_SIZE);
#ifdef CONFIG_ENV_OFFSET_REDUND
env_new->flags = ++env_flags; /* increase the serial */
env_idx = (gd->env_valid == 1);
#endif
ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
#ifdef CONFIG_ENV_OFFSET_REDUND
if (!ret) {
/* preset other copy for next write */
gd->env_valid = gd->env_valid == 2 ? 1 : 2;
return ret;
}
env_idx = (env_idx + 1) & 1;
ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
if (!ret)
printf("Warning: primary env write failed,"
" redundancy is lost!\n");
#endif
return ret;
}
定义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的操作。
"qboot_addr=0x000000\0"\
"qbootenv_addr=0x080000\0"\
"qbootenv_size=0x020000\0"\
"qkernel_addr=0x500000\0"\
"qdevtree_addr=0x980000\0"\
"qramdisk_addr=0x990000\0"\
"kernel_size=0x480000\0" \
"devicetree_size=0x010000\0" \
"ramdisk_size=0x600000\0" \
"boot_size=0x080000\0" \
"ethaddr=00:0a:35:00:01:22\0" \
"kernel_image=uImage\0" \
"kernel_load_address=0x2080000\0"\
"ramdisk_image=uramdisk.image.gz\0" \
"ramdisk_load_address=0x4000000\0" \
"devicetree_image=devicetree.dtb\0" \
"devicetree_load_address=0x2000000\0" \
"bitstream_image=system.bit.bin\0" \
"boot_image=BOOT.bin\0" \
"loadbit_addr=0x100000\0" \
"loadbootenv_addr=0x2000000\0"\
"fdt_high=0x20000000\0" \
"initrd_high=0x20000000\0" \
"bootenv=uEnv.txt\0"\
"loadbootenv=fatloadmmc 0 ${loadbootenv_addr} ${bootenv}\0" \
"importbootenv=echoImporting environment from SD ...; " \
"envimport -t ${loadbootenv_addr} $filesize\0" \
"mmc_loadbit_fat=echoLoading bitstream from SD/MMC/eMMC to RAM.. && " \
"get_bitstream_name&& mmcinfo && " \
"fatloadmmc 0 ${loadbit_addr} ${bitstream_image} && " \
"fpgaloadb 0 ${loadbit_addr} ${filesize}\0" \
"norboot=echoCopying Linux from NOR flash to RAM... && " \
"cp.b0xE2100000 ${kernel_load_address} ${kernel_size} && " \
"cp.b0xE2600000 ${devicetree_load_address} ${devicetree_size} && " \
"echoCopying ramdisk... && " \
"cp.b0xE2620000 ${ramdisk_load_address} ${ramdisk_size} && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"uenvboot="\
"ifrun loadbootenv; then " \
"echoLoaded environment from ${bootenv}; " \
"runimportbootenv; " \
"fi;" \
"iftest -n $uenvcmd; then " \
"echoRunning uenvcmd ...; " \
"runuenvcmd; " \
"fi\0"\
"sdboot=ifmmcinfo; then " \
"runuenvboot; " \
"get_bitstream_name&& " \
"echo- load ${bitname} to PL... && " \
"fatloadmmc 0 0x200000 ${bitname} && " \
"fpgaloadb 0 0x200000 ${filesize} && " \
"echoCopying Linux from SD to RAM... && " \
"fatloadmmc 0 ${kernel_load_address} ${kernel_image} && " \
"fatloadmmc 0 ${devicetree_load_address} ${devicetree_image} && " \
"fatloadmmc 0 ${ramdisk_load_address} ${ramdisk_image} && " \
"bootm${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address};" \
"fi\0"\
"usbboot=ifusb start; then " \
"runuenvboot; " \
"echoCopying Linux from USB to RAM... && " \
"fatloadusb 0 ${kernel_load_address} ${kernel_image} && " \
"fatloadusb 0 ${devicetree_load_address} ${devicetree_image} && " \
"fatloadusb 0 ${ramdisk_load_address} ${ramdisk_image} && " \
"bootm${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address};" \
"fi\0"\
"nandboot=echoCopying Linux from NAND flash to RAM... && " \
"nandread ${kernel_load_address} 0x100000 ${kernel_size} && " \
"nandread ${devicetree_load_address} 0x600000 ${devicetree_size} && " \
"echoCopying ramdisk... && " \
"nandread ${ramdisk_load_address} 0x620000 ${ramdisk_size} && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"jtagboot=echoTFTPing Linux to RAM... && " \
"tftpboot${kernel_load_address} ${kernel_image} && " \
"tftpboot${devicetree_load_address} ${devicetree_image} && " \
"tftpboot${ramdisk_load_address} ${ramdisk_image} && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_norboot=echoCopying Image from NOR flash to RAM... && " \
"cp.b0xE2100000 0x100000 ${boot_size} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_nandboot=echoCopying Image from NAND flash to RAM... && " \
"nandread 0x100000 0x0 ${boot_size} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_qspiboot=echoCopying Image from QSPI flash to RAM... && " \
"sfprobe 0 0 0 && " \
"sfread 0x100000 0x0 ${boot_size} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_sdboot=echoCopying Image from SD to RAM... && " \
"fatloadmmc 0 0x100000 ${boot_image} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"rsa_jtagboot=echoTFTPing Image to RAM... && " \
"tftpboot0x100000 ${boot_image} && " \
"zynqrsa0x100000 && " \
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"qspiboot=echoCopying Linux from QSPI flash to RAM... && " \
"sfprobe 0 0 0 && " \
"qspi_get_bitsize0x0A0000 && " \
"sfread ${loadbit_addr} 0x0A0004 ${bitsize} && " \
"fpgaloadb 0 ${loadbit_addr} ${bitsize} && " \
"sfread ${kernel_load_address} ${qkernel_addr} ${kernel_size} && " \
"sfread ${devicetree_load_address} ${qdevtree_addr} ${devicetree_size} &&" \
"echoCopying ramdisk... && " \
"sfread ${ramdisk_load_address} ${qramdisk_addr} ${ramdisk_size} && "\
"bootm${kernel_load_address} ${ramdisk_load_address}${devicetree_load_address}\0" \
"qspiupdate=echoUpdate qspi images from sd card... && " \
"echo- Init mmc... && mmc rescan && " \
"echo- Init qspi flash... && sf probe 0 0 0 && " \
"echo- Write boot.bin... && " \
"fatloadmmc 0 0x200000 boot.bin && " \
"sferase ${qboot_addr} ${boot_size} && " \
"sferase ${qbootenv_addr} ${qbootenv_size} && " \
"sfwrite 0x200000 0 ${filesize} && " \
"get_bitstream_name&& " \
"echo- Write ${bitstream_image}... && " \
"fatloadmmc 0 0x200000 ${bitstream_image} && " \
"sferase 0x0A0000 0x460000 && " \
"mw.l0x100000 ${filesize} && " \
"sfwrite 0x100000 0x0A0000 4 && " \
"sfwrite 0x200000 0x0A0004 ${filesize} && " \
"echo- Write uImage... && " \
"fatloadmmc 0 0x200000 uImage && " \
"sferase ${qkernel_addr} ${kernel_size} && " \
"sfwrite 0x200000 ${qkernel_addr} ${filesize} && " \
"echo- Write device tree... && " \
"fatloadmmc 0 0x200000 devicetree.dtb && " \
"sferase ${qdevtree_addr} ${devicetree_size} && " \
"sfwrite 0x200000 ${qdevtree_addr} ${filesize} && " \
"echo- Write Ramdisk... && " \
"fatloadmmc 0 0x200000 uramdisk.image.gz && " \
"sferase ${qramdisk_addr} ${ramdisk_size} && " \
"sfwrite 0x200000 ${qramdisk_addr} ${filesize} && " \
"echo- Done.\0"
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,如下:[cpp] view plain copy#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
# define ENV_HEADER_SIZE (sizeof(uint32_t) + 1)
# define ACTIVE_FLAG 1
# define OBSOLETE_FLAG 0
#else
# define ENV_HEADER_SIZE (sizeof(uint32_t))
#endif
#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} 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 相关的部分 [cpp] view plain copytypedef struct global_data
{
…
unsigned long env_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct ??? */
unsigned long env_valid /* Checksum of Environment valid */
…
} 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中的实现如下:[cpp] view plain copy/*
* This is called before nand_init() so we can't read NAND to
* validate env data.
*
* Mark it OK for now. env_relocate() in env_common.c will call our
* relocate function which does the real validation.
*
* When using a NAND boot image (like sequoia_nand), the environment
* can be embedded or attached to the U-Boot image in NAND flash.
* This way the SPL loads not only the U-Boot image from NAND but
* also the environment.
*/
int env_init(void)
{
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
return 0;
}
从注释就基本可以看出这个函数的作用,因为env_init要早于静态存储器的初始化,所以无法进行env的读写,这里将gd中的env相关变量进行配置,默认设置env为valid。方便后面env_relocate函数进行真正的env从nand到ram的relocate。继续执行,在board_init_r中,如下:[cpp] view plain copy/* initialize environment */
if (should_load_env())
env_relocate();
else
set_default_env(NULL);
这是在所有存储器初始化完成后执行的。首先调用should_load_env,如下:[cpp] view plain copy/*
* Tell if it's OK to load the environment early in boot.
*
* If CONFIG_OF_CONFIG is defined, we'll check with the FDT to see
* if this is OK (defaulting to saying it's not OK).
*
* NOTE: Loading the environment early can be a bad idea if security is
* important, since no verification is done on the environment.
*
* @return 0 if environment should not be loaded, !=0 if it is ok to load
*/
static int should_load_env(void)
{
#ifdef CONFIG_OF_CONTROL
return fdtdec_get_config_int(gd->fdt_blob, "load-environment", 1);
#elif defined CONFIG_DELAY_ENVIRONMENT
return 0;
#else
return 1;
#endif
}
从注释可以看出,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中:[cpp] view plain copyvoid env_relocate(void)
{
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
env_reloc();
env_htab.change_ok += gd->reloc_off;
#endif
if (gd->env_valid == 0) {
#if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)
/* Environment not changable */
set_default_env(NULL);
#else
bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);
set_default_env("!bad CRC");
#endif
} else {
env_relocate_spec();
}
}
Gd->env_valid在之前的env_init中设置为1,所以这里调用env_relocate_spec,这个函数也是不同存储器的中间封装层提供的函数,对于nand在common/env_nand.c中,如下:[cpp] view plain copyvoid env_relocate_spec(void)
{
int ret;
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
if (ret) {
set_default_env("!readenv() failed");
return;
}
env_import(buf, 1);
}
首先定义一个长度为CONFIG_ENV_SIZE的buf,然后调用readenv,CONFIG_ENV_OFFSET是在配置文件中定义的env在nand中偏移位置。我们这里定义的是在4M的位置。Readenv也在env_nand.c中,如下:[cpp] view plain copyint readenv(size_t offset, u_char *buf)
{
size_t end = offset + CONFIG_ENV_RANGE;
size_t amount_loaded = 0;
size_t blocksize, len;
u_char *char_ptr;
blocksize = nand_info[0].erasesize;
if (!blocksize)
return 1;
len = min(blocksize, CONFIG_ENV_SIZE);
while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
if (nand_block_isbad(&nand_info[0], offset)) {
offset += blocksize;
} else {
char_ptr = &buf[amount_loaded];
if (nand_read_skip_bad(&nand_info[0], offset,
&len, NULL,
nand_info[0].size, char_ptr))
return 1;
offset += blocksize;
amount_loaded += len;
}
}
if (amount_loaded != CONFIG_ENV_SIZE)
return 1;
return 0;
}
Readenv函数利用nand_info[0]对nand进行读操作,读出指定位置,指定长度的数据到buf中。Nand_info[0]是一个全局变量,来表征第一个nand device,这里在nand_init时会初始化这个变量。Nand_init必须在env_relocate之前。回到env_relocate_spec中,buf读回后调用env_import,如下:[cpp] view plain copy/*
* Check if CRC is valid and (if yes) import the environment.
* Note that "buf" may or may not be aligned.
*/
int env_import(const char *buf, int check)
{
env_t *ep = (env_t *)buf;
if (check) {
uint32_t crc;
memcpy(&crc, &ep->crc, sizeof(crc));
if (crc32(0, ep->data, ENV_SIZE) != crc) {
set_default_env("!bad CRC");
return 0;
}
}
if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0,
0, NULL)) {
gd->flags |= GD_FLG_ENV_READY;
return 1;
}
error("Cannot import environment: errno = %d\n", errno);
set_default_env("!import failed");
return 0;
}
首先将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实现。[cpp] view plain copystatic int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
printf("Saving Environment to %s...\n", env_name_spec);
return saveenv() ? 1 : 0;
}
U_BOOT_CMD(
saveenv, 1, 0, do_env_save,
"save environment variables to persistent storage",
""
);
在do_env_save调用saveenv,这个函数是不同存储器实现的封装层函数。对于nand,在common/env_nand.c中,如下:[cpp] view plain copyint saveenv(void)
{
int ret = 0;
ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
ssize_t len;
char *res;
int env_idx = 0;
static const struct env_location location[] = {
{
.name = "NAND",
.erase_opts = {
.length = CONFIG_ENV_RANGE,
.offset = CONFIG_ENV_OFFSET,
},
},
#ifdef CONFIG_ENV_OFFSET_REDUND
{
.name = "redundant NAND",
.erase_opts = {
.length = CONFIG_ENV_RANGE,
.offset = CONFIG_ENV_OFFSET_REDUND,
},
},
#endif
};
if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
return 1;
res = (char *)&env_new->data;
len = hexport_r(&env_htab, '\0', 0, &res, ENV_SIZE, 0, NULL);
if (len < 0) {
error("Cannot export environment: errno = %d\n", errno);
return 1;
}
env_new->crc = crc32(0, env_new->data, ENV_SIZE);
#ifdef CONFIG_ENV_OFFSET_REDUND
env_new->flags = ++env_flags; /* increase the serial */
env_idx = (gd->env_valid == 1);
#endif
ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
#ifdef CONFIG_ENV_OFFSET_REDUND
if (!ret) {
/* preset other copy for next write */
gd->env_valid = gd->env_valid == 2 ? 1 : 2;
return ret;
}
env_idx = (env_idx + 1) & 1;
ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
if (!ret)
printf("Warning: primary env write failed,"
" redundancy is lost!\n");
#endif
return ret;
}
定义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的操作。
相关文章推荐
- u-boot 分析之 默认环境变量配置
- Spring boot基础:配置文件配置变量、多环境的配置
- Spring boot 项目 maven的profile多环境配置 不自动替换变量的问题解决
- U-BOOT环境变量的获取和保存的实现分析
- SpringBoot学习<二>——SpringBoot的默认配置文件application和多环境配置
- linux下配置java的环境变量(解决默认的openjdk无法修改)
- Spring boot项目maven的profile多环境配置不自动替换变量的问题解决
- 用户环境变量的配置和分析
- Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量)
- 用户环境变量的配置和分析
- android uboot 环境变量配置fb及双屏显示(vga、lcd)
- jdk环境变量配置和问题分析
- openssl 通过环境变量设置默认的配置文件路径(conf cnf)
- uboot 源码分析(2)uboot 环境变量实现简析
- 4000 U-BOOT环境变量的获取和保存的实现分析
- 用户环境变量的配置和分析
- [IMX6Q]u-boot环境变量原理分析
- Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量
- Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量)
- Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量)