程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3588 - Rockchip Linux SDK脚本分析

----------------------------------------------------------------------------------------------------------------------------

开发板 :ArmSoM-Sige7开发板
eMMC64GB
LPDDR48GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2017.09
linux5.10
----------------------------------------------------------------------------------------------------------------------------

在《Rockchip RK3588 - Rockchip Linux SDK编译》我们介绍了SDK的编译流程以及固件升级相关的内容,本节将会对编译脚本进行深入的分析。

一、build.sh分析

Rockchip Linux SDK编译命令是由build.sh脚本实现的,其入口函数为main函数。

1.1 main函数

由于main函数代码内容较长,我们只对重点内容进行分析:

点击查看代码
main()
{
        [ -z "$DEBUG" ] || set -x

        trap 'err_handler' ERR
        set -eE
    
        # Save intial envionments
        unset INITIAL_SESSION
        INITIAL_ENV=$(mktemp -u)
        if [ -z "$RK_SESSION" ]; then
                INITIAL_SESSION=1
                env > "$INITIAL_ENV"
        fi
    
        export LC_ALL=C
    
        export SCRIPTS_DIR="$(dirname "$(realpath "$BASH_SOURCE")")"
        export COMMON_DIR="$(realpath "$SCRIPTS_DIR/..")"
        export SDK_DIR="$(realpath "$COMMON_DIR/../../..")"
        export DEVICE_DIR="$SDK_DIR/device/rockchip"
        export CHIPS_DIR="$DEVICE_DIR/.chips"
        export CHIP_DIR="$DEVICE_DIR/.chip"
    
        export RK_DATA_DIR="$COMMON_DIR/data"
        export RK_TOOL_DIR="$COMMON_DIR/tools"
        export RK_IMAGE_DIR="$COMMON_DIR/images"
        export RK_KBUILD_DIR="$COMMON_DIR/linux-kbuild"
        export RK_CONFIG_IN="$COMMON_DIR/configs/Config.in"
    
        export RK_BUILD_HOOK_DIR="$COMMON_DIR/build-hooks"
        export BUILD_HELPER="$RK_BUILD_HOOK_DIR/build-helper"
        export RK_POST_HOOK_DIR="$COMMON_DIR/post-hooks"
        export POST_HELPER="$RK_POST_HOOK_DIR/post-helper"
    
        export PARTITION_HELPER="$SCRIPTS_DIR/partition-helper"
    
        export RK_SESSION="${RK_SESSION:-$(date +%F_%H-%M-%S)}"
    
        export RK_OUTDIR="$SDK_DIR/output"
        export RK_SESSION_DIR="$RK_OUTDIR/sessions"
        export RK_LOG_BASE_DIR="$RK_OUTDIR/log"
        export RK_LOG_DIR="$RK_SESSION_DIR/$RK_SESSION"
        export RK_INITIAL_ENV="$RK_LOG_DIR/initial.env"
        export RK_CUSTOM_ENV="$RK_LOG_DIR/custom.env"
        export RK_FINAL_ENV="$RK_LOG_DIR/final.env"
        export RK_ROCKDEV_DIR="$SDK_DIR/rockdev"
        export RK_FIRMWARE_DIR="$RK_OUTDIR/firmware"
        export RK_SECURITY_FIRMWARE_DIR="$RK_OUTDIR/security-firmware"
        export RK_CONFIG="$RK_OUTDIR/.config"
        export RK_DEFCONFIG_LINK="$RK_OUTDIR/defconfig"
    
        # For Makefile
        case "$@" in
                make-targets)
                        # Chip targets
                        ls "$CHIPS_DIR"
                        ;&
                make-usage)
                        run_build_hooks "$@"
                        rm -f "$INITIAL_ENV"
                        exit 0 ;;
        esac


        # Log SDK information
        MANIFEST="$SDK_DIR/.repo/manifest.xml"
        if [ -e "$MANIFEST" ]; then
                if [ ! -L "$MANIFEST" ]; then
                        MANIFEST="$SDK_DIR/.repo/manifests/$(grep -o "[^\"]*\.xml" "$MANIFEST")"
                fi
                TAG="$(grep -o "linux-.*-gen-rkr[^.\"]*" "$MANIFEST" | \
                        head -n 1 || true)"
                MANIFEST="$(basename "$(realpath "$MANIFEST")")"
                echo
                echo -e "\e[35m############### Rockchip Linux SDK ###############\e[0m"
                echo
                echo -e "\e[35mManifest: $MANIFEST\e[0m"
                if [ "$TAG" ]; then
                        echo -e "\e[35mVersion: $TAG\e[0m"
                fi
                echo
        fi
    
        # Prepare firmware dirs
        mkdir -p "$RK_FIRMWARE_DIR" "$RK_SECURITY_FIRMWARE_DIR"
    
        cd "$SDK_DIR"
        [ -f README.md ] || ln -rsf "$COMMON_DIR/README.md" .
        [ -d common ] || ln -rsf "$COMMON_DIR" .
    
        # TODO: Remove it in the repo manifest.xml
        rm -f envsetup.sh
    
        OPTIONS=${@:-allsave}
    
        # Special handle for chip and defconfig
        # e.g. ./build.sh rk3588:rockchip_defconfig
        for opt in $OPTIONS; do
                if [ -d "$CHIPS_DIR/${opt%%:*}" ]; then
                        OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
                                sed "s/^$opt$/chip:$opt/" | xargs)
                elif echo "$opt" | grep -q "^[0-9a-z_]*_defconfig$"; then
                        OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
                                sed "s/^$opt$/defconfig:$opt/" | xargs)
                fi
        done
    
        # Options checking
        CMDS="$(run_build_hooks support-cmds all | xargs)"
        for opt in $OPTIONS; do
                case "$opt" in
                        help | h | -h | --help | usage | \?) usage ;;
                        clean:*)
                                # Check cleanup modules
                                for m in $(echo ${opt#clean:} | tr ':' ' '); do
                                        grep -wq clean_hook \
                                                "$SCRIPTS_DIR/mk-$m.sh" \
                                                2>/dev/null || usage
                                done
                                ;&
                        shell | cleanall)
                                # Check single options
                                if [ "$opt" = "$OPTIONS" ]; then
                                        break
                                fi
    
                                echo "ERROR: $opt cannot combine with other options!"
                                ;;
                        post-rootfs)
                                if [ "$opt" = "$1" -a -d "$2" ]; then
                                        # Hide other args from build stages
                                        OPTIONS=$opt
                                        break
                                fi
    
                                echo "ERROR: $opt should be the first option followed by rootfs dir!"
                                ;;
                        *)
                                # Make sure that all options are handled
                                if option_check "$CMDS" $opt; then
                                        continue
                                fi
    
                                echo "ERROR: Unhandled option: $opt"
                                ;;
                esac
    
                usage
        done
    
        # Prepare log dirs
        if [ ! -d "$RK_LOG_DIR" ]; then
                rm -rf "$RK_LOG_BASE_DIR" "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"
                mkdir -p "$RK_LOG_DIR"
                ln -rsf "$RK_SESSION_DIR" "$RK_LOG_BASE_DIR"
                ln -rsf "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"
                echo -e "\e[33mLog saved at $RK_LOG_DIR\e[0m"
                echo
        fi
    
        # Drop old logs
        cd "$RK_LOG_BASE_DIR"
        rm -rf $(ls -t | sed '1,10d')
        cd "$SDK_DIR"
    
        # Save initial envionments
        if [ "$INITIAL_SESSION" ]; then
                rm -f "$RK_INITIAL_ENV"
                mv "$INITIAL_ENV" "$RK_INITIAL_ENV"
                ln -rsf "$RK_INITIAL_ENV" "$RK_OUTDIR/"
        fi
    
        # Init stage (preparing SDK configs, etc.)
        run_build_hooks init $OPTIONS
        rm -f "$RK_OUTDIR/.tmpconfig*"
    
        # No need to go further
        CMDS="$(run_build_hooks support-cmds pre-build build \
                post-build | xargs) cleanall clean post-rootfs"
        option_check "$CMDS" $OPTIONS || return 0
    
        # Force exporting config environments
        set -a
    
        # Load config environments
        source "$RK_CONFIG"
        cp "$RK_CONFIG" "$RK_LOG_DIR"
    
        if [ -z "$INITIAL_SESSION" ]; then
                # Inherit session environments
                sed -n 's/^\(RK_.*=\)\(.*\)/\1"\2"/p' "$RK_FINAL_ENV" > \
                        "$INITIAL_ENV"
                source "$INITIAL_ENV"
                rm -f "$INITIAL_ENV"
        else
                # Detect and save custom environments
    
                # Find custom environments
                rm -f "$RK_CUSTOM_ENV"
                for cfg in $(grep "^RK_" "$RK_INITIAL_ENV" || true); do
                        env | grep -q "^${cfg//\"/}$" || \
                                echo "$cfg" >> "$RK_CUSTOM_ENV"
                done
    
                # Allow custom environments overriding
                if [ -e "$RK_CUSTOM_ENV" ]; then
                        ln -rsf "$RK_CUSTOM_ENV" "$RK_OUTDIR/"
    
                        echo -e "\e[31mWARN: Found custom environments: \e[0m"
                        cat "$RK_CUSTOM_ENV"
    
                        echo -e "\e[31mAssuming that is expected, please clear them if otherwise.\e[0m"
                        read -t 10 -p "Press enter to continue."
                        source "$RK_CUSTOM_ENV"
    
                        if grep -q "^RK_KERNEL_VERSION=" "$RK_CUSTOM_ENV"; then
                                echo -e "\e[31mCustom RK_KERNEL_VERSION ignored!\e[0m"
                                load_config RK_KERNEL_VERSION
                        fi
    
                        if grep -q "^RK_ROOTFS_SYSTEM=" "$RK_CUSTOM_ENV"; then
                                echo -e "\e[31mCustom RK_ROOTFS_SYSTEM ignored!\e[0m"
                                load_config RK_ROOTFS_SYSTEM
                        fi
                fi
        fi
    
        source "$PARTITION_HELPER"
        rk_partition_init
    
        set +a
    
        export PYTHON3=/usr/bin/python3
        export RK_KERNEL_VERSION_REAL=$(kernel_version_real)
    
        # Handle special commands
        case "$OPTIONS" in
                cleanall)
                        run_build_hooks clean
                        rm -rf "$RK_OUTDIR" "$SDK_DIR/rockdev"
                        finish_build cleanall
                        exit 0 ;;
                clean:*)
                        MODULES="$(echo ${OPTIONS#clean:} | tr ':' ' ')"
                        for m in $MODULES; do
                                "$SCRIPTS_DIR/mk-$m.sh" clean
                        done
                        finish_build clean - $MODULES
                        exit 0 ;;
                post-rootfs)
                        shift
                        run_post_hooks $@
                        finish_build post-rootfs
                        exit 0 ;;
        esac
    
        # Save final environments
        rm -f "$RK_FINAL_ENV"
        env > "$RK_FINAL_ENV"
        ln -rsf "$RK_FINAL_ENV" "$RK_OUTDIR/"
    
        # Log configs
        echo
        echo "=========================================="
        echo "          Final configs"
        echo "=========================================="
        env | grep -E "^RK_.*=.+" | grep -vE "PARTITION_[0-9]" | \
                grep -vE "=\"\"$|_DEFAULT=y" | \
                grep -vE "^RK_CONFIG|_BASE_CFG=|_LINK=|DIR=|_ENV=|_NAME=" | sort
        echo
    
        # Pre-build stage (submodule configuring, etc.)
        run_build_hooks pre-build $OPTIONS
    
        # No need to go further
        CMDS="$(run_build_hooks support-cmds build post-build | xargs)"
        option_check "$CMDS" $OPTIONS || return 0
    
        # Build stage (building, etc.)
        run_build_hooks build $OPTIONS
    
        # No need to go further
        CMDS="$(run_build_hooks support-cmds post-build | xargs)"
        option_check "$CMDS" $OPTIONS || return 0
    
        # Post-build stage (firmware packing, etc.)
        run_build_hooks post-build $OPTIONS
}
1.1.1 提示模式和错误处理

调试模式设置:首先通过判断是否设置了环境变量DEBUG,决定是否启用调试模式。如果 DEBUG 被设置为非空值,则运行 set -x 开启调试模式,这样脚本执行时会显示每一行命令的执行情况。

错误处理:使用 trapERR 捕获脚本中的错误。一旦发生错误,将调用 err_handler 函数;同时,set -eE 确保如果任何命令失败,脚本会立即退出,并且支持 ERR 跟踪。

保存初始环境变量:接着尝试保存当前的环境变量到一个临时文件中。首先,它解除定义了 INITIAL_SESSION 变量(如果存在),然后使用 mktemp -u 生成一个唯一的临时文件路径。如果 RK_SESSION 变量未设置,则认为这是一个新的会话,并将所有当前环境变量保存到生成的临时文件中。

1.1.2 设置环境变量

接着是设置一系列环境变,这些变量定义了一些重要的目录路径,通常在脚本执行过程中会用到这些路径。

export LC_ALL=C

export SCRIPTS_DIR="$(dirname "$(realpath "$BASH_SOURCE")")"
export COMMON_DIR="$(realpath "$SCRIPTS_DIR/..")"
......

realpath "$BASH_SOURCE" 获取当前脚本的绝对路径,由于build.sh指向了device/rockchip/common/scripts/build.sh;所以:

