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

Rockchip RK3588 - Rockchip Linux Recovery updateEngine升级

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

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

如果对Rockchip Linux SDK不了解的前提下,请先阅读以下两篇文章:

一、Recovery模式

1.1 简介

Rockchip Linux平台支持两种启动方案,Recovery模式和Linux A/B模式:

  • Recovery模式,设备上有一个单独的分区(recovery)用于升级操作;
  • Linux A/B模式,设备上有两套固件,可切换使用。

这两种启动模式各有优缺点,用户根据需求选择使用。本篇博客主要针对Recovery模式进行深入剖析。

1.1.1 Recovery模式概述

Recovery模式是在设备上多一个Recovery分区,该分区由kernel + dtb + ramdisk组成,主要用于升级操作。

u-boot会根据misc分区存放的字段来判断将要引导的系统是Normal系统还是Recovery系统。

由于系统的独立性,所以Recovery模式能保证升级的完整性,即升级过程被中断,如异常掉电,升级仍然能继续执行。

优点:

  • 能保证升级的完整性;

缺点:

  • 系统多了一个分区,该分区仅用于升级;
  • 升级过程必须重启进入recovery模式,不能在Normal系统直接进行升级;
1.1.2 升级方式

Rockchip Linux平台有两套升级方案代码;

升级方案 升级方案代码路径 是否支持Recovery启动模式升级 是否支持A/B启动模式升级 简介
updateEngine external/recovery/update_engine
支持 支持 RV1126/RV1109平台使用
实际测试也适用于RK3588
rkupdate external/rkupdate 支持 不支持 其它平台使用

updateEngine源码:位于目录 external/recovery/update_engine,生成updateEngine二进制bin程序;

rkupdate源码:位于目录 external/rkupdate,生成rkupdate二进制bin程序,解析update.img固件中各个分区数据,并执行对各分区执行升级的关键程序;

external/recovery:位于目录 external/recovery,生成recovery二进制bin程序,其中recovery二进制bin程序部会根据编译配置调用updateEngine或者rkupdate进行升级。

1.2 配置recovery

准备工作:请参考《Rockchip RK3588 - Rockchip Linux SDK编译》下载SDK

1.2.1 设置环境变量

进入buildroot目录,执行如下命令:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo -s source envsetup.sh

选择一个平台的recovery配置,这里选择rockchip_rk3588_recovery,文件rockchip_rk3588_recovery_defconfig位于configs目录下,内容如下:

#include "base/base.config"
#include "base/recovery.config"             // 位于configs/rockchip/base/recovery.config
#include "chips/rk3588_aarch64.config"

执行结果如下:

Top of tree: /work/sambashare/rk3588/armsom/armsom-rk3588-bsp

Pick a board:
.......
57. rockchip_rk3588_recovery
Which would you like? [1]: 57
make: 进入目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot”
  GEN     /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/Makefile
/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/build/parse_defconfig.sh /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/configs/rockchip_rk3588_recovery_defconfig /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/.config.in
Parsing defconfig: /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/configs/rockchip_rk3588_recovery_defconfig
Using configs/rockchip/base/kernel.config as base   #基准配置
Merging configs/rockchip/fs/e2fs.config
Merging configs/rockchip/base/common.config
Merging configs/rockchip/base/base.config
Merging configs/rockchip/base/kernel.config
Merging configs/rockchip/fs/e2fs.config
Merging configs/rockchip/base/common.config
Merging configs/rockchip/fs/vfat.config
Merging configs/rockchip/base/recovery.config   #recovery配置
Merging configs/rockchip/chips/rk3588.config
Value of BR2_ROOTFS_OVERLAY is redefined by configs/rockchip/chips/rk3588.config:
Previous value: BR2_ROOTFS_OVERLAY="board/rockchip/common/base"
Modify value:   BR2_ROOTFS_OVERLAY+="board/rockchip/rk3588/fs-overlay/"
New value:      BR2_ROOTFS_OVERLAY="board/rockchip/common/base board/rockchip/rk3588/fs-overlay/"

Merging configs/rockchip/chips/rk3588_aarch64.config
Merging /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/configs/rockchip_rk3588_recovery_defconfig
#
# merged configuration written to /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/.config.in (needs make)
#
BR2_DEFCONFIG='' KCONFIG_AUTOCONFIG=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/buildroot-config/auto.conf KCONFIG_AUTOHEADER=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/buildroot-config/autoconf.h KCONFIG_TRISTATE=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/buildroot-config/tristate.config BR2_CONFIG=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/.config HOST_GCC_VERSION="9" BASE_DIR=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery SKIP_LEGACY= CUSTOM_KERNEL_VERSION="5.10" BR2_DEFCONFIG=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/configs/rockchip_rk3588_recovery_defconfig /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/buildroot-config/conf --defconfig=/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/.config.in Config.in
#
# configuration written to /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/.config
#
make: 离开目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot”

生成.config配置文件,位于output/rockchip_rk3588_recovery目录。

1.2.2 功能配置

进行recovery配置:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo make menuconfig
1.2.2.1 开启updateEngine升级方式

配置如下选项:

Target packages --->
    Hardware Platforms  --->
    	[*] Rockchip Platform  --->
    		Rockchip BSP packages ---> 
    			[*] Rockchip recovery for linux     # BR2_PACKAGE_RECOVERY
		    	[ ]   No UI for recovery            # BR2_PACKAGE_RECOVERY_NO_UI
		    	[ ]   Linux AB bool control         # BR2_PACKAGE_RECOVERY_BOOTCONTROL 
        		     Linux A/B bringup features. (successful_boot)  ---> 
        		     	 (X) successful_boot        # BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT
        		     	 ( ) retry time             # BR2_PACKAGE_RECOVERY_RETRY       
		    	 	  choice the update bin of recovery. (updateEngine)  --->
		    	 	  	 () rkupdate                # BR2_PACKAGE_RECOVERY_USE_RKUPDATE,如果选中自动选中recovery bin 
		    	 	  	 (*) updateEngine           # BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE、如果选中自动选中updateEngine bin
		    	[*]   recovery bin                  # BR2_PACKAGE_RECOVERY_RECOVERYBIN
		    	-*-   updateEngine bin              # BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN  
		    	[ ]   Enable static                 # BR2_PACKAGE_RECOVERY_STATIC  

注意:配置源码位于package/rockchip/recovery/Config.in

1.2.2.2 开启rkupdate升级方式

配置如下选项:

Target packages --->
    Hardware Platforms  --->
    	[*] Rockchip Platform  --->
    		Rockchip BSP packages ---> 
		    	[*] Rockchip rkupdate for linux     # BR2_PACKAGE_RKUPDATE,rkupdate升级方式
		    	[ ]   update signature firmware (NEW)
		    	[ ]   simulate abnormal power off during updating fw (NEW)
		    	[ ]   Enable static (NEW)

注意:配置源码位于package/rockchip/rkupdate/Config.in

1.2.2.3 保存配置

最终生成配置文件output/rockchip_rk3588_recovery/.config ,包含配置项:

#updateEngine升级方式配置项
BR2_PACKAGE_RECOVERY=y                    #开启升级相关功能
BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT=y    #升级直至成功启动
BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE=y   #使用新升级程序,不配置则默认使用原有升级流程
BR2_PACKAGE_RECOVERY_RECOVERYBIN=y        #开启recovery bin 文件
BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN=y    #编译新升级程序

#rkupdate升级方式配置项
BR2_PACKAGE_RKUPDATE=y

我们需要将以上这些配置追加到configs/rockchip/base/recovery.config文件中(注意:BR2_PACKAGE_RECOVERYBR2_PACKAGE_RKUPDATE默认已经配置),这样默认就会开启这些配置。

注意:对于rockchip_rk3588_defconfig配置文件,是不可以配置这些的。

二、updateEngine in & mk分析

接下来我们来研究《配置recovery》小节中我们添加了如下这些配置项究竟实现了什么功能;

#updateEngine升级方式配置项
BR2_PACKAGE_RECOVERY=y                    #开启升级相关功能
BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT=y    #升级直至成功启动
BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE=y   #使用新升级程序,不配置则默认使用原有升级流程
BR2_PACKAGE_RECOVERY_RECOVERYBIN=y        #开启recovery bin 文件
BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN=y    #编译新升级程序

注意:本节仅仅分析updateEngine升级方式。

假如你对Buildroot框架有深入了解的话,你就很容易明白软件包recoveryRockchipBuildroot扩展的软件包,用来解决系统升级的问题。

有关如何在Buildroot系统自定义软件包可以参考:《Rockchip RK3588 - Rockchip Linux SDK Buildroot文件系统构建》。

在为Buildroot自定义软件包时,在软件包目录下通常为引入.in.mk文件;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ ll package/rockchip/recovery/
-rw-r--r--  1 root root 1596  6月  9 12:58 Config.in
-rw-r--r--  1 root root   81  6月  9 12:58 recovery.hash
-rw-r--r--  1 root root 2400  6月  9 12:58 recovery.mk
-rwxr-xr-x  1 root root  273  6月  9 12:58 S40recovery*

2.1 Config.in

我们首先从Config.in文件入手,该文件实际上就是定义make menuconfig支持的配置选项;

config BR2_PACKAGE_RECOVERY
        bool "Rockchip recovery for linux"
        depends on BR2_TOOLCHAIN_HAS_THREADS # libpthread-stubs 此配置项依赖于BR2_TOOLCHAIN_HAS_THREADS(其开启的情况下才可用)
        select BR2_PACKAGE_LIBDRM    # 如果启动,自动选择启用以下列出的其他软件包和库
        select BR2_PACKAGE_LIBPNG
        select BR2_PACKAGE_LIBPTHREAD_STUBS
        select BR2_PACKAGE_LIBZ
        select BR2_PACKAGE_DOSFSTOOLS
        select BR2_PACKAGE_DOSFSTOOLS_FSCK_FAT
        select BR2_PACKAGE_DOSFSTOOLS_MKFS_FAT
        select BR2_PACKAGE_E2FSPROGS
        select BR2_PACKAGE_E2FSPROGS_RESIZE2FS
        select BR2_PACKAGE_LIBCURL
        select BR2_PACKAGE_OPENSSL
        select BR2_PACKAGE_BZIP2
        help
          Rockchip recovery for linux.

if BR2_PACKAGE_RECOVERY

config BR2_PACKAGE_RECOVERY_NO_UI   # 不开启UI
        bool "No UI for recovery"
        help
          No UI for recovery

config BR2_PACKAGE_RECOVERY_BOOTCONTROL    # Linux A/B模式
        bool "Linux AB bool control"

choice                          # 接下来的配置项将是一个选择项,用户可以从其中选择一个选项
        default BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT  # 默认选项
        prompt "Linux A/B bringup features."
config BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT
        bool "successful_boot"          # 直至成功启动
config BR2_PACKAGE_RECOVERY_RETRY       # 重试次数
        bool "retry time"
endchoice

choice                                  # 升级方式选择
        default BR2_PACKAGE_RECOVERY_USE_RKUPDATE  # 默认使用rkupdate
        prompt "choice the update bin of recovery."

config BR2_PACKAGE_RECOVERY_USE_RKUPDATE         # recovery bin
        bool "rkupdate"
        select BR2_PACKAGE_RECOVERY_RECOVERYBIN

config BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE   # updateEngine bin
        bool "updateEngine"
        select BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN   

endchoice

config BR2_PACKAGE_RECOVERY_RECOVERYBIN           
        bool "recovery bin"
        
config BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN
        bool "updateEngine bin"

config BR2_PACKAGE_RECOVERY_STATIC
        bool "Enable static"
        default y if BR2_STATIC_LIBS
        select BR2_PACKAGE_LIBCURL_STATIC
        select BR2_PACKAGE_LIBDRM_STATIC
        select BR2_PACKAGE_LIBPNG_STATIC

endif

比如我们勾选了updateEngine,由于选中该配置项的同时,会默认选中updateEngine bin配置项;

因此在生成的output/rockchip_rk3588_recovery/.config配置文件会配置:

BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE=y
BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN=y

2.2 recovery.mk

buildroot编译recovery所需要的设置recovery.mk,包括源码位置、安装目录、权限设置等。

################################################################################
#
# Rockchip Recovery For Linux
#
################################################################################

RECOVERY_VERSION = develop
RECOVERY_SITE = $(TOPDIR)/../external/recovery
RECOVERY_SITE_METHOD = local

RECOVERY_LICENSE = ROCKCHIP
RECOVERY_LICENSE_FILES = NOTICE

RECOVERY_CFLAGS = $(TARGET_CFLAGS) -I. \
        -fPIC \
        -lpthread \
        -lcurl \
        -lssl \
        -lcrypto \
        -lbz2

RECOVERY_MAKE_ENV = $(TARGET_MAKE_ENV)

RECOVERY_DEPENDENCIES += libpthread-stubs util-linux libcurl openssl

ifeq ($(BR2_PACKAGE_RECOVERY_NO_UI),y)
RECOVERY_MAKE_ENV += RecoveryNoUi=true
else
RECOVERY_CFLAGS += -lpng -ldrm -lz -lm -I$(STAGING_DIR)/usr/include/libdrm
RECOVERY_DEPENDENCIES += libpng libdrm libzlib
endif

# For static link with libcurl
ifeq ($(BR2_PACKAGE_RTMPDUMP)$(BR2_PACKAGE_RECOVERY_STATIC),yy)
RECOVERY_CFLAGS += -lrtmp
RECOVERY_DEPENDENCIES += rtmpdump
endif

ifeq ($(BR2_PACKAGE_RECOVERY_USE_RKUPDATE),y)
RECOVERY_CFLAGS += -DUSE_RKUPDATE=ON
endif

ifeq ($(BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE),y)
RECOVERY_CFLAGS += -DUSE_UPDATEENGINE=ON
endif

ifeq ($(BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT),y)
RECOVERY_CFLAGS += -DSUCCESSFUL_BOOT=ON
endif

ifeq ($(BR2_PACKAGE_RECOVERY_RETRY),y)
RECOVERY_CFLAGS += -DRETRY_BOOT=ON
endif

ifeq ($(BR2_PACKAGE_RECOVERY_STATIC),y)
RECOVERY_CFLAGS += -static
endif

define RECOVERY_BUILD_CMDS
        $(RECOVERY_MAKE_ENV) $(MAKE) -C $(@D) \
                CC="$(TARGET_CC)" CFLAGS="$(RECOVERY_CFLAGS)"
endef

