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: <imageType> <part_name> <loadaddr> \n"   //usage
   "    - <image_type> 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: <imgread kernel boot loadaddr> \n"   //usage
   "        to read recovery.img from part recovery from flash: <imgread kernel recovery loadaddr $offset> \n"   //usage
   "        to read logo.img     from part logo     from flash: <imgread res    logo loadaddr> \n"   //usage
   "        to read one picture named 'bootup' from logo.img    from logo: <imgread pic logo bootup loadaddr> \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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章