SCRIPTS_DIR=<SDK>/device/rockchip/common/scripts
COMMON_DIR=<SDK>/device/rockchip/common
SDK_DIR=<SDK>
DEVICE_DIR=<SDK>/device/rockchip
CHIPS_DIR=<SDK>/device/rockchip/.chips
CHIP_DIR=<SDK>/device/rockchip/.chip
RK_DATA_DIR=<SDK>/device/rockchip/common/data
RK_TOOL_DIR=<SDK>/device/rockchip/common/tools
RK_IMAGE_DIR=<SDK>/device/rockchip/common/images
RK_KBUILD_DIR=<SDK>/device/rockchip/common/linux-kbuild
RK_CONFIG_IN=<SDK>/device/rockchip/common/configs/Config.in
RK_BUILD_HOOK_DIR=<SDK>/device/rockchip/common/build-hooks
BUILD_HELPER=<SDK>/device/rockchip/common/build-hooks/build-helper
RK_POST_HOOK_DIR=<SDK>/device/rockchip/common/post-hooks
POST_HELPER=<SDK>/device/rockchip/common/post-hooks/post-helper
PARTITION_HELPER=<SDK>/device/rockchip/common/scripts/partition-helper
RK_SESSION=2024-07-08_21-52-58
RK_OUTDIR=<SDK>/output
RK_SESSION_DIR=<SDK>output/sessions
RK_LOG_BASE_DIR=<SDK>/output/log
RK_LOG_DIR=<SDK>/output/sessions/2024-07-08_21-52-58
RK_INITIAL_ENV=<SDK>/output/sessions/2024-07-08_21-52-58/initial.env
RK_CUSTOM_ENV=<SDK>/output/sessions/2024-07-08_21-52-58/custom.env
RK_FINAL_ENV=<SDK>/output/sessions/2024-07-08_21-52-58/final.env
RK_ROCKDEV_DIR=<SDK>/rockdev
RK_FIRMWARE_DIR=<SDK>/output/firmware
RK_SECURITY_FIRMWARE_DIR=<SDK>/output/security-firmware
RK_CONFIG=<SDK>/output/.config
RK_DEFCONFIG_LINK=<SDK>/output/defconfig
1.1.3 选项make-targets/make-usage

接着就是对选项make-targetsmake-usage的支持,实际上也是输出帮助信息。

1.1.4 选项chip/defconfig

接着就是对如下选项的支持:

  • chip[:<chip>[:<config>]] <chip>可选,表示SoC,比如rk3588<config>可选,表示板级配置,比如rockchip_defconfig
  • defconfig[:<config>]:<config>可选,表示板级配置,比如rockchip_defconfig

(1) 如果有参数传递给脚本,则OPTIONS被设置为这些参数的组合;如果没有参数传递,则OPTIONS被设置为 allsave

(2) 接着对$OPTIONS变量进行特殊处理,用于处理 chipdefconfig的情况。它会检查每个参数,并根据特定的条件对其进行转换。

首先:

if [ -d "$CHIPS_DIR/${opt%%:*}" ]; then
    OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
    sed "s/^$opt$/chip:$opt/" | xargs)

检查是否存在以$CHIPS_DIR开头、后面跟着$opt变量:分隔之前的字符串作为目录名, 如果满足;修改 $OPTIONS 变量,替换内容$optchip:$opt

  • 比如./builsh rk3588:rockchip_defconfigopt=rk3588:rockchip_defconfig,处理后OPTIONS=chip:rk3588:rockchip_defconfig

接着:

elif echo "$opt" | grep -q "^[0-9a-z_]*_defconfig$"; then
    OPTIONS=$(echo "$OPTIONS" | xargs -n 1 | \
        sed "s/^$opt$/defconfig:$opt/" | xargs)
fi

如果是以_defconfig结尾命名,修改 $OPTIONS 变量,替换内容$optdefconfig:$opt

  • 比如./builsh rockchip_defconfigopt=rockchip_defconfig,处理后OPTIONS=defconfig:rockchip_defconfig
1.1.5 选项检查

首先是获取支持的所有编译选项:

CMDS="$(run_build_hooks support-cmds all | xargs)"

xargs 将会把 run_build_hooks support-cmds all 的输出(echo函数的输出)捕获,并通过管道传递给 xargs

由于run_build_hooks函数会依次执行<SDK>/device/rockchip/common/build-hooks下的*.sh脚本,因此CMDS存储的就是这些脚本执行的输出,具体分析参考后文《run_build_hooks》。

执行完毕CMDS中存储build.sh支持的所有编译选项;

CMDS='chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config shell print-parts mod-parts edit-parts new-parts insert-part del-part move-part rename-part resize-part kernel-config kernel-make kmake kernel modules linux-headers wifibt rtos buildroot debian yocto buildroot-config buildroot-make bmake rootfs buildroot debian yocto recovery pcba security_check createkeys security_ramboot security_uboot security_boot security_recovery security_rootfs loader uboot uefi firmware edit-package-file edit-ota-package-file edit-package-file edit-ota-package-file updateimg otapackage all allsave save'

接着是一段选项检查的代码;

for opt in $OPTIONS; do
		case "$opt" in
				help | h | -h | --help | usage | \?) usage ;;
				clean:*)
						# Check cleanup modules
						for m in $(echo ${opt#clean:} | tr ':' ' '); do
								grep -wq clean_hook \
										"$SCRIPTS_DIR/mk-$m.sh" \
										2>/dev/null || usage
						done
						;&
				shell | cleanall)
						# Check single options
						if [ "$opt" = "$OPTIONS" ]; then
								break
						fi

						echo "ERROR: $opt cannot combine with other options!"
						;;
				post-rootfs)
						if [ "$opt" = "$1" -a -d "$2" ]; then
								# Hide other args from build stages
								OPTIONS=$opt
								break
						fi

						echo "ERROR: $opt should be the first option followed by rootfs dir!"
						;;
				*)
						# Make sure that all options are handled
						if option_check "$CMDS" $opt; then
								continue
						fi

						echo "ERROR: Unhandled option: $opt"
						;;
		esac

		usage
done

根据传入的编译选项($OPTIONS)进行判断和处理,这里遍历选项依次执行:

  • 当选项满足help | h | -h | --help | usage | \?,执行usage函数。比如我们前文执行 ./build.sh help,就是则调用usage函数来显示帮助信息;
  • 如果选项以 clean: 开头,则验证验证相应的清理模块是否存在,比如./build.sh clean:kernel,则判断"device/rockchip/common/scripts/mk-kernel.sh/mk-kernel.sh中是否包含clean_hook方法;后续会调用mk-kernel.sh clean执行清理工作;
  • 如果选项是shellcleanall,执行函数run_build_hooks clean,该函数则会遍历device/rockchip/common/scriptsshell脚本文件,并传入clean参数函数依次执行;
  • 如果选项是post-rootfs,并且满足上面的条件,则设置OPTIONS=$opt
  • 如果不满足以上条件,则调用option_check函数检查给定的命令选项是否在指定的编译选项列表中,如果匹配失败,则输出错误信息,并调用usage函数。
1.1.6 准备日志目录

接着是创建编译日志目录:

# Prepare log dirs
if [ ! -d "$RK_LOG_DIR" ]; then
		rm -rf "$RK_LOG_BASE_DIR" "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"
		mkdir -p "$RK_LOG_DIR"
		ln -rsf "$RK_SESSION_DIR" "$RK_LOG_BASE_DIR"
		ln -rsf "$RK_LOG_DIR" "$RK_SESSION_DIR/latest"
		echo -e "\e[33mLog saved at $RK_LOG_DIR\e[0m"
		echo
fi

首先判断日志目录$RK_LOG_DIR是否存在,如果该目录不存在,则执行一系列操作来创建目录结构并设置符号链接,将 $RK_LOG_DIR符号链接到$RK_SESSION_DIR/latest,确保最新日志目录符号链接始终指向最新的日志目录。

1.1.7 清理旧日志

接着是清理日志目录,只保留最新的10次编译的日志记录:

# Drop old logs
cd "$RK_LOG_BASE_DIR"
rm -rf $(ls -t | sed '1,10d')
cd "$SDK_DIR"

跳转到<SDK>/output/log目录,把除了最新的10个文件或目录外的所有内容传递给rm -rf命令进行删除操作。

1.1.8 保存初始化环境变量

接着是保存初始化环境变量;

# Save initial envionments
if [ "$INITIAL_SESSION" ]; then
		rm -f "$RK_INITIAL_ENV"
		mv "$INITIAL_ENV" "$RK_INITIAL_ENV"
		ln -rsf "$RK_INITIAL_ENV" "$RK_OUTDIR/"
fi

由于前文设置INITIAL_ENV=$(mktemp-u),即使用mktemp -u生成一个唯一的临时文件路径,比如/tmp/tmp.ag1ffjvNMG

因此该段脚本:

  • 它会先删除旧的<SDK>/output/sessions/initial.env文件;
  • 然后将临时文件移动并重命名为<SDK>/output/sessions/$RK_SESSION/initial.env
  • 最后在指定的输出目录<SDK>/output下创建一个指向<SDK>/output/sessions/$RK_SESSION/initial.env的符号链接。
1.1.9 初始化阶段

脚本进入初始化阶段,用于准备SDK配置等工作;

# Init stage (preparing SDK configs, etc.)
run_build_hooks init $OPTIONS
rm -f "$RK_OUTDIR/.tmpconfig*"

这里再次调用run_build_hooks函数,入参为init $OPTIONS,具体分析参考后文《run_build_hooks》。

1.1.10 选项检查

接下来就是判断我们传入的选项是否是build.sh pre-build build post-build阶段中定义的编译选项。

# No need to go further
CMDS="$(run_build_hooks support-cmds pre-build build \
		post-build | xargs) cleanall clean post-rootfs"
option_check "$CMDS" $OPTIONS || return 0

xargs 将会把 run_build_hooks support-cmds pre-build build post-build 的输出(echo函数的输出)捕获,并通过管道传递给 xargs

由于run_build_hooks函数会依次执行<SDK>/device/rockchip/common/build-hooks下的sh脚本,因此CMDS存储的就是这些脚本执行的输出。

执行完毕CMDS中存储build.shpre-build build post-build阶段中支持的编译选项;

CMDS='shell print-parts mod-parts edit-parts new-parts insert-part del-part move-part rename-part resize-part kernel-config kernel-make kmake kernel modules linux-headers wifibt rtos buildroot-config buildroot-make bmake rootfs buildroot debian yocto recovery pcba security_check createkeys security_ramboot security_uboot security_boot security_recovery security_rootfs loader uboot uefi firmware edit-package-file edit-ota-package-file updateimg otapackage all allsave save cleanall clean post-rootfs'

接着调用option_check检查变量CMDS中的是否包含$OPTIONS中指定的编译选项。

1.1.11 导出config environments
# Force exporting config environments
set -a

# Load config environments
source "$RK_CONFIG"
cp "$RK_CONFIG" "$RK_LOG_DIR"

if [ -z "$INITIAL_SESSION" ]; then
		# Inherit session environments
		sed -n 's/^\(RK_.*=\)\(.*\)/\1"\2"/p' "$RK_FINAL_ENV" > \
				"$INITIAL_ENV"
		source "$INITIAL_ENV"
		rm -f "$INITIAL_ENV"
else
		# Detect and save custom environments

		# Find custom environments
		rm -f "$RK_CUSTOM_ENV"
		for cfg in $(grep "^RK_" "$RK_INITIAL_ENV" || true); do
				env | grep -q "^${cfg//\"/}$" || \
						echo "$cfg" >> "$RK_CUSTOM_ENV"
		done

		# Allow custom environments overriding
		if [ -e "$RK_CUSTOM_ENV" ]; then
				ln -rsf "$RK_CUSTOM_ENV" "$RK_OUTDIR/"

				echo -e "\e[31mWARN: Found custom environments: \e[0m"
				cat "$RK_CUSTOM_ENV"

				echo -e "\e[31mAssuming that is expected, please clear them if otherwise.\e[0m"
				read -t 10 -p "Press enter to continue."
				source "$RK_CUSTOM_ENV"

				if grep -q "^RK_KERNEL_VERSION=" "$RK_CUSTOM_ENV"; then
						echo -e "\e[31mCustom RK_KERNEL_VERSION ignored!\e[0m"
						load_config RK_KERNEL_VERSION
				fi

				if grep -q "^RK_ROOTFS_SYSTEM=" "$RK_CUSTOM_ENV"; then
						echo -e "\e[31mCustom RK_ROOTFS_SYSTEM ignored!\e[0m"
						load_config RK_ROOTFS_SYSTEM
				fi
		fi
fi

source "$PARTITION_HELPER"
rk_partition_init

set +a
1.1.12 选项claenall/clean:*/post-rootfs
export PYTHON3=/usr/bin/python3
export RK_KERNEL_VERSION_REAL=$(kernel_version_real)

# Handle special commands
case "$OPTIONS" in
		cleanall)
				run_build_hooks clean
				rm -rf "$RK_OUTDIR" "$SDK_DIR/rockdev"
				finish_build cleanall
				exit 0 ;;
		clean:*)
				MODULES="$(echo ${OPTIONS#clean:} | tr ':' ' ')"
				for m in $MODULES; do
						"$SCRIPTS_DIR/mk-$m.sh" clean
				done
				finish_build clean - $MODULES
				exit 0 ;;
		post-rootfs)
				shift
				run_post_hooks $@
				finish_build post-rootfs
				exit 0 ;;
esac