ifeq ($(BR2_PACKAGE_RECOVERY_RECOVERYBIN),y)
define RECOVERYBIN_INSTALL_TARGET
        $(INSTALL) -D -m 755 $(@D)/recovery $(TARGET_DIR)/usr/bin/

        mkdir -p $(TARGET_DIR)/res/images
        cp $(@D)/res/images/* $(TARGET_DIR)/res/images/
endef

define RECOVERY_INSTALL_INIT_SYSV
        $(INSTALL) -D -m 755 $(RECOVERY_PKGDIR)/S40recovery \
                $(TARGET_DIR)/etc/init.d/S40recovery
endef
endif

ifeq ($(BR2_PACKAGE_RECOVERY_BOOTCONTROL), y)
define BOOTCONTROLBIN_INSTALL_TARGET
        $(INSTALL) -D -m 755 $(@D)/update_engine/S99_bootcontrol \
                $(TARGET_DIR)/etc/init.d/
endef
endif

ifeq ($(BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN),y)
define UPDATEENGINEBIN_INSTALL_TARGET
        $(INSTALL) -D -m 755 $(@D)/updateEngine $(TARGET_DIR)/usr/bin/
endef
endif

define RECOVERY_INSTALL_TARGET_CMDS
        $(RECOVERYBIN_INSTALL_TARGET)
        $(UPDATEENGINEBIN_INSTALL_TARGET)
        $(BOOTCONTROLBIN_INSTALL_TARGET)
endef

$(eval $(generic-package))
2.2.1 约定配置

首先映入眼帘的是一些约定的配置:

  • RECOVERY_VERSION:定义了源码的版本号为develop
  • RECOVERY_SITE:定义了源码下载地址,这里指定为<SDK>/external/recovery
  • RECOVERY_SITE_METHOD:定义了源码下载的方式,该处指定为本地获取(local);
  • RECOVERY_LICENSE:设置许可证类型为ROCKCHIP
  • RECOVERY_LICENSE_FILES:设置许可证文件NOTICE
2.2.2 编译选项

接下来的是编译选项和依赖关系配置:

(1) RECOVERY_CFLAGS:编译选项配置,包括开启线程支持、网络支持、SSL加密等;

RECOVERY_CFLAGS = $(TARGET_CFLAGS) -I. \
        -fPIC \
        -lpthread \
        -lcurl \
        -lssl \
        -lcrypto \
        -lbz2

其中:

  • -I告诉编译器在当前目录中查找头文件;
  • -fPICGCC和其他兼容编译器的选项,表示生成位置无关的代码,用于动态链接库。这是因为动态链接库在编译时无法确定加载地址,因此需要生成位置无关的代码;
  • -lpthread:表示链接pthread库,即POSIX线程库,用于多线程编程;
  • -lcurl:表示链接libcurl库,这是一个支持多协议的URL传输库,用于实现网络操作;
  • -lssl:表示链接OpenSSLSSL库,提供了安全套接层 (SSL) 和传输层安全性 (TLS) 的实现;
  • -lcrypto:表示链接OpenSSL的加密库,提供了各种加密算法的实现,例如哈希函数、对称加密和非对称加密等;
  • -lbz2:表示链接libbz2库,这是用于处理Bzip2压缩格式的库,提供了文件压缩和解压缩的功能。

这些选项通常用于构建需要多线程、网络操作、安全套接层以及数据压缩支持的应用程序或库。在编译时,它们告诉编译器在链接阶段需要使用这些特定的外部库。

(2) RECOVERY_MAKE_ENV:这里设置为TARGET_MAKE_ENVTARGET_MAKE_ENV是在Buildroot中用来定义传递给 make 命令的环境变量;

TARGET_MAKE_ENVpackage/Makefile.in中有定义:

TARGET_MAKE_ENV = PATH=$(BR_PATH)

BR_PATH又定义在Makefile中:

......

# Check if the current Buildroot execution meets all the pre-requisites.
# If they are not met, Buildroot will actually do its job in a sub-make meeting
# its pre-requisites, which are:
#  1- Permissive enough umask:
#       Wrong or too restrictive umask will prevent Buildroot and packages from
#       creating files and directories.
#  2- Absolute canonical CWD (i.e. $(CURDIR)):
#       Otherwise, some packages will use CWD as-is, others will compute its
#       absolute canonical path. This makes harder tracking and fixing host
#       machine path leaks.
#  3- Absolute canonical output location (i.e. $(O)):
#       For the same reason as the one for CWD.

# Remove the trailing '/.' from $(O) as it can be added by the makefile wrapper
# installed in the $(O) directory.
# Also remove the trailing '/' the user can set when on the command line.
override O := $(patsubst %/,%,$(patsubst %.,%,$(O)))
# Make sure $(O) actually exists before calling realpath on it; this is to
# avoid empty CANONICAL_O in case on non-existing entry.
CANONICAL_O := $(shell mkdir -p $(O) >/dev/null 2>&1)$(realpath $(O))


BASE_DIR := $(CANONICAL_O)  # 值为<SDK>/buildroot/output/rockchip_rk3588_recovery
HOST_DIR := $(BASE_DIR)/host

# Quotes are needed for spaces and all in the original PATH content.
BR_PATH = "$(HOST_DIR)/bin:$(HOST_DIR)/sbin:$(PATH)"

实际上HOST_DIR=<SDK>/buildroot/output/rockchip_rk3588_recovery/host(其中<SDK>表示SDK所在全路径),因此不难得到:

TARGET_MAKE_ENV = PATH=<SDK>/buildroot/output/rockchip_rk3588_recovery/host/bin;<SDK>/buildroot/output/rockchip_rk3588_recovery/host/sbin;$(PATH)

其中在<SDK>/buildroot/output/rockchip_rk3588_recovery/host/bin目录下包含了交叉编译工具的。

(3) RECOVERY_DEPENDENCIES:定义依赖项,包括线程库、工具库、网络库和加密库等,这样在编译recovery软件包的时候才会构建和安装依赖包。

2.2.3 条件编译配置

紧跟着的就是一些条件编译配置项;

ifeq ($(BR2_PACKAGE_RECOVERY_NO_UI),y)  
RECOVERY_MAKE_ENV += RecoveryNoUi=true     # 关闭UI         
else           # 开启UI 添加编译选项和依赖项     走这里
RECOVERY_CFLAGS += -lpng -ldrm -lz -lm -I$(STAGING_DIR)/usr/include/libdrm
RECOVERY_DEPENDENCIES += libpng libdrm libzlib
endif

# For static link with libcurl  静态链接,该选项未开启
ifeq ($(BR2_PACKAGE_RTMPDUMP)$(BR2_PACKAGE_RECOVERY_STATIC),yy)
RECOVERY_CFLAGS += -lrtmp
RECOVERY_DEPENDENCIES += rtmpdump
endif

ifeq ($(BR2_PACKAGE_RECOVERY_USE_RKUPDATE),y)   
RECOVERY_CFLAGS += -DUSE_RKUPDATE=ON              # 不走这里
endif

ifeq ($(BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE),y)  
RECOVERY_CFLAGS += -DUSE_UPDATEENGINE=ON          # 走这里   
endif

ifeq ($(BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT),y)  
RECOVERY_CFLAGS += -DSUCCESSFUL_BOOT=ON           # 走这里
endif

ifeq ($(BR2_PACKAGE_RECOVERY_RETRY),y) 
RECOVERY_CFLAGS += -DRETRY_BOOT=ON     # 未开启             
endif

ifeq ($(BR2_PACKAGE_RECOVERY_STATIC),y) 
RECOVERY_CFLAGS += -static              # 静态链接,未开启
endif

比如:

  • 根据BR2_PACKAGE_RECOVERY_NO_UI的设置决定是否启用UI(用户界面),这里我们未设置BR2_PACKAGE_RECOVERY_NO_UI,则在编译选项以及依赖项中添加PNG图像处理、DRMzlib支持;
  • 由于我们开启了BR2_PACKAGE_RECOVERY_USE_RKUPDATE,因此添加编译选项RECOVERY_CFLAGS += -DUSE_RKUPDATE=ON
  • 由于我们开启了BR2_PACKAGE_RECOVERY_SUCCESSFUL_BOOT,因此添加编译选项RECOVERY_CFLAGS += -DSUCCESSFUL_BOOT=ON
2.2.4 CMD命令之BUILD

接着是定义一系列的编译命令,这些_CMD结尾的变量会在buildroot框架编译的时候执行,用于给源码的Makefile传递编译选项和链接选项,调用源码的Makefile

define RECOVERY_BUILD_CMDS
        $(RECOVERY_MAKE_ENV) $(MAKE) -C $(@D) \
                CC="$(TARGET_CC)" CFLAGS="$(RECOVERY_CFLAGS)"
endef

RECOVERY_BUILD_CMDSmake <pkg>build阶段被执行;

$(RECOVERY_MAKE_ENV) $(MAKE) -C $(@D) CC="$(TARGET_CC)" CFLAGS="$(RECOVERY_CFLAGS)"
2.2.4.1 RECOVERY_MAKE_ENV

$(RECOVERY_MAKE_ENV): 通过前面的分析,我们已经了解到该变量被设置为:

RECOVERY_MAKE_ENV = $(TARGET_MAKE_ENV)
2.2.4.2 MAKE

$(MAKE): 定义在package/Makefile.in,表示调用make命令,它会执行Makefile

ifndef MAKE
MAKE := make
endif
2.2.4.3 -C $(@D)

-C $(@D): 这是make的一个选项,-C选项后跟目录,表示到子目录下执行子目录的Makefile,顶层Makefile中的export变量还有make的变量时可以传递给子目录中的Makefile的;$(@D)是一个自动变量,表示当前目标文件所在的目录,也就是recovery源码所在目录(具体路径为<SDK>/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop),我们我们可以知道该条语句实际上就是到recovery源码目录执行make操作,同时并传递了CCCFLAGS变量。

2.2.4.4 TARGET_CC

CC="$(TARGET_CC)"TARGET_CC定义在package/Makefile.in,它指定了gcc编译器的路径;

ifeq ($(BR2_TOOLCHAIN_BUILDROOT),y)    # 使用内部工具链
TARGET_VENDOR = $(call qstrip,$(BR2_TOOLCHAIN_BUILDROOT_VENDOR))  # 走这里,去除BR2_TOOLCHAIN_BUILDROOT_VENDOR值前后空格,设置为buildroot
else
TARGET_VENDOR = buildroot
endif


# Compute GNU_TARGET_NAME
GNU_TARGET_NAME = $(ARCH)-$(TARGET_VENDOR)-$(TARGET_OS)-$(LIBC)$(ABI)  # 延时变量,在使用的时候才确定

# FLAT binary format needs uclinux, except RISC-V 64-bits which needs
# the regular linux name.
ifeq ($(BR2_BINFMT_FLAT):$(BR2_RISCV_64),y:)
TARGET_OS = uclinux
else
TARGET_OS = linux   # 走这里
endif

ifeq ($(BR2_TOOLCHAIN_USES_UCLIBC),y)
LIBC = uclibc
else ifeq ($(BR2_TOOLCHAIN_USES_MUSL),y)
LIBC = musl
else
LIBC = gnu         # 走这里
endif

......

ifeq ($(BR2_TOOLCHAIN_BUILDROOT),y)        # 使用内部工具链
TARGET_CROSS = $(HOST_DIR)/bin/$(GNU_TARGET_NAME)-    # 走这里
else
TARGET_CROSS = $(HOST_DIR)/bin/$(TOOLCHAIN_EXTERNAL_PREFIX)-
endif


TARGET_CC       = $(TARGET_CROSS)gcc

此外,在output/rockchip_rk3588_recovery/.config中配置了:

#
# Target options
#
BR2_aarch64=y
......

#
# Toolchain
#
BR2_TOOLCHAIN=y
# BR2_TOOLCHAIN_PREFER_CLANG is not set
BR2_TOOLCHAIN_USES_GLIBC=y
BR2_TOOLCHAIN_BUILDROOT=y
# BR2_TOOLCHAIN_EXTERNAL is not set

#
# Toolchain Buildroot Options
#
BR2_TOOLCHAIN_BUILDROOT_VENDOR="buildroot"
BR2_TOOLCHAIN_BUILDROOT_GLIBC=y
BR2_TOOLCHAIN_BUILDROOT_LIBC="glibc"

Makefile中配置了:

Strip off the annoying quoting
ARCH := $(call qstrip,$(BR2_ARCH)) # 去除BR2_ARCH值前后空格,设置为aarch64

因此不难得出:

GNU_TARGET_NAME=aarch64-buildroot-linux-gnu
TARGET_CROSS=<SDK>/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-
CC=$(TARGET_CC)=<SDK>/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc

因此,我们得出CC实际上即使指定了gcc编译器路径。

2.2.4.5 RECOVERY_CFLAGS

CFLAGS="$(RECOVERY_CFLAGS)": 这是一个将编译标志传递给make的变量赋值语句;RECOVERY_CFLAGS 是一个变量,它存储着特定于recovery构建的编译标志,这里被设置为:

RECOVERY_CFLAGS = $(TARGET_CFLAGS) -I. \
        -fPIC \
        -lpthread \
        -lcurl \
        -lssl \
        -lcrypto \
        -lbz2 \
        -lpng -ldrm -lz -lm -I$(STAGING_DIR)/usr/include/libdrm \
        -DUSE_UPDATEENGINE=ON \
        -DSUCCESSFUL_BOOT=ON
2.2.4.6 build之后

我们知道buildroot软件包在编译时会将源码拷贝到output/rockchip_rk3588_recovery/build目录下,recovery软件包对应的目录为recovery-develop

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ ll output/rockchip_rk3588_recovery/build/recovery-develop/
-rw-r--r--  1 root root   6236  6月  9 12:58 bootloader.c
-rw-r--r--  1 root root   4543  6月  9 12:58 bootloader.h
-rw-r--r--  1 root root   9296  6月 18 01:48 bootloader.o
-rwxr-xr-x  1 root root   1072  6月 18 01:48 .build.sh*
-rw-r--r--  1 root root    423  6月  9 12:58 ChangeLog.md
-rw-r--r--  1 root root   3882  6月  9 12:58 common.h
-rwxr-xr-x  1 root root    158  6月 18 01:48 .configure.sh*
-rw-r--r--  1 root root   1887  6月  9 12:58 default_recovery_ui.c
-rw-r--r--  1 root root   2616  6月 18 01:48 default_recovery_ui.o
-rwxr-xr-x  1 root root    795  6月 18 01:48 .deploy.sh*
-rw-r--r--  1 root root   9170  6月  9 12:58 encryptedfs_provisioning.c
-rw-r--r--  1 root root   1533  6月  9 12:58 encryptedfs_provisioning.h
-rw-r--r--  1 root root      0  6月 18 01:48 .files-list-host.txt
-rw-r--r--  1 root root      0  6月 18 01:48 .files-list-images.txt
-rw-r--r--  1 root root      0  6月 18 01:48 .files-list-staging.txt
-rw-r--r--  1 root root    364  6月 18 01:48 .files-list-target.txt
-rw-r--r--  1 root root    500  6月 18 01:48 .files-list.txt
drwxr-xr-x  2 root root   4096  6月  9 12:58 hooks/
-rw-r--r--  1 root root    889  6月  9 12:58 install.h
-rwxr-xr-x  1 root root  11556  6月  9 12:58 LICENSE*
-rw-r--r--  1 root root   2250  6月  9 12:58 Makefile
drwxr-xr-x  2 root root   4096  6月 18 01:48 minui/
drwxr-xr-x  2 root root   4096  6月 18 01:48 minzip/
drwxr-xr-x  2 root root   4096  6月 18 01:48 mtdutils/
-rw-r--r--  1 root root  10695  6月  9 12:58 NOTICE
-rw-r--r--  1 root root   1546  6月  9 12:58 noui.c
-rwxr-xr-x  1 root root 126768  6月 18 01:48 recovery*
-rw-r--r--  1 root root     49  6月 18 01:48 recovery_autogenerate.h
-rw-r--r--  1 root root  43859  6月  9 12:58 recovery.c
-rw-r--r--  1 root root 143360  6月 18 01:48 recovery-develop.tar
-rw-r--r--  1 root root  42096  6月 18 01:48 recovery.o
-rw-r--r--  1 root root   3175  6月  9 12:58 recovery_ui.h
-rw-r--r--  1 root root   1269  6月  9 12:58 recovery_version.h
drwxr-xr-x  3 root root   4096  6月  9 12:58 res/
-rw-r--r--  1 root root   6011  6月  9 12:58 rktools.c
-rw-r--r--  1 root root   1308  6月  9 12:58 rktools.h
-rw-r--r--  1 root root  10680  6月 18 01:48 rktools.o
-rw-r--r--  1 root root   6779  6月  9 12:58 rkupdate.c
-rw-r--r--  1 root root   8368  6月 18 01:48 rkupdate.o
-rw-r--r--  1 root root  14072  6月  9 12:58 roots.c
-rw-r--r--  1 root root   1835  6月  9 12:58 roots.h
-rw-r--r--  1 root root  16728  6月 18 01:48 roots.o
-rw-r--r--  1 root root  42629  6月  9 12:58 safe_iop.c
-rw-r--r--  1 root root  24520  6月  9 12:58 safe_iop.h
-rw-r--r--  1 root root   3776  6月 18 01:48 safe_iop.o
-rw-r--r--  1 root root   4274  6月  9 12:58 sdboot.c
-rw-r--r--  1 root root    872  6月  9 12:58 sdboot.h
-rw-r--r--  1 root root   6472  6月 18 01:48 sdboot.o
-rw-r--r--  1 root root      0  6月 18 01:48 .stamp_built
-rw-r--r--  1 root root      0  6月 18 01:48 .stamp_configured
-rw-r--r--  1 root root      0  6月 18 01:48 .stamp_installed
-rw-r--r--  1 root root      0  6月 18 01:48 .stamp_rsynced
-rw-r--r--  1 root root      0  6月 18 01:48 .stamp_target_installed
-rw-r--r--  1 root root   1840  6月  9 12:58 strlcat.c
-rw-r--r--  1 root root   1536  6月 18 01:48 strlcat.o
-rw-r--r--  1 root root   1699  6月  9 12:58 strlcpy.c
-rw-r--r--  1 root root   1320  6月 18 01:48 strlcpy.o
-rwxr-xr-x  1 root root   3506  6月 18 01:48 .target_install.sh*
-rw-r--r--  1 root root  16670  6月  9 12:58 ui.c
-rw-r--r--  1 root root  17904  6月 18 01:48 ui.o
drwxr-xr-x  2 root root   4096  6月 18 01:48 update_engine/
-rwxr-xr-x  1 root root  94688  6月 18 01:48 updateEngine*
-rwxr-xr-x  1 root root    984  6月 18 01:48 .update.sh*
-rwxr-xr-x  1 root root   4731  6月  9 12:58 usbboot.c*
-rwxr-xr-x  1 root root    922  6月  9 12:58 usbboot.h*
-rw-r--r--  1 root root   6768  6月 18 01:48 usbboot.o
2.2.5 CMD命令之INSTALL
ifeq ($(BR2_PACKAGE_RECOVERY_RECOVERYBIN),y) 
define RECOVERYBIN_INSTALL_TARGET           # 用于安装recovery、res/images
        $(INSTALL) -D -m 755 $(@D)/recovery $(TARGET_DIR)/usr/bin/

        mkdir -p $(TARGET_DIR)/res/images
        cp $(@D)/res/images/* $(TARGET_DIR)/res/images/
endef

define RECOVERY_INSTALL_INIT_SYSV         #用于安装S40recovery启动脚本
        $(INSTALL) -D -m 755 $(RECOVERY_PKGDIR)/S40recovery \
                $(TARGET_DIR)/etc/init.d/S40recovery
endef
endif

ifeq ($(BR2_PACKAGE_RECOVERY_BOOTCONTROL), y)  # 不走这里
define BOOTCONTROLBIN_INSTALL_TARGET
        $(INSTALL) -D -m 755 $(@D)/update_engine/S99_bootcontrol \
                $(TARGET_DIR)/etc/init.d/
endef
endif

ifeq ($(BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN),y)
define UPDATEENGINEBIN_INSTALL_TARGET       # 用于安装updateEngine
        $(INSTALL) -D -m 755 $(@D)/updateEngine $(TARGET_DIR)/usr/bin/
endef
endif

define RECOVERY_INSTALL_TARGET_CMDS
        $(RECOVERYBIN_INSTALL_TARGET)        # 安装rkupdate
        $(UPDATEENGINEBIN_INSTALL_TARGET)    # 安装updateEngine  
        $(BOOTCONTROLBIN_INSTALL_TARGET)     # 未定义
endef

RECOVERY_INSTALL_TARGET_CMDSmake <pkg>install阶段被执行;

$(RECOVERYBIN_INSTALL_TARGET)
$(UPDATEENGINEBIN_INSTALL_TARGET)
$(BOOTCONTROLBIN_INSTALL_TARGET)
2.2.5.1 RECOVERYBIN_INSTALL_TARGET

$RECOVERYBIN_INSTALL_TARGET:它包含了一系列安装recovery 模块相关文件的操作,包括安装 recovery 文件到 /usr/bin/ 目录,并复制 res/images/ 目录下的文件到目标系统的相同位置;

  • $(INSTALL) -D -m 755 $(@D)/recovery $(TARGET_DIR)/usr/bin/: 这行命令使用了makefile中定义的INSTALL变量,以及自动变量$(@D)和预定义变量TARGET_DIR。它的作用是将源码recovery编译出来的recovery文件安装到目标系统的/usr/bin/目录下,并设置相应的权限;
  • mkdir -p $(TARGET_DIR)/res/images: 这行命令创建了目标系统下的/res/images目录,其中-p选项表示如果目录不存在则递归创建;
  • cp $(@D)/res/images/* $(TARGET_DIR)/res/images/: 这行命令将源代码中res/images/目录下的所有文件复制到目标系统的相同目录下。

注意:INSTALL定义在package/Makefile.in文件:

INSTALL := $(shell which install || type -p install)

INSTALL其值等于/usr/bin/install/usr/bin/install 是一个命令行工具,用于复制文件并设置文件的权限和属性。它通常用于将文件从一个位置复制到另一个位置,并且可以设置文件的权限、所有者和组;

TARGET_DIR定义在Makefile文件;

BASE_TARGET_DIR := $(BASE_DIR)/target
.......

ifeq ($(BR2_PER_PACKAGE_DIRECTORIES),y)
HOST_DIR = $(if $(PKG),$(PER_PACKAGE_DIR)/$($(PKG)_NAME)/host,$(call qstrip,$(BR2_HOST_DIR)))
TARGET_DIR = $(if $(ROOTFS),$(ROOTFS_$(ROOTFS)_TARGET_DIR),$(if $(PKG),$(PER_PACKAGE_DIR)/$($(PKG)_NAME)/target,$(BASE_TARGET_DIR)))
else           # 走这里
HOST_DIR := $(call qstrip,$(BR2_HOST_DIR)) # BR2_HOST_DIR="$(BASE_DIR)/host"
TARGET_DIR = $(if $(ROOTFS),$(ROOTFS_$(ROOTFS)_TARGET_DIR),$(BASE_TARGET_DIR))$(if $($(PKG)_OEM_INSTALL),/oem) 

ifeq ($(BR2_PACKAGE_OEM),y)    # 不走这里
$(eval $(foreach pkg,$(call qstrip,$(BR2_PACKAGE_OEM_PACKAGES)), \
        $(eval $(call UPPERCASE,$(pkg))_OEM_INSTALL := y)$(sep) \
        $(eval OEM_DEPENDENCIES += $(pkg))$(sep)))
endif
endif

由于未定义ROOTFS变量,所以TARGET_DIR=$(BASE_TARGET_DIR),也就是$(BASE_DIR)/target,而BASE_DIR前面已经介绍过,值为<SDK>/buildroot/output/rockchip_rk3588_recovery,因此:

TARGET_DIR=<SDK>/buildroot/output/rockchip_rk3588_recovery/target

因此如果在recovery软件包编译安装完成后,我们可以在<SDK>/buildroot/output/rockchip_rk3588_recovery/target/usr/bin中看到recovery 可执行文件;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ ll output/rockchip_rk3588_recovery/target/usr/bin/recovery
-rwxr-xr-x 1 root root 103496  6月 18 01:48 output/rockchip_rk3588_recovery/target/usr/bin/recovery*

同时在<SDK>/buildroot/output/rockchip_rk3588_recovery/target/res/images目录下看到一些资源文件:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ ll output/rockchip_rk3588_recovery/target/res/images/
-rwxr-xr-x 1 root root 10398  6月 18 01:48 icon_error.png*
-rwxr-xr-x 1 root root 11493  6月 18 01:48 icon_installing.png*
-rw-r--r-- 1 root root  1919  6月 18 01:48 indeterminate1.png
-rw-r--r-- 1 root root  1912  6月 18 01:48 indeterminate2.png
-rw-r--r-- 1 root root  1917  6月 18 01:48 indeterminate3.png
-rw-r--r-- 1 root root  1912  6月 18 01:48 indeterminate4.png
-rw-r--r-- 1 root root  1902  6月 18 01:48 indeterminate5.png
-rw-r--r-- 1 root root  1914  6月 18 01:48 indeterminate6.png
-rw-r--r-- 1 root root   361  6月 18 01:48 progress_empty.png
-rw-r--r-- 1 root root   286  6月 18 01:48 progress_fill.png
2.2.5.2 UPDATEENGINEBIN_INSTALL_TARGET

$UPDATEENGINEBIN_INSTALL_TARGET:安装 updateEngine 文件到 /usr/bin/ 目录;

$(INSTALL) -D -m 755 $(@D)/updateEngine $(TARGET_DIR)/usr/bin/

这行命令使用了makefile中定义的INSTALL变量,以及自动变量$(@D)和预定义变量TARGET_DIR。它的作用是将源码recovery编译出来的updateEngine 可执行文件安装到目标系统的/usr/bin/目录下,并设置相应的权限;

因此如果在recovery软件包编译安装完成后,我们可以在<SDK>/buildroot/output/rockchip_rk3588_recovery/target/usr/bin中看到updateEngine可执行文件;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$  ll output/rockchip_rk3588_recovery/target/usr/bin/updateEngine
-rwxr-xr-x 1 root root 80544  6月 18 01:48 output/rockchip_rk3588_recovery/target/usr/bin/updateEngine*
2.2.5.3 RECOVERY_INSTALL_INIT_SYSV

Buildroot中,使用 INSTALL_INIT_SYSV 列出了为类似systemV的初始系统(busybox、sysvinit等)、openrcsystemd单元安装初始脚本的操作;

$(INSTALL) -D -m 755 $(RECOVERY_PKGDIR)/S40recovery $(TARGET_DIR)/etc/init.d/S40recovery

这行命令使用了makefile中定义的INSTALL变量,以及自动变量$(@D)和预定义变量TARGET_DIR。它的作用是将S40recovery 脚本安装到目标系统的/etc/init.d目录下,并设置相应的权限;

S40recovery脚本内容如下:

#!/bin/sh
#
# Start Rockchip recovery...
#

case "$1" in
  start)
                echo "starting recovery... "
                touch /dev/.coldboot_done
                /usr/bin/recovery &

        ;;
  stop)
                killall recovery
        ;;
  *)
        echo "Usage: $0 {start|stop}"
        exit 1
        ;;
esac
exit 0

我们知道内核启动的第一个用户级进程是init,那么聪init进程是如何触发/etc/init.d下服务的执行的呢;

(1) init进程执行会读取/etc/inittab文件,然后执行inittab中的sysinit所指的脚本;我们读取/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/etc/inittab文件内容:

# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels.  The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id        == tty to run on, or empty for /dev/console
# runlevels == ignored
# action    == one of sysinit, respawn, askfirst, wait, and once
# process   == program to run

# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
::sysinit:/bin/mount -a
::sysinit:/bin/mkdir -p /run/lock/subsys
::sysinit:/sbin/swapon -a
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS

# Put a getty on the serial port
::respawn:-/bin/sh # ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 # GENERIC_SERIAL

# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r

因此会执行/etc/init.d/rcS这个脚本;

(2) /etc/init.d/rcS脚本会依次执行/etc/init.d目录下以S命令的脚本文件;

#!/bin/sh


# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do

     # Ignore dangling symlinks (if any).
     [ ! -f "$i" ] && continue

     case "$i" in
        *.sh)
            # Source shell script for speed.
            (
                trap - INT QUIT TSTP
                set start
                . $i
            )
            ;;
        *)
            # No sh extension, so fork subprocess.
            $i start
            ;;
    esac
done

我们查看/etc/init.d目录;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target$ ls etc/init.d/
rcK  S00mountall.sh  S02klogd   S05async-commit.sh  S20urandom  S40recovery   S99input-event-daemon
rcS  S01syslogd      S02sysctl  S10udev             S40network  S50usbdevice

其中S40recovery就是编译recovery软件包是安装的脚本。

注意:更多细节可以参考《init进程逐步到/etc/init.d,整体分析Openwrt的软件启动机制》。

由于rockchip_rk3588_recovery_defconfig配置编译出来的recovery.img安装了S40recovery服务,因此不难猜测,当烧录了包含该镜像的系统进入recovery模式将会执行S40recovery脚本进行固件的升级,并输出类似如下的日志:

starting recovery...
LOG_INFO: devices is not MTD.             # /usr/bin/recovery进程输出
LOG_ERROR: unknown volume for path [/userdata/recovery/command]
LOG_ERROR: Can't mount /userdata/recovery/command
LOG_INFO: devices is not MTD.

同样,如果我们的根文件系统rootfs.img如果安装了S40recovery服务,将会导致系统陷入系统固件升级的死循环中,不停的重启。

2.2.6 $(eval $(generic-package))

($eval$(generic-package)) 最核心的就是这个东西了,一定不能够漏了,不然源码不会被编译,这个函数就是把整个.mk构建脚本,通过Buildroot框架的方式,展开到Buildroot/目录下的Makfile中,生成的构建目标。

2.3 Makefile

经过前面对recovery.mk的分析,我们已经知道recovery的源码位于本地<SDK>/external/recovery目录;

zhengyang@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll external/recovery/
-rw-r--r--  1 root root  6236  6月  9 12:58 bootloader.c
-rw-r--r--  1 root root  4543  6月  9 12:58 bootloader.h
-rw-r--r--  1 root root   423  6月  9 12:58 ChangeLog.md
-rw-r--r--  1 root root  3882  6月  9 12:58 common.h
-rw-r--r--  1 root root  1887  6月  9 12:58 default_recovery_ui.c
-rw-r--r--  1 root root  9170  6月  9 12:58 encryptedfs_provisioning.c
-rw-r--r--  1 root root  1533  6月  9 12:58 encryptedfs_provisioning.h
drwxr-xr-x  2 root root  4096  6月  9 12:58 hooks/
-rw-r--r--  1 root root   889  6月  9 12:58 install.h
-rwxr-xr-x  1 root root 11556  6月  9 12:58 LICENSE*
-rw-r--r--  1 root root  2250  6月  9 12:58 Makefile
drwxr-xr-x  2 root root  4096  6月  9 12:58 minui/
drwxr-xr-x  2 root root  4096  6月  9 12:58 minzip/
drwxr-xr-x  2 root root  4096  6月  9 12:58 mtdutils/
-rw-r--r--  1 root root 10695  6月  9 12:58 NOTICE
-rw-r--r--  1 root root  1546  6月  9 12:58 noui.c
-rw-r--r--  1 root root 43859  6月  9 12:58 recovery.c
-rw-r--r--  1 root root  3175  6月  9 12:58 recovery_ui.h
-rw-r--r--  1 root root  1269  6月  9 12:58 recovery_version.h
drwxr-xr-x  3 root root  4096  6月  9 12:58 res/
-rw-r--r--  1 root root  6011  6月  9 12:58 rktools.c
-rw-r--r--  1 root root  1308  6月  9 12:58 rktools.h
-rw-r--r--  1 root root  6779  6月  9 12:58 rkupdate.c
-rw-r--r--  1 root root 14072  6月  9 12:58 roots.c
-rw-r--r--  1 root root  1835  6月  9 12:58 roots.h
-rw-r--r--  1 root root 42629  6月  9 12:58 safe_iop.c
-rw-r--r--  1 root root 24520  6月  9 12:58 safe_iop.h
-rw-r--r--  1 root root  4274  6月  9 12:58 sdboot.c
-rw-r--r--  1 root root   872  6月  9 12:58 sdboot.h
-rw-r--r--  1 root root  1840  6月  9 12:58 strlcat.c
-rw-r--r--  1 root root  1699  6月  9 12:58 strlcpy.c
-rw-r--r--  1 root root 16670  6月  9 12:58 ui.c
drwxr-xr-x  2 root root  4096  6月  9 12:58 update_engine/
-rwxr-xr-x  1 root root  4731  6月  9 12:58 usbboot.c*
-rwxr-xr-x  1 root root   922  6月  9 12:58 usbboot.h*

首先我们需要了解源码的Makefile是怎么样的,其内容如下;

点击查看代码
PROJECT_DIR := $(shell pwd)
CC = gcc
PROM = recovery
UPDATE_ENGINE = updateEngine

all: $(PROM) $(UPDATE_ENGINE)
.PHONY : all

OBJ = recovery.o \
        default_recovery_ui.o \
        rktools.o \
        roots.o \
        bootloader.o \
        safe_iop.o \
        strlcpy.o \
        strlcat.o \
        rkupdate.o \
        sdboot.o \
        usbboot.o \
        mtdutils/mounts.o \
        mtdutils/mtdutils.o \
        mtdutils/rk29.o \
        minzip/DirUtil.o \
        update_engine/log.o

ifdef RecoveryNoUi
OBJ += noui.o
else
OBJ += ui.o\
        minzip/Hash.o \
        minzip/SysUtil.o \
        minzip/Zip.o \
        minui/events.o \
        minui/graphics.o \
        minui/resources.o \
        minui/graphics_drm.o
endif

CFLAGS += -I$(PROJECT_DIR) -I/usr/include -I/usr/include/libdrm/ -lc -DUSE_UPDATEENGINE=ON

ifdef RecoveryNoUi
CFLAGS += -lpthread -lbz2
else
CFLAGS += -lz -lpng -ldrm -lpthread -lcurl -lcrypto -lbz2
endif

UPDATE_ENGINE_OBJ = mtdutils/mounts.o \
        mtdutils/mtdutils.o \
        mtdutils/rk29.o \
        update_engine/rkbootloader.o \
        update_engine/download.o \
        update_engine/flash_image.o \
        update_engine/log.o \
        update_engine/main.o \
        update_engine/md5sum.o \
        update_engine/rkimage.o \
        update_engine/rktools.o \
        update_engine/rkboot.o \
        update_engine/crc.o \
        update_engine/update.o \
        update_engine/do_patch.o

# build in buildroot, it need change work directory
recovery_version:
        cd $(PROJECT_DIR)/../../../../../external/recovery && \
        COMMIT_HASH=$$(git rev-parse --verify --short HEAD) && \
        GIT_COMMIT_TIME=$$(git log -1 --format=%cd --date=format:%y%m%d) && \
        GIT_DIRTY=$$(git diff-index --quiet HEAD -- || echo "-dirty") && \
        commit_info=-g$${COMMIT_HASH}-$${GIT_COMMIT_TIME}$${GIT_DIRTY} && \
        cd $(PROJECT_DIR) && \
        echo "#define GIT_COMMIT_INFO $${commit_info}" > recovery_autogenerate.h

$(PROM): $(OBJ)
        $(CC) -o $(PROM) $(OBJ) $(CFLAGS)

$(UPDATE_ENGINE): $(UPDATE_ENGINE_OBJ)
        $(CC) -o $(UPDATE_ENGINE) $(UPDATE_ENGINE_OBJ) $(CFLAGS)

%.o: %.cpp
        $(CC) -c $< -o $@ $(CFLAGS)

%.o: %.c recovery_version
        $(CC) -c $< -o $@ $(CFLAGS)

clean:
        rm -rf $(OBJ) $(PROM) $(UPDATE_ENGINE_OBJ) $(UPDATE_ENGINE)

install:
        mkdir -p $(DESTDIR)/res/images $(DESTDIR)/usr/bin
        install -D -m 755 $(PROJECT_DIR)/recovery $(DESTDIR)/usr/bin/
        install -D -m 755 $(PROJECT_DIR)/updateEngine $(DESTDIR)/usr/bin/
        cp $(PROJECT_DIR)/res/images/* $(DESTDIR)/res/images/

这段脚本写的比较简单,在编译recovery软件包的build阶段,会执行如下命令;

$(RECOVERY_MAKE_ENV) $(MAKE) -C $(@D) CC="$(TARGET_CC)" CFLAGS="$(RECOVERY_CFLAGS)"

在前文分析,我们已经知道:

  • RECOVERY_MAKE_ENV变量值被设置为$(TARGET_MAKE_ENV),即PATH=环境变量路径
  • MAKE变量值被设置为make
  • CC变量值被设置为$(TARGET_CC)
  • CFLAGS变量值被设置为$(RECOVERY_CFLAGS)

其中:

TARGET_CC = <SDK>/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc

RECOVERY_CFLAGS = $(TARGET_CFLAGS) -I. \
        -fPIC \
        -lpthread \
        -lcurl \
        -lssl \
        -lcrypto \
        -lbz2 \
        -lpng -ldrm -lz -lm -I$(STAGING_DIR)/usr/include/libdrm \
        -DUSE_UPDATEENGINE=ON \
        -DSUCCESSFUL_BOOT=ON
2.3.1 变量定义

脚本开始定义了一些变量:

  • PROJECT_DIR:既时变量,值在定义时就确定,为当前工作路径;
  • CC:延时变量,值在使用的时候才确定,由于我们在RECOVERY_BUILD_CMDS编译命令中指定了CC,因此该值会被设置为$(TARGET_CC)
  • PROM:延时变量,值在使用的时候才确定,这里设置为recovery
  • UPDATE_ENGINE:延时变量,值在使用的时候才确定,这里设置为updateEngine
2.3.2 目标all

第一个目标为伪目标all,其依赖recoveryupdateEngine

all: $(PROM) $(UPDATE_ENGINE)
.PHONY : all

在执行make命令时,如果未指定目标,默认执行第一个目标,即all

接下来我们需要寻找all依赖recoveryupdateEngine的生成规则。

2.3.3 目标recovery
OBJ = recovery.o \
        default_recovery_ui.o \
        rktools.o \
        roots.o \
        bootloader.o \
        safe_iop.o \
        strlcpy.o \
        strlcat.o \
        rkupdate.o \
        sdboot.o \
        usbboot.o \
        mtdutils/mounts.o \
        mtdutils/mtdutils.o \
        mtdutils/rk29.o \
        minzip/DirUtil.o \
        update_engine/log.o

ifdef RecoveryNoUi  
OBJ += noui.o      # 不走这里
else
OBJ += ui.o\       # 走这里  
        minzip/Hash.o \
        minzip/SysUtil.o \
        minzip/Zip.o \
        minui/events.o \
        minui/graphics.o \
        minui/resources.o \
        minui/graphics_drm.o
endif

CFLAGS += -I$(PROJECT_DIR) -I/usr/include -I/usr/include/libdrm/ -lc -DUSE_UPDATEENGINE=ON

ifdef RecoveryNoUi
CFLAGS += -lpthread -lbz2        # 不走这里
else
CFLAGS += -lz -lpng -ldrm -lpthread -lcurl -lcrypto -lbz2   # 走这里  
endif

$(PROM): $(OBJ)
        $(CC) -o $(PROM) $(OBJ) $(CFLAGS)

可以看到目标recovery是由若干个.o文件通过aarch64-buildroot-linux-gnu-gcc编译器链接生成的可执行文件。

.o文件实际上是由.c文件通过aarch64-buildroot-linux-gnu-gcc编译器编译生成的。

# build in buildroot, it need change work directory
recovery_version:
        cd $(PROJECT_DIR)/../../../../../external/recovery && \
        COMMIT_HASH=$$(git rev-parse --verify --short HEAD) && \
        GIT_COMMIT_TIME=$$(git log -1 --format=%cd --date=format:%y%m%d) && \
        GIT_DIRTY=$$(git diff-index --quiet HEAD -- || echo "-dirty") && \
        commit_info=-g$${COMMIT_HASH}-$${GIT_COMMIT_TIME}$${GIT_DIRTY} && \
        cd $(PROJECT_DIR) && \
        echo "#define GIT_COMMIT_INFO $${commit_info}" > recovery_autogenerate.h

%.o: %.cpp
        $(CC) -c $< -o $@ $(CFLAGS)

%.o: %.c recovery_version
        $(CC) -c $< -o $@ $(CFLAGS)

其中:%.o 表示所有以.o结尾的文件作为目标文件,%.cpp 表示所有以.c结尾的文件作为依赖文件。

2.3.4 目标updateEngine
UPDATE_ENGINE_OBJ = mtdutils/mounts.o \
        mtdutils/mtdutils.o \
        mtdutils/rk29.o \
        update_engine/rkbootloader.o \
        update_engine/download.o \
        update_engine/flash_image.o \
        update_engine/log.o \
        update_engine/main.o \
        update_engine/md5sum.o \
        update_engine/rkimage.o \
        update_engine/rktools.o \
        update_engine/rkboot.o \
        update_engine/crc.o \
        update_engine/update.o \
        update_engine/do_patch.o

$(UPDATE_ENGINE): $(UPDATE_ENGINE_OBJ)
        $(CC) -o $(UPDATE_ENGINE) $(UPDATE_ENGINE_OBJ) $(CFLAGS)

recovery,可以看到目标updateEngine是由若干个.o文件通过aarch64-buildroot-linux-gnu-gcc编译器链接生成的可执行文件。

.o文件实际上是由.c文件通过aarch64-buildroot-linux-gnu-gcc编译器编译生成的。

2.3.5 目标clean

伪目标clean用于执行清理工作;

clean:
        rm -rf $(OBJ) $(PROM) $(UPDATE_ENGINE_OBJ) $(UPDATE_ENGINE)
2.3.6 目标install

伪目标install用于执行安装工作;

install:
        mkdir -p $(DESTDIR)/res/images $(DESTDIR)/usr/bin
        install -D -m 755 $(PROJECT_DIR)/recovery $(DESTDIR)/usr/bin/
        install -D -m 755 $(PROJECT_DIR)/updateEngine $(DESTDIR)/usr/bin/
        cp $(PROJECT_DIR)/res/images/* $(DESTDIR)/res/images/

2.4 编译

在《Rockchip RK3588 - Rockchip Linux SDK Buildroot文件系统构建》中介绍过buildroot编译命令;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo make -j8

或者使用如下命令单独编译recovery软件包:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo make recovery-dirclean
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo make recovery

其中recovery编译日志如下:

点击查看代码
>>> recovery develop Syncing from source dir /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/../external/recovery
rsync -au --chmod=u=rwX,go=rX  --exclude .svn --exclude .git --exclude .hg --exclude .bzr --exclude CVS /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/../external/recovery/ /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop
>>> recovery develop Configuring
>>> recovery develop Building
PATH="/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin" /usr/bin/make  -C /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop CC="/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc" CFLAGS="-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -Os -g0 -D_FORTIFY_SOURCE=1 -I. -fPIC -lpthread -lcurl -lssl -lcrypto -lbz2 -lpng -ldrm -lz -lm -I/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/usr/include/libdrm -DUSE_UPDATEENGINE=ON -DSUCCESSFUL_BOOT=ON"
make[1]: 进入目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop”
cd /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/../../../../../external/recovery && \
COMMIT_HASH=$(git rev-parse --verify --short HEAD) && \
GIT_COMMIT_TIME=$(git log -1 --format=%cd --date=format:%y%m%d) && \
GIT_DIRTY=$(git diff-index --quiet HEAD -- || echo "-dirty") && \
commit_info=-g${COMMIT_HASH}-${GIT_COMMIT_TIME}${GIT_DIRTY} && \
cd /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop && \
echo "#define GIT_COMMIT_INFO ${commit_info}" > recovery_autogenerate.h
/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc -c recovery.c -o recovery.o -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -Os -g0 -D_FORTIFY_SOURCE=1 -I. -fPIC -lpthread -lcurl -lssl -lcrypto -lbz2 -lpng -ldrm -lz -lm -I/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/usr/include/libdrm -DUSE_UPDATEENGINE=ON -DSUCCESSFUL_BOOT=ON
......
/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc -o updateEngine mtdutils/mounts.o mtdutils/mtdutils.o mtdutils/rk29.o update_engine/rkbootloader.o update_engine/download.o update_engine/flash_image.o update_engine/log.o update_engine/main.o update_engine/md5sum.o update_engine/rkimage.o update_engine/rktools.o update_engine/rkboot.o update_engine/crc.o update_engine/update.o update_engine/do_patch.o -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -Os -g0 -D_FORTIFY_SOURCE=1 -I. -fPIC -lpthread -lcurl -lssl -lcrypto -lbz2 -lpng -ldrm -lz -lm -I/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/usr/include/libdrm -DUSE_UPDATEENGINE=ON -DSUCCESSFUL_BOOT=ON
make[1]: 离开目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop”
>>> recovery develop Installing to target
/usr/bin/install -D -m 755 /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/recovery /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/usr/bin/
mkdir -p /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/res/images
cp /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/res/images/* /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/res/images/
/usr/bin/install -D -m 755 /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/updateEngine /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/usr/bin/
/usr/bin/install -D -m 755 package/rockchip/recovery//S40recovery /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/etc/init.d/S40recovery

在日志中输出了recovery软件包构建的各个阶段的信息,可以验证我们之前的分析是否正确。比如编译命令:

PATH="/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin" /usr/bin/make  -C /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop CC="/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc" CFLAGS="-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64  -Os -g0 -D_FORTIFY_SOURCE=1 -I. -fPIC -lpthread -lcurl -lssl -lcrypto -lbz2 -lpng -ldrm -lz -lm -I/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/usr/include/libdrm -DUSE_UPDATEENGINE=ON -DSUCCESSFUL_BOOT=ON"

三、updateEngine源码分析

由于updateEngine可执行文件是由 update_engine目录以及mtdutils目录下若干.c文件编译而成,程序的入口为update_engine/main.cpp文件。

3.1 main.c

main.c代码比较长,如下所示:

点击查看代码
static const struct option engine_options[] = {
    { "update", optional_argument, NULL, 'u' },
    { "version_url", required_argument, NULL, 'v' + 'u' },
    { "image_url", required_argument, NULL, 'i' + 'u'},
    { "check", required_argument, NULL, 'c' },
    { "misc", required_argument, NULL, 'm' },
    { "misc_custom", required_argument, NULL, 'd' },
    { "partition", required_argument, NULL, 'p' },
    { "reboot", no_argument, NULL, 'r' },
    { "help", no_argument, NULL, 'h' },
    { "pipefd", required_argument, NULL, 'p' + 'f' },
    { "savepath", required_argument, NULL, 's'},
    { "version", no_argument, NULL, 'v'},
    { "rkdebug", no_argument, NULL, 'k'},
    { "ui_rotation", required_argument, NULL, 'o'},
    { NULL, 0, NULL, 0 },
};

int main(int argc, char *argv[])
{
    LOGI("*** update_engine: %s ***.\n", update_engine_version);
    int arg;
    char *image_url = NULL;
    char *version_url = NULL;
    char *misc_func = NULL;
    char *save_path = NULL;
    char *partition = NULL;
    char *custom_define = NULL;
    bool is_update = false;
    bool is_reboot = false;
    int pipefd = -1;

    while ((arg = getopt_long(argc, argv, "", engine_options, NULL)) != -1) {
        switch (arg) {
        case 'u': is_update = true; if (optarg != NULL) is_sdboot = true; continue;
        case 'c': version_url = optarg; continue;
        case 'm': misc_func = optarg; continue;
        case 'p': partition = optarg; continue;
        case 's': save_path = optarg; continue;
        case 'r': is_reboot = true; continue;
        case 'v' + 'u': version_url = optarg; continue;
        case 'i' + 'u': image_url = optarg; continue;
        case 'p' + 'f': pipefd = atoi(optarg); continue;
        case 'h': display(); break;
        case 'd': custom_define = optarg; continue;
        case 'v': LOGI("*** update_engine: %s ***.\n", update_engine_version); break;
        case 'k': rkdebug = true; break;
        case 'o': ui_rotation = optarg; break;
        case '?':
            LOGE("Invalid command argument\n");
            continue;
        }
    }

    if (pipefd != -1) {
        cmd_pipe = fdopen(pipefd, "wb");
        setlinebuf(cmd_pipe);
    }

    if (is_update) {
        if (optarg && (strstr(optarg, "usb") != NULL || strstr(optarg, "udisk") != NULL)) {
            is_usbboot = true;
            is_sdboot = false;
            LOGI("*** will upgrade firmware from udisk ***");
        }

        if (is_sdboot || is_usbboot) {
            int res = 0x3FFC00; //默认升级的分区
            LOGI("%s-%d: is %s update.\n", __func__, __LINE__, is_usbboot ? "usbboot" : "sdboot");
            if (partition != NULL) {
                res = strtol(partition + 2, NULL, 16);
            }
            RK_ota_set_url(image_url, save_path);
            if ( !RK_ota_set_partition(res) ) {
                LOGE("ota file is error.\n");
                return -1;
            }

            if (version_url != NULL) {
                if (!RK_ota_check_version(version_url) ) {
                    LOGE("you shouldn't update the device.\n");
                    return -1;
                }
            }

            RK_ota_start(handle_upgrade_callback, handle_print_callback);
        } else {
            LOGI("%s-%d: is ota update\n", __func__, __LINE__);
            if (MiscUpdate(image_url, partition, save_path) == 0) {
                m_status = RK_UPGRADE_FINISHED;
            }
        }
    } else if (misc_func != NULL) {
        if (strcmp(misc_func, "now") == 0) {
            if (setSlotSucceed() == 0) {
                m_status = RK_UPGRADE_FINISHED;
            }
        } else if (strcmp(misc_func, "other") == 0) {
            if (setSlotActivity() == 0) {
                m_status = RK_UPGRADE_FINISHED;
            }
        } else if (strcmp(misc_func, "wipe_userdata") == 0) {
            if (wipe_userdata(0) == 0) {
                m_status = RK_UPGRADE_FINISHED;
            }
        } else if (strcmp(misc_func, "update") == 0) {
            if (MiscUpdate(image_url, partition, save_path) == 0) {
                m_status = RK_UPGRADE_FINISHED;
            }
        } else if (strcmp(misc_func, "display") == 0) {
            miscDisplay();
        } else {
            LOGE("unknow misc cmdline : %s.\n", misc_func);
            return 0;
        }
    } else if (custom_define != NULL) {
        if (strcmp(custom_define, "read") == 0) {
            if (readCustomMiscCmdline())
                return -1;
        } else if (strcmp(custom_define, "write") == 0) {
            if (writeCustomMiscCmdline())
                return -1;
        } else if (strcmp(custom_define, "clean") == 0) {
            if (cleanCustomMiscCmdline())
                return -1;
        } else {
            LOGI("Not supported\n");
            return m_status;
        }
        m_status = RK_UPGRADE_FINISHED;
    }

    if (is_reboot && (m_status == RK_UPGRADE_FINISHED)) {
        sync();
        //reboot(RB_AUTOBOOT);
        if (system(" echo b > /proc/sysrq-trigger ") == -1)
            return -1;
    }

    return m_status;
}
3.1.1 解析命令行参数

由于updateEngine是通过传参来实现固件升级的,因此不难猜出updateEngine是基于命令行参数的程序。

main函数首先输出当前update_engine版本信息,比如:

LOG_INFO: *** update_engine: V1.0.1-g28f720bc5-240524-dirty ***

接着使用了getopt_long函数来解析命令行参数,并根据参数的不同执行相应的操作。

其中-h,调用display输出支持的命令信息:

void display(void)
{
    LOGI("--misc=now             Linux A/B mode: Setting the current partition to bootable.\n");
    LOGI("--misc=other           Linux A/B mode: Setting another partition to bootable.\n");
    LOGI("--misc=update          Recovery mode: Setting the partition to be upgraded.\n");
    LOGI("--misc=display         Display misc info.\n");
    LOGI("--misc=wipe_userdata   Format data partition.\n");
    LOGI("--misc_custom= < op >  Operation on misc for custom cmdline");
    LOGI("        op:     read   Read custom cmdline to /tmp/custom_cmdline");
    LOGI("                write  Write /tmp/custom_cmdline to custom area");
    LOGI("                clean  clean custom area");
    LOGI("--update               Upgrade mode.\n");
    LOGI("--partition=0x3FFC00   Set the partition to be upgraded.(NOTICE: OTA not support upgrade loader and parameter)\n");
    LOGI("                       0x3FFC00: 0011 1111 1111 1100 0000 0000.\n");
    LOGI("                                 uboot trust boot recovery rootfs oem\n");
    LOGI("                                 uboot_a uboot_b boot_a boot_b system_a system_b.\n");
    LOGI("                       100000000000000000000000: Upgrade loader\n");
    LOGI("                       010000000000000000000000: Upgrade parameter\n");
    LOGI("                       001000000000000000000000: Upgrade uboot\n");
    LOGI("                       000100000000000000000000: Upgrade trust\n");
    LOGI("                       000010000000000000000000: Upgrade boot\n");
    LOGI("                       000001000000000000000000: Upgrade recovery\n");
    LOGI("                       000000100000000000000000: Upgrade rootfs\n");
    LOGI("                       000000010000000000000000: Upgrade oem\n");
    LOGI("                       000000001000000000000000: Upgrade uboot_a\n");
    LOGI("                       000000000100000000000000: Upgrade uboot_b\n");
    LOGI("                       000000000010000000000000: Upgrade boot_a\n");
    LOGI("                       000000000001000000000000: Upgrade boot_b\n");
    LOGI("                       000000000000100000000000: Upgrade system_a\n");
    LOGI("                       000000000000010000000000: Upgrade system_b\n");
    LOGI("                       000000000000001000000000: Upgrade misc\n");
    LOGI("                       000000000000000100000000: Upgrade userdata\n");
    LOGI("--reboot               Restart the machine at the end of the program.\n");
    LOGI("--version_url=url      The path to the file of version.\n");
    LOGI("--image_url=url        Path to upgrade firmware.\n");
    LOGI("--savepath=url         save the update.img to url.\n");
    LOGI("--version              the version of updateEngine\n");
    LOGI("--rkdebug              Log output to serial port\n");
    LOGI("--ui_rotation          UI rotation,has 4 angles(0-3).\n");
}

我们在使用updateEngine中最常使用的命令格式如下:

updateEngine --image_url=http://192.168.0.200:8080/recovery/update.img --misc=update --savepath=/userdata/update.img --reboot

因此我们就以该命令为例,进行分析;

  • --image_url:进入case 'i' + 'u'分支,因此设置变量image_url='http://192.168.0.200:8080/recovery/update.img'
  • --misc:进入case 'm'分支,因此设置变量misc_func='update'
  • --savepath:进入case 's'分支,因此设置变量save_path='/userdata/update.img'
  • --reboot:进入case 'r',因此设备变量is_reboot=true
3.1.2 misc分支

由于设置了misc,所以进入misc_func相关分支,并执行MiscUpdate函数;

if (strcmp(misc_func, "now") == 0) {
	if (setSlotSucceed() == 0) {
		m_status = RK_UPGRADE_FINISHED;
	}
} else if (strcmp(misc_func, "other") == 0) {
	if (setSlotActivity() == 0) {
		m_status = RK_UPGRADE_FINISHED;
	}
} else if (strcmp(misc_func, "wipe_userdata") == 0) {
	if (wipe_userdata(0) == 0) {
		m_status = RK_UPGRADE_FINISHED;
	}
} else if (strcmp(misc_func, "update") == 0) {      // 走这里
	if (MiscUpdate(image_url, partition, save_path) == 0) {
		m_status = RK_UPGRADE_FINISHED;
	}
} else if (strcmp(misc_func, "display") == 0) {
	miscDisplay();
} else {
	LOGE("unknow misc cmdline : %s.\n", misc_func);
	return 0;
}
3.1.3 MiscUpdate

MiscUpdate函数同样位于main.c文件,函数有三个参数,依次传入:

  • url:即--image_url指定的值;
  • update_partition:即--partition指定的值;
  • save_path:即savepath指定的值;

函数原型如下:

void handle_upgrade_callback(void *user_data, RK_Upgrade_Status_t status)
{
    if (status == RK_UPGRADE_FINISHED) {
        LOGI("rk ota success.\n");
        setSlotActivity();
    }
    m_status = status;
    LOGI("rk m_status = %d.\n", m_status);
}

void handle_print_callback(char *szPrompt)
{
    if (cmd_pipe != NULL) {
        fprintf(cmd_pipe, "ui_print %s\n", szPrompt);
    }
}

static int MiscUpdate(char *url,  char *update_partition, char *save_path)
{
    int partition;
    char *savepath = NULL;
    int slot = -1;
    if (url == NULL) {
        // 如果没有传入URL,则可以去查找是否有存在
        LOGE("MiscUpdate URL must be set.\n");
        return -1;
    }
    // 从cmdline获取从哪里引导
    slot = getCurrentSlot();    
    // 没有传入要升级的分区,默认升级        
    if (update_partition == NULL) {    
         // recovery模式
        if (slot == -1) {  
            // recovery mdoe
            // u-boot/trust/boot/recovery/boot/rootfs/oem
            partition = 0X3F0000;  // 0011 1111 0000 0000 0000 0000
        } else {            // linux A/B模式
            // A/B mdoe
            // uboot_a/uboot_b/boot_a/boot_b/system_a/system_b
            partition = 0XFC00;
        }
    } else {
        partition = strtol(update_partition + 2, NULL, 16);
    }
	
    // 可以看到如果save_path为空,其值设置为url
    if (save_path == NULL) {
        savepath = url;
    } else {
        savepath = save_path;
    }

    // 保存升级固件源路径和目标路径到全局变量_url和_save_path
    RK_ota_set_url(url, savepath);
    LOGI("url = %s.\n", url);
    LOGI("[%s:%d] save path: %s\n", __func__, __LINE__, savepath);
    // If it's recovery mode, upgrade recovery in normal system.  进入这里
    if (slot == -1 && !is_sdboot && !is_usbboot) { 
        // 在normal模式,升级recovery分区
        if (partition & 0x040000) {  
            LOGI("update recovery in normal system.\n");
            partition = partition & 0xFBFFFF;

            // upgrade recoery in normal system  对需要升级的分区打标记(parameter、recovery)
            if (!RK_ota_set_partition(0x040000)) {
                LOGE("ota file is error.\n");
                return -1;
            }
            // 进行分区升级
            RK_ota_start(handle_upgrade_callback, handle_print_callback);
            if (m_status != RK_UPGRADE_FINISHED) {
                return -1;
            }

            //写MISC
            struct bootloader_message msg;
            memset(&msg, 0, sizeof(msg));
            char recovery_str[] = "recovery\n--update_package=";
            strcpy(msg.command, "boot-recovery");
            sprintf(msg.recovery, "%s%s\n", recovery_str, savepath);
            memcpy(msg.needupdate, &partition, 4);
            if (true == rkdebug) {
                strcat(msg.recovery, "--rkdebug\n");
            }
            if (NULL != ui_rotation) {
                strcat(msg.recovery, "--ui_rotation=");
                strcat(msg.recovery, ui_rotation);
                msg.recovery[strlen(msg.recovery)] = '\n';
            }
            set_bootloader_message(&msg);
            return 0;
        }
    } else if (slot == 0) {
        LOGI("In A system, now upgrade B system.\n");
        partition = partition & 0x155ff;
    } else if (slot == 1) {
        LOGI("In B system, now upgrade A system.\n");
        partition = partition & 0x1a9ff;
    }

    // 升级其它分区
    if (!RK_ota_set_partition(partition)) {
        LOGE("ota file is error.\n");
        return -1;
    }
    RK_ota_start(handle_upgrade_callback, handle_print_callback);
    if (m_status != RK_UPGRADE_FINISHED) {
        return -1;
    }

    return 0;
}

如果我们执行的是如下命令:

updateEngine --image_url=http://192.168.0.200:8080/recovery/update.img --misc=update --savepath=/userdata/update.img --reboot

由于未指定升级分区,并且使用的是recovery模式,因此升级顺序如下;

(1) 在normal模式,升级recovery分区:依次调用RK_ota_set_partitionRK_ota_start方法;

调用RK_ota_set_partition对需要升级的分区打标记,由于只升级recovery分区,所以函数入参为0x40000;该函数:

  • 会对命令数组update_cmd中的parameter/recovery命令的need_update 成员打标记,即设置为true
  • 设置parameter命令的dest_path/dev/block/by-name/gpt
  • 设置recovery命令的dest_path/dev/block/by-name/recovery

调用RK_ota_start方法,对recovery分区进行升级,函数的入参为handle_upgrade_callback, handle_print_callback,有关该函数的细节我们在后面单独介绍。

3.2 rktools.c

3.2.1 getCurrentSlot

getCurrentSlot函数用于判断Linux的启动模式,Recovery模式或者Linux A/B模式;

/**
 * 从cmdline 获取从哪里引导
 * 返回值:
 *     0: a分区
 *     1: b分区
 *    -1: recovery 模式
 */
