uboot环境变量分析

项目情景

最近我在一个新平台的开发过程中遇到烧录问题. 具体的问题是使用原厂提供的烧录脚本烧录成功,但是固件却没有更新.
其中kernel和dtb烧录指令如下:

adnl.exe Partition -M mem -P 0x1000000 -F linux.dtb
adnl.exe Partition -M mem -P 0x1000 -F boot.img
adnl.exe oem "run storeargs;run bootcmd"

我老想着走捷径解决问题,不想去看代码.折腾到筋疲力尽,最后我还是妥协了,好好分析了代码,总结了原因, 需要解决两个疑问点:

  1. 烧录的dtb的地址
  2. 烧录内核的格式和地址

分析总结

遇到烧录后固件没更新的问题,第一个能想到的是烧录地址与启动地址不匹配.具体从uboot的环境变量开始分析问题.
在设备上电后,首先敲回车,进入uboot终端模式,敲入printenv,回车,查看如下:

ad402# print
EnableSelinux=enforcing
Irq_check_en=0
active_slot=normal
arch=arm
baudrate=115200
bcb_cmd=get_valid_slot;
board=a1_ad402
board_name=a1_ad402
boot_part=boot
bootargs=init=/init console=ttyS0,115200 no_console_suspend earlycon=aml_uart,0xfe002000 ramoops.pstore_en=1 ramoops.record_size=0x8000 ramoops.console_size=0x4000 rootfstype=ramfs otg_device=1 logo=osd0,loaded,0x00300000 vout=1080p60hz,enable panel_type=lcd_1 hdmitx=, hdmimode=1080p60hz frac_rate_policy=1 hdmi_read_edid= cvbsmode=576cvbs osd_reverse=0 video_reverse=0 irq_check_en=0 androidboot.selinux=enforcing androidboot.firstboot=1 jtag=disable androidboot.hardware=amlogic androidboot.wificountrycode=US androidboot.serialno=1234567890
bootcmd=run storeboot
bootdelay=2
bootm_low=0
bootm_size=8000000
cmdline_keys=setenv usid 1234567890; setenv region_code US;if keyman init 0x1234; then if keyman read usid ${loadaddr} str; then fi;if keyman read region_code ${loadaddr} str; then fi;if keyman read mac ${loadaddr} str; then setenv bootargs ${bootargs} mac=${mac} androidboot.mac=${mac};fi;if keyman read deviceid ${loadaddr} str; then setenv bootargs ${bootargs} androidboot.deviceid=${deviceid};fi;if keyman read mac_wifi ${loadaddr} str; then setenv bootargs ${bootargs} mac_wifi=${mac_wifi} androidboot.mac_wifi=${mac_wifi};fi;if keyman read mac_bt ${loadaddr} str; then setenv bootargs ${bootargs} mac_bt=${mac_bt} androidboot.mac_bt=${mac_bt};fi;fi;setenv bootargs ${bootargs} androidboot.wificountrycode=${region_code};setenv bootargs ${bootargs} androidboot.serialno=${usid};setenv serial ${usid}; setenv serial# ${usid};
common_dtb_load=imgread dtb _aml_dtb ${dtb_mem_addr}
cpu=armv8
cvbs_drv=0
cvbsmode=576cvbs
display_bpp=16
display_color_bg=0
display_color_fg=0xffff
display_color_index=16
display_height=1080
display_layer=osd0
display_width=1920
dtb_mem_addr=0x01000000
fatload_dev=usb
fb_addr=0x00300000
fb_height=1080
fb_width=1920
fdt_high=0x20000000
fdtaddr=1000000
fdtcontroladdr=fff2be40
firstboot=1
frac_rate_policy=1
fs_type=rootfstype=ramfs
gatewayip=10.18.9.1
get_os_type=if store read ${os_ident_addr} ${boot_part} 0 0x1000; then os_ident ${os_ident_addr}; fi
hdmimode=1080p60hz
hostname=arm_gxbb
initargs=init=/init console=ttyS0,115200 no_console_suspend earlycon=aml_uart,0xfe002000 ramoops.pstore_en=1 ramoops.record_size=0x8000 ramoops.console_size=0x4000 
ipaddr=10.18.9.97
jtag=disable
loadaddr=0x00020000
loadaddr_kernel=0x00020000
loadaddr_rtos=0x00001000
netmask=255.255.255.0
os_ident_addr=0x00500000
osd_reverse=0
otg_device=1
outputmode=1080p60hz
panel_type=lcd_1
preboot=run bcb_cmd; run upgrade_check;run storeargs;
recovery_from_fat_dev=setenv loadaddr ${loadaddr_kernel};if fatload ${fatload_dev} 0 ${loadaddr} aml_autoscript; then autoscr ${loadaddr}; fi;if fatload ${fatload_dev} 0 ${loadaddr} recovery.img; then if fatload ${fatload_dev} 0 ${dtb_mem_addr} dtb.img; then echo ${fatload_dev} dtb.img loaded; fi;bootm ${loadaddr};fi;
recovery_from_flash=setenv loadaddr ${loadaddr_kernel};setenv bootargs ${bootargs} aml_dt=${aml_dt} recovery_part={recovery_part} recovery_offset={recovery_offset};if imgread dtb recovery ${dtb_mem_addr}; then else echo restore dtb; run common_dtb_load;fi;if imgread kernel ${recovery_part} ${loadaddr} ${recovery_offset}; then bootm ${loadaddr}; fi;
recovery_from_udisk=setenv fatload_dev usb;if usb start 0; then run recovery_from_fat_dev; fi;
recovery_offset=0
recovery_part=recovery
region_code=US
sdcburncfg=aml_sdc_burn.ini
serial=1234567890
serial#=1234567890
serverip=10.18.9.113
soc=a1
stderr=serial@2000
stdin=serial@2000
stdout=serial@2000
storeargs=setenv bootargs ${initargs} ${fs_type} otg_device=${otg_device} logo=${display_layer},loaded,${fb_addr} vout=${outputmode},enable panel_type=${panel_type} hdmitx=${cecconfig},${colorattribute} hdmimode=${hdmimode} frac_rate_policy=${frac_rate_policy} hdmi_read_edid=${hdmi_read_edid} cvbsmode=${cvbsmode} osd_reverse=${osd_reverse} video_reverse=${video_reverse} irq_check_en=${Irq_check_en}  androidboot.selinux=${EnableSelinux} androidboot.firstboot=${firstboot} jtag=${jtag}; setenv bootargs ${bootargs} androidboot.hardware=amlogic;run cmdline_keys;
storeboot=run get_os_type;if test ${os_type} = rtos; then setenv loadaddr ${loadaddr_rtos};store read ${loadaddr} ${boot_part} 0 0x400000;bootm ${loadaddr};else if test ${os_type} = kernel; then if fdt addr ${dtb_mem_addr}; then else echo retry common dtb; run common_dtb_load; fi;setenv loadaddr ${loadaddr_kernel};if imgread kernel ${boot_part} ${loadaddr}; then bootm ${loadaddr}; fi;else echo wrong OS format ${os_type}; fi;fi;echo try upgrade as booting failure; run update;
switch_bootmode=get_rebootmode;if test ${reboot_mode} = factory_reset; then run recovery_from_flash;else if test ${reboot_mode} = update; then run update;else if test ${reboot_mode} = quiescent; then setenv bootargs ${bootargs} androidboot.quiescent=1;else if test ${reboot_mode} = recovery_quiescent; then setenv bootargs ${bootargs} androidboot.quiescent=1;run recovery_from_flash;else if test ${reboot_mode} = cold_boot; then else if test ${reboot_mode} = fastboot; then fastboot;fi;fi;fi;fi;fi;fi;
update=run usb_burning; run recovery_from_udisk;run recovery_from_flash;
upgrade_check=echo recovery_status=${recovery_status};if itest.s "${recovery_status}" == "in_progress"; then run storeargs; run recovery_from_flash;else fi;echo upgrade_step=${upgrade_step}; if itest ${upgrade_step} == 3; then run storeargs; run update; fi;
upgrade_step=2
usb_burning=adnl 1000
usid=1234567890
vendor=amlogic
video_reverse=0