1.1.13 保存final环境变量
# Save final environments
rm -f "$RK_FINAL_ENV"
env > "$RK_FINAL_ENV"
ln -rsf "$RK_FINAL_ENV" "$RK_OUTDIR/"
1.1.14 预编译/编译/编译后
# Log configs
echo
echo "=========================================="
echo "          Final configs"
echo "=========================================="
env | grep -E "^RK_.*=.+" | grep -vE "PARTITION_[0-9]" | \
		grep -vE "=\"\"$|_DEFAULT=y" | \
		grep -vE "^RK_CONFIG|_BASE_CFG=|_LINK=|DIR=|_ENV=|_NAME=" | sort
echo

# Pre-build stage (submodule configuring, etc.)
run_build_hooks pre-build $OPTIONS

# No need to go further
CMDS="$(run_build_hooks support-cmds build post-build | xargs)"
option_check "$CMDS" $OPTIONS || return 0

# Build stage (building, etc.)
run_build_hooks build $OPTIONS

# No need to go further
CMDS="$(run_build_hooks support-cmds post-build | xargs)"
option_check "$CMDS" $OPTIONS || return 0

# Post-build stage (firmware packing, etc.)
run_build_hooks post-build $OPTIONS

1.2 option_check

option_check函数检查给定的编译选项是否在指定的编译选项列表中。

option_check()
{
        # 将第一个参数$1赋值给变量CMDS,这个参数是一组编译选项列表,用空格分隔
        CMDS="$1"
        # 将参数列表向左移动一位,去掉了第一个参数(即CMDS),剩下的参数存储在$@中
        shift

        # 嵌套循环检查编译选项
        for opt in $@; do     # 遍历传递给函数的所有剩余参数(除了第一个参数CMDS)
                for cmd in $CMDS; do    # 再嵌套一个循环,遍历存储在CMDS变量中的编译选项列表
                        # NOTE: There might be patterns in commands 检查命令是否匹配选项,如果grep 命令没有匹配到,则继续下一次循>环
                        echo "${opt%%:*}" | grep -q "^$cmd$" || continue
                        # 如果匹配成功,则返回状态码 0,表示找到了匹配的编译选项
                        return 0
                done
        done

        return 1
}

比如我们给函数传入:

commands="run jump swim"
option_check "$commands" run swim sleep

在这个示例中:

  • $commands被传递给CMDS变量,即CMDS="run jump swim;
  • run swim sleep是要检查的选项,即option1=runoption2=swimoption3=sleep

函数会逐个检查每个编译选项是否在编译选项列表CMDS中,如果没有找到任意匹配的编译,则返回状态码0;否则返回状态码1。

1.3 check_config

check_config主要功能是检查传入的参数列表中的环境变量是否已经设置,如果有未设置的环境变量,则输出相应的信息并返回错误码。

check_config()
{
        unset missing
        for var in $@; do
        		# 获取变量的值,如果该变量已经设置(非空),则继续下一个循环迭代;
                eval [ \$$var ] && continue
                missing="$missing $var"
        done
        [ -z "$missing" ] && return 0
        echo "Skipping $(basename "${BASH_SOURCE[1]}") - ${FUNCNAME[1]} for missing configs: $missing."
        return 1
}

1.4 run_hooks

run_hooks在指定目录中查找并执行所有以 .sh 结尾的脚本文件。如果执行任何一个脚本失败,它将调用错误处理函数 err_handler 处理错误,并以失败的返回码退出整个脚本。

run_hooks()
{
        # 将第一个参数$1赋值给变量DIR
        DIR="$1"
        # 将参数列表向左移动一位,去掉了第一个参数(即DIR),剩下的参数存储在$@中
        shift

		# 遍历目录
        for dir in "$CHIP_DIR/$(basename "$DIR")/" "$DIR"; do
       	 	    # 当前的$dir是否是一个存在的目录。如果不是,则继续到下一个循环迭代
                [ -d "$dir" ] || continue
				# 在$dir目录下(不深入子目录,-maxdepth 1)查找所有以.sh结尾的文件
                for hook in $(find "$dir" -maxdepth 1 -name "*.sh" | sort); do
                        "$hook" $@ && continue   # *.sh退出状态码为0则执行continue
                        HOOK_RET=$?
                        err_handler $HOOK_RET "${FUNCNAME[0]} $*" "$hook $*"
                        exit $HOOK_RET
                done
        done
}

比如我们给函数传入:

RK_BUILD_HOOK_DIR=<SDK>/device/rockchip/common/build-hooks
run_hooks "$RK_BUILD_HOOK_DIR" support-cmds all

在这个示例中:

  • 将遍历目录<SDK>/device/rockchip/.chip/build-hooks<SDK>/device/rockchip/common/build-hooks

  • 由于<SDK>/device/rockchip/.chip/build-hooks不是目录将跳过;

  • 接着在<SDK>/device/rockchip/common/build-hooks 目录下(不深入子目录,-maxdepth 1)查找所有以.sh 结尾的文件;依次执行

    • 执行找到的每一个脚本文件,传递参数support-cmds all
    • 如果执行成功(exit 0),则继续下一个钩子脚本的执行;
    • 如果执行失败(exit 非0),调用err_handler 函数处理错误,传递错误码、当前函数名以及传递给当前函数的所有参数,以HOOK_RET的值退出脚本,终止整个构建过程。

我们查看<SDK>/device/rockchip/common/build-hooks目录下的sh脚本;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll device/rockchip/common/build-hooks/
lrwxrwxrwx  1 root root   23  6月  9 12:58 00-config.sh -> ../scripts/mk-config.sh*
lrwxrwxrwx  1 root root   22  6月  9 12:58 00-shell.sh -> ../scripts/mk-shell.sh*
lrwxrwxrwx  1 root root   27  6月  9 12:58 05-partitions.sh -> ../scripts/mk-partitions.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 10-kernel.sh -> ../scripts/mk-kernel.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 20-wifibt.sh -> ../scripts/mk-wifibt.sh*
lrwxrwxrwx  1 root root   21  6月  9 12:58 25-rtos.sh -> ../scripts/mk-rtos.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 30-rootfs.sh -> ../scripts/mk-rootfs.sh*
lrwxrwxrwx  1 root root   25  6月  9 12:58 40-recovery.sh -> ../scripts/mk-recovery.sh*
lrwxrwxrwx  1 root root   21  6月  9 12:58 50-pcba.sh -> ../scripts/mk-pcba.sh*
lrwxrwxrwx  1 root root   25  6月  9 12:58 60-security.sh -> ../scripts/mk-security.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 70-loader.sh -> ../scripts/mk-loader.sh*
lrwxrwxrwx  1 root root   25  6月  9 12:58 80-firmware.sh -> ../scripts/mk-firmware.sh*
lrwxrwxrwx  1 root root   26  6月  9 12:58 90-updateimg.sh -> ../scripts/mk-updateimg.sh*
lrwxrwxrwx  1 root root   20  6月  9 12:58 99-all.sh -> ../scripts/mk-all.sh*
lrwxrwxrwx  1 root root   23  6月  9 12:58 build-helper -> ../scripts/build-helper
-rwxr-xr-x  1 root root 4210  6月  9 12:58 example.sh.in*

其中,比较重要的有:

  • 00-config.sh:板载配置相关,支持的编译选项有chipdefconfigmenuconfig等;
  • 05-partitions.sh:分区配置相关,支持的编译选项有:print-partsmod-parts edit-parts等;
  • 10-kernel.sh:内核编译相关,支持的编译选项有:kernelmoduleslinux-headers等;
  • 20-wifibt.shwifibt编译相关;
  • 30-rootfs.sh:根文件系统编译相关,支持的编译选项有:buildrootyoctodebian等;
  • 40-recovery.shrecovery编译相关;
  • 70-loader.shuboot编译相关,支持的编译选项有:loader[:cmds]uboot[:cmds] uefi[:cmds] 等;
  • 80-firmware.sh:分区镜像打包相关;
  • 90-updateimg.sh:统一固件打包相关;
  • 99-all.sh :所有,支持的编译选项有:allsave等;

在该示例中将会按顺序依次执行*.sh脚本,并传递参数support-cmds all,有关这些*.sh脚本的执行我们在后面介绍。

1.5 run_build_hooks

run_build_hooks函数作用是运行一些构建过程中的钩子(hooks),并且记录执行日志。

run_build_hooks()
{
        # Don't log these hooks
        case "$1" in
                init | pre-build | make-* | usage | support-cmds)
                        run_hooks "$RK_BUILD_HOOK_DIR" $@ || true
                        return 0
                        ;;
        esac

        # 设置日志文件
        LOG_FILE="$(start_log "$1")"

        # 记录运行日志
        echo -e "# run hook: $@\n" >> "$LOG_FILE"
        
        # 运行钩子并记录输出
        run_hooks "$RK_BUILD_HOOK_DIR" $@ 2>&1 | tee -a "$LOG_FILE"
        
        # 处理钩子执行结果
        HOOK_RET=${PIPESTATUS[0]}
        if [ $HOOK_RET -ne 0 ]; then
                err_handler $HOOK_RET "${FUNCNAME[0]} $*" "$@"
                exit $HOOK_RET
        fi
}

函数执行流程如下:

  • 根据传递给函数的第一个参数$1进行匹配,如果$1匹配到initpre-build、以make-开头的任意字符串、usage或者 support-cmds中的一个;

    • 调用run_hooks函数,并传递参数<SDK>/device/rockchip/common/build-hooks$@(所有传递给当前函数的参数);
    • || true 确保即使run_hooks函数失败,也不会中断执行;
    • 返回状态码 0,表示成功执行;
  • 设置日志文件:LOG_FILE被设置为一个日志文件的路径,使用start_log函数生成基于传入参数$1的日志文件名;

  • 记录运行日志:将# run hook: $@打印到$LOG_FILE中;

  • 运行钩子并记录输出:

    • 再次调用run_hooks函数,传递<SDK>/device/rockchip/common/build-hooks$@,标准错误(2)输出到(>)到标准输出(&1);
    • 将标准错误输出重定向到标准输出,这样可以确保错误信息也被tee命令捕获到;
    • tee -a将管道中的输出同时输出到标准输出和追加到$LOG_FILE文件中;
  • 处理钩子执行结果:将管道中上一个命令的退出状态保存在HOOK_RET变量中,PIPESTATUS[0]Bash 内置的数组,包含了最近一个管道中命令的退出状态;

    • 如果HOOK_RET不等于 0,则表示有错误发生;
    • 调用err_handler 数处理错误,传递错误码、当前函数名以及传递给当前函数的所有参数;
    • HOOK_RET的值退出脚本,终止整个构建过程。

在编译过程中,main函数多次调用run_build_hooks 函数的,其执行顺序依次为;

  • run_build_hooks support-cmds all
  • run_build_hooks init recovery
  • run_build_hooks support-cmds pre-build build post-build
  • run_build_hooks pre-build recovery
  • run_build_hooks support-cmds build post-build
  • run_build_hooks build recovery
  • run_build_hooks support-cmds post-build
  • run_build_hooks post-buildrecovery

run_build_hooks 会调用<SDK>/device/rockchip/common/build-hooks下的所有*.sh脚本,并将参数传入。

二、build-helper

build-helper从文件名字不难猜测出来这是一个编译辅助脚本,其位于<SDK>/device/rockchip/common/build-hooks目录下,提供了若干个辅助函数。

2. 1 脚本入口

build-helper脚本会被<SDK>/device/rockchip/common/build-hooks下的所有*.sh脚本调用,并传入不同的参数,比如:

  • support-cmds all:输出支持的所有编译选项;
  • init recovery:初始化阶段(init)执行,调用init_hook函数,入参为recovery
  • support-cmds pre-build build post-build:输出预编译、编译、编译后三个阶段支持的编译选项;
  • pre-build recovery:预编译阶段(pre-build)执行,调用pre_build_hook函数,入参为recovery
  • support-cmds build post-build:输出编译、编译后这两个阶段支持的编译选项;
  • build recovery:编译阶段(build)执行,调用build_hook函数,入参为recovery
  • support-cmds post-build:输出编编译后阶段支持的编译选项;
  • post-build recovery:编译后阶段(post-build)执行,调用post_build_hook函数,入参为recovery

其中:recovery可以替换成./build.sh支持的任意编译选项,比如allbuildrootubootkernel等。

脚本入口代码如下:

......

# 跳转到<SDK>目录
cd "$SDK_DIR"

# 比如 LOG_FILE_NAME=$(*.sh脚本名称,比如00-config)-(参数1,比如`support-cmds`)_2024-07-08_21-12-25
LOG_FILE_NAME="$(basename "${0%.sh}")-$1_$(date +"%F_%H-%M-%S")"

case "$1" in
        help | h | -h | --help | \?) usage ;;
        make-targets) make_targets; exit 0 ;;
        make-usage) make_usage; exit 0 ;;
        usage) usage_hook; exit 0 ;;
        support-cmds)   
        	    # 将参数列表向左移动一位,去掉了第一个参数(support-cmds),剩下的参数存储在$@中(即all)
                shift
                {
                        ALL_CMDS="$INIT_CMDS $PRE_BUILD_CMDS $BUILD_CMDS \
                                $POST_BUILD_CMDS"
                        for stage in ${@:-all}; do
                                case $stage in
                                        init) echo "$INIT_CMDS" ;;
                                        pre-build) echo "$PRE_BUILD_CMDS" ;;
                                        build) echo "$BUILD_CMDS" ;;
                                        post-build) echo "$POST_BUILD_CMDS" ;;
                                        # 走这里,输出支持的命令 chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config
                                        all) echo "$ALL_CMDS" ;;   
                                esac
                        done
                } | xargs -n 1 | grep -v "^default$" | xargs || true   # 过滤掉单独包含 "default" 的行,然后将剩余的行作为参数依次传递给下一个命令
                exit 0
                ;;
        clean)
                try_func clean_hook
                exit 0
                ;;
        init)
                shift
                try_hook init_hook "$INIT_CMDS" $@
                exit 0
                ;;
        pre-build)
                shift
                try_hook pre_build_hook "$PRE_BUILD_CMDS" $@
                exit 0
                ;;
        build)
                shift
                try_hook build_hook "$BUILD_CMDS" $@
                exit 0
                ;;
        post-build)
                shift
                try_hook post_build_hook "$POST_BUILD_CMDS" $@
                exit 0
                ;;