int getCurrentSlot()
{
    char cmdline[CMDLINE_LENGTH];
    int fd = open("/proc/cmdline", O_RDONLY);
    if (read(fd, (char*)cmdline, CMDLINE_LENGTH) < 1) {
        close(fd);
        return -1;
    }
    close(fd);
    char *slot = strstr(cmdline, "android_slotsufix");
    if (slot == NULL) slot = strstr(cmdline, "androidboot.slot_suffix");
    if (slot != NULL) {
        slot = strstr(slot, "=");
        if (slot != NULL && *(++slot) == '_') {
            slot += 1;
            LOGI("Current Mode is '%c' system.\n", (*slot == 'a') ? 'A' : 'B');
            if ((*slot) == 'a') {
                return 0;
            } else if ((*slot) == 'b') {
                return 1;
            }
        }
    }
    LOGI("Current Mode is recovery.\n");
    return -1;
}

通过读取命令行参数是否拥有android_slotsufix或者slot_suffix参数,以及参数内容来区分哪种启动模式。

此外,该函数会输出当前的启动模式信息,比如:

LOG_INFO: Current Mode is recovery.
3.2.2 isMtdDevice

通过读取命令行参数storagemedia,判断启动设备是不是MTD设备;

  • 如果未指定命令行参数storagemedia,默认不是MTD设备;
  • 如果命令行指定了storagemedia参数;
    • 判定参数是否包含mtd,如果是则认为是MTD设备;
    • 判断参数是否包含sd,并且存在/proc/mtd文件,如果文件中写有mtd分区信息,则认为是MTD设备;