Environment size: 5556/8188 bytes

抓住uboot命令行的两个重点参数:
bootargs: 传递给Linux内核的启动参数
bootcmd: 自动启动时执行命令

分析bootcmd

bootcmd=run storeboot

其中run是一个命令,对应u-boot源代码cmd/nvedit.c目录:

U_BOOT_CMD_COMPLETE(
>...run,>...CONFIG_SYS_MAXARGS,>1,>.do_run,
>..."run commands in an environment variable",
>..."var [...]\n"
>..."    - run the commands in the environment variable(s) 'var'",
>...var_complete
);

在uboot中,大多数命令都能在代码中找到前缀为do_xxx的函数实现. 其中do_run函数是run命令的具体实现.

#if defined(CONFIG_CMD_RUN)
int do_run(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])                                                                                                                                                                          
{
>...int i;

>...if (argc < 2)
>...>...return CMD_RET_USAGE;

>...for (i = 1; i < argc; ++i) {
>...>...char *arg;
		//获取命令参数:如run storeboot,通过env_get得到storeboot
>...>...arg = env_get(argv[i]);
>...>...if (arg == NULL) {
>...>...>...printf("## Error: \"%s\" not defined\n", argv[i]);
>...>...>...return 1;
>...>...}
		//执行命令,调用函数do_storeboot
>...>...if (run_command(arg, flag | CMD_FLAG_ENV) != 0)
>...>...>...return 1;
>...}
>...return 0;
}
#endif