esac

if [ "$DRY_RUN" ]; then
        echo "Environment 'DRY_RUN' ignored!"
        unset DRY_RUN
fi

if [ "$2" = cmds ]; then
        export DRY_RUN=1
fi
2.1.1 入参为support-cmds all

如果入参为support-cmds all,代码会进入support-cmds分支;

support-cmds)    # 走这里
		# 将参数列表向左移动一位,去掉了第一个参数(support-cmds),剩下的参数存储在$@中(即all)
		shift
		{
				ALL_CMDS="$INIT_CMDS $PRE_BUILD_CMDS $BUILD_CMDS \
						$POST_BUILD_CMDS"
				for stage in ${@:-all}; do
						case $stage in
								init) echo "$INIT_CMDS" ;;
								pre-build) echo "$PRE_BUILD_CMDS" ;;
								build) echo "$BUILD_CMDS" ;;
								post-build) echo "$POST_BUILD_CMDS" ;;
								# 走这里,输出支持的编译选项 chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config
								all) echo "$ALL_CMDS" ;;   
						esac
				done
		} | xargs -n 1 | grep -v "^default$" | xargs || true   # 过滤掉单独包含 "default" 的行,然后将剩余的行作为参数依次传递给下一个命令
		exit 0
		;;

首先输出ALL_CMDS,最后执行exit 0退出*.sh脚本(build-heleper是在*.sh脚本中以source方式执行的)文件的执行;

通过代码分析,我们可以得到入参第一个参数为support-cmds的命令均会进入该分支:

  • support-cmds all

  • support-cmds pre-build build post-build

  • support-cmds build post-build

  • support-cmds post-build;

脚本根据support-cmds之后的参数来执行不同的处理逻辑,比如:

  • all:输出所有支持的编译选项,包括INIT_CMDSPRE_BUILD_CMDSBUILD_CMDSPOST_BUILD_CMDS
  • pre-build:输出PRE_BUILD_CMDS
  • build:输出BUILD_CMDS
  • post-build:输出POST_BUILD_CMDS

由于<SDK>/device/rockchip/common/build-hooks目录下各个*.sh脚本中支持的INIT_CMDSPRE_BUILD_CMDSBUILD_CMDSPOST_BUILD_CMDS是不同的,因此每个*.sh脚本在使用source命令执行build-helper脚本时输出也是不同的。

我们以如下命令为例:

CMDS="$(run_build_hooks support-cmds all | xargs)"

xargs 将会把 run_build_hooks support-cmds all 的输出(echo函数的输出)捕获,并通过管道传递给 xargs。由于run_build_hooks函数会依次执行*.sh脚本,因此CMDS存储的就是这些脚本执行的输出结果。

2.1.2 入参为init recovery

如果入参为init recovery,代码会进入init分支;

init)
        # 将参数列表向左移动一位,去掉了第一个参数(support-cmds),剩下的参数存储在$@中(即recovery)
		shift
		try_hook init_hook "$INIT_CMDS" $@
		exit 0
		;;

接着会执行try_hook函数,具体参考《try_hook分析》。

2.1.3 入参为pre-build recovery

如果入参为pre-build recovery,代码会进入pre-build分支;

pre-build)
		shift
		try_hook pre_build_hook "$PRE_BUILD_CMDS" $@
		exit 0
		;;

接着会执行try_hook函数,具体参考《try_hook分析》。

2.1.4 入参为build recovery

如果入参为build recovery,代码会进入build分支;

build)
		shift
		try_hook build_hook "$BUILD_CMDS" $@
		exit 0
		;;

接着会执行try_hook函数,具体参考《try_hook分析》。

2.1.5 入参为post-build recovery

如果入参为post-build recovery,代码会进入post-build分支;

post-build)
		shift
		try_hook post_build_hook "$POST_BUILD_CMDS" $@
		exit 0
		;;

接着会执行try_hook函数,具体参考《try_hook分析》。

2.2 err_handler

error处理函数,脚本中的任何命令失败(即退出状态为非零),那么就会调用err_handler函数;

err_handler()
{
	    # 如果参数1为空,则获取最近一次执行命令的退出状态
        ret=${1:-$?}
        # 退出状态为0,直接返回
        [ "$ret" -eq 0 ] && return

		# 入参参数2为空,输出调用栈中的第二个函数名
        echo "ERROR: Running $0 - ${2:-${FUNCNAME[1]}} failed!"
        # 输出错误状态码,以及发生错误的行号
        echo "ERROR: exit code $ret from line ${BASH_LINENO[0]}:"
        # 如果参数3为空,输出当前正在执行的命令
        echo "    ${3:-$BASH_COMMAND}"
        echo "ERROR: call stack:"
        for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); do
                SOURCE="${BASH_SOURCE[$i]}"
                LINE=${BASH_LINENO[$(( $i - 1 ))]}
                echo "    $(basename "$SOURCE"): ${FUNCNAME[$i]}($LINE)"
        done
        exit $ret
}
trap 'err_handler' ERR
set -eE

2.3 try_hook

try_hook函数接收3个参数:

  • 参数1是被调用的hook函数;
  • 参数2是hook函数支持的编译选项数组CMDS
  • 参数3是当前需要执行的任一编译选项;

函数定义如下:

try_hook()
{
	    # init_hook、pre_build_hook等
        FUNC=$1
        # 将参数列表向左移动一位,去掉了第一个参数(init_hook)
        shift
        # 支持的命令数组
        CMDS="$1"
         # 将参数列表向左移动一位,去掉了第二个参数(CMDS)
        shift

		# 配以default开头并且后面跟着一个空格或者直接是行尾的行
        if echo "$CMDS" | grep -qE "^default( |$)"; then
                OPTS="default $@"
        else
        		# 一般走这里
                OPTS="$@ default"
        fi
		# 检查名为$FUNC的函数是否存在并且可执行,不存在或不可执行则return 0
        type $FUNC >/dev/null 2>/dev/null || return 0
		
		# 遍历需要执行的编译选项数组
        for opt in $OPTS; do
                #匹配包含:cmds这个字符串,并且后面紧跟着一个冒号:,或者是行尾的地方  比如opt="recovery:cmds",
                IS_DRY_RUN=$(echo $opt | grep -E ":cmds(:|$)" || true)
                # 遍历支持的编译选项数组
                for cmd in $CMDS; do
                        # NOTE: There might be patterns in commands .... default 姑且认为查找opt、cmd相等的项
                        # 如果opt=defconfig:rockchip_rk3588_sige7_defconfig,cmd=defconfig,执行后返回defconfig rockchip_rk3588_sige7_defconfig
                        ARGS="$(echo $opt | grep -E "^$cmd(:|$)" | \
                                tr ':' ' ' || true)"  # 如果有:,将会替换成' ',即拆分
                        [ "$ARGS" ] || continue
						
						# D优先使用DRY_RUN变量的值,如果DRY_RUN不存在或为空,则根据IS_DRY_RUN的值来决定是否展开为1。
                        DRY_RUN=${DRY_RUN:-${IS_DRY_RUN:+1}} \
                                try_func $FUNC $ARGS
                done
        done
}

这里我们姑且认为双层循环就是查找在CMDS数组中支持的编译选项,然后调用try_func函数,并传递参数$FUNC以及匹配到的选项。

2.3.1 入参为init_hook

build-helper脚本中入参为init_hook的函数调用如下:

try_hook init_hook "$INIT_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目录下各个*.sh脚本中定义的init_hook函数、INIT_CMDS数组不相同,尽管$@参数一样但是执行的结果均是不同的。

注意:实际上只有部分*.sh脚本定义有init_hook,如果该函数未定义的try_hook会直接return 0

2.3.2 入参为pre_build_hook

build-helper脚本中入参为pre_build_hook的函数调用如下:

try_hook pre_build_hook "$PRE_BUILD_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目录下各个*.sh脚本中定义的pre_build_hook函数、PRE_BUILD_CMDS数组不相同,尽管$@参数一样但是执行的结果均是不同的。

注意:实际上只有部分*.sh脚本定义有pre_build_hook,如果该函数未定义的try_hook会直接return 0

2.3.3 入参为build_hook

build-helper脚本中入参为build_hook的函数调用如下:

try_hook build_hook "$BUILD_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目录下各个*.sh脚本中定义的build_hook函数、BUILD_CMDS数组不相同,尽管$@参数一样但是执行的结果均是不同的。

注意:实际上只有部分*.sh脚本定义有build_hook,如果该函数未定义的try_hook会直接return 0

2.3.4 入参为post_build_hook

build-helper脚本中入参为post_build_hook的函数调用如下:

try_hook post_build_hook "$POST_BUILD_CMDS" $@

由于<SDK>/device/rockchip/common/build-hooks目录下各个*.sh脚本中定义的post_build_hook函数、POST_BUILD_CMDS数组不相同,尽管$@参数一样但是执行的结果均是不同的。

注意:实际上只有部分*.sh脚本定义有post_build_hook,如果该函数未定义的try_hook会直接return 0

2.3 try_func

尝试调用某个函数,函数接收多个参数:

  • 参数1为函数将要调用的hook函数,比如usage_hookinit_hookpre_build_hook build_hook post_build_hook clean_hook;这些函数通常在查看使用说明、构建的初始化阶段(init)、预构建(pre-build)、构建(build)、构建后(post-build)、以及清理时被触发;
  • 参数2为hook函数的入参,传入hook函数所支持的编译选项;

比如try_func init_hook default,将会执行init_hook default

# try_func init_hook default
try_func()
{
		# 检查名为init_hook的函数是否存在并且可执行,不存在或不可执行则return 0
        type $1 >/dev/null 2>/dev/null || return 0

        # Don't log these hooks
        case "${1%_hook}" in   # 第一个参数移除_hook,即init
                init | pre_build)
                        $@          # 执行init_hook default
                        return 0
                        ;;
        esac

        if [ "$DRY_RUN" ]; then
                DRY_FUNC=$1_dry         # 比如build_hook_dry
                type $DRY_FUNC >/dev/null 2>/dev/null || return 0
				
				# 将参数列表向左移动一位,去掉了第1个参数
                shift
                # 函数调用
                $DRY_FUNC $@
                return 0
        fi

		# 设置日志文件
        LOG_FILE="$(start_log ${LOG_FILE_NAME%%_*} $LOG_FILE_NAME)"、
        
        # 记录运行日志
        echo -e "# run func: $@\n" >> "$LOG_FILE"
        
        # 运行钩子并记录输出
        $@ 2>&1 | tee -a "$LOG_FILE"

        # 处理钩子执行结果
        FUNC_RET=${PIPESTATUS[0]}
        if [ $FUNC_RET -ne 0 ]; then
                err_handler $FUNC_RET "${FUNCNAME[0]} $*" "$@"
                exit $FUNC_RET
        fi
}

判断调用的是不是init_hookpre_build_hook函数,如果是执行该函数并将参数传入,执行完后return 0

如果是build_hook post_build_hook 等函数,

  • 如果$DRY_RUN存在将会执行$1_dry,比如build_hook_dry 参数2 ,执行完后return 0
  • 否则将会执行如下过程:
    • 设置日志文件:LOG_FILE=output/sessions/latest/$(*.sh脚本名称,比如00-config)-(参数1,比如support-cmds)_创建时间
    • 记录运行日志:将# run hook: $@打印到$LOG_FILE中;
    • 运行钩子并记录输出:
      • 调用钩子函数,并将标准错误(2)输出到(>)到标准输出(&1);
      • 将标准错误输出重定向到标准输出,这样可以确保错误信息也被tee命令捕获到;
      • tee -a将管道中的输出同时输出到标准输出和追加到$LOG_FILE文件中;
    • 处理钩子执行结果:将管道中上一个命令的退出状态保存在FUNC_RET变量中,PIPESTATUS[0]Bash 内置的数组,包含了最近一个管道中命令的退出状态;
      • 如果FUNC_RET不等于 0,则表示有错误发生;
      • 调用err_handler数处理错误,传递错误码、当前函数名以及传递给当前函数的所有参数;
      • FUNC_RET的值退出脚本,终止整个构建过程。

三、00-config.sh

00-config.sh脚本位于<SDK>/device/rockchip/common/build-hooks/目录,其实现的编译选项有chip*_defconfigsavedefconfigmenuconfigconfig等;

由于该文件内容比较多,这里我们挑选一些重点函数介绍。

3.1 脚本入口

首先执行SDK/device/rockchip/common/build-hooks/build-helper脚本,会将这个脚本的内容直接加载到当前shell环境中执行,build-helper.sh 能够获取父文件中的变量和参数;

#  source SDK/device/rockchip/common/build-hooks/build-helper
source "${BUILD_HELPER:-$(dirname "$(realpath "$0")")/../build-hooks/build-helper}"

init_hook $@

有关build-helper我们已经具体分析了,这里不再重复。