具体源码如下:

#define MTD_PATH "/proc/mtd"

//判断是MTD还是block 设备
bool isMtdDevice()
{
    char param[2048];
    int fd, ret;
    char *s = NULL;
    fd = open("/proc/cmdline", O_RDONLY);
    ret = read(fd, (char*)param, 2048);
    close(fd);
    s = strstr(param, "storagemedia");
    if (s == NULL) {
        LOGI("no found storagemedia in cmdline, default is not MTD.\n");
        return false;
    } else {
        // 在字符串s中查找第一个出现的 = 符号,并返回该符号及其后面的字符串的指针
        s = strstr(s, "=");
        if (s == NULL) {
            LOGI("no found storagemedia in cmdline, default is not MTD.\n");
            return false;
        }
		// 跳过=符号本身
        s++;
        // 跳过可能存在的空格,直到遇到非空格字符为止
        while (*s == ' ') {
            s++;
        }
		// 如果字符串s开头的三个字符是mtd
        if (strncmp(s, "mtd", 3) == 0 ) {
            LOGI("Now is MTD.\n");
            return true;
        } else if (strncmp(s, "sd", 2) == 0) { // 如果字符串s开头的两个字符是sd
            LOGI("Now is SD.\n");
            // 判断是否可以访问/proc/mtd,如果path存在且mode设置为F_OK,则返回0
            if ( !access(MTD_PATH, F_OK) ) {
                fd = open(MTD_PATH, O_RDONLY);
                ret = read(fd, (char*)param, 2048);
                close(fd);
				// 判断是否存在mtd分区 比如:mtd0: 00040000 00020000 "u-boot"
                s = strstr(param, "mtd");
                if (s == NULL) {
                    LOGI("no found mtd.\n");
                    return false;
                }
                LOGI("Now is MTD.\n");
                return true;
            }
        }
    }
    LOGI("Current device is not MTD\n");
    return false;
}