分析分析storeboot

storeboot=run get_os_type;if test ${os_type} = rtos; then setenv loadaddr ${loadaddr_rtos};store read ${loadaddr} ${boot_part} 0 0x400000;bootm ${loadaddr};else if test ${os_type} = kernel; then if fdt addr ${dtb_mem_addr}; then else echo retry common dtb; run common_dtb_load; fi;setenv loadaddr ${loadaddr_kernel};if imgread kernel ${boot_part} ${loadaddr}; then bootm ${loadaddr}; fi;else echo wrong OS format ${os_type}; fi;fi;echo try upgrade as booting failure; run update;

首先调用了get_os_type函数,判断Flash上某地址的镜像类型.

get_os_type的具体实现

os_ident_addr=0x00500000
get_os_type=if store read ${os_ident_addr} ${boot_part} 0 0x1000; then os_ident ${os_ident_addr}; fi

其中store命令对应do_store函数,仿照分析do_run函数的流程得知, do_store函数的用法:
意思是从flash boot分区0地址读取0x1000大小内容到DDR内存的 0x00500000地址.

"store read addr [partition name] off size\n"
        "       read 'size' bytes from offset 'off'\n"
        "       of device/partition 'partition name' to.\n"
        "       address 'addr' of memory.\n"
        "       if partition name not value. read start with\n"
        "       offset in normal logic area,if tpl area exist\n"
        "       read offset at end of tpl area\n"

os_ident 命令对应do_os_ident函数:

static int do_os_ident(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
        int ret = -1;

        if (argc < 2) {
                printf("Err! OS hdr addr not specified!\n");
                return ret;
        }

        const void *img_addr = simple_strtoul(argv[1], NULL, 16);
        debug_print("os hdr addr: 0x%lx\n", (ulong)img_addr);

        ret = genimg_get_format(img_addr);
        switch (ret) {
                case IMAGE_FORMAT_LEGACY:
                        debug_print("IMAGE_FORMAT_LEGACY format\n");
                        //将os类型填入环境变量"os_type"中.
                        env_set("os_type", "rtos");
                        break;
                case IMAGE_FORMAT_FIT:
                        debug_print("IMAGE_FORMAT_FIT format\n");
                        /* ignore fdt format, it's not an OS */
                        //env_set("os_type", "fdt");
                        break;
                case IMAGE_FORMAT_ANDROID:
                        debug_print("IMAGE_FORMAT_ANDROID format\n");
                        env_set("os_type", "kernel");
                        break;
                case IMAGE_FORMAT_INVALID:
                        debug_print("IMAGE_FORMAT_INVALID format\n");
                        env_set("os_type", "invalid");
                        break;
                default:
                        debug_print("default format\n");
                        break;
        }

        return ret;
}

由于项目开发用到的是linux系统,所以从上代码分析得知,Flash boot分区的0地址烧录android格式的kernel镜像, 也就是boot.img是android格式的镜像.

接着分析dtb的读取:

else if test ${os_type} = kernel; then if fdt addr ${dtb_mem_addr}; then else echo retry common dtb; run common_dtb_load; fi;setenv loadaddr ${loadaddr_kernel};if imgread kernel ${boot_part} ${loadaddr}; then bootm ${loadaddr}; fi;