使用source命令执行脚本的一些注意事项:

  • 环境变量和函数的影响:被执行的脚本可以修改当前shell的环境变量和定义的函数,这些修改将持续影响到当前shell的会话,直到会话结束或者重新定义了这些变量和函数。
  • 退出状态:被执行的脚本的退出状态(即最后一个命令的退出状态)会影响到当前shell。可以通过$?变量来获取最近一次执行命令的退出状态;
  • 交互性:与直接执行脚本不同,使用source执行脚本时,不会创建新的shell环境,因此不会有新的子shell进程。这使得它适合于需要脚本和当前shell环境之间相互影响的场景,例如定义函数或设置环境变量;

3.2 usage_hook

usage_hook用于输出当前脚本支持的编译选项;

usage_hook()
{
        echo -e "chip[:<chip>[:<config>]]          \tchoose chip"
        echo -e "defconfig[:<config>]              \tchoose defconfig"
        echo -e " *_defconfig                      \tswitch to specified defconfig"
        echo "    available defconfigs:"
        ls "$CHIP_DIR/" | grep "defconfig$" | sed "s/^/\t/"
        echo -e " olddefconfig                     \tresolve any unresolved symbols in .config"
        echo -e " savedefconfig                    \tsave current config to defconfig"
        echo -e " menuconfig                       \tinteractive curses-based configurator"
        echo -e "config                            \tmodify SDK defconfig"
}

通过以上输出的信息,我们不难猜测出00-config.sh脚本是用来实现SDK配置的,其提供了选择芯片、选择板级配置、切换板级配置、保存板级配置、用户自定义配置等功能。

注意:以上命令只会在init阶段执行,因为其是被init_hook函数调用。

3.2.1 rockchip_rk3588_sige7_defconfig

对于ArmSoM-Sige7开发板,我们选择的板级配置文件为rockchip_rk3588_sige7_defconfig,位于<SDK>/device/rockchip/.chips/rk3588目录下。

当我们执行make rockchip_rk3588_sige7_defconfig或者./build.sh ${target}选择rockchip_rk3588_sige7_defconfig时均会编译生成<SDK>/output/.config配置文件。

这里我们以./build.sh rockchip_rk3588_sige7_defconfig命令为例;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp# DEBUG=true ./build.sh rockchip_rk3588_sige7_defconfig

在输出日志中,可以定位到如下日志:

<SDK>/device/rockchip/common/build-hooks/00-config.sh init defconfig:rockchip_rk3588_sige7_defconfig

注意:其它*.sh脚本的执行日志忽略,因为只有00-config.sh才会处理*_defconfig 编译选项。

即触发阶段为init,入参为defconfig rockchip_rk3588_sige7_defconfiginit阶段触发的hook函数为init_hook,当入参为defconfig rockchip_rk3588_sige7_defconfig时将会执行choose_defconfig函数。

注意:调用init_hook函数时入参是defconfig rockchip_rk3588_sige7_defconfig,并不是defconfig:rockchip_rk3588_sige7_defconfig

3.3 init_hook

init_hook函数在构建的初始化阶段(init阶段)被调用,用于处理INIT_CMDS命令;

INIT_CMDS="chip defconfig lunch .*_defconfig olddefconfig savedefconfig menuconfig config default"

init_hook()
{
        case "${1:-default}" in
                chip) shift; choose_chip $@ ;;
                lunch|defconfig) shift; choose_defconfig $@ ;;
                *_defconfig) switch_defconfig "$1" ;;
                olddefconfig | savedefconfig | menuconfig)
                        prepare_config
                        $MAKE $1
                        ;;
                config)
                        prepare_config
                        $MAKE menuconfig
                        $MAKE savedefconfig
                        ;;
                default) prepare_config ;; # End of init
                *) usage ;;
        esac
}

在调用init_hook函数时可以传入以上任一命令作为选项,init_hook将会执行不同的分支:

  • chip:执行 choose_chip $@函数;
  • lunch|defconfig:执行choose_defconfig $@函数;
  • _defconfig:执行switch_defconfig "$1" 函数;
  • olddefconfig | savedefconfig | menuconfig:执行prepare_config函数;
  • config:执行prepare_config函数、$MAKE menuconfig$MAKE savedefconfig
  • default:执行prepare_config函数;
  • 不满足以上命令,将会执行usage输出该脚本用法;
3.3.1 入参为default

执行prepare_config函数;

# 如果板载配置不存在在,选择板载配置
prepare_config()
{	
		# 如果<SDK>/device/rockchip/.chip路径不存在,则执行choose_chip选择芯片
		# 如果存在,其指向如下:.chip -> .chips/rk3588/
        [ -e "$CHIP_DIR" ] || choose_chip

		# 进入<SDK>/device/rockchip
        cd "$DEVICE_DIR"
        # 查找/device/rockchip/.chips目录下文件,即删除rk3588   rk3588 -> .chips/rk3588/
        rm -f $(ls "$CHIPS_DIR")   
        # ln -rsf .chips/rk3588 .
        ln -rsf "$(readlink "$CHIP_DIR")" .
        
        # 跳转到SDK目录
        cd "$SDK_DIR"

		# 如果<SDK>/output/defconfig文件不存在或不可读,则执行choose_chip选择芯片		
        if [ ! -r "$RK_DEFCONFIG_LINK" ]; then
                echo "WARN: $RK_DEFCONFIG_LINK not exists"
                choose_defconfig
                return 0
        fi

	    # 由<SDK>/output/defconfig -> ../device/rockchip/.chips/rk3588/rockchip_rk3588_sige7_defconfig,得到rockchip_rk3588_sige7_defconfig
        DEFCONFIG=$(basename "$(realpath "$RK_DEFCONFIG_LINK")")
        
        # 检查两个文件是否不是同一个文件
        if [ ! "$RK_DEFCONFIG_LINK" -ef "$CHIP_DIR/$DEFCONFIG" ]; then
                echo "WARN: $RK_DEFCONFIG_LINK is invalid"
                choose_defconfig
                return 0
        fi

		# 检查文件$RK_CONFIG是否比$RK_DEFCONFIG_LINK更旧
        if [ "$RK_CONFIG" -ot "$RK_DEFCONFIG_LINK" ]; then
                echo "WARN: $RK_CONFIG is out-dated"
                $MAKE $DEFCONFIG &>/dev/null
                return 0
        fi

        CONFIG_DIR="$(dirname "$RK_CONFIG_IN")"
        if find "$CONFIG_DIR" -cnewer "$RK_CONFIG" | grep -q ""; then
                echo "WARN: $CONFIG_DIR is updated"
                $MAKE $DEFCONFIG &>/dev/null
                return 0
        fi

        CFG="RK_DEFCONFIG=\"$(realpath "$RK_DEFCONFIG_LINK")\""
        if ! grep -wq "$CFG" "$RK_CONFIG"; then
                echo "WARN: $RK_CONFIG is invalid"
                $MAKE $DEFCONFIG &>/dev/null
                return 0
        fi

        if [ "$RK_CONFIG" -nt "${RK_CONFIG}.old" ]; then
                $MAKE olddefconfig &>/dev/null
                touch "${RK_CONFIG}.old"
        fi
}
3.1.2 入参为defconfig

通过上面的分析我们知道执行./build.sh rockchip_rk3588_sige7_defconfig命令会执行choose_defconfig函数,函数的入参为rockchip_rk3588_sige7_defconfig

# 列出当前SDK支持的板载配置
rockchip_defconfigs()
{
        # <SDK>/device/rockchip/.chip
        cd "$CHIP_DIR"        
        ls rockchip_defconfig 2>/dev/null || true
        # 输出所有*_defconfig文件,比如rockchip_defconfig、rockchip_rk3588s_evb1_lp4x_v10_defconfig等
        ls *_defconfig | grep -v rockchip_defconfig || true
}


choose_defconfig()
{
		# rockchip_defconfigs为支持的板载配置数组,判断是否支持rockchip_rk3588_sige7_defconfig
        DEFCONFIG_ARRAY=( $(rockchip_defconfigs | grep "$1" || true) )
        # 获取数组长度,这里为1
        DEFCONFIG_ARRAY_LEN=${#DEFCONFIG_ARRAY[@]}
        case $DEFCONFIG_ARRAY_LEN in
                0)
                        echo "No available defconfigs${1:+" for: $1"}"
                        return 1
                        ;;
                1)      DEFCONFIG=${DEFCONFIG_ARRAY[0]} ;;  # 保存当前选中的板载配置文件
                *)
                        if [ "$1" = ${DEFCONFIG_ARRAY[0]} ]; then
                                # Prefer exact-match
                                DEFCONFIG="$1"
                        else
                                echo "Pick a defconfig:"
                                echo ""

                                echo ${DEFCONFIG_ARRAY[@]} | xargs -n 1 | \
                                        sed "=" | sed "N;s/\n/. /"

                                local INDEX
                                read -p "Which would you like? [1]: " INDEX
                                INDEX=$((${INDEX:-1} - 1))
                                DEFCONFIG="${DEFCONFIG_ARRAY[$INDEX]}"
                        fi
                        ;;
        esac
		# 执行switch_defconfig rockchip_rk3588_sige7_defconfig
        switch_defconfig $DEFCONFIG
}
3.1.3 入参为*_defconfig

执行switch_defconfig函数:

switch_defconfig()
{
		# rockchip_rk3588_sige7_defconfig
        DEFCONFIG="$1"

		# DEFCONFIG=<SDK>/device/rockchip/.chip/rockchip_rk3588_sige7_defconfig
        [ -f "$DEFCONFIG" ] || DEFCONFIG="$CHIP_DIR/$DEFCONFIG"

        if [ ! -f "$DEFCONFIG" ]; then
                echo "No such defconfig: $1"
                exit 1
        fi

        echo "Switching to defconfig: $DEFCONFIG"
        # 删除<SDK>/output/defconfig
        rm -f "$RK_DEFCONFIG_LINK"
        # 创建<SDK>/output/defconfig -> <SDK>/device/rockchip/.chip/rockchip_rk3588_sige7_defconfig
        ln -rsf "$DEFCONFIG" "$RK_DEFCONFIG_LINK"

        DEFCONFIG="$(realpath "$DEFCONFIG")"
        # 删除<SDK>/device/rockchip/.chip
        rm -rf "$CHIP_DIR"
        # 创建<SDK>/device/rockchip/.chip -> <SDK>/device/rockchip/.chips/rk3588
        ln -rsf "$(dirname "$DEFCONFIG")" "$CHIP_DIR"

		# 执行make V=1 -C device/rockchip/common rockchip_rk3588_sige7_defconfig
        $MAKE $(basename "$DEFCONFIG")
}

可以看到分析到最后就是执行了make V=1 -C device/rockchip/common rockchip_rk3588_sige7_defconfig

因此我们需要进入<SDK>/Makefile分析<SDK>/output/.config文件是如何生成的。

3.4 .config配置文件

我们定位到<SDK>/Makefile

# <SDK>/device/rockchip/.chi -> <SDK>/device/rockchip/.chips/rk3588,目录下有板级配置rockchip_rk3588_sige7_defconfig
CHIP_DIR := $(DEVICE_DIR)/.chip
# <SDK>/device/rockchip/common/configs
CONFIG_DIR := $(COMMON_DIR)/configs
CONFIG_CONFIG_IN = Config.in
# <SDK>/device/rockchip/common/kconfig
CONFIG = $(COMMON_DIR)/kconfig
# <SDK>/output/.config
RK_CONFIG = $(OUTDIR)/.config
# <SDK>/output/kconf
BUILD_DIR := $(OUTDIR)/kconf

#<SDK>/output/kconf/conf工具生成命令
$(BUILD_DIR)/%onf:
		# 创建目录<SDK>/output/kconf/lxdialog
        mkdir -p $(@D)/lxdialog
        # make CC=gcc HOSTCC=g++ obj=<SDK>/output/kconf/conf -C <SDK>/device/rockchip/common/kconfig -f Makefile.br conf 切换到<SDK>/device/rockchip/common/kconfig目录,编译生成conf
        make CC="$(HOSTCC)" HOSTCC="$(HOSTCC)" \
            obj=$(@D) -C $(CONFIG) -f Makefile.br $(@F)

......
COMMON_CONFIG_ENV = \
        RK_DEFCONFIG='$(call qstrip,$(RK_DEFCONFIG))' \
        RK_CHIP_FAMILY='$(call qstrip,$(RK_CHIP_FAMILY))' \
        RK_CHIP='$(call qstrip,$(RK_CHIP))' \
        KCONFIG_AUTOCONFIG=$(BUILD_DIR)/auto.conf \
        KCONFIG_AUTOHEADER=$(BUILD_DIR)/autoconf.h \
        KCONFIG_TRISTATE=$(BUILD_DIR)/tristate.config \
        srctree=$(CONFIG_DIR) RK_CONFIG=$(RK_CONFIG)

# .config配置文件生成命令
%_defconfig: $(BUILD_DIR)/conf $(CHIP_DIR)/%_defconfig
        $(Q)$(COMMON_CONFIG_ENV) $< --defconfig=$(CHIP_DIR)/$@ $(CONFIG_CONFIG_IN)

比如我们执行如下命令:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ sudo make V=1 -C device/rockchip/common rockchip_rk3588_sige7_defconfig
make: 进入目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/device/rockchip/common”
RK_DEFCONFIG='/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/device/rockchip/.chips/rk3588/rockchip_rk3588_sige7_defconfig' RK_CHIP_FAMILY='rk3588' RK_CHIP='rk3588' KCONFIG_AUTOCONFIG=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/output/kconf/auto.conf KCONFIG_AUTOHEADER=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/output/kconf/autoconf.h KCONFIG_TRISTATE=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/output/kconf/tristate.config srctree=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/device/rockchip/common/configs RK_CONFIG=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/output/.config /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/output/kconf/conf --defconfig=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/device/rockchip/.chip/rockchip_rk3588_sige7_defconfig Config.in
#
# configuration written to /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/output/.config
#
make: 离开目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/device/rockchip/common”