对于ArmSoM-Sige7开发板而言,最常用的就是SD卡/eMMC启动,并没有使用过任何Flash启动,因此函数这里使用返回false

由于MTD设备更多内容可以参考《linux驱动移植-Nand Flash ONFI标准和MTD子系统》。

3.3 update.c

3.3.1 RK_ota_set_url

RK_ota_set_url用于保存升级固件源路径和目标路径到全局变量_url_save_path

#define DEFAULT_DOWNLOAD_PATH "/tmp/update.img"
static char * _url = NULL;
static char const * _save_path = NULL;
static char _url_dir[128];

void RK_ota_set_url(char *url, char *savepath)
{
    LOGI("start RK_ota_url url [%s] save path [%s].\n", url, savepath);
    if ( url == NULL ) {
        LOGE("RK_ota_set_url : url is NULL.\n");
        return ;
    }
    if (savepath == NULL) {
        _save_path = DEFAULT_DOWNLOAD_PATH;
    } else {
        _save_path = savepath;
    }
    LOGI("save image to %s.\n", _save_path);
    _url = url;

    sprintf(_url_dir, "%s", _url);
    dirname(_url_dir);
}

该函数会输出url以及save path信息,比如:

LOG_INFO: start RK_ota_url url [http://192.168.0.200:8080/recovery/update.img] save path [/userdata/update.img].
LOG_INFO: save image to /userdata/update.img.
3.3.2 RK_ota_set_partition

RK_ota_set_partition函数如下;

点击查看代码
typedef int (*update_func)(char *src_path, void* pupdate_cmd);
typedef struct {
    char name[32];                // 升级命令名称
    bool need_update;             // 是否执行该命令升级
    bool is_ab;                   // Linux A/B模式
    long long size;
    long long offset;
    long long flash_offset;  
    char dest_path[100];          // 目标分区节点名称
    bool skip_verify;             // 跳过校验 
    update_func cmd;              // 执行升级的函数    
} UPDATE_CMD, *PUPDATE_CMD;

// 升级命令数组,分别用于升级不同的分区
UPDATE_CMD update_cmd[] = {
    {"bootloader", false, false, 0, 0, 0, "", false, flash_bootloader},
    {"parameter", false, false, 0, 0, 0, "", false, flash_parameter},
    {"uboot", false, false, 0, 0, 0, "", false, flash_normal},
    {"trust", false, false, 0, 0, 0, "", false, flash_normal},
    {"boot", false, true, 0, 0, 0, "", false, flash_normal},
    {"recovery", false, false, 0, 0, 0, "", false, flash_normal},
    {"rootfs", false, true, 0, 0, 0, "", false, flash_normal},
    {"oem", false, false, 0, 0, 0, "", false, flash_normal},
    {"uboot_a", false, false, 0, 0, 0, "", false, flash_normal},
    {"uboot_b", false, false, 0, 0, 0, "", false, flash_normal},
    {"boot_a", false, false, 0, 0, 0, "", false, flash_normal},
    {"boot_b", false, false, 0, 0, 0, "", false, flash_normal},
    {"system_a", false, false, 0, 0, 0, "", false, flash_normal},
    {"system_b", false, false, 0, 0, 0, "", false, flash_normal},
    {"misc", false, false, 0, 0, 0, "", false, flash_normal},
    {"userdata", false, false, 0, 0, 0, "", false, flash_normal},
};

bool RK_ota_set_partition(int partition)
{
    //000000000000000000000000: 没有升级分区          0x000000
    //100000000000000000000000: 升级loader分区       0x800000
    //010000000000000000000000: 升级parameter分区    0x400000
    //001000000000000000000000: 升级uboot分区        0x200000
    //000100000000000000000000: 升级trust分区        0x100000
    //000010000000000000000000: 升级boot分区          0x80000
    //000001000000000000000000: 升级recovery分区      0x40000
    //000000100000000000000000: 升级rootfs分区        0x20000
    //000000010000000000000000: 升级oem分区           0x10000
    //000000001000000000000000: 升级uboot_a分区        0x8000
    //000000000100000000000000: 升级uboot_b分区        0x4000
    //000000000010000000000000: 升级boot_a分区         0x2000
    //000000000001000000000000: 升级boot_b分区         0x1000
    //000000000000100000000000: 升级system_a分区        0x800
    //000000000000010000000000: 升级system_b分区        0x400
    //000000000000001000000000: 升级misc分区,sdboot使用 0x200
    //000000000000000100000000: 升级userdata分区        0x100 

    // 计算数组长度16
    int num = sizeof(update_cmd) / sizeof(UPDATE_CMD);
    LOGI("[%s:%d] num [%d]\n", __func__, __LINE__, num);

    // 获取镜像文件信息
    if (partition == -1) {
        // 设置目标分区大小
        RKIMAGE_HDR rkimage_hdr;
        // 解析固件update.img头部信息,解析原始固件update.raw.img头部信息,存储到rkimage_hdr(包含了各个分区镜像的偏移和大小)
        if ( analyticImage(_url, &rkimage_hdr) != 0) {
            LOGE("analyticImage error.\n");
            return false;
        }

        // 遍历每一个升级命令
        for (int i = 0; i < num; i++) {
            // 对于打了标记需要升级的分区
            if ( update_cmd[i].need_update || is_sdboot || is_usbboot) {
                // 取消升级标记
                update_cmd[i].need_update = false;
                // 遍历升级固件update.img中每一个分区镜像信息
                for (int j = 0; j < rkimage_hdr.item_count; j++) {
                    // 升级命令匹配,并且升级固件update.img有该分区信息
                    if (strcmp(rkimage_hdr.item[j].name, update_cmd[i].name) == 0) {
                        LOGI("found rkimage_hdr.item[%d].name = %s.\n", j, update_cmd[i].name);
                        if (rkimage_hdr.item[j].file[50] == 'H') {
                            update_cmd[i].offset = *((DWORD *)(&rkimage_hdr.item[j].file[51]));
                            update_cmd[i].offset <<= 32;
                            update_cmd[i].offset += rkimage_hdr.item[j].offset;
                            LOGI("offset more than 4G, after adjusting is %lld.\n", update_cmd[i].offset);
                        } else {
                            update_cmd[i].offset = rkimage_hdr.item[j].offset;
                        }

                        if (rkimage_hdr.item[j].file[55] == 'H') {
                            update_cmd[i].size = *((DWORD *)(&rkimage_hdr.item[j].file[56]));
                            update_cmd[i].size <<= 32;
                            update_cmd[i].size += rkimage_hdr.item[j].size;
                            LOGI("size more than 4G, after adjusting is %lld.\n", update_cmd[i].size);
                        } else {
                            update_cmd[i].size = rkimage_hdr.item[j].size;
                        }

                        if (is_sdboot || is_usbboot) {
                            update_cmd[i].flash_offset = (long long)rkimage_hdr.item[j].flash_offset * SECTOR_SIZE;
                        }
                        update_cmd[i].need_update = true;
                        continue ;
                    }
                }
            }
        }

        if (!is_sdboot && !is_usbboot) {
            for ( int i = 0; i < num; i++ ) {
                if (*update_cmd[i].dest_path && (update_cmd[i].need_update == false)) {

                    unsigned char len = strlen(update_cmd[i].name);
                    if (update_cmd[i].name[len - 2] == '_' && (update_cmd[i].name[len - 1] == 'a' || update_cmd[i].name[len - 1] == 'b')) {

                        char slot_find = (update_cmd[i].name[len - 1] == 'a') ? 'b' : 'a';

                        update_cmd[i].name[len - 1] = slot_find;
                        for (int j = 0; j < rkimage_hdr.item_count; j++) {
                            if (strcmp(rkimage_hdr.item[j].name, update_cmd[i].name) == 0) {
                                LOGI("again found rkimage_hdr.item[%d].name = %s.\n", j, update_cmd[i].name);
                                if (rkimage_hdr.item[j].file[50] == 'H') {
                                    update_cmd[i].offset = *((DWORD *)(&rkimage_hdr.item[j].file[51]));
                                    update_cmd[i].offset <<= 32;
                                    update_cmd[i].offset += rkimage_hdr.item[j].offset;
                                    LOGI("offset more than 4G, after adjusting is %lld.\n", update_cmd[i].offset);
                                } else {
                                    update_cmd[i].offset = rkimage_hdr.item[j].offset;
                                }

                                if (rkimage_hdr.item[j].file[55] == 'H') {
                                    update_cmd[i].size = *((DWORD *)(&rkimage_hdr.item[j].file[56]));
                                    update_cmd[i].size <<= 32;
                                    update_cmd[i].size += rkimage_hdr.item[j].size;
                                    LOGI("size more than 4G, after adjusting is %lld.\n", update_cmd[i].size);
                                } else {
                                    update_cmd[i].size = rkimage_hdr.item[j].size;
                                }

                                update_cmd[i].need_update = true;
                                continue ;
                            }
                        }
                    }
                }
            }
        }
        // for ( int i=0; i<num; i++ ) {
        // printf ( "[%s:%d] update_cmd[%d].name [%s] dest path [%s] flash offset [%#llx] offset [%#llx] size [%#llx] \n",
        // __func__, __LINE__, i, update_cmd[i].name, update_cmd[i].dest_path, update_cmd[i].flash_offset, update_cmd[i].offset, update_cmd[i].size);
        // }

        return true;
    }

    // 遍历每一个升级命令, 标记每个需要升级的分区,
    for (int i = 0; i < num; i++) {
        // For OTA and SD update MUST read gpt from update***.img  
        // 首次循环,执行paramert升级命令;之后的循环根据partition位映射升级相应分区
        if ( (partition & 0x800000 || is_sdboot || is_usbboot || (strcmp(update_cmd[i].name, "parameter") == 0) ) ) {
            LOGI("need update %s.\n", update_cmd[i].name);
            update_cmd[i].need_update = true;

            // sd卡/usb启动特殊处理
            if (is_sdboot || is_usbboot) {
                memset(update_cmd[i].dest_path, 0, sizeof(update_cmd[i].dest_path) / sizeof(update_cmd[i].dest_path[0]));
                if (strcmp(update_cmd[i].name, "parameter") == 0) {
                    sprintf(update_cmd[i].dest_path, "%s/gpt", _url_dir);
                } else {
                    sprintf(update_cmd[i].dest_path, "%s/%s", _url_dir, update_cmd[i].name);
                }
            } else { // 走这里
                // 如果是parameter升级,设置升级的分区的名称
                if (strcmp(update_cmd[i].name, "parameter") == 0) {
                    sprintf(update_cmd[i].dest_path, "/dev/block/by-name/gpt");
                } else {
                    // 判断是不是MTD设备(Nor/Nand Flash设备),对于SD、eMMC不属于MTD设备
                    if (!isMtdDevice()) {   // 进入
                        // 设置各个升级命令 => 目标分区名称 /dev/block/by-name/分区名
                        sprintf(update_cmd[i].dest_path, "/dev/block/by-name/%s", update_cmd[i].name);
                    } else {
                        if ( update_cmd[i].need_update && (mtd_scan_partitions() > 0) ) {
                            const MtdPartition *mtdp = mtd_find_partition_by_name(update_cmd[i].name);
                            if (mtdp) {
                                sprintf(update_cmd[i].dest_path, "/dev/mtd%d", mtdp->device_index);
                                LOGI("need update %s ,.dest_path: %s.\n", update_cmd[i].name, update_cmd[i].dest_path);
                            }
                        } else {
                            sprintf(update_cmd[i].dest_path, "/dev/block/by-name/%s", update_cmd[i].name);
                        }
                    }
                }
            }
        }
        partition = (partition << 1);
    }

    return true;

}

该函数会输出RK_ota_set_partition支持的命令数量,比如:

LOG_INFO: [RK_ota_set_partition:106] num [16]

接着标记每个需要升级的分区,输出日志大致如下:

LOG_INFO: need update parameter.
LOG_INFO: need update uboot.
LOG_INFO: Current device is not MTD
LOG_INFO: need update trust.
LOG_INFO: Current device is not MTD
LOG_INFO: need update boot.
LOG_INFO: Current device is not MTD
LOG_INFO: need update rootfs.
LOG_INFO: Current device is not MTD
LOG_INFO: need update oem.
LOG_INFO: Current device is not MTD
3.3.3 RK_ota_start

RK_ota_start对命令数组update_cmd中打了标记的命令所指向的分区进行升级,函数接收参数:

  • RK_upgrade_callback:升级状态回调函数,第二个参数为状态枚举值RK_Upgrade_Status_t
  • RK_print_callback:升级状态回调函数,参数是一个字符指针;

这两个函数在我看来没多大区别,都是用来输出升级状态信息的。

RK_ota_start函数比较复杂,主要可以分为以下几个流程;

  • 调用download_file函数从_url下载升级固件,文件下载成功设置_url=_save_path
    • 如果是从远端下载的文件则会保存到_save_path
    • 否则则认为文件位于本地_url,并不会保存到_save_path;因此本地升级时,需要将_save_path设置成和_url一样的路径;
  • 调用RK_ota_set_partition函数获取升级固件_url的信息;

函数原型如下:

点击查看代码
void RK_ota_start(RK_upgrade_callback cb, RK_print_callback print_cb)
{
    LOGI("start RK_ota_start.\n");
    processvalue = 95;
    cb(NULL, RK_UPGRADE_START);

    //确认升级路径
    if (_url == NULL) {
        LOGE("url is NULL\n");
        cb(NULL, RK_UPGRADE_ERR);
        return ;
    }

    // 1. 获取文件
    int res = download_file(_url, _save_path);
    // 如果文件系在成功,__url修改为文件的保存路径
    if (res == 0) {
        _url = (char *)_save_path;
    } else if (res == -1) {
        LOGE("download_file error.\n");
        cb(NULL, RK_UPGRADE_ERR);
        return ;
    }

    // 2. 获取文件信息
    if (!RK_ota_set_partition(-1)) {
        LOGE("RK_ota_set_partition failed.\n");
        cb(NULL, RK_UPGRADE_ERR);
        return ;
    }

    STRUCT_PARAM_ITEM param_item[20] = {0};
    long long gpt_backup_offset = -1;
    memset(param_item, 0, sizeof(param_item));
    flash_register_partition_data(param_item, &gpt_backup_offset);

    // 是不是MTD设备
    int is_mtd_flag = isMtdDevice();

    // 3. 下载文件到分区并校验
    int num = sizeof(update_cmd) / sizeof(UPDATE_CMD);
    char prompt[128] = {0};
    for (int i = 0; i < num; i++ ) {
        // 处理需要升级的分区
        if (update_cmd[i].need_update) {
            if (update_cmd[i].cmd != NULL) {
                LOGI("now write %s to %s.\n", update_cmd[i].name, update_cmd[i].dest_path);
                sprintf(prompt, "[%s] upgrade start...\n", update_cmd[i].name);
                print_cb(prompt);
                // 如果是misc、parameter升级命令,直接跳过
                if (!is_sdboot && !is_usbboot &&
                    ( (strcmp(update_cmd[i].name, "misc") == 0) ||
                      (strcmp(update_cmd[i].name, "parameter") == 0) )) {
                    LOGI("ingore misc.\n");
                    continue;
                }
                // 下载固件到分区
                LOGI("update_cmd.flash_offset = %lld.\n", update_cmd[i].flash_offset);
                // 执行升级命令的升级函数cmd,比如recovery => flash_normal
                if (update_cmd[i].cmd(_url, (void*)(&update_cmd[i])) != 0) {
                    LOGE("update %s error.\n", update_cmd[i].dest_path);
                    sprintf(prompt, "[%s] upgrade fail\n", update_cmd[i].name);
                    print_cb(prompt);
                    cb(NULL, RK_UPGRADE_ERR);
                    return ;
                } else {
                    sprintf(prompt, "[%s] upgraede success!\n", update_cmd[i].name);
                    print_cb(prompt);
                }
                if (is_sdboot) {
                    if (ota_recovery_cmds(update_cmd[i].flash_offset, update_cmd[i].dest_path)) {
                        LOGE("write recovery cmds to %s failed.\n", CMD4RECOVERY_FILENAME);
                        cb(NULL, RK_UPGRADE_ERR);
                        return ;
                    }
                    LOGI("not check in sdboot (sdcard).\n");
                    continue;
                } else if (is_usbboot) {
                    if (ota_recovery_cmds(update_cmd[i].flash_offset, update_cmd[i].dest_path)) {
                        LOGE("write recovery cmds to %s failed.\n", CMD4RECOVERY_UDISK_FILENAME);
                        cb(NULL, RK_UPGRADE_ERR);
                        return ;
                    }
                    LOGI("not check in usb storage (udisk).\n");
                    continue;
                }
                // parameter 和loader 先不校验
                if (strcmp(update_cmd[i].name, "parameter") == 0 || strcmp(update_cmd[i].name, "bootloader") == 0) {
                    LOGI("not check parameter and loader.\n");
                    continue;
                }
                // 校验分区
                if (!update_cmd[i].skip_verify &&
                    comparefile(update_cmd[i].dest_path, _url,
                                update_cmd[i].flash_offset,
                                update_cmd[i].offset, update_cmd[i].size)) {
                    LOGI("check %s ok.\n", update_cmd[i].dest_path);
                } else if (!update_cmd[i].skip_verify) {
                    LOGE("check %s failed.\n", update_cmd[i].dest_path);
                    cb(NULL, RK_UPGRADE_ERR);
                    return ;
                }
            }
        }
    }

    /*
     * Fix if update_xxx.img not found some A/B partition image.
     */
    if (is_sdboot || is_usbboot) {
        for (int i = 0; i < num; i++) {
            if ( (!update_cmd[i].need_update) || (update_cmd[i].cmd == NULL)) {
                continue;
            }
            unsigned char len = strlen(update_cmd[i].name);
            if (update_cmd[i].name[len - 2] == '_' && (update_cmd[i].name[len - 1] == 'a' || update_cmd[i].name[len - 1] == 'b') ) {
                char slot_find = (update_cmd[i].name[len - 1] == 'a') ? 'b' : 'a';
                int part_need_fix = 1;
                char part_name[32];
                memset(part_name, 0, sizeof(part_name) / sizeof(part_name[0]));
                memcpy(part_name, update_cmd[i].name, len);
                part_name[len - 1] = slot_find;

                for (int k = 0; k < num; k++) {
                    if ( (!update_cmd[k].need_update) || (update_cmd[k].cmd == NULL)) {
                        continue;
                    }
                    if ( (strcmp(update_cmd[k].name, part_name) == 0) ) {
                        part_need_fix = 0;
                    }
                }

                if (part_need_fix) {
                    for (int j = 0; j < sizeof(param_item) / sizeof(param_item[0]); j++) {
                        if (strcmp(param_item[j].name, part_name) == 0) {
                            if (ota_recovery_cmds(param_item[j].offset * SECTOR_SIZE, update_cmd[i].dest_path)) {
                                LOGE("sdboot fix write recovery cmds to %s failed.\n", CMD4RECOVERY_FILENAME);
                                cb(NULL, RK_UPGRADE_ERR);
                                return ;
                            }
                        }
                    }
                }
            }
        }

        // write gpt backup
        /*
         * char gpt_backup_img_path[100] = {0};
         * sprintf(gpt_backup_img_path, "%s/%s", _url_dir, GPT_BACKUP_FILE_NAME);
         * if (ota_recovery_cmds(gpt_backup_offset, gpt_backup_img_path)) {
         *     LOGE("write gpt backup to recovery cmds failed (%s).\n", gpt_backup_img_path);
         *     cb(NULL, RK_UPGRADE_ERR);
         *     return ;
         * }
         */

    }

    // 4. 是否设置misc

    LOGI("RK_ota_start is ok!");
    processvalue = 100;
    cb(NULL, RK_UPGRADE_FINISHED);
    print_cb((char *)"updateEngine upgrade OK!\n");

    /* We're successful upgraded, Remove the done_file(see flash_image.cpp) if exist. */
    {
        char done_file[256];
        snprintf(done_file, 256, "%s.done", _url);
        unlink(done_file);
    }
}

3.4 flash_image.c

3.4.1 flash_normal
点击查看代码
int flash_normal(char *src_path, void *pupdate_cmd)
{
    LOGI("%s:%d start.\n", __func__, __LINE__);
    PUPDATE_CMD pcmd = (PUPDATE_CMD)pupdate_cmd;
    int ret = 0;

    // sd卡启动 或者 不是MTD设备	
    if (is_sdboot || !isMtdDevice()) {  // 进入
        //block
        char dst_file[256];
        /* record the already upgraded img name,
         * so it can continue on upgrading after unexpected shutdown/reboot
         */
        char done_file[256];
        /* The pattern writes to done_file, e.g.
         *  >uboot<
         *  >boot<
         *  >rootfs<
         */
        char pattern[64];
        int fd;
        off_t size, len;
        char buf[512];
        struct stat dst_stat;

        LOGI("%s:%d, diff check for %s\n", __func__, __LINE__, pcmd->name);
        snprintf(dst_file, 256, "%s.%s", src_path, pcmd->name);
        snprintf(done_file, 256, "%s.done", src_path);
        if ((fd = open(done_file, O_RDWR | O_CREAT | O_DSYNC, 0)) < 0 ||
            (size = lseek(fd, 0, SEEK_END) < 0) ||
            (lseek(fd, 0, SEEK_SET) < 0)) {
            LOGE("open %s failed, upgrading abort!\n", done_file);
            return -1;
        }
        memset(buf, 0, 512);
        len = 0;
        while (size > 0 && len != size) {
            ssize_t val;
            if ((val = read(fd, buf + len, size - len)) < 0) {
                close(fd);
                LOGE("read %s failed: %s\n", done_file, strerror(errno));
                return val;
            }
            len += val;
        }
        snprintf(pattern, 64, ">%s<", pcmd->name);
        /* If this partition already upgraded,
         * the old image (in flash) could be broken,
         * it's fine to skip it.
         */
        if (strstr(buf, pattern) != NULL) {
            close(fd);
            return 0;
        }
        /* Otherwise, we shall look inside to make sure:
         * do_patch_rkimg() can generate a corrent new image even if
         * machine could lost-power/crash during upgrading
         * By doing this, do_patch_rkimg() will checking:
         *   - if dst_file already exist
         *     - md5sum is correct, then we may reboot during writing-to-flash
         *       >>  do not do patch again.
         *     - md5sum is wrong, then we may reboot during do_patch_rkimg()
         *       >>  the old image (in flash) good enough as is, do patch again.
         *   - if dst_file is not exist
         *     - this is normal case, we're safe to continue
         */
        if (stat(dst_file, &dst_stat) == 0)
            LOGI("file %s exist for <%s>, unexpected detected, trying to continue\n",
                 dst_file, pcmd->name);
        ret = do_patch_rkimg(src_path, pcmd->offset, pcmd->size,
                             pcmd->dest_path, dst_file);
        if (ret == 0) {
            ret = block_write(src_path, pcmd->offset, pcmd->size,
                              pcmd->flash_offset, pcmd->dest_path);
        } else if (ret > 0) {
            ret = block_write(dst_file, 0, ret,
                              pcmd->flash_offset, pcmd->dest_path);
            //TODO: do not skip diff image verify
            pcmd->skip_verify = true;
        } else {
            return ret;
        }

        lseek(fd, 0, SEEK_END);
        if (write(fd, pattern, strlen(pattern)) != strlen(pattern)) {
            LOGW("write len error");
        }
        close(fd);

        sync();
        /* Make sure that dst file written to flash before unlink it */
        unlink(dst_file);
        sync();
    } else {
        //mtd
        LOGI("pcmd->flash_offset = %lld.\n", pcmd->flash_offset);
        ret = mtd_write(src_path, pcmd->offset, pcmd->size, pcmd->flash_offset, pcmd->dest_path);
    }
    return ret;
}
3.4.2 block_write
点击查看代码
static int block_write(char *src_path, long long offset, long long size, long long flash_offset, char *dest_path)
{
    LOGI("block_write src %s dest %s.\n", src_path, dest_path);
    int fd_dest = 0, fd_src = 0;
    long long src_offset = 0, dest_offset = 0;
    long long src_remain, dest_remain;
    int src_step, dest_step;
    long long src_file_offset = 0;
    long long read_count, write_count;
    char data_buf[BLOCK_WRITE_LEN] = {0};

    fd_src = open(src_path, O_RDONLY);
    if (fd_src < 0) {
        LOGE("Can't open %s\n", src_path);
        return -2;
    }
    src_offset = offset;
    dest_remain = src_remain = size;
    dest_step = src_step = BLOCK_WRITE_LEN;

    if (lseek64(fd_src, src_offset, SEEK_SET) == -1) {
        close(fd_src);
        LOGE("lseek64 failed (%s:%d).\n", __func__, __LINE__);
        return -2;
    }
    src_file_offset = src_offset;
    // dest_offset = flash_offset;
    // This step is going to write (src_path: sdupdate.img) to the file which is partition data (e.g. uboot)
    // So dest_offset is 0.
    dest_offset = 0;

    fd_dest = open(dest_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd_dest < 0) {
        close(fd_src);
        LOGE("Can't open %s\n", dest_path);
        return -2;
    }
    if ( lseek64(fd_dest, dest_offset, SEEK_SET) == -1 ) {
        LOGE("(%s:%d) lseek64 failed(%s).\n", __func__, __LINE__, strerror(errno));
        close(fd_src);
        close(fd_dest);
        return -2;
    }
    while (src_remain > 0 && dest_remain > 0) {
        memset(data_buf, 0, BLOCK_WRITE_LEN);
        read_count = src_remain > src_step ? src_step : src_remain;

        if (read(fd_src, data_buf, read_count) != read_count) {
            close(fd_dest);
            close(fd_src);
            LOGE("Read failed(%s):(%s:%d)\n", strerror(errno), __func__, __LINE__);
            return -2;
        }

        src_remain -= read_count;
        src_file_offset += read_count;
        write_count = dest_remain > dest_step ? dest_step : dest_remain;

        if (write(fd_dest, data_buf, write_count) != write_count) {
            close(fd_dest);
            close(fd_src);
            LOGE("(%s:%d) write failed(%s).\n", __func__, __LINE__, strerror(errno));
            return -2;
        }
        dest_remain -= write_count;
    }

    fsync(fd_dest);
    close(fd_dest);
    close(fd_src);
    return 0;
}

3.5 download.c

download.c文件从文件名字就可以看出来,这个文件主要实现的就是文件下载功能。

3.5.1 download_file

download_file函数用于从url下载文件,函数接收两个参数:

  • url:需要下载的文件地址,可以是远端url路径,也可以是本地路径;
  • output_filename:下载的文件保存路径;

函数主要流程如下:

  • 如果urlhttp开头,则从远端使用wget下载文件,并保存到output_filename指定的路径;

  • 否则则认为文件位于本地,函数直接返回;

download_file函数原型如下:

#include <stdio.h>
#include <curl/curl.h>
#include <string.h>
#include <stdbool.h>
#include "log.h"
.....
    
int download_file(char *url, char const *output_filename)
{
    // 判断url是否是以http开头,即从远端采用http协议下载文件
    if (strncmp(url, "http", 4) != 0) {   // 非http开头,则认为文件位于本地
        LOGI("where the file is local.\n");
        return -2;
    }
    CURL *curl;
    CURLcode res;
    FILE *outfile;
    char const *progress_data = "* ";
    down_processvalue = -1;
	
    // 初始化CURL对象	
    curl = curl_easy_init();
    if (curl) {
        // 打开指定的output_filename文件以写入二进制数据
        outfile = fopen(output_filename, "wb");

        // 设置CURL选项	
        curl_easy_setopt(curl, CURLOPT_URL, url);  // 设置下载的URL地址
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); //  设置写入数据的文件指针为 outfile
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_func); // 设置自定义的写入回调函数my_write_func,用于处理接收到的数据。
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false);  // 设置为false,允许显示下载进度
        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, my_progress_func); // 设置进度回调函数my_progress_func,用于更新下载进度
        curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, progress_data); // 设置进度数据为progress_data,这里可能用于自定义进度显示的标志

        // 执行下载操作
        res = curl_easy_perform(curl);

        if (res != CURLE_OK)
            LOGE("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        // 关闭文件outfile和清理CURL对象
        fclose(outfile);
        /* always cleanup */
        curl_easy_cleanup(curl);
    }
    if (res != CURLE_OK) {
        LOGE("download Error.\n");
        return -1;
    }
    return 0;
}

