bootcmd

ubootshell中使用printenv命令可以看到bootcmd的字符串:

=> printenv bootcmd
bootcmd=boot_android ${devtype} ${devnum};boot_fit;bootrkp;run distro_bootcmd;

命令解析:
- boot_android: 查找并尝试启动安卓镜像。
- boot_fix: 查找并尝试启动fit格式镜像。
- bootrkp: 查找并尝试启动rk分区镜像。
- run distro_bootcmd: 这就是这篇文章的主角了,distro_bootcmd可以根据不同介质去启动kernel内核,比如kernel可以在sd卡,emmc上,都可以顺利启动。

以上提到的bootcmd可以在include/configs/rockchip-common.h中看到相关定义:

#if defined(CONFIG_AVB_VBMETA_PUBLIC_KEY_VALIDATE)
#define RKIMG_BOOTCOMMAND \
"boot_android ${devtype} ${devnum};"
#elif defined(CONFIG_FIT_SIGNATURE)
#define RKIMG_BOOTCOMMAND \
"boot_fit;"
#else
#define RKIMG_BOOTCOMMAND \
"boot_android ${devtype} ${devnum};" \
"boot_fit;" \
"bootrkp;" \
"run distro_bootcmd;"
#endif

distro_bootcmd

现在来重点分析一下distro_bootcmd,首先使用打印出distro_bootcmd:

=> printenv distro_bootcmd 
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done

uboot源码中找到对应实现,可以看到BOOTENV_SET_SCSI_NEED_INIT这个宏定义是空的,所以不必深究:

#define BOOTENV \
BOOTENV_BOOT_TARGETS
...
BOOT_TARGET_DEVICES(BOOTENV_DEV) \
\
"distro_bootcmd=" BOOTENV_SET_SCSI_NEED_INIT \
"for target in ${boot_targets}; do " \
"run bootcmd_${target}; " \
"done\0"

BOOTENV

在这个BOOTENV宏定义中,定义了distro_bootcmd的值,BOOTENVinclude/configs/rk3568_common.h中被使用到:

CONFIG_EXTRA_ENV_SETTINGS \
	ENV_MEM_LAYOUT_SETTINGS \
"partitions=" PARTS_RKIMG \
ROCKCHIP_DEVICE_SETTINGS \
RKIMG_DET_BOOTDEV \
BOOTENV
#endif

CONFIG_EXTRA_ENV_SETTINGS又在include/env_default.h中被使用到:

#ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
env_t environment __UBOOT_ENV_SECTION__ = {
ENV_CRC, /* CRC Sum */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
1, /* Flags: valid */
#endif
{
#elif defined(DEFAULT_ENV_INSTANCE_STATIC)
static char default_environment[] = {
#else
const uchar default_environment[] = {
#endif
#ifdef CONFIG_USE_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#endif
...
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
#ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
}
#endif

TODO 了解这个数组

回到distro_bootcmd

#define BOOTENV \
BOOTENV_BOOT_TARGETS
...
BOOT_TARGET_DEVICES(BOOTENV_DEV) \
\
"distro_bootcmd=" BOOTENV_SET_SCSI_NEED_INIT \
"for target in ${boot_targets}; do " \
"run bootcmd_${target}; " \
"done\0"

这里其实就是一个循环,循环遍历 ${boot_targets}并执行bootcmd_${target},查看 ${boot_targets}相关定义:

// path: include/config_distro_bootcmd.h
#define BOOTENV_DEV_NAME(devtypeu, devtypel, instance) \
BOOTENV_DEV_NAME_##devtypeu(devtypeu, devtypel, instance)
#define BOOTENV_BOOT_TARGETS \
"boot_targets=" BOOT_TARGET_DEVICES(BOOTENV_DEV_NAME) "\0"

BOOTENV_BOOT_TARGETS生成boot_targets字符串

通过BOOTENV_BOOT_TARGETS宏定义生成了boot_targets字符串:

// path: include/config_distro_bootcmd.h
#define BOOTENV_DEV_NAME(devtypeu, devtypel, instance) \
BOOTENV_DEV_NAME_##devtypeu(devtypeu, devtypel, instance)
#define BOOTENV_BOOT_TARGETS \
"boot_targets=" BOOT_TARGET_DEVICES(BOOTENV_DEV_NAME) "\0"

[!NOTE]
BOOTENV_BOOT_TARGETS也是在BOOTENV中定义的

BOOTENV_BOOT_TARGETS调用了BOOT_TARGET_DEVICES宏定义,BOOT_TARGET_DEVICES就是在对应的芯片上或芯片系列的定义了,不同的芯片应有不同的实现:

// path: include/configs/rockchip-common.h
#define BOOT_TARGET_DEVICES(func) \
BOOT_TARGET_MMC(func) \
BOOT_TARGET_MTD(func) \
BOOT_TARGET_RKNAND(func) \
BOOT_TARGET_USB(func) \
BOOT_TARGET_PXE(func) \
BOOT_TARGET_DHCP(func)

这里宏定义定义成了6个宏,这里只研究BOOT_TARGET_MMC,其他都是差不多的:

// path: include/configs/rockchip-common.h
/* First try to boot from SD (index 1), then eMMC (index 0) */
#if CONFIG_IS_ENABLED(CMD_MMC)
#define BOOT_TARGET_MMC(func) \
func(MMC, mmc, 1) \
func(MMC, mmc, 0)
#else
#define BOOT_TARGET_MMC(func)
#endif

逐级展开:

#define BOOTENV_DEV_NAME(devtypeu, devtypel, instance) \
BOOTENV_DEV_NAME_##devtypeu(devtypeu, devtypel, instance)
#define BOOTENV_BOOT_TARGETS \
"boot_targets=" BOOT_TARGET_DEVICES(BOOTENV_DEV_NAME) "\0"

->(这里就只展开mmc了,道理都一样)
"boot_targets=" BOOT_TARGET_MMC(BOOTENV_DEV_NAME) "\0"
->
"boot_targets=" BOOTENV_DEV_NAME(MMC, mmc, 1) \
BOOTENV_DEV_NAME(MMC, mmc, 0)"\0"
->
"boot_targets=" BOOTENV_DEV_NAME_MMC(MMC, mmc, 1) \
BOOTENV_DEV_NAME_MMC(MMC, mmc, 0)"\0"

BOOTENV_DEV_NAME_MMC宏定义:

// path: include/config_distro_bootcmd.h
#define BOOTENV_DEV_NAME_BLKDEV(devtypeu, devtypel, instance) \
#devtypel #instance " "
#define BOOTENV_DEV_NAME_MMC BOOTENV_DEV_NAME_BLKDEV

继续展开:

#define BOOTENV_DEV_NAME(devtypeu, devtypel, instance) \
BOOTENV_DEV_NAME_##devtypeu(devtypeu, devtypel, instance)
#define BOOTENV_BOOT_TARGETS \
"boot_targets=" BOOT_TARGET_DEVICES(BOOTENV_DEV_NAME) "\0"

->(这里就只展开mmc了,道理都一样)
"boot_targets=" BOOT_TARGET_MMC(BOOTENV_DEV_NAME) "\0"
->
"boot_targets=" BOOTENV_DEV_NAME(MMC, mmc, 1) \
BOOTENV_DEV_NAME(MMC, mmc, 0)"\0"
->
"boot_targets=" BOOTENV_DEV_NAME_MMC(MMC, mmc, 1) \
BOOTENV_DEV_NAME_MMC(MMC, mmc, 0)"\0"
->
"boot_targets=" BOOTENV_DEV_NAME_BLKDEV(MMC, mmc, 1) \
BOOTENV_DEV_NAME_BLKDEV(MMC, mmc, 0)"\0"
->
"boot_targets=" mmc1 mmc0 "\0"

最后boot_targets就变成了下面这样,同时在ubootshell中验证也是这样:

=> printenv boot_targets 
boot_targets=mmc1 mmc0 mtd2 mtd1 mtd0 usb0 pxe dhcp

boot_targets分析完了,但这只是我们distro_bootcmd循环的变量而已,还有一个run bootcmd_${target};没有分析,通过boot_targets能知道,这运行的大概就是bootcmd_mmc0, bootcmd_mmc1这样的东西。

生成distro_cmd运行命令

BOOTENV中有一个宏调用是BOOT_TARGET_DEVICES(BOOTENV_DEV)(上文BOOTENV代码中贴了),现在回顾一下BOOT_TARGET_DEVICES:

// path: include/configs/rockchip-common.h
#define BOOT_TARGET_DEVICES(func) \
BOOT_TARGET_MMC(func) \
BOOT_TARGET_MTD(func) \
BOOT_TARGET_RKNAND(func) \
BOOT_TARGET_USB(func) \
BOOT_TARGET_PXE(func) \
BOOT_TARGET_DHCP(func)

BOOTENV_DEV定义如下:

#define BOOTENV_DEV_BLKDEV(devtypeu, devtypel, instance) \
"bootcmd_" #devtypel #instance "=" \
"setenv devnum " #instance "; " \
"run " #devtypel "_boot\0"

逐级展开(只讨论mmc):

   BOOT_TARGET_DEVICES(BOOTENV_DEV)

->(这里就只展开mmc了,道理都一样)
BOOT_TARGET_MMC(BOOTENV_DEV) \
->
BOOTENV_DEV(MMC, mmc, 1) \
BOOTENV_DEV(MMC, mmc, 0)"\0"
->
"bootcmd_mmc1=setenv devnum 1;run mmc1_boot\0" \
"bootcmd_mmc0=setenv devnum 0;run mmc0_boot\0" \

Ref

https://blog.csdn.net/Andyshrk/article/details/89136721
https://juejin.cn/post/7202851843488088125