编译生成配置文件<SDK>/output/.config.config文件是一个至关重要的配置文件,它包含了SDK编译所需的配置信息,比如Rootfsu-bootKernel等。

<SDK>/output/.config通过<SDK/output/kconf/conf可执行文件生成的,而<SDK/>output/kconf/conf工具是由device/rockchip/common/kconfig源码编译生成的。

该工具接受的参数分别是:

  • --defconfig=<SDK>/device/rockchip/.chip/rockchip_rk3588_sige7_defconfig
  • Config.in

SDK使用Kconfig系统来管理其配置选项。Kconfig是一个用于内核配置的脚本语言,它允许开发者定义配置选项、条件依赖关系以及帮助文本。
SDK的源代码中,会有一系列的Kconfig文件(位于<SDK>device/rockchip/common/configs目录),这些文件定义了可用的配置选项。

因此不难猜测.config文件中的配置项主要来源于板级的默认配置、Kconfig系统定义的可选配置以及用户的自定义配置(make menuconfig配置)。

四、40-recovery.sh

00-config.sh脚本位于<SDK>/device/rockchip/common/build-hooks/目录,其实现的编译选项只有recovery

4.1 脚本入口

首先执行SDK/device/rockchip/common/build-hooks/build-helper脚本,会将这个脚本的内容直接加载到当前shell环境中执行,build-helper.sh 能够获取父文件中的变量和参数;

#  source SDK/device/rockchip/common/build-hooks/build-helper
source "${BUILD_HELPER:-$(dirname "$(realpath "$0")")/../build-hooks/build-helper}"

build_hook $@

有关build-helper我们已经具体分析了,这里不再重复。

4.2 usage_hook

usage_hook输出当前脚本支持的编译选项信息;

usage_hook()
{
        echo -e "recovery                          \tbuild recovery"
}

4.3 clean_hook

clean_hook用于进行清理工作;

clean_hook()
{
        check_config RK_RECOVERY_CFG || return 0
        rm -rf buildroot/output/$RK_RECOVERY_CFG
        rm -rf "$RK_OUTDIR/recovery"
}

删除板载配置RK_RECOVERY_CFG目录编译的文件, 文件位于<SDK>/buildroot/output/$RK_RECOVERY_CFG目录,。

RK_RECOVERY_CFG定义在<SDK>/device/rockchip/common/configs/Config.in.recovery

config RK_RECOVERY_CFG
        string
        default "rockchip_${RK_RECOVERY_BASE_CFG}_recovery"

比如我们使用rk3588开发板编译时,RK_RECOVERY_CFG=rockchip_rk3588_recovery

删除<SDK>/output/recovery目录下的文件,比如:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll output/recovery
lrwxrwxrwx 1 root root 51  6月 11 22:59 output/recovery -> ../buildroot/output/rockchip_rk3588_recovery/images/
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll output/recovery/
-rw-r--r-- 1 root root 44296704  6月 11 23:00 recovery.img
-rw-r--r-- 1 root root 15763968  6月 11 23:00 rootfs.cpio
-rw-r--r-- 1 root root  6673958  6月 11 23:00 rootfs.cpio.gz
-rw-r--r-- 1 root root 43187200  6月 11 23:00 rootfs.ext2
lrwxrwxrwx 1 root root       11  6月 11 23:00 rootfs.ext4 -> rootfs.ext2
-rw-r--r-- 1 root root  6635520  6月 11 23:00 rootfs.squashfs
-rw-r--r-- 1 root root 16844800  6月 11 23:00 rootfs.tar

4.4 build_hook

build_hook用于recovery构建,构建步骤如下:

  • buildroot文件系统编译,编译输出目录为<SDK>/buildroot/output/rockchip_rk3588_recovery/需要注意的是编译buildroot使用的单板配置文件是rockchip_rk3588_recovery_defconfig
  • 构建系统镜像recovery.imgFIT uImage类型内核镜像),由Image + dtb + ramdiskbuildroot编译得到的ramdisk文件系统rootfs.cpio.gz)组成;
  • 创建链接:output/firmware/recovery.img -> <SDK>/output/rockchip_rk3588_recovery/images/recovery.img

源码如下:

# 构建阶段的编译选项
BUILD_CMDS="recovery"

build_hook()
{
        # 在<SDK>/output/.config中查找,# RK_AB_UPDATE is not set
        [ -z "$RK_AB_UPDATE" ] || return 0
	
		# 检查RK_RECOVERY_CFG变量是否已经设置
        check_config RK_RECOVERY_CFG || return 0

        echo "=========================================="
        echo "          Start building recovery(buildroot)"
        echo "=========================================="


        # 设置输出目录<SDK>/output/recovery
        DST_DIR="$RK_OUTDIR/recovery"

        # 调用<SDK>/device/rockchip/common/scripts/mk-buildroot.sh rockchip_rk3588_recovery <SDK>/output/recovery
        /usr/bin/time -f "you take %E to build recovery(buildroot)" \
                "$SCRIPTS_DIR/mk-buildroot.sh" $RK_RECOVERY_CFG "$DST_DIR"

        # 调用<SDK>/device/rockchip/common/scripts/mk-ramdisk.sh <SDK>/output/recovery/rootfs.cpio.gz  <SDK>/output/recovery/recovery.img  <SDK>/device/rockchip/.chips/rk3588/boot4recovery.its
        /usr/bin/time -f "you take %E to pack recovery image" \
                "$SCRIPTS_DIR/mk-ramdisk.sh" "$DST_DIR/rootfs.cpio.gz" \
                "$DST_DIR/recovery.img" "$RK_RECOVERY_FIT_ITS"
                
        # 创建链接<SDK>/output/firmware/recovery.img -> <SDK>/output/recovery/recovery.img -> <SDK>/output/rockchip_rk3588_recovery/images/recovery.img
        ln -rsf "$DST_DIR/recovery.img" "$RK_FIRMWARE_DIR"

        finish_build build_recovery
}

由于该函数调用了mk-buildroot.shmk-ramdisk.sh等脚本,因此我们还需要对这些脚本进行分析。

4.4.1 mk-buildroot.sh

mk-buildroot.sh脚本位于<SDK>/device/rockchip/common/scripts目录下,用于编译 building recovery(buildroot),实际上就是进入<SDK>/buildroot目录去编译buildroot

编译buildroot使用的单板配置文件是rockchip_rk3588_recovery_defconfig,文件位于<SDK>/buildroot/configs目录。

<SDK>/device/rockchip/common/scripts/mk-buildroot.sh rockchip_rk3588_recovery <SDK>/output/recovery

脚本核心代码:

# <SDK>/device/rockchip/common/script
SCRIPTS_DIR="${SCRIPTS_DIR:-$(dirname "$(realpath "$0")")}"
# <SDK>
SDK_DIR="${SDK_DIR:-$SCRIPTS_DIR/../../../..}"

# rockchip_rk3588_recovery
BUILDROOT_BOARD=$1
# <SDK>/output/recovery
ROOTFS_OUTPUT_DIR="${2:-$SDK_DIR/output/buildroot}"
# <SDK>/buildroot
BUILDROOT_DIR="$SDK_DIR/buildroot"

"$SCRIPTS_DIR/check-buildroot.sh"

# 设置编译输出目录 <SDK>/buildroot/output/rockchip_rk3588_recovery
BUILDROOT_OUTPUT_DIR="$BUILDROOT_DIR/output/$BUILDROOT_BOARD"
# <SDK>/buildroot/output/rockchip_rk3588_recovery/.config
BUILDROOT_CONFIG="$BUILDROOT_OUTPUT_DIR/.config"
# <SDK>/buildroot/output/rockchip_rk3588_recovery/.config.orig
BUILDROOT_CONFIG_ORIG="$BUILDROOT_OUTPUT_DIR/.config.orig"

......

# Save the original .config if exists 判断.config文件存在并且可读,复制.config为.config.orig,即保存之前的配置文件
if [ -r "$BUILDROOT_CONFIG" ] && [ ! -r "$BUILDROOT_CONFIG_ORIG" ]; then
        cp "$BUILDROOT_CONFIG" "$BUILDROOT_CONFIG_ORIG"
fi

# 1. 配置:进入<SDK>/buildroot目录,执行make,配置文件为rockchip_rk3588_recovery_defconfig,输出目录为<SDK>/buildroot/output/rockchip_rk3588_recovery,执行完成会生成.config文件。
make -C "$BUILDROOT_DIR" O="$BUILDROOT_OUTPUT_DIR" ${BUILDROOT_BOARD}_defconfig

# Warn about config changes 判断.config.orig文件存在并且可读
if [ -r "$BUILDROOT_CONFIG_ORIG" ]; then
		# 比较.config和.config.orig文件差异,如果配置文件发生改变了,提示需要先进行clean工作
        if ! diff "$BUILDROOT_CONFIG" "$BUILDROOT_CONFIG_ORIG"; then
                echo -e "\e[35m"
                echo "Buildroot config changed!"
                echo "You might need to clean it before building:"
                echo "rm -rf $BUILDROOT_OUTPUT_DIR"
                echo -e "\e[0m"
                echo
        fi
fi

# 设置编译镜像文件存放目录   <SDK>/buildroot/output/rockchip_rk3588_recovery/images
IMAGE_DIR="$BUILDROOT_OUTPUT_DIR/images"

# <SDK>/output/recovery
rm -rf "$ROOTFS_OUTPUT_DIR"
# 创建编译镜像文件存放目录   
mkdir -p "$IMAGE_DIR"

# 创建链接: <SDK>/output/recovery -> <SDK>/buildroot/output/rockchip_rk3588_recovery/images
ln -rsf "$IMAGE_DIR" "$ROOTFS_OUTPUT_DIR"

# 进入目录<SDK>/output/sessions/创建时间
cd "${RK_LOG_DIR:-$ROOTFS_OUTPUT_DIR}"

# br-rockchip_rk3588_recovery
LOG_PREFIX="br-$(basename "$BUILDROOT_OUTPUT_DIR")"
# 设置日志输出文件:<SDK>/output/sessions/创建时间/br-rockchip_rk3588_recovery_创建时间.log
LOG_FILE="$(start_log "$LOG_PREFIX" 2>/dev/null || echo $PWD/$LOG_PREFIX.log)"
# br.log -> $LOG_FILE
ln -rsf "$LOG_FILE" br.log

# Buildroot doesn't like it
unset LD_LIBRARY_PATH

# 2.编译 <SDK>/buildroot/utils/brmake -C <SDK>/buildroot O=<SDK>/buildroot/output/rockchip_rk3588_recovery
if ! "$BUILDROOT_DIR"/utils/brmake -C "$BUILDROOT_DIR" O="$BUILDROOT_OUTPUT_DIR"; then
        echo "Failed to build $BUILDROOT_BOARD:"
        tail -n 100 "$LOG_FILE"
        echo -e "\e[35m"
        echo "Please check details in $LOG_FILE"
        echo -e "\e[0m"
        exit 1
fi

echo "Log saved on $LOG_FILE"
echo "Generated images:"
# 查看<SDK>/output/recovery/rootfs.*文件
ls "$ROOTFS_OUTPUT_DIR"/rootfs.*

编译后会在<SDK>/buildroot/output/rockchip_rk3588_recovery目录生成下生成.config以及若干镜像文件;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll buildroot/output/rockchip_rk3588_recovery/
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.init
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.jpeg
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.linux
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.menus
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.openssl
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.paths
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.skeleton
-rw-r--r--  1 root root     82  7月  8 23:05 .br2-external.in.toolchains
-rw-r--r--  1 root root    162  7月  8 23:05 .br2-external.mk
drwxr-xr-x 96 root root   4096  7月  8 23:06 build/    # 除了交叉编译的工具链之外的所有组件  
-rw-r--r--  1 root root 114043  7月  8 23:05 .config   # 生成的配置文件
-rw-------  1 root root   3019  7月  8 23:05 .config.in
-rw-r--r--  1 root root 114043  6月 23 16:57 .config.new
-rw-r--r--  1 root root 114043  7月  8 22:45 .config.old
-rw-r--r--  1 root root 114043  7月  8 22:43 .config.orig
-rw-r--r--  1 root root 105270  7月  8 23:05 ..config.tmp
drwxr-xr-x 11 root root   4096  6月 18 01:46 host/     # 包含为主机编译的工具的安装
drwxr-xr-x  2 root root   4096  7月  8 23:06 images/   # 有镜像(文件系统,比如ext2/4、squashfs、cpio等格式镜像)存储目录 
-rw-r--r--  1 root root    681  7月  8 23:05 Makefile
lrwxrwxrwx  1 root root    131  6月 18 01:48 staging -> /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/
drwxr-xr-x 17 root root   4096  7月  8 23:06 target/    # 几乎包含了目标的完整根文件系统
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll buildroot/output/rockchip_rk3588_recovery/images/
-rw-r--r-- 1 root root 44336128  7月  8 23:06 recovery.img  # 最终需要的系统镜像
-rw-r--r-- 1 root root 15856640  7月  8 23:06 rootfs.cpio     # rootfs.ext2经过cpio压缩的根文件系统镜像
-rw-r--r-- 1 root root  6713350  7月  8 23:06 rootfs.cpio.gz  # 经过cpio和gzip压缩的根文件系统镜像,常用于创建初始RAM磁盘 (initramfs)
-rw-r--r-- 1 root root 43342848  7月  8 23:06 rootfs.ext2     # buildroot编译生成的文件系统镜像,ext2格式
lrwxrwxrwx 1 root root       11  7月  8 23:06 rootfs.ext4 -> rootfs.ext2
-rw-r--r-- 1 root root  6676480  7月  8 23:06 rootfs.squashfs
-rw-r--r-- 1 root root 16947200  7月  8 23:06 rootfs.tar      # rootfs.ext2经过tar打包的根文件系统镜像  