在设置CURL选项的过程中,有两个回调函数:

  • 写入回调函数my_write_func,用于处理接收到的数据;
  • 进度回调函数my_progress_func,用于更新下载进度。
3.5.2 my_write_func

``my_progress_func`函数用于处理接收到的数据,函数接收4个参数:

  • ptr:指向要写入数据的内存块的指针,通常是一个void*类型的指针;
  • size:要写入的每个数据项的字节数(大小),通常使用sizeof()运算符来获取数据项的大小,例如sizeof(int)sizeof(struct YourStruct)
  • nmemb:要写入的数据项的数量,即数据块中包含多少个size大小的数据项。
  • stream:指向FILE对象的指针,该FILE对象标识了要写入数据的文件或者一个内存流。

函数返回值size_t表示实际写入的数据项数量,如果写入成功,返回值通常等于nmemb,如果失败,则返回一个小于nmemb的值。

函数原型如下:

size_t my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fwrite(ptr, size, nmemb, stream);
}
3.5.3 my_progress_func

my_progress_func函数用于输出下载进度,函数原型如下;

int my_progress_func(char *progress_data,
                     double t, /* dltotal */
                     double d, /* dlnow */
                     double ultotal,
                     double ulnow)
{
    processvalue = ulnow / ultotal * 100 / 110;
    //LOGI("ultotal is %f, ulnow is %f, t is %f, d is %f\n", ultotal, ulnow, t, d);
    if (down_processvalue != (int)(d / t * 100)) {
        down_processvalue = (int)(d / t * 100);
        LOGI("down_processvalue is %d%\n", down_processvalue);
    }

    return 0;
}

3.6 rkimage.c

rkimage.c文件主要用于解析rockchip升级固件update.img的内容。

3.6.1 analyticImage

analyticImage函数用于解析升级固件,获取固件头部信息,函数接收两个参数:

  • filepath:固件文件在系统的存储位置,比如/userdata/update.img
  • phdr:用于存储解析后的原始固件update.raw.img头部信息。

函数原型如下:

点击查看代码
// defineHeader.h
typedef enum {    // 每个枚举常量的赋值决定了其在内存中所占的大小,占用4个字节
    RKNONE_DEVICE = 0,
    RK27_DEVICE = 0x10,
    RKCAYMAN_DEVICE,
    RK28_DEVICE = 0x20,
    RK281X_DEVICE,
    RKPANDA_DEVICE,
    RKNANO_DEVICE = 0x30,
    RKSMART_DEVICE,
    RKCROWN_DEVICE = 0x40,
    RK29_DEVICE = 0x50,
    RK292X_DEVICE,
    RK30_DEVICE = 0x60,
    RK30B_DEVICE,
    RK31_DEVICE = 0x70,
    RK32_DEVICE = 0x80
} ENUM_RKDEVICE_TYPE;
.......

// rkimage.h 
#pragma pack(1)
typedef struct {                       // 时间 一共占用7个字节
    USHORT  usYear;                    // 2个字节
    BYTE    ucMonth;                   // 1个字节  
    BYTE    ucDay;                     // 1个字节  
    BYTE    ucHour;                    // 1个字节  
    BYTE    ucMinute;                  // 1个字节  
    BYTE    ucSecond;                  // 1个字节  
} STRUCT_RKTIME, *PSTRUCT_RKTIME;

typedef struct tagRKIMAGE_ITEM {       // 分区镜像信息  112个字节
    char name[PART_NAME];			   // 分区名称	    32个字节  比如parameter
    char file[RELATIVE_PATH];          // 镜像文件名称   64个字节  比如parameter.txt
    unsigned int offset;               // 偏移          4个字节
    unsigned int flash_offset;         // 4个字节           
    unsigned int usespace;             // 4个字节
    unsigned int size;                 // 分区镜像大小    4个字节
} RKIMAGE_ITEM, *PRKIMAGE_ITEM;

typedef struct tagRKIMAGE_HDR { // 原始固件update.raw.img头部信息 3724个字节
    unsigned int tag;                               // 4个字节
    unsigned int size;                              // 4个字节
    char machine_model[MAX_MACHINE_MODEL];          // 64个字节   
    char manufacturer[MAX_MANUFACTURER];            // 60个字节 
    unsigned int version;                           // 4个字节
    int item_count;                                 // 4个字节  
    RKIMAGE_ITEM item[MAX_PACKAGE_FILES];           // 112*32个字节    
} RKIMAGE_HDR, *PRKIMAGE_HDR;

typedef struct {  // 固件update.img头部信息 102个字节  
    UINT uiTag;     //标志,固定为0x57 0x46 0x4B 0x52   4个字节
    USHORT usSize;  // 结构体大小                       2个字节
    DWORD  dwVersion;   //Image 文件版本                4个字节          
    DWORD  dwMergeVersion;  //打包工具版本               4个字节 
    STRUCT_RKTIME stReleaseTime;    //生成时间          7个字节
    ENUM_RKDEVICE_TYPE emSupportChip;   //使用芯片      4个字节 
    DWORD  dwBootOffset;    //bootloade分区镜像偏移                 4个字节 
    DWORD  dwBootSize;  //bootloade分区镜像大小                     4个字节  
    DWORD  dwFWOffset;  //原始固件偏移(指的是update.raw.img)4个字节
    DWORD  dwFWSize;    //原始固件大小(指的是update.raw.img)4个字节
    BYTE   reserved[61];    //预留空间,用于存放不同固件特征  61个字节
} STRUCT_RKIMAGE_HEAD, *PSTRUCT_RKIMAGE_HEAD;

......
#pragma pack()

// rkimage.c
// 解析固件,获得固件头部信息
int analyticImage(const char *filepath, PRKIMAGE_HDR phdr)
{
    // 用于存存放update.img除了MD5校验数据外的文件大小
    long long ulFwSize;
    STRUCT_RKIMAGE_HEAD rkimage_head;
    // 存放MD5校验数据,一共32个字节
    unsigned char m_md5[32];

	// 打开升级固件文件update.img
    int fd = open(filepath, O_RDONLY);
    if (fd < 0) {
        LOGE("Can't open %s\n", filepath);
        return -2;
    }

    // 1. image 头部信息读取
    if (read(fd, &rkimage_head, sizeof(STRUCT_RKIMAGE_HEAD)) != sizeof(STRUCT_RKIMAGE_HEAD)) {
        LOGE("Can't read %s\n(%s)\n", filepath, strerror(errno));
        close(fd);
        return -2;
    }
	
    // 判断预留空间偏移14、15是不是HI,即升级固件update.img偏移0x37、0x38
    if ((rkimage_head.reserved[14] == 'H') && (rkimage_head.reserved[15] == 'I')) {
        // 如果原始固件update.raw.img大小大于4GB,0x39开始的4个字节存放的是原始固件update.raw.img大小/4GB的(即单位是4GB)
        ulFwSize = *((DWORD *)(&rkimage_head.reserved[16]));
        ulFwSize <<= 32;
        ulFwSize += rkimage_head.dwFWOffset;
        ulFwSize += rkimage_head.dwFWSize;
    } else {
        // 原始固件update.raw.img在update.img中的偏移 + 原始固件update.raw.img大小
        ulFwSize = rkimage_head.dwFWOffset + rkimage_head.dwFWSize;
    }
    // 计算原始固件update.raw.img的大小
    rkimage_head.dwFWSize = ulFwSize - rkimage_head.dwFWOffset;
    // 输出固件update.img头部信息
    display_head(&rkimage_head);

    // 2. 固件md5 校验
    long long fileSize;
    int nMd5DataSize;

    // 定位文件末尾,获取文件大小fileSize
    fileSize = lseek64(fd, 0L, SEEK_END);
    // 文件大小fileSize减去ulFwSize,即update.img除了MD5校验数据外的文件大小
    nMd5DataSize = fileSize - ulFwSize;
    if (nMd5DataSize >= 160) {
        LOGE("md5 : not support sign image.\n");
        //sign image
        //m_bSignFlag = true;
        //m_signMd5Size = nMd5DataSize-32;
        //fseeko64(m_pFile,ulFwSize,SEEK_SET);
        //fread(m_md5,1,32,m_pFile);
        //fread(m_signMd5,1,nMd5DataSize-32,m_pFile);
    } else {
        // 定位到文件末尾向前32个字节,并尝试读取这32个字节数据到m_md5数组中
        lseek64(fd, -32, SEEK_END);
        if ( read(fd, m_md5, 32) != 32) {
            LOGE("lseek failed.\n");
            close(fd);
            return -2;
        }
    }

    // 3. image 地址信息读取  将文件指针移动到rkimage_head.dwFWOffset指定的位置,即读取原始固件update.raw.img头部信息
    if (lseek64(fd, rkimage_head.dwFWOffset, SEEK_SET) == -1) {
        LOGE("lseek failed.\n");
        close(fd);
        return -2;
    }

    if (read(fd, phdr, sizeof(RKIMAGE_HDR)) != sizeof(RKIMAGE_HDR)) {
        LOGE("Can't read %s\n(%s)\n", filepath, strerror(errno));
        close(fd);
        return -2;
    }

    // 校验标签:0x46414B52
    if (phdr->tag != RKIMAGE_TAG) {
        LOGE("tag: %x\n", phdr->tag);
        LOGE("Invalid image\n");
        close(fd);
        return -3;
    }

    // 偏移0x80  0x81
    if ((phdr->manufacturer[56] == 0x55) && (phdr->manufacturer[57] == 0x66)) {
        USHORT *pItemRemain;
        pItemRemain = (USHORT *)(&phdr->manufacturer[58]);
        phdr->item_count += *pItemRemain;
    }

    // 由于phdr存放的各个分区镜像的偏移都是相对原始固件update.raw.img,因此调整各个分区镜像的偏移配置为相对固件update.img,即偏移+update.raw.img在update.img的偏移
    if (rkimage_head.dwFWOffset) {
        adjustFileOffset(phdr, rkimage_head.dwFWOffset, rkimage_head.dwBootOffset, rkimage_head.dwBootSize);
    }

    // 输出原始固件update.raw.img头部信息
    display_hdr(phdr);

    close(fd);
#if 1
    if (!compareMd5sum((char*)filepath, m_md5, 0, fileSize - 32)) {
        LOGE("Md5Check update.img fwSize:%ld", fileSize - 32);
        return -1;
    }
#endif
    LOGI("analyticImage ok.\n");
    return 0;
}
3.6.1.1 解析update.img头信息

这里我们可以以二进制方式打开我们编译的统一固件update.imgbuildroot系统);

#  -e  little-endian dump (incompatible with -ps,-i,-r).
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ xxd -b -e -l 102 output/update/Image/update.img
00000000: 57464b52 00000066 00000100 07e80200  RKFWf...........
00000010: 0b0e1e06 35383824 00006633 0731c000  ....$8853f....1.
00000020: 07322600 d9500400 00000033 00000100  .&2...P.3.......
00000030: 00000000 00000000 00000000 00000000  ................
00000040: 00000000 00000000 00000000 00000000  ................
00000050: 00000000 00000000 00000000 00000000  ................
00000060: 00000000     0000                    ......

以该文件为例,我们以小端方式(大多数ARM处理器在默认情况下采用小端序:数据的低位字节存储在低地址,高位字节存储在高地址)来解析,解析到的STRUCT_RKIMAGE_HEAD内容如下:

typedef struct {  // 102个字节
    UINT uiTag;     //标志,固定为0x57 0x46 0x4B 0x52   4个字节,偏移0x00开始:0x52 0x4B 0x46 0x57  
    USHORT usSize;  // 结构体大小                       2个字节,偏移0x04开始:0x66 0x00,解析出来为102
    DWORD  dwVersion;   //Image 文件版本                4个字节,偏移0x06开始:0x00 0x00 0x00 0x01        
    DWORD  dwMergeVersion;  //打包工具版本               4个字节,偏移0x0A开始:0x00 0x00 0x00 0x02   
    STRUCT_RKTIME stReleaseTime;    //生成时间          7个字节,偏移0x0E开始:0xE8 0x07 0x06 0x1E 0x0E 0x0B 0x24, 解析生时间就是2024-06-30 14:11:24
    ENUM_RKDEVICE_TYPE emSupportChip;   //使用芯片      4个字节,偏移0x15开始:0x38 0x38 0x35 0x33,解析成字符串就是3588
    DWORD  dwBootOffset;    //bootloade分区镜像偏移      4个字节,偏移0x19开始:0x66 0x00 0x00 0x00,解析成数字就是102
    DWORD  dwBootSize;  //bootloade分区镜像大小          4个字节,偏移0x1D开始:0x0c 0x31 0x07 0x00,解析成数字就是471308 
    DWORD  dwFWOffset;  //原始固件update.raw.img偏移     4个字节,偏移0x21开始:0x26 0x32 0x07 0x00,解析成数字就是471590
    DWORD  dwFWSize;    //原始固件update.raw.img大小     4个字节,偏移0x25开始:0x04 0x50 0xD9 0x33,解析成数字就是869879812,output/update/Image/update.raw.img大小
    BYTE   reserved[61];    //预留空间,用于存放不同固件特征  61个字节
} STRUCT_RKIMAGE_HEAD, *PSTRUCT_RKIMAGE_HEAD;

因此我们大概可以了解到update.img的文件结构:

  • 第一部分0x00~0x65:固件头信息,一共102个字节;
  • 第二部分0x66~0x073171bootloader分区镜像移除最后180个字节之后的数据,一共471308 个字节;
  • 第三部分0x073172~0x073225bootloader分区镜像最后180个字节,一共180个字节;
  • 第四部分0x073226~0x33e08229:存放update.raw.img,即原始固件,一共869879812个字节;
  • 第五部分0x33e0822A~0x33e08249:其中最后32个字节,为MD5校验数据;

实际上我们的update.img文件大小为870351434102+471308+180+869879812+32=870351434

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll output/update/Image/update.*
-rw-r--r-- 1 root root 870351434  6月 30 14:11 output/update/Image/update.img
-rw-r--r-- 1 root root 869879812  6月 30 14:11 output/update/Image/update.raw.img

bootloade分区镜像文件为MiniLoaderAll.bin,其指向u-boot/rk3588_spl_loader_v1.13.112.bin,大小为471488,比471308多了180个字节;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ ll u-boot/rk3588_spl_loader_v1.13.112.bin
-rw-r--r-- 1 root root 471488  6月 18 01:48 u-boot/rk3588_spl_loader_v1.13.112.bin

update.img中第二部分+第三部分合在一起就是rk3588_spl_loader_v1.13.112.bin文件,我们可以通过如下命令来证实:

# 查看rk3588_spl_loader_v1.13.112.bin最后180个字节
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ xxd -b -e  -s 471308 -l 180 u-boot/rk3588_spl_loader_v1.13.112.bin
0007310c: 0c6526d5 54ecb1cb 39e54545 ff777bd5  .&e....TEE.9.{w.
0007311c: 38c8c09b 4a6ee54e 439f42bb 045ed0d1  ...8N.nJ.B.C..^.
0007312c: 3e000f78 3c07bc06 642a4dc9 cf7743b9  x..>...<.M*d.Cw.
0007313c: 68f8d793 7bb93800 8af3aae8 8b279647  ...h.8.{....G.'.
0007314c: c4c44544 26a1e0ed 37dd6f02 01c69287  DE.....&.o.7....
0007315c: e6a01403 da6c2baa 357598e5 ccbf2c3a  .....+l...u5:,..
0007316c: 5f6227b5 9b2adcd0 d74da2d7 d17a6572  .'b_..*...M.rez.
0007317c: e7983a17 b68e573c c93432a0 cdcad16e  .:..<W...24.n...
0007318c: 6ebd9581 a65c840e 5f420ccb 3ee016c8  ...n..\...B_...>
0007319c: 8e32f3ac 25025644 1eafcddf b659361d  ..2.DV.%.....6Y.
000731ac: d5d15844 6bc304ea 936f8d91 4527281c  DX.....k..o..('E
000731bc: 9445a116                             ..E.

# 查看update.img第三部分180个字节
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ xxd -b -e -s 0x073172 -l 180 output/update/Image/update.img
00073172: 0c6526d5 54ecb1cb 39e54545 ff777bd5  .&e....TEE.9.{w.
00073182: 38c8c09b 4a6ee54e 439f42bb 045ed0d1  ...8N.nJ.B.C..^.
00073192: 3e000f78 3c07bc06 642a4dc9 cf7743b9  x..>...<.M*d.Cw.
000731a2: 68f8d793 7bb93800 8af3aae8 8b279647  ...h.8.{....G.'.
000731b2: c4c44544 26a1e0ed 37dd6f02 01c69287  DE.....&.o.7....
000731c2: e6a01403 da6c2baa 357598e5 ccbf2c3a  .....+l...u5:,..
000731d2: 5f6227b5 9b2adcd0 d74da2d7 d17a6572  .'b_..*...M.rez.
000731e2: e7983a17 b68e573c c93432a0 cdcad16e  .:..<W...24.n...
000731f2: 6ebd9581 a65c840e 5f420ccb 3ee016c8  ...n..\...B_...>
00073202: 8e32f3ac 25025644 1eafcddf b659361d  ..2.DV.%.....6Y.
00073212: d5d15844 6bc304ea 936f8d91 4527281c  DX.....k..o..('E
00073222: 9445a116                             ..E.
3.6.1.2 解析update.raw.img头信息

这里我们可以以二进制方式打开原始固件update.raw.imgbuildroot系统);

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ xxd -b -e -l 3724 output/update/Image/update.raw.img
00000000: 46414b52 33d95000 334b5220 00383835  RKAF.P.3 RK3588.
00000010: 00000000 00000000 00000000 00000000  ................
00000020: 00000000 00000000 30200000 00003730  .......... 007..
00000030: 00000000 00000000 00000000 00000000  ................
00000040: 00000000 00000000 334b5220 00383835  ........ RK3588.
00000050: 00000000 00000000 00000000 00000000  ................
00000060: 00000000 00000000 00000000 00000000  ................
00000070: 00000000 00000000 00000000 00000000  ................
00000080: 00000000 01000000 0000000a 6b636170  ............pack        0x8c - 0xfb
00000090: 2d656761 656c6966 00000000 00000000  age-file........
000000a0: 00000000 00000000 00000000 6b636170  ............pack
000000b0: 2d656761 656c6966 00000000 00000000  age-file........
000000c0: 00000000 00000000 00000000 00000000  ................
000000d0: 00000000 00000000 00000000 00000000  ................
000000e0: 00000000 00000000 00000000 00000800  ................
000000f0: ffffffff 00000001 000000e2 61726170  ............para       0xfc - 0x16b parameter分区
00000100: 6574656d 00000072 00000000 00000000  meter...........
00000110: 00000000 00000000 00000000 61726170  ............para
00000120: 6574656d 78742e72 00000074 00000000  meter.txt.......
00000130: 00000000 00000000 00000000 00000000  ................
00000140: 00000000 00000000 00000000 00000000  ................
00000150: 00000000 00000000 00004000 00001000  .........@......
00000160: 00000000 00000001 00000227 746f6f62  ........'...boot      0x16c - 0x1db bootloader分区
00000170: 64616f6c 00007265 00000000 00000000  loader..........
00000180: 00000000 00000000 00000000 696e694d  ............Mini
00000190: 64616f4c 6c417265 69622e6c 0000006e  LoaderAll.bin...
000001a0: 00000000 00000000 00000000 00000000  ................
000001b0: 00000000 00000000 00000000 00000000  ................
000001c0: 00000000 00000000 00000000 00001800  ................
000001d0: ffffffff 000000e7 000731c0 6f6f6275  .........1..uboo      0x1dc - 0x24b uboot分区
000001e0: 00000074 00000000 00000000 00000000  t...............
000001f0: 00000000 00000000 00000000 6f6f6275  ............uboo
00000200: 6d692e74 00000067 00000000 00000000  t.img...........
00000210: 00000000 00000000 00000000 00000000  ................
00000220: 00000000 00000000 00000000 00000000  ................
00000230: 00000000 00000000 00002000 00075000  ......... ...P..
00000240: 00004000 00000800 00400000 6373696d  .@........@.misc     0x24c - 0x2bb  misc分区
00000250: 00000000 00000000 00000000 00000000  ................
00000260: 00000000 00000000 00000000 6373696d  ............misc
00000270: 676d692e 00000000 00000000 00000000  .img............
00000280: 00000000 00000000 00000000 00000000  ................
00000290: 00000000 00000000 00000000 00000000  ................
000002a0: 00000000 00000000 00002000 00475000  ......... ...PG.
000002b0: 00006000 00000018 0000c000 746f6f62  .`..........boot     0x2bc - 0x32b  boot分区
000002c0: 00000000 00000000 00000000 00000000  ................
000002d0: 00000000 00000000 00000000 746f6f62  ............boot
000002e0: 676d692e 00000000 00000000 00000000  .img............
000002f0: 00000000 00000000 00000000 00000000  ................
00000300: 00000000 00000000 00000000 00000000  ................
00000310: 00000000 00000000 00020000 00481000  ..............H.
......