U_BOOT_CMD(
        fdt,    255,    0,      do_fdt,
        "flattened device tree utility commands", fdt_help_text
        //展平的设备树实用程序命令
);
static int do_fdt(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
>...if (argc < 2) 
>...>...return CMD_RET_USAGE;

>.../*
>... * Set the address of the fdt
>... */
>...if (strncmp(argv[1], "ad", 2) == 0) { 
>...>...unsigned long addr;
>...>...int control = 0; 
>...>...struct fdt_header *blob;
>...>.../*
>...>... * Set the address [and length] of the fdt.
>...>... */
>...>...argc -= 2;
>...>...argv += 2;
/* Temporary #ifdef - some archs don't have fdt_blob yet */
#ifdef CONFIG_OF_CONTROL
>...>...if (argc && !strcmp(*argv, "-c")) {
>...>...>...control = 1; 
>...>...>...argc--;
>...>...>...argv++;
>...>...}
#endif
>...>...if (argc == 0) { 
>...>...>...if (control)
>...>...>...>...blob = (struct fdt_header *)gd->fdt_blob;
>...>...>...else
>...>...>...>...blob = working_fdt;
>...>...>...if (!blob || !fdt_valid(&blob))
>...>...>...>...return 1;
>...>...>...printf("The address of the fdt is %#08lx\n",
>...>...>...       control ? (ulong)map_to_sysmem(blob) :
>...>...>...>...>...env_get_hex("fdtaddr", 0)); 
>...>...>...return 0;
>...>...}

>...>...addr = simple_strtoul(argv[0], NULL, 16); 
>...>...blob = map_sysmem(addr, 0);
>...>...if (!fdt_valid(&blob))
>...>...>...return 1;
>...>...if (control)
>...>...>...gd->fdt_blob = blob;
>...>...else
>...>...>...set_working_fdt_addr(addr);

>...>...if (argc >= 2) { 
>...>...>...int  len; 
>...>...>...int  err; 
>...>...>.../*
>...>...>... * Optional new length
>...>...>... */
>...>...>...len = simple_strtoul(argv[1], NULL, 16); 
>...>...>...if (len < fdt_totalsize(blob)) {
>...>...>...>...printf ("New length %d < existing length %d, "
>...>...>...>...>..."ignoring.\n",
>...>...>...>...>...len, fdt_totalsize(blob));
>...>...>...} else {

do_fdt的主要工作是:对fdt格式检查, 读取dtb. The working_fdt points to our working flattened device tree, (指向flash地址0x01000000). 所以对于第一个疑问:烧录dtb到flash的地址就应该是0x01000000

分析内核的读取和解压

setenv loadaddr ${loadaddr_kernel}
imgread kernel ${boot_part} ${loadaddr};
bootm ${loadaddr};

其中:
第一行意思是设置环境变量loadaddr的值为0x00020000
第二行意思是从boot分区值为loadaddr的地址读取内核,解压到内存中.
第三行意思是启动内核

下面看imgread命令对应do_imgread函数的介绍

U_BOOT_CMD(
   imgread,         //command name
   5,               //maxargs
   0,               //repeatable
   do_image_read,   //command function
   "Read the image from internal flash with actual size",           //description
   "    argv:    \n"   //usage
   "    -  Current support is kernel/res(ource).\n"
   "imgread kernel  --- Read image in fomart IMAGE_FORMAT_ANDROID\n"
   "imgread dtb     --- Read dtb in fomart IMAGE_FORMAT_ANDROID\n"
   "imgread res     --- Read image packed by 'Amlogic resource packer'\n"
   "imgread picture --- Read one picture from Amlogic logo"
   "    - e.g. \n"
   "        to read boot.img     from part boot     from flash:  \n"   //usage
   "        to read recovery.img from part recovery from flash:  \n"   //usage
   "        to read logo.img     from part logo     from flash:  \n"   //usage
   "        to read one picture named 'bootup' from logo.img    from logo:  \n"   //usage
);

进一步印证了必须烧录android格式的kernel镜像.

对于bootm命令对应do_bootm函数,这个函数涉及到内核的读取,格式解析,解压和启动的过程,在后面的章节会详细论述. 这也是疑问二的解答重点.

结论
关于烧录问题目前的疑问解答了疑问1, 第二个疑问的解决在分析do_bootm函数时详细论证.
后面技术支持给出烧录文档,最新订正了内核的烧录地址. 这也是烧录后固件没有变化的原因.

adnl.exe Partition -M mem -P 0x1080000  -F /d/temp/a1/a1/boot.img

学习与探讨

  1. android格式的固件boot.img本身包含kernel, dtb, rootfs, 所以上面bootcmd中使用命令imgread从Flash读取boot.img, 又使用fdt addr ${dtb_mem_addr}命令读取dtb了.这是否是多此一举?

参考资料

Image uImage与zImage的区别
HOWTO: Unpack, Edit, and Re-Pack Boot Images
[uboot] uboot启动kernel篇(一)——Legacy-uImage & FIT-uImage

你可能感兴趣的:(【项目经验总结】,【Linux学习】,uboot,linux)