关于编译的具体细节,以及生成的目录介绍具体可以参考:

4.4.2 mk-ramdisk.sh

mk-ramdisk.sh脚本位于<SDK>/device/rockchip/common/scripts目录下,用于构建recovery系统镜像。

<SDK>/device/rockchip/common/scripts/mk-ramdisk.sh \
<SDK>/output/recovery/rootfs.cpio.gz \  
<SDK>/output/recovery/recovery.img  \
<SDK>/device/rockchip/.chips/rk3588/boot4recovery.its 

脚本核心代码:

# ramsidk根文件系统:<SDK>/output/recovery/rootfs.cpio.gz
RAMDISK_IMG="$1"
# recovery系统镜像:<SDK>/output/recovery/recovery.img
TARGET_IMG="$2"
# <SDK>/device/rockchip/.chips/rk3588/boot4recovery.its 
ITS="$3"

# 校验ramsidk根文件系统文件是否存在
if [ ! -f "$RAMDISK_IMG" ]; then
        echo "$RAMDISK_IMG doesn't exist"
        exit 0
fi

# 内核镜像<SDK>/kernel/arch/arm64/boot/Image
KERNEL_IMG="$RK_KERNEL_IMG"

# 校验内核镜像是否存在
if [ ! -f "$KERNEL_IMG" ]; then
        echo "Build kernel for initrd"
        "$SCRIPTS_DIR/mk-kernel.sh"
fi

# 检验ramsidk根文件系统是否以.romfs结尾,如果是,则对$RAMDISK_IMG和$KERNEL_IMG分别进行gzip压缩,
if echo $RAMDISK_IMG | grep -q ".romfs$"; then
        cat "$RAMDISK_IMG" | gzip -n -f -9 > "$RAMDISK_IMG.gz"
        cat "$KERNEL_IMG" | gzip -n -f -9 > "$KERNEL_IMG.gz"
        RAMDISK_IMG="$RAMDISK_IMG.gz"
        KERNEL_IMG="$KERNEL_IMG.gz"
fi

# 开始打包
echo "Packing $RAMDISK_IMG to $TARGET_IMG"
# 判断its存在
if [ -n "$ITS" ]; then
		# 走这里
        "$SCRIPTS_DIR/mk-fitimage.sh" "$TARGET_IMG" "$ITS" \
                "$KERNEL_IMG" "$RAMDISK_IMG"
else
        kernel/scripts/mkbootimg --kernel "$KERNEL_IMG" \
                --ramdisk "$RAMDISK_IMG" --second "kernel/resource.img" \
                -o "$TARGET_IMG"
fi

看到最后我们实际上就已经很熟悉了,这里就是调用mk-fitimage.sh脚本去生成FIT uImage

这里我们校验介绍一下FIT uImageFIT uImage是在Legacy uImage的基础上,为了满足Linux Flattened Device Tree(FDT)的标准,而重新改进和定义出来的一种镜像文件格式;它一般将kerneldtbramdisk等等镜像打包到一个itb镜像文件中;u-boot只要获得了这个镜像文件,就可以得到kerneldtbramdisk等等镜像的具体信息和内容。

4.4.3 mk-fitimage.sh

这里我们还是来看一下mk-fitimage.sh脚本,脚本位于<SDK>/device/rockchip/common/scripts目录下,该脚本根据its文件中的描述来打包镜像生成itb文件(FIT uImage)。

# FIT uImage镜像:<SDK>/output/recovery/recovery.img
TARGET_IMG="$1"
# its文件: <SDK>/device/rockchip/.chips/rk3588/boot4recovery.its 
ITS="$CHIP_DIR/$2"
# 内核镜像:<SDK>/kernel/arch/arm64/boot/Image
KERNEL_IMG="$3"
# ramsidk根文件系统:<SDK>/output/recovery/rootfs.cpio.gz
RAMDISK_IMG="$4"
# 内核设备树:<SDK>/kernel/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dtb
KERNEL_DTB="$RK_KERNEL_DTB"
# 资源:<SDK>/kernel/resource.img
RESOURCE_IMG=kernel/resource.img

# 判断its文件存在
if [ ! -f "$ITS" ]; then
        echo "$ITS not exists!"
        exit 1
fi

# 生成临时文件,复制its到临时文件
TMP_ITS=$(mktemp)
cp "$ITS" "$TMP_ITS"

if [ "$RK_SECURITY" ]; then
        echo "Security boot enabled, removing uboot-ignore ..."
        sed -i "/uboot-ignore/d" "$TMP_ITS"
fi

# 使用sed命令替换一个临时文件$TMP_ITS中的占位符, 比如替换@KERNEL_DTB@-><SDK>/kernel/arch/arm64/boot/dts/rockchip/rk3588-armsom-sige7.dtb
sed -i -e "s~@KERNEL_DTB@~$(realpath -q "$KERNEL_DTB")~" \
        -e "s~@KERNEL_IMG@~$(realpath -q "$KERNEL_IMG")~" \
        -e "s~@RAMDISK_IMG@~$(realpath -q "$RAMDISK_IMG")~" \
        -e "s~@RESOURCE_IMG@~$(realpath -q "$RESOURCE_IMG")~" "$TMP_ITS"

# 使用mkimage工具编译,这里一定要指定-E参数
rkbin/tools/mkimage -f "$TMP_ITS"  -E -p 0x800 "$TARGET_IMG"

# 删除临时文件
rm -f "$TMP_ITS"

这里重点关注一下its文件,路径为device/rockchip/.chip/boot4recovery.its

/*
 * Copyright (C) 2021 Rockchip Electronics Co., Ltd
 *
 * SPDX-License-Identifier: GPL-2.0
 */

/dts-v1/;
/ {
    description = "U-Boot FIT source file for arm";

    images {
        fdt {
            data = /incbin/("@KERNEL_DTB@");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            load = <0xffffff00>;

            hash {
                algo = "sha256";
            };
        };
		# 注意这里未指定压缩方式,因此内核镜像使用Image
        kernel {
            data = /incbin/("@KERNEL_IMG@");
            type = "kernel";
            arch = "arm64";
            os = "linux";
            compression = "none";
            entry = <0xffffff01>;
            load = <0xffffff01>;

            hash {
                algo = "sha256";
            };
        };

                ramdisk {
                        data = /incbin/("@RAMDISK_IMG@");
                        type = "ramdisk";
                        arch = "arm64";
                        os = "linux";
                        compression = "none";
                        load = <0xffffff02>;

                        hash {
                                algo = "sha256";
                        };
                };

        resource {
            data = /incbin/("@RESOURCE_IMG@");
            type = "multi";
            arch = "arm64";
            compression = "none";

            hash {
                algo = "sha256";
            };
        };
    };

    configurations {
        default = "conf";

        conf {
            rollback-index = <0x00>;
            fdt = "fdt";
            kernel = "kernel";
            ramdisk = "ramdisk";
            multi = "resource";

            signature {
                algo = "sha256,rsa2048";
                padding = "pss";
                key-name-hint = "dev";
                sign-images = "fdt", "kernel", "ramdisk", "multi";
            };
        };
    };
};

关于FIT uImage镜像的具体制作可以参考:《Rockchip RK3399 - 移植linux 5.2.8》。

五、90-updateimg.sh

90-updateimg.sh脚本位于<SDK>/device/rockchip/common/build-hooks/目录,其实现的编译选项有edit-package-fileedit-ota-package-filupdateimgotapackage

5.1 脚本入口

首先执行SDK/device/rockchip/common/build-hooks/build-helper脚本,会将这个脚本的内容直接加载到当前shell环境中执行,build-helper.sh 能够获取父文件中的变量和参数;

source "${BUILD_HELPER:-$(dirname "$(realpath "$0")")/../build-hooks/build-helper}"

case "$@" in
        edit-package-file|edit-ota-package-file)
                init_hook $@
                pre_build_hook $@
                ;;
        *) post_build_hook ${@:-updateimg} ;;
esac

有关build-helper我们已经具体分析了,这里不再重复。

5.2 usage_hook

usage_hook输出当前脚本支持的编译选项信息;

usage_hook()
{
        echo -e "edit-package-file                 \tedit package-file"
        echo -e "edit-ota-package-file             \tedit A/B OTA package-file"
        echo -e "updateimg                         \tbuild update image"
        echo -e "otapackage                        \tbuild A/B OTA update image"
}

5.3 clean_hook

clean_hook用于进行清理工作,删除我们编译出的update.img

clean_hook()
{
        rm -rf "$RK_OUTDIR/update"
        rm -rf "$RK_OUTDIR/ota"
        rm -rf "$RK_OUTDIR/ab"
        rm -rf "$RK_FIRMWARE_DIR/*update.img"
        rm -rf "$RK_ROCKDEV_DIR/*update.img"
}

删除<SDK>/output目录下的updateotaab,比如:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll output/update/
drwxr-xr-x 2 root root 4096  6月 11 23:00 Image/
lrwxrwxrwx 1 root root   18  6月 11 23:00 package-file -> Image/package-file
lrwxrwxrwx 1 root root   16  6月 11 23:00 update.img -> Image/update.img

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll output/update/Image/
lrwxrwxrwx 1 root root        24  6月 11 23:00 boot.img -> ../../../kernel/boot.img
lrwxrwxrwx 1 root root        47  6月 11 23:00 MiniLoaderAll.bin -> ../../../u-boot/rk3588_spl_loader_v1.13.112.bin
lrwxrwxrwx 1 root root        23  6月 11 23:00 misc.img -> ../../firmware/misc.img
lrwxrwxrwx 1 root root        22  6月 11 23:00 oem.img -> ../../firmware/oem.img
-rw-r--r-- 1 root root       225  6月 11 23:00 package-file
lrwxrwxrwx 1 root root        52  6月 11 23:00 parameter.txt -> ../../../device/rockchip/.chips/rk3588/parameter.txt
lrwxrwxrwx 1 root root        70  6月 11 23:00 recovery.img -> ../../../buildroot/output/rockchip_rk3588_recovery/images/recovery.img
lrwxrwxrwx 1 root root        60  6月 11 23:00 rootfs.img -> ../../../buildroot/output/rockchip_rk3588/images/rootfs.ext2
lrwxrwxrwx 1 root root        25  6月 11 23:00 uboot.img -> ../../../u-boot/uboot.img
-rw-r--r-- 1 root root 872409674  6月 11 23:00 update.img
-rw-r--r-- 1 root root 871938052  6月 11 23:00 update.raw.img
lrwxrwxrwx 1 root root        27  6月 11 23:00 userdata.img -> ../../firmware/userdata.img

删除<SDK>/output/firmware/update.img以及<SDK>/rockdev/update.img,实际上就是删除<SDK>/output/update/Image/update.img

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll output/firmware/
lrwxrwxrwx 1 root root       21  6月 11 22:52 boot.img -> ../../kernel/boot.img
lrwxrwxrwx 1 root root       44  6月 11 23:00 MiniLoaderAll.bin -> ../../u-boot/rk3588_spl_loader_v1.13.112.bin
-rw-r--r-- 1 root root    49152  6月 11 23:00 misc.img
-rw-r--r-- 1 root root 17457152  6月 11 23:00 oem.img
lrwxrwxrwx 1 root root       49  6月 11 23:00 parameter.txt -> ../../device/rockchip/.chips/rk3588/parameter.txt
lrwxrwxrwx 1 root root       67  6月 11 23:00 recovery.img -> ../../buildroot/output/rockchip_rk3588_recovery/images/recovery.img
lrwxrwxrwx 1 root root       57  6月 11 22:59 rootfs.img -> ../../buildroot/output/rockchip_rk3588/images/rootfs.ext2
lrwxrwxrwx 1 root root       22  6月 11 23:00 uboot.img -> ../../u-boot/uboot.img
lrwxrwxrwx 1 root root       26  6月 11 23:00 update.img -> ../update/Image/update.img   # 指向的就是<SDK>/output/update/update.img
-rw-r--r-- 1 root root  4472832  6月 11 23:00 userdata.img

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll rockdev
lrwxrwxrwx 1 root root 15  6月 11 23:00 rockdev -> output/firmware/

5.4 init_hook

init_hook函数在构建的初始化阶段(init阶段)被调用,用于处理INIT_CMDS命令;

INIT_CMDS="edit-package-file edit-ota-package-file"
init_hook()
{
        case "$1" in
                edit-package-file)
                        BASE_CFG=RK_PACKAGE_FILE
                        PKG_FILE="$CHIP_DIR/package-file"
                        ;;
                edit-ota-package-file)
                        BASE_CFG=RK_AB_OTA_PACKAGE_FILE
                        PKG_FILE="$CHIP_DIR/ab-ota-package-file"
                        ;;
                *) return 0 ;;
        esac

        load_config $BASE_CFG
        if ! check_config $BASE_CFG &>/dev/null; then
                sed -i '/$BASE_CFG/d' "$RK_CONFIG"
                echo "${BASE_CFG}_CUSTOM=y" >> "$RK_CONFIG"
                echo "$BASE_CFG=$PKG_FILE" >> "$RK_CONFIG"
                "$SCRIPTS_DIR/mk-config.sh" olddefconfig &>/dev/null
                "$SCRIPTS_DIR/mk-config.sh" savedefconfig &>/dev/null
        fi
}