或者直接查看update.img偏移0x073226处的内容,两者内容是一致的;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ xxd -b -e -s 0x073226 -l 3724 output/update/Image/update.img

以该文件为例,我们以小端方式(大多数ARM处理器在默认情况下采用小端序:数据的低位字节存储在低地址,高位字节存储在高地址)来解析,解析到的PRKIMAGE_HDR内容如下:

typedef struct tagRKIMAGE_HDR { // 原始固件update.raw.img头部信息 364个字节
    unsigned int tag;                               // 4个字节 偏移0x00开始:0x52 0x4b 0x41 0x46
    unsigned int size;                              // 4个字节 偏移0x04开始:0x00 0x50 0xd9 0x33,解析出来为869879808
    char machine_model[MAX_MACHINE_MODEL];          // 64个字节  偏移0x08开始  解析成字符串:RK3588
    char manufacturer[MAX_MANUFACTURER];            // 60个字节  偏移0x48开始  解析成字符串:RK3588
    unsigned int version;                           // 4个字节   偏移0x84开始  0x00 0x00 0x00 0x01 
    int item_count;                                 // 4个字节   偏移0x88开始  0x0a 0x00 0x00 0x00
    RKIMAGE_ITEM item[MAX_PACKAGE_FILES];           // 112*32个字节   偏移0x8C开始  
} RKIMAGE_HDR, *PRKIMAGE_HDR;
3.6.2 display_head