在调用init_hook函数时可以传入以上任一命令作为选项,init_hook将会执行不同的分支:

  • edit-package-file:配置package-file,该文件描述了将哪些分区镜像打包到统一固件里;
  • edit-ota-package-file:配置ota-package-file
5.4.1 入参为edit-package-file

设置BASE_CFG=RK_PACKAGE_FILEPKG_FILE=""<SDK>/device/rockchip/.chip/package-file"

我们查看一下该文件内容:

# NAME  PATH
package-file    package-file
parameter       parameter.txt
bootloader      MiniLoaderAll.bin
uboot   uboot.img
misc    misc.img
boot    boot.img
recovery        recovery.img
backup  RESERVED
rootfs  rootfs.img
oem     oem.img
userdata  userdata.img
5.4.2 入参为edit-ota-package-file

设置BASE_CFG=RK_AB_OTA_PACKAGE_FILEPKG_FILE=""<SDK>/device/rockchip/.chip/ab-ota-package-file",该文件并没有找到。

5.5 pre_build_hook

pre_build_hook函数在构建前(pre-build阶段)被调用,用于处理PRE_BUILD_CMDS命令;

PRE_BUILD_CMDS="edit-package-file edit-ota-package-file"
pre_build_hook()
{
        case "$1" in
                edit-package-file)
                        check_config RK_PACKAGE_FILE || return 0
                        PKG_FILE="$CHIP_DIR/$RK_PACKAGE_FILE" ;;
                edit-ota-package-file)
                        check_config RK_AB_OTA_PACKAGE_FILE || return 0
                        PKG_FILE="$CHIP_DIR/$RK_AB_OTA_PACKAGE_FILE"
                        ;;
                *) return 0 ;;
        esac

        PKG_FILE="$(realpath "$PKG_FILE")"
        # 如果文件不可读
        if [ ! -r "$PKG_FILE" ]; then
        		# 重新生成package_file
                echo "Generating template $PKG_FILE"
                # 根据<SDK>/device/rockchip/.chip/parameter.txt中分区表信息生成package-file文件
                # <SDK>/device/rockchip/.chip/package-file
                gen_package_file template "$CHIP_DIR/$RK_PARAMETER" "$PKG_FILE"
        fi
        eval ${EDITOR:-vi} "$PKG_FILE"

        finish_build $@
}

RK_PACKAGE_FILE配置在device/rockchip/common/configs/Config.in.update,默认配置为package-file

#
# Update (Update image, OTA and A/B)
#
RK_UPDATE=y
# RK_PACKAGE_FILE_DEFAULT is not set
RK_PACKAGE_FILE_CUSTOM=y
RK_PACKAGE_FILE="package-file"
# RK_AB_UPDATE is not set

该函数会获取package-file文件的全路径,然后判断该文件是否存在并且可读写,否则重新创建该文件。

5.6 post_build_hook

post_build_hook函数在构建后(post-build阶段)被调用,用于处理POST_BUILD_CMDS命令;

POST_BUILD_CMDS="updateimg otapackage"
post_build_hook()
{
        case "$1" in
                updateimg)
                        if [ "$RK_AB_UPDATE" ]; then
                                build_ab_updateimg
                        else
                                build_updateimg
                        fi
                        ;;
                otapackage) build_ota_updateimg ;;
                *) usage ;;
        esac
}

这里我们主要介绍updateimg分支,执行build_updateimg生成统一固件。

build_updateimg函数用于根据package-file以及分区镜像打包生成统一固件update.img

源码如下:

build_updateimg()
{
		# 检查变量RK_UPDATE是否设置, RK_UPDATE=y
        check_config RK_UPDATE || return 0

		# TARGET=<SDK>/rockdev/updae.img
        TARGET="${1:-$RK_ROCKDEV_DIR/update.img}"
        # TYPE=update
        TYPE="${2:-update}"
        # PKG_FILE=package-file
        PKG_FILE="${3:-$RK_PACKAGE_FILE}"
        # OUT_DIR=<SDK>/output/update
        OUT_DIR="$RK_OUTDIR/$TYPE"
        # IMAGE_DIR=<SDK>/output/update/Image
        IMAGE_DIR="$OUT_DIR/Image"

        # Make sure that the firmware is ready   <SDK>/rockdev/parameter.txt
        if [ ! -r "$RK_ROCKDEV_DIR/parameter.txt" ]; then
                echo "Firmware is not ready, building it..."
                "$SCRIPTS_DIR/mk-firmware.sh"
        fi

        # Make sure that the loader is ready     <SDK>/rockdev/MiniLoaderAll.bin
        if [ ! -r "$RK_ROCKDEV_DIR/MiniLoaderAll.bin" ]; then
                echo "Loader is not ready, building it..."
                "$SCRIPTS_DIR/mk-loader.sh"
        fi

        echo "=========================================="
        echo "          Start packing $2 update image"
        echo "=========================================="

		# 删除<SDK>/output/update/Image、<SDK>/output/update
        rm -rf "$TARGET" "$OUT_DIR"
        # 创建<SDK>/output/update/Image
        mkdir -p "$IMAGE_DIR"
        # 跳转到<SDK>/output/update/Image
        cd "$IMAGE_DIR"

        # 准备各个分区镜像 <SDK>/output/update/Image -> <SDK>/rockdev -> <SDK>/output/firmware/
        ln -rsf "$RK_ROCKDEV_DIR"/* .
        # 删除统一固件
        rm -f update.img

        # 准备package-file文件
        if [ "$PKG_FILE" ]; then
        		# <SDK>/device/rockchip/.chip/package-file
                PKG_FILE="$CHIP_DIR/$PKG_FILE"
                if [ ! -r "$PKG_FILE" ]; then
                        echo "$PKG_FILE not exists!"
                        exit 1
                fi
                # package-file -> <SDK>/device/rockchip/.chip/package-file
                ln -rsf "$PKG_FILE" package-file
        else
                echo "Generating package-file for $TYPE:"
                gen_package_file $TYPE
                cat package-file
        fi
        echo "Packing $TARGET for $TYPE..."

        if [ ! -r MiniLoaderAll.bin ]; then
                echo -e "\e[31mMiniLoaderAll.bin is missing\e[0m"
                exit 1
        fi

		# 读取MiniLoaderAll.bin从偏移21字节开始4个字节,以字符形式输出,倒序之后得到3588
        TAG=RK$(hexdump -s 21 -n 4 -e '4 "%c"' MiniLoaderAll.bin | rev)
        "$RK_PACK_TOOL_DIR/afptool" -pack ./ update.raw.img
        "$RK_PACK_TOOL_DIR/rkImageMaker" -$TAG MiniLoaderAll.bin \
                update.raw.img update.img -os_type:androidos

		# 创建链接文件
		# <SDK>/output/update/package-file -> <SDK>/output/update/Image/package-file
        ln -rsf "$IMAGE_DIR/package-file" "$OUT_DIR"
		# <SDK>/output/update/update.img -> <SDK>/output/update/Image/update.img
        ln -rsf "$IMAGE_DIR/update.img" "$OUT_DIR"
		# <SDK>/rockdev/updae.img -> <SDK>/output/update/Image/update.img
        ln -rsf "$IMAGE_DIR/update.img" "$TARGET"

        finish_build build_updateimg $@
}

函数主要包含三个步骤,我们接下来介绍。

5.6.1 准备文件

首先准备打包需要用到的文件;

  • 准备package-file文件、位于<SDK>/output/update目录;
  • 准备各个分区镜像文件,比如boot.imgMiniLoaderAll.binmisc.imgoem.imgrecovery.imgrootfs.imguboot.imguserdata.img位于<SDK>/output/update/Image目录;

需要配置<SDK>/output/update目录结构如下;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ tree output/update/
output/update/
└── Image
    ├── boot.img -> ../../../kernel/boot.img
    ├── MiniLoaderAll.bin -> ../../../u-boot/rk3588_spl_loader_v1.13.112.bin
    ├── misc.img -> ../../firmware/misc.img
    ├── oem.img -> ../../firmware/oem.img
    ├── package-file -> ../../../device/rockchip/.chips/rk3588/package-file
    ├── parameter.txt -> ../../../device/rockchip/.chips/rk3588/parameter.txt
    ├── recovery.img -> ../../../buildroot/output/rockchip_rk3588_recovery/images/recovery.img
    ├── rootfs.img -> ../../../buildroot/output/rockchip_rk3588/images/rootfs.ext2
    ├── uboot.img -> ../../../u-boot/uboot.img
    └── userdata.img -> ../../firmware/userdata.img
5.6.2 update.raw.img

通过afptool工具根据package-file 打包分区镜像生成原始固件update.raw.img,该工具位于<SDK>/tools/linux/Linux_Pack_Firmware/rockdev目录下;

"$RK_PACK_TOOL_DIR/afptool" -pack ./ update.raw.img

由于该工具并未开源,具体实现我们尚未可知,执行完后会在<SDK>/output/update/Image目录下生成 update.raw.img

我们在《Rockchip RK3588 - Rockchip Linux Recovery updateEngine源码分析》简单介绍了一下 update.raw.img 文件的组成部分。

5.6.3 update.img

通过rkImageMaker工具对原始固件update.raw.imgMiniLoaderAll.bin再次打包生成update.img,该工具位于<SDK>/tools/linux/Linux_Pack_Firmware/rockdev目录下;

"$RK_PACK_TOOL_DIR/rkImageMaker" -$TAG MiniLoaderAll.bin update.raw.img update.img -os_type:androidos

由于该工具并未开源,具体实现我们尚未可知,执行完后会在<SDK>/output/update/Image目录下生成update.img

我们在《Rockchip RK3588 - Rockchip Linux Recovery updateEngine源码分析》简单介绍了一下 update.img 文件的组成部分。

5.6.4 打包日志

以下是实际打包分区镜像生成统一固件输出的日志信息:

==========================================
          Start packing  update image
==========================================
Packing /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/rockdev/update.img for update...
Android Firmware Package Tool v2.2  # afptool工具输出日志
------ PACKAGE ------    
Add file: ./package-file
package-file,Add file: ./package-file done,offset=0x800,size=0xe2,userspace=0x1
Add file: ./parameter.txt
parameter,Add file: ./parameter.txt done,offset=0x1000,size=0x227,userspace=0x1,flash_address=0x00000000
Add file: ./MiniLoaderAll.bin
bootloader,Add file: ./MiniLoaderAll.bin done,offset=0x1800,size=0x731c0,userspace=0xe7
Add file: ./uboot.img
uboot,Add file: ./uboot.img done,offset=0x75000,size=0x400000,userspace=0x800,flash_address=0x00004000
Add file: ./misc.img
misc,Add file: ./misc.img done,offset=0x475000,size=0xc000,userspace=0x18,flash_address=0x00006000
Add file: ./boot.img
boot,Add file: ./boot.img done,offset=0x481000,size=0x23e1200,userspace=0x47c3,flash_address=0x00008000
Add file: ./recovery.img
recovery,Add file: ./recovery.img done,offset=0x2862800,size=0x2a48200,userspace=0x5491,flash_address=0x00028000
Add file: ./rootfs.img
rootfs,Add file: ./rootfs.img done,offset=0x52ab000,size=0x2d800000,userspace=0x5b000,flash_address=0x00078000
Add file: ./oem.img
oem,Add file: ./oem.img done,offset=0x32aab000,size=0x10a6000,userspace=0x214c,flash_address=0x01c78000
Add file: ./userdata.img
userdata,Add file: ./userdata.img done,offset=0x33b51000,size=0x444000,userspace=0x888,flash_address=0x01cb8000
Add CRC...
Make firmware OK!
------ OK ------
********rkImageMaker ver 2.23********   # rkImageMaker工具输出日志
Generating new image, please wait...
Writing head info...
Writing boot file...
Writing firmware...
Generating MD5 data...
MD5 data generated successfully!
New image generated successfully!
Running mk-updateimg.sh - build_updateimg succeeded.

六、补充

在编译过程中我们总结一下部分文件的作用;

  • <SDK>/device/rockchip/.chips:保存了SDK支持的各种SoC型号的板级配置文件的目录,目前只有rk3588(保存了多种型号rk3588开发板的板载配置文件);

  • <SDK>/device/rockchip/.chip:选中的SoC型号,由于当前SDK只支持rk3588,因此该目录始终链接到<SDK>/device/rockchip/.chips/rk3588;

  • <SDK>/device/rockchip/common/kconfig:配置工具源码,用于生成<SDK>/output/kconf/conf工具,.config配置文件就是由该工具生成的;

  • <SDK>/device/rockchip/common/build-hook:保存了<SDK>编译使用到的若干*.sh文件;

  • <SDK>/device/rockchip/.chips/rk3588/package-file:该文件描述了将哪些分区镜像打包到统一固件(update.img);

  • <SDK>/device/rockchip/.chips/rk3588/boot4recovery.itsrecovery编译制作FIT uImage使用的its文件。

参考文章

[1] Rockchip_Developer_Guide_Linux_Software_CN.pdf

[2] repo介绍与使用

[3] Rockchip_Developer_Guide_Linux_Upgrade_CN.pdf

[4] Rockchip RK3588 - NanoPC-T6开发板介绍

[5] Rockchip RK3399 - NanoPC-T4开发板介绍

[6] Buildroot根文件系统构建

[7] Debian根文件系统构建

posted @ 2024-07-09 21:22  大奥特曼打小怪兽  阅读(819)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步