display_head函数用于输出固件update.img的头部信息;

static void display_head(PSTRUCT_RKIMAGE_HEAD pHead)
{
    LOGD("uiTag = %x.\n", pHead->uiTag);
    LOGD("usSize = %x.\n", pHead->usSize);
    LOGD("dwVersion = %x.\n", pHead->dwVersion);
    UINT btMajor = ((pHead->dwVersion) & 0XFF000000) >> 24;
    UINT btMinor = ((pHead->dwVersion) & 0X00FF0000) >> 16;
    UINT usSmall = ((pHead->dwVersion) & 0x0000FFFF);
    LOGD("btMajor = %x, btMinor = %x, usSmall = %02x.\n", btMajor, btMinor, usSmall);
    LOGD("dwBootOffset = %x.\n", pHead->dwBootOffset);
    LOGD("dwBootSize = %x.\n", pHead->dwBootSize);
    LOGD("dwFWOffset = %x.\n", pHead->dwFWOffset);
    LOGD("dwFWSize = %x.\n", pHead->dwFWSize);
}
3.6.3 adjustFileOffset

由于phdr是解析原始固件update.raw.img头部信息得到的,其成员item数组存放的各个分区镜像的偏移都是相对原始固件update.raw.img,因此调整各个分区镜像的偏移配置为相对固件update.img

phdr->item[i].offset += offset

+update.raw.imgupdate.img的偏移。

此外该函数,还将bootloader分区镜像的偏移和大小直接设置为rkimage_head.dwBootOffsetrkimage_head.dwBootSize

adjustFileOffset函数原型如下;

void adjustFileOffset(PRKIMAGE_HDR phdr, int offset, int loader_offset, int loader_size)
{
    for (int i = 0; i < phdr->item_count; i++) {
        // bootloader分区镜像信息
        if ( strcmp(phdr->item[i].name, "bootloader") == 0) {
            phdr->item[i].offset = loader_offset;
            phdr->item[i].size = loader_size;
            continue ;
        }
        phdr->item[i].offset += offset;
    }
}
3.6.4 display_hdr

display_head函数用于输出原始固件update.raw.img的头部信息;

static void display_hdr(PRKIMAGE_HDR phdr)
{

    //unsigned int tag;
    //unsigned int size;
    //char machine_model[MAX_MACHINE_MODEL];
    //char manufacturer[MAX_MANUFACTURER];
    //unsigned int version;
    //int item_count;
    //RKIMAGE_ITEM item[MAX_PACKAGE_FILES];

    LOGD("tag = %d\n", phdr->tag);
    LOGD("size = %d\n", phdr->size);
    LOGD("machine_model = %s\n", phdr->machine_model);
    LOGD("manufacturer = %s\n", phdr->manufacturer);
    LOGD("version = %d\n", phdr->version);
    LOGD("item = %d.\n", phdr->item_count);
    for (int i = 0; i < phdr->item_count; i++) {
        LOGI("================================================\n");
        display_item(&(phdr->item[i]));
    }
}

四、编译

除了在buildroot目录下采用make -j8编译(只编译buildroot根文件系统)外,我们还可以在SDK目录下采用一键全自动编译(包含了ubootkernel等):

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp# ./build.sh all

注意:如果出现编译失败等问题,直接移除buildroot/output目录重新编译即可。

当然也可以只编译模块代码(rootfsrecovery),接下来介绍按模块编译的具体步骤。

4.1 buildroot编译

这里我们使用buildroot系统,在系统根目录下执行如下命令重新编译rootfs.img

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp# sudo ./build.sh buildroot

由于编译使用的配置项是rockchip_rk3588_defconfig的配置,配置项远远rockchip_rk3588_recovery_defconfig的配置;

#include "base/base.config"
#include "chips/rk3588_aarch64.config"
#include "font/chinese.config"
#include "fs/exfat.config"
#include "fs/ntfs.config"
#include "fs/vfat.config"
#include "gpu/gpu.config"
#include "multimedia/audio.config"
#include "multimedia/camera.config"
#include "multimedia/gst/audio.config"
#include "multimedia/gst/camera.config"
#include "multimedia/gst/rtsp.config"
#include "multimedia/gst/video.config"
#include "multimedia/mpp.config"
#include "wifibt/bt.config"
#include "wifibt/wireless.config"
#include "benchmark.config"
#include "chromium.config"
#include "debug.config"
#include "npu2.config"
#include "powermanager.config"
#include "test.config"
#include "weston.config"

编译后在buildroot/output/rockchip_rk3588_recovery/images下生成不同格式的镜像, 默认使用rootfs.ext4文件;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ ll  output/rockchip_rk3588/images/
-rw-r--r-- 1 root root 615688192  6月 25 00:51 rootfs.cpio
-rw-r--r-- 1 root root 273879343  6月 25 00:52 rootfs.cpio.gz
-rw-r--r-- 1 root root 761266176  6月 25 00:52 rootfs.ext2
lrwxrwxrwx 1 root root        11  6月 25 00:52 rootfs.ext4 -> rootfs.ext2
-rw-r--r-- 1 root root 275013632  6月 25 00:52 rootfs.squashfs
-rw-r--r-- 1 root root 624496640  6月 25 00:52 rootfs.tar

可以看到编译出来的rootfs.img700M+

4.2 recovery编译

在系统根目录下执行如下命令重新编译recovery.img

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp# sudo ./build.sh recovery

编译后在buildroot/output/rockchip_rk3588_recovery/images/目录下生成recovery.img

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ ll  output/rockchip_rk3588_recovery/images/
-rw-r--r-- 1 root root 44336128  6月 18 01:48 recovery.img
-rw-r--r-- 1 root root 15856640  6月 18 01:48 rootfs.cpio
-rw-r--r-- 1 root root  6713449  6月 18 01:48 rootfs.cpio.gz
-rw-r--r-- 1 root root 43342848  6月 18 01:48 rootfs.ext2
lrwxrwxrwx 1 root root       11  6月 18 01:48 rootfs.ext4 -> rootfs.ext2
-rw-r--r-- 1 root root  6676480  6月 18 01:48 rootfs.squashfs
-rw-r--r-- 1 root root 16947200  6月 18 01:48 rootfs.tar

可以看到编译出来的recovery.img700M+

4.3 打包

4.3.1 打包分区镜像

执行以下命令更新output/firmware/下各个分区镜像;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ sudo ./build.sh firmware

如果recovery.img文件不存在,创建recovery.img链接文件指向buildroot/output/rockchip_rk3588_recovery/images/recovery.img

如果rootfs.img文件不存在,创建recovery.img链接文件指向buildroot/output/rockchip_rk3588_recovery/images/rootfs.ext2

4.3.2 打包统一固件

执行如下命令重新生成统一固件;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ sudo ./build.sh updateimg

编译会生成统一镜像update.img,位于output/update/update目录.

如果output/firmware/update.img文件不存在,创建output/firmware/update.img链接文件指向output/update/Image/update.img

五、buildroot系统升级

OTAOver-the-Air)即空间下载技术。 OTA升级是Android系统提供的标准软件升级方式。它功能强大,可以无损失升级系统,主要通过网络,例如WIFI3G/4G/5G自动下载OTA升级包、自动升级,也支持通过下载OTA升级包到SD卡/U盘升级,OTA的升级包非常的小,一般几MB到十几MB

本节主要介绍了使用OTA技术升级时,本地升级程序updateEngine/rkupdate执行升级的流程及技术细节,以便用户在开发过程中了解升级的过程及注意事项。

5.1 制作升级固件

5.1.1 系统镜像制作

首先我们需要将我们制作的统一固件update.img(这里采用的buildroot系统)烧录到开发板eMMC中,具体烧录步骤可以参考《Rockchip RK3588 - Rockchip Linux SDK编译》。

需要注意的是:我们烧录的buildroot文件系统要按照本篇博客配置进行编译,即编译出来的rootfs.imgrecovery.img包含用于升级的updateEnginerkupdate可执行文件;

root@rk3588-buildroot:~# ls /usr/bin/updateEngine -l
-rwxr-xr-x    1 root     root         80544 Jun 17 17:48 /usr/bin/updateEngine
root@rk3588-buildroot:~# ls /usr/bin/rkupdate -l
-rwxr-xr-x    1 root     root        106568 Jun 17 17:48 /usr/bin/rkupdate
5.1.2 升级镜像制作

按照正常的固件编译流程,制作用于升级的update.img固件,可以参考《Rockchip RK3588 - Rockchip Linux SDK编译》。

升级固件不一定要全分区升级,可修改 package-file 文件,将不要升级的分区去掉,这样可以减少升级包的大小。

例如,执行如下命令(可参考文件tools/linux/Linux_Pack_Firmware/rockdev/rk3588-package-file):

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ sudo ./build.sh edit-package-file

rootfs的相对路径改为 RESERVED,这样就不会打包根文件系统,即不升级根文件系统分区。

# 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  RESERVED

注意: 若将升级固件放至设备的/userdata/目录,则不要打包userdata.img,需要将image/userdata.img改为RESERVED

执行如下命令生成统一固件;

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ sudo ./build.sh updateimg

将制作好的升级固件拷贝到U盘、TF卡或者开发版的/userdata/目录下。

我们可以将统一镜像复制到/work/tftpwork目录下:

root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp$ cp ./rockdev/update.img /work/tftpboot/

5.2 updateEngine升级测试

5.2.1 升级流程

updateEngine升级流程如下:

  • 固件版本比较(--version_url);
  • 下载固件(--image_url),并保存到本地(--savepath);
  • 升级recovery分区;
  • 重启,进入recovery模式,升级指定的分区(--partition);
  • 升级成功,重启进入normal系统。
5.2.2 升级命令

升级支持网络下载和本地升级,且可指定要升级的分区,在normal系统运行如下命令:

网络升级:

updateEngine --image_url=http://192.168.0.200:8080/recovery/update.img --misc=update --savepath=/userdata/update.img --reboot

本地升级:

updateEngine --image_url=/userdata/update.img --misc=update --savepath=/userdata/update.img --reboot

可缺省参数:

  • --version_url:远程地址或本地地址,没有设置该参数,则不会进行版本比较;
  • --savepath:固件保存地址,缺省时为/tmp/update.img,建议传入/userdata/update.img
  • --misc
    • nowLinux A/B mode: Setting the current partition to bootable,即Linux A/B模式,设置当前分区可引导;
    • otherLinux A/B mode: Setting another partition to bootable,即Linux A/B模式,设置另外一个分区可引导;
    • updateRecovery mode: Setting the partition to be upgraded,即recovey模式,设置要升级的分区;
    • displayDisplay misc info,输出misc信息;
    • wipe_userdataFormat data partition
  • --partition:设置将要升级的分区,比如uboottrustbootrecoveryrootfsoem,不支持升级parameterloader分区;如果没有传入要升级的分区,在recovery模式默认升级uboot/trust/boot/recovery/boot/rootfs/oempartition=0X3F0000
  • --updateUpgrade mode
  • --reboot:升级完成后重启。

具体可以通过updateEngine --h查看。

5.2.3 本地升级测试

接下来我们进行测试,开发板从ubuntu tftp服务器下载统一镜像:

root@rk3588-buildroot:/userdata# tftp -g -l update.img 192.168.0.200

然后执行updateEngine本地升级命令:

root@rk3588-buildroot:/# updateEngine --image_url=/userdata/update.img --misc=update  --savepath=/userdata/update.img --reboot

输出日志:

点击查看代码
LOG_DEBUG: uiTag = 57464b52.
LOG_DEBUG: usSize = 66.
LOG_DEBUG: dwVersion = 1000000.
LOG_DEBUG: btMajor = 1, btMinor = 0, usSmall = 00.
LOG_DEBUG: dwBootOffset = 66.
LOG_DEBUG: dwBootSize = 731c0.
LOG_DEBUG: dwFWOffset = 73226.
LOG_DEBUG: dwFWSize = 33c51004.
LOG_DEBUG: tag = 1178684242
LOG_DEBUG: size = 868552704
LOG_DEBUG: machine_model =  RK3588
LOG_DEBUG: manufacturer =  RK3588
LOG_DEBUG: version = 16777216
LOG_DEBUG: item = 9.
LOG_DEBUG: name = package-file
LOG_DEBUG: file = package-file
LOG_DEBUG: offset = 473638
LOG_DEBUG: flash_offset = -1
LOG_DEBUG: usespace = 1
LOG_DEBUG: size = 222
LOG_DEBUG: name = parameter
LOG_DEBUG: file = parameter.txt
LOG_DEBUG: offset = 475686
LOG_DEBUG: flash_offset = 0
LOG_DEBUG: usespace = 1
LOG_DEBUG: size = 551
LOG_DEBUG: name = bootloader
LOG_DEBUG: file = MiniLoaderAll.bin
LOG_DEBUG: offset = 102
LOG_DEBUG: flash_offset = -1
LOG_DEBUG: usespace = 231
LOG_DEBUG: size = 471488
LOG_DEBUG: name = uboot
LOG_DEBUG: file = uboot.img
LOG_DEBUG: offset = 950822
LOG_DEBUG: flash_offset = 16384
LOG_DEBUG: usespace = 2048
LOG_DEBUG: size = 4194304
LOG_DEBUG: name = misc
LOG_DEBUG: file = misc.img
LOG_DEBUG: offset = 5145126
LOG_DEBUG: flash_offset = 24576
LOG_DEBUG: usespace = 24
LOG_DEBUG: size = 49152
LOG_DEBUG: name = boot
LOG_DEBUG: file = boot.img
LOG_DEBUG: offset = 5194278
LOG_DEBUG: flash_offset = 32768
LOG_DEBUG: usespace = 18371
LOG_DEBUG: size = 37622272
LOG_DEBUG: name = recovery
LOG_DEBUG: file = recovery.img
LOG_DEBUG: offset = 42818086
LOG_DEBUG: flash_offset = 163840
LOG_DEBUG: usespace = 21649
LOG_DEBUG: size = 44336128
LOG_DEBUG: name = rootfs
LOG_DEBUG: file = rootfs.img
LOG_DEBUG: offset = 87155238
LOG_DEBUG: flash_offset = 491520
LOG_DEBUG: usespace = 373248
LOG_DEBUG: size = 764411904
LOG_DEBUG: name = oem
LOG_DEBUG: file = oem.img
LOG_DEBUG: offset = 851567142
LOG_DEBUG: flash_offset = 29851648
LOG_DEBUG: usespace = 8524
LOG_DEBUG: size = 17457152
38eb36336007597e07a22e328536ff78
46a1507531c328bd6cd710c5f7391fe8
46a1507531c328bd6cd710c5f7391fe8

在升级过程中我们需要一直打开串口,等待升级完成。

如果升级失败,此时系统停留在recovery模式,可以通过查看/userdata/recovery/log日志排查失败原因;在recovery模式查看磁盘空间信息,是看不到目录/的挂载点的。

root@rk3588-recovery:/# df -hT
Filesystem           Type            Size      Used Available Use% Mounted on
devtmpfs             devtmpfs        3.8G         0      3.8G   0% /dev
tmpfs                tmpfs           3.9G      4.0K      3.9G   0% /tmp
tmpfs                tmpfs           3.9G    440.0K      3.9G   0% /run
tmpfs                tmpfs           3.9G    252.0K      3.9G   0% /var/log
tmpfs                tmpfs           3.9G         0      3.9G   0% /dev/shm
/dev/block/by-name/userdata
                     ext4           42.9G     76.0K     40.7G   0% /userdata
                                         
root@rk3588-recovery:/# ls -l /usr/bin/recovery
-rwxr-xr-x    1 root     root        103496 Jun 17 17:48 /usr/bin/recovery
root@rk3588-recovery:/# ls -l /usr/bin/updateEngine
-rwxr-xr-x    1 root     root         80544 Jun 17 17:48 /usr/bin/updateEngine
root@rk3588-recovery:/# ls -l /usr/bin/rkupdate
-rwxr-xr-x    1 root     root        106568 Jun 17 17:48 /usr/bin/rkupdate

如果升级成功开发板会自动进入normal模式,查看磁盘空间信息;

root@rk3588-buildroot:/# df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/root      ext4       14G  604M   13G   5% /
devtmpfs       devtmpfs  3.9G  8.0K  3.9G   1% /dev
tmpfs          tmpfs     3.9G  112K  3.9G   1% /tmp
tmpfs          tmpfs     3.9G  552K  3.9G   1% /run
tmpfs          tmpfs     3.9G  304K  3.9G   1% /var/log
tmpfs          tmpfs     3.9G     0  3.9G   0% /dev/shm
/dev/mmcblk0p7 ext4      121M   12M  103M  11% /oem
/dev/mmcblk0p8 ext4       43G   84K   41G   1% /userdata
/dev/sda       fuseblk    29G  898M   28G   4% /mnt/udisk
5.2.4 网络升级测试

我们也可以在ubunut开启http服务,搭建一个web服务;

hengyang@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp# python -m http.server 8080

然后执行updateEngine网络升级命令,比如升级oem分区;

root@rk3588-buildroot:/# updateEngine --image_url=http://192.168.0.200:8080/output/update/Image/update.img --misc=update --partition=oem --savepath=/userdata/update.img --reboot

5.3 恢复出厂设置

命令行运行update程序,设备会进入recovery,并进行格式化,格式化完成之后会自动进入normal系统;

root@rk3588-buildroot:~# ls /usr/bin/update -l
-rwxr-xr-x 1 root root 427740 Jun  9 04:58 /usr/bin/update
root@rk3588-buildroot:~# update 
update: Rockchip Update Tool
update: --wipe_all
command: --wipe_all
update: write command to command file: done
update: write command to misc file: done
update: reboot!

参考文章

[1] Rockchip Linux updateEngine升级方案介绍

[2] Rockchip Linux Recovery升级开发指南

[3] 嵌入式Linux开发之Makefile

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