首页 > 其他分享 >ota升级包编译过程中firmware如何添加进来

ota升级包编译过程中firmware如何添加进来

时间:2023-09-06 13:06:47浏览次数:49  
标签:files target zip ota firmware 升级包 OPTIONS TARGET


整个分析过程中,机型名以xxxx为例

主要可分为

一 firmware如何添加进target-files.zip

二 编译ota升级包时如何从target-files.zip取出firmware并添加到ota升级包

三 如何向升级脚本updater-script中加入控制firmware升级的语句

四 增量升级包相比全量包不同的步骤

五 结论及修复方案

INSTALLED_RADIOIMAGE_TARGET:

device/******/xxxx/下的AndroidBoard.mk

1  android编译系统如何将device/******/xxxx/下的AndroidBoard.mk包含进来

在 build/core/main.mk中,会根据用户编译条件,选择Android源码中不同的目录,将编译目录中的所有Android.mk文件包含进来,


#          


           # Include all of the makefiles in the system          


           #          


           # Can't use first-makefiles-under here because          


           # --mindepth=2 makes the prunes not work.          


           subdir_makefiles := \          


           $(shell build           /tools/findleaves           .py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)          


                      


                      


           $(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(           eval            include $(mk)))


在build/target/board/Android.mk中有:


#          


           # Set up product-global definitions and include product-specific rules.          


           #          


           -include $(TARGET_DEVICE_DIR)           /AndroidBoard           .mk


TARGET_DEVICE_DIR的获取:

TARGET_DEVICE_DIR的取值最终是device/******/xxxx

******/xxxx/下的AndroidBoard.mk包含进来,AndroidBoard.mk中包含了target  INSTALLED_RADIOIMAGE_TARGET,将升级需要的所有firmware 作为了它的object file。

 

一 firmware如何添加进target-files.zip

生成target-files.zip过程中如何添加firmware

1 命令make target-files-package

build/core/Makefile


.PHONY: target-files-package          


           target-files-package: $(BUILT_TARGET_FILES_PACKAGE)          


                      


                      


           name := $(TARGET_PRODUCT)          


           ifeq ($(TARGET_BUILD_TYPE),debug)          


           name := $(name)_debug          


           endif          


           name := $(name)-target_files-$(FILE_NAME_TAG)          


                      


                      


           intermediates := $(call intermediates-           dir           -           for           ,PACKAGING,target_files)          


           BUILT_TARGET_FILES_PACKAGE := $(intermediates)/$(name).zip          


           $(BUILT_TARGET_FILES_PACKAGE): intermediates := $(intermediates)          


           $(BUILT_TARGET_FILES_PACKAGE): \          


           zip_root := $(intermediates)/$(name)


FILE_NAME_TAG取值为编译时的选项 ,如user,eng,userdebug,再加上USER环境变量,因此以编译机型xxx的eng版本为例,name = xxxx-target_files-eng.username

intermediates通过调用 intermediates-dir-for,返回值为out/target/product/xxxx/obj/PACKAGING/target_files_intermediates/

因此 zip_root取值为  out/target/product/xxxx/obj/PACKAGING/target_files_intermediates/xxxx-target_files-eng.username

 

接下来有:


$(BUILT_TARGET_FILES_PACKAGE): \          


           $(INSTALLED_BOOTIMAGE_TARGET) \          


           $(INSTALLED_RADIOIMAGE_TARGET) \          


           $(INSTALLED_RECOVERYIMAGE_TARGET) \          


           $(INSTALLED_SYSTEMIMAGE) \          


           $(INSTALLED_USERDATAIMAGE_TARGET) \          


           $(INSTALLED_CACHEIMAGE_TARGET) \          


           $(INSTALLED_VENDORIMAGE_TARGET) \          


           $(INSTALLED_ANDROID_INFO_TXT_TARGET) \          


           $(SELINUX_FC) \          


           $(built_ota_tools) \          


           $(APKCERTS_FILE) \          


           $(HOST_OUT_EXECUTABLES)           /fs_config            \          


           | $(ACP)          


           @           echo            "Package target files: $@"          


           $(hide)            rm            -rf $@ $(zip_root)          


           $(hide)            mkdir            -p $(           dir            $@) $(zip_root)          


           @           # Components of the recovery image          


           $(hide)            mkdir            -p $(zip_root)           /RECOVERY          


           $(hide) $(call package_files-copy-root, \          


           $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)           /RECOVERY/RAMDISK           )          


           ……          


           ……          


                      


                      


           $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),\          


           mkdir            -p $(zip_root)           /RADIO           ; \          


           $(ACP) $(t) $(zip_root)           /RADIO/           $(notdir $(t));)


在foreach循环中,将 INSTALLED_RADIOIMAGE_TARGET依赖的所有firmware文件都拷贝到(zip_root)/RADIO路径下。

最后将所有文件拷贝完成之后有:


@           # Zip everything up, preserving symlinks          


           $(hide) (           cd            $(zip_root) && zip -qry ../$(notdir $@) .)


这样就将xxxx-target_files-eng.username下的所有文件压缩成了ota升级包文件xxxx-target_files-eng.username.zip,其中所有的firmware就在压缩文件的RADIO目录下。

 

 

二 编译ota升级包时如何从target-files.zip取出firmware并添加到ota升级包

1 前期准备操作

编译全量包的命令为make otapackage

在builld/core/Makefile中有:


.PHONY: otapackage          


           otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)          


           所以全量包的生成依赖于INTERNAL_OTA_PACKAGE_TARGET,          


           同时在Makefile中还有:          


           # -----------------------------------------------------------------          


           # OTA update package          


                      


                      


           name := $(TARGET_PRODUCT)          


           ifeq ($(TARGET_BUILD_TYPE),debug)          


           name := $(name)_debug          


           endif          


           name := $(name)-ota-$(FILE_NAME_TAG)          


                      


                      


           INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip          


                      


                      


           $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)          


                      


                      


           $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(DISTTOOLS)          


           @           echo            "Package OTA: $@"          


           PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \          


           $(call build-ota-package, -           v            \          


           --block \          


           -p $(HOST_OUT) \          


           -k $(KEY_CERT_PAIR) \          


           $(           if            $(OEM_OTA_CONFIG), -o $(OEM_OTA_CONFIG)))


说明$(INTERNAL_OTA_PACKAGE_TARGET)依赖于$(BUILT_TARGET_FILES_PACKAGE)。

根据之前的分析,目标$(BUILT_TARGET_FILES_PACKAGE)的生成依赖的规则中有$(INSTALLED_RADIOIMAGE_TARGET),因此线刷包的生成依赖的object file中有代表firmware的INSTALLED_RADIOIMAGE_TARGET,因此在编译升级包之前会生成AndroidBoard.mk中定义的INSTALLED_RADIOIMAGE_TARGET

在build/core/Makefile中添加调试语句


$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(DISTTOOLS)          


           @           echo            "Package OTA: $@"          


           @           # MOD:          


           @           echo            "make otapackage go here"          


           PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \          


           $(call build-ota-package, -           v            \          


           --block \          


           -p $(HOST_OUT) \          


           -k $(KEY_CERT_PAIR) \          


           $(           if            $(OEM_OTA_CONFIG), -o $(OEM_OTA_CONFIG)))          


                      


                      


           .PHONY: otapackage          


           echo            "make otapackage"          


           otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)


执行时会输出make otapackage,make otapackage go here,同时@echo "Package OTA: $@"还会输出Package OTA: out/target/product/xxxx/xxxx-ota-eng.username.zip,说明当前的target($@)就是out/target/product/xxxx/xxxx-ota-eng.username.zip 验证了之前的推断

 

为了产生target依赖的object file $(INTERNAL_OTA_PACKAGE_TARGET),接下来会通过call调用函数build-ota-package,经过查找,认为这个函数是定义在xxxx/build下的definitions.mk中,为了验证,

在xxxx/build下的definitions.mk中修改函数build-ota-package:


# $(1) - parameters          


           define build-ota-package          


           @           echo            "call build-ota-package in Makefile go here!"          


           ORIGINAL_OTA_FROM_TARGET_FILES_TOOL=build           /tools/releasetools/ota_from_target_files            \          


           TARGET_FILES_PACKAGE=$(BUILT_TARGET_FILES_PACKAGE) \          


           xxxx_HAS_xxxx_PARTITION=$(xxxx_HAS_xxxx_PARTITION) \          


           OTA_KEEP_FILE_LIST_xxxx=xxxx           /build/ota_keep_xxxx_file_list            \          


           OTA_KEEP_FILE_LIST_DATA_xxxx=xxxx           /build/ota_keep_data_xxxx_file_list            \          


           TARGET_OTA_FILE=$@ \          


           $(xxxx_OTA_FROM_TARGET_FILES_TOOL) $(strip $(1))          


           endef


发现在编译全两包时,输出log中依次有:

make otapackage go here

PATH=out/host/linux-x86/bin/:$PATH MKBOOTIMG=out/host/linux-x86/bin/mkbootimg \

@echo "call build-ota-package in Makefile go here!"

其中PATH=out/host/linux-x86/bin/:$PATH MKBOOTIMG=out/host/linux-x86/bin/mkbootimg \对应于PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \

因此可以肯定,接下来会执行xxxx/build/definitions.mk下的函数build-ota-package

在build-ota-package中添加调试语句,有


ORIGINAL_OTA_FROM_TARGET_FILES_TOOL=build           /tools/releasetools/ota_from_target_files          


           TARGET_FILES_PACKAGE=out           /target/product/xxxx/obj/PACKAGING/target_files_intermediates/xxxx-target_files-eng           .username.zip          


           XXXX_HAS_CXXX_PARTITION=           true          


           OTA_KEEP_FILE_LIST_CXXX=xxxx           /build/ota_keep_cxxx_file_list          


           OTA_KEEP_FILE_LIST_DATA_MXXX=xxxx           /build/ota_keep_data_mxxx_file_list          


           TARGET_OTA_FILE=out           /target/product/xxxx/xxxx-ota-eng           .username.zip          


           MXXX_OTA_FROM_TARGET_FILES_TOOL=mxxx           /build/ota_from_target_files           .sh


因此在函数最后会执行shell脚本ota_from_target_files.sh,其中输入参数$(1)通过在build/core/Makefile中调用build-ota-package时传入,可以在build-ota-package中通过echo "$(1)"输出,

-v --block -p out/host/linux-x86 -k build/target/product/security/testkey

因此接下来就会执行:

mxxx/build/ota_from_target_files.sh -v --block -p out/host/linux-x86 -k build/target/product/security/testkey

shell脚本ota_from_target_files.sh:

在shell脚本ota_from_target_files.sh中,首先执行print_global_variables打印一些变量

 


ORIGINAL_OTA_FROM_TARGET_FILES_TOOL=build           /tools/releasetools/ota_from_target_files          


                      


           TARGET_FILES_PACKAGE=out           /target/product/xxxx/obj/PACKAGING/target_files_intermediates/xxxx-target_files-eng           .username.zip          


                      


           INCREMENTAL_TARGET_FILES_PACKAGE=          


                      


           TARGET_OTA_FILE=out           /target/product/xxxx/xxxx-ota-eng           .username.zip          


                      


           MXXX_HAS_CXXX_PARTITION=           true          


                      


           OTA_KEEP_FILE_LIST_DATA_MXXX=mxxx           /build/ota_keep_data_mxxx_file_list          


                      


           OTA_KEEP_FILE_LIST_CXXX=mxxx           /build/ota_keep_cxxx_file_list


然后执行check_global_variables,检查ORIGINAL_OTA_FROM_TARGET_FILES_TOOL、TARGET_FILES_PACKAGE、TARGET_OTA_FILE这个三变量是否为空,任何一个为空就中断编译。

在ota_from_target_files.sh脚本的最后,会根据INCREMENTAL_TARGET_FILES_PACKAGE值是否为空,来选择执行build/tools/releasetools/ota_from_target_files时是否添加参数-i,也就是说会根据INCREMENTAL_TARGET_FILES_PACKAGE的值来决定生成增量包还是全量包。

现在INCREMENTAL_TARGET_FILES_PACKAGE为空,打印出接下来脚本执行的命令为:

build/tools/releasetools/ota_from_target_files -v --block -p out/host/linux-x86 -k build/target/product/security/testkey /tmp/tmp.mxxx.target_files_package.ota.eOL/new-target-files.zip out/target/product/xxxx/xxxx-ota-eng.username.zip

 

在 ota_from_target_files执行完后会判断它的返回值,如果返回值为0则认为生成失败,输出Failed to genrate otapackage并退出

3 ota_from_target_files.py

ota_from_target_files.py负责产生升级包的升级脚本,这里主要分析与添加firmware有关的必要操作。

在ota_from_target_files.py的main中,首先将传入的第一个参数,也就是target-files.zip文件解压:

OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])

根据common.UnzipTemp的返回值,OPTIONS.input_tmp就代表target-files.zip解压的临时文件夹,input_zip 代表用zipfile.ZipFile(filename, "r")打开的target-files.zip文件

接下来:


if            OPTIONS.device_specific            is            None           :          


           from_input            =            os.path.join(OPTIONS.input_tmp,            "META"           ,            "releasetools.py"           )          


           if            os.path.exists(from_input):          


           print            "(using device-specific extensions from target_files)"          


           OPTIONS.device_specific            =            from_input          


           else           :          


           OPTIONS.device_specific            =            OPTIONS.info_dict.get(           "tool_extensions"           ,            None           )          


                      


           if            OPTIONS.device_specific            is            not            None           :          


           OPTIONS.device_specific            =            os.path.abspath(OPTIONS.device_specific)


这里首先看调用脚本时是否制定了--device_specific,如果制定了--device_specific的值,则这个值就会被赋予 OPTIONS.device_specific

因为调用参数中没有 device_specific,因此接下来会在target_files.zip解压的临时文件夹下的META/中寻找releasetools.py,如果找到了这个文件,就将这个文件的完整路径传给 OPTIONS.device_specific,如/tmp/targetfiles-hJ2BmE/META/releasetools.py(META前面的路径就是target_files.zip解压的临时文件夹的路径)

如果没有找到就从 OPTIONS.info_dict中寻找,OPTIONS.info_dict是python中的字典映射,这里就是在字典中查找key为 tool_extensions的这个映射的value,打印出OPTIONS.info_dict可知tool_extensions这个key对应的值为device/qcom/common,因此如果在target_files.zip解压的临时文件夹下找不到 releasetools.py,那么 OPTIONS.device_specific的值就为device/qcom/common。OPTIONS.info_dict的tool_extensions对应的value的值的获取参考子页面《OPTIONS.info_dict的tool_extensions取值过程分析》

 

output_zip:


if            OPTIONS.no_signing:          


           if            os.path.exists(args[           1           ]):          


           os.unlink(args[           1           ])          


           output_zip            =            zipfile.ZipFile(args[           1           ],            "w"           ,          


           compression           =           zipfile.ZIP_DEFLATED)          


           else           :          


           temp_zip_file            =            tempfile.NamedTemporaryFile()          


           output_zip            =            zipfile.ZipFile(temp_zip_file,            "w"           ,          


           compression           =           zipfile.ZIP_DEFLATED)


然后将target-files.zip和 output_zip作为参数,调用

WriteFullOTAPackage(input_zip, output_zip)


如果是生成全两包,接下来会执行WriteFullOTAPackage(),在其中有

device_specific            =            common.DeviceSpecificParams(          


                      input_zip           =           input_zip,          


                      input_version           =           OPTIONS.info_dict[           "recovery_api_version"           ],          


                      output_zip           =           output_zip,          


                      script           =           script,          


                      input_tmp           =           OPTIONS.input_tmp,          


                      metadata           =           metadata,          


                      info_dict           =           OPTIONS.info_dict)


这里创建了一个 common.py中的class DeviceSpecificParams的实例,因此会执行 DeviceSpecificParams的__init__方法


def            __init__(           self           ,            *           *           kwargs):          


                      """Keyword arguments to the constructor become attributes of this          


                      object, which is passed to all functions in the device-specific          


                      module."""          


                      for            k, v            in            kwargs.iteritems():          


                      setattr           (           self           , k, v)          


                      self           .extras            =            OPTIONS.extras          


                      self           ._init_mxxx_module()          


                      if            self           .module            is            None           :          


                      print            "0--"          


                      path            =            OPTIONS.device_specific          


                      print           (path)          


                      if            not            path:          


                      return          


                      try           :          


                      if            os.path.isdir(path):          


                      info            =            imp.find_module(           "releasetools"           , [path])          


                      print           (info)          


                      else           :          


                      d, f            =            os.path.split(path)          


                      b, x            =            os.path.splitext(f)          


                      if            x            =           =            ".py"           :          


                      f            =            b          


                      info            =            imp.find_module(f, [d])          


                      print            "loaded device-specific extensions from"           , path          


                      self           .module            =            imp.load_module(           "device_specific"           ,            *           info)          


                      except            ImportError:          


                      print            "unable to load device-specific module; assuming none"


class DeviceSpecificParams对应的同名成员变量

_init_mxxx_module中执行一些mxxx添加的功能

ota_from_target_files中获得的OPTIONS.device_specific的值传给path,并判断path是否为目录

如果之前OPTIONS.device_specific取值是target-files.zip解压的临时文件夹下的releasetools.py,如/tmp/targetfiles-hJ2BmE/META/releasetools.py,那么这里分割出releasetools.py的路径,文件名,扩展名。如果OPTIONS.device_specific取值为device/x

*****/mxxx的完整路径,那么


os.path.isdir(path)取值为true。然后在device/x*****/mxxx下查找releasetools文件。


最后调用python内置的imp的 find_module方法,将releasetools文件作为一个名为device_specific的模块加载,并传给self.module,也就是device_specific.module


5  device_specific.FullOTA_InstallEnd()

在WriteFullOTAPackage中接下来执行:

device_specific.FullOTA_InstallEnd()

这最终会调用class DeviceSpecificParams的_DoCall,输入参数中只有function_name为“FullOTA_InstallEnd”,args与kwargs均为空。在 FullOTA_InstallEnd中:


def            _DoCall(           self           , function_name,            *           args,            *           *           kwargs):          


                      run_default            =            True          


                      if            self           .module            is            not            None            and            hasattr           (           self           .module, function_name):          


                      run_default            =            False          


                      ret            =            getattr           (           self           .module, function_name)(           *           ((           self           ,)            +            args),            *           *           kwargs)          


                      if            self           .mxxx_module            is            not            None            and            hasattr           (           self           .mxxx_module, function_name):          


                      getattr           (           self           .mxxx_module, function_name)(           *           ((           self           ,)            +            args),            *           *           kwargs)          


                      if            run_default:          


                      return            kwargs.get(           "default"           ,            None           )          


                      else           :          


                      return            ret          


                      #END


因为self.module是从releasetools文件加载的名为device_specific模块,打开target-files.zip的META/releasetools.py文件,可以发现里面有名为 FullOTA_InstallEnd的函数。由于args与kwargs均为空,因此接下来执行 ret = getattr(self.module, function_name)(*((self,) + args), **kwargs)

就是调用releasetools.py这个文件中的 FullOTA_InstallEnd,并且将ota_from_target_files中定义的device-specific这个class DeviceSpecificParams类型的object传入。最后 FullOTA_InstallEnd执行的返回值传给ret

 

releasetools中的 FullOTA_InstallEnd:


def            FullOTA_InstallEnd_MMC(info):          


                      if            OTA_VerifyEnd(info, info.input_version, info.input_zip):          


                      OTA_InstallEnd(info)          


                      return          





           def            FullOTA_InstallEnd(info):          


                      FullOTA_InstallEnd_MMC(info)          


                      return


首先调用 OTA_VerifyEnd,输出参数中info就是ota_from_target_files中定义的device-specific这个class DeviceSpecificParams类型的object,在初始化object后,info.input_version 为3,input_zip取值为zipfile.ZipFile,就代表的是target_files.zip这个zip文件。

在 OTA_VerifyEnd中,先调用LoadFilesMap

target_files.zip中读取 RADIO/filesmap文件,判断文件的每一行,去掉其中的空行和以#开头的注释,剩余的行就是有效行,将其中第一列作为key,第二列作为value保存到字典d中,如果某一行分割后列数不为2,就使用raise抛出 ValueError异常,最后将获得的字典d返回给OTA_VerifyEnd。

GetRadioFiles中读取target-files.zip内RADIO下除了filesmap的所有文件并保存到数组中返回给OTA_VerifyEnd中的tgt_files

然后返回OTA_VerifyEnd,在一个for循环中,依次从tgt_files中取出每一个firmware调用GetFileDestination,GetFileDestination会取出每个文件在filesmap中对应的分区并返回给dest,destBak。

GetFileDestination 执行完返回到OTA_VerifyEnd中,在for循环继续调用


f            =            "firmware-update/"            +            fn          


           common.ZipWriteStr(info.output_zip, f, tf.data)          


           update_list[f]            =            (dest, destBak,            None           ,            None           )


common.ZipWriteStr(info.output_zip, f, tf.data),其中tf就是从tgt_files中取出的每一个firmware文件, 这里调用了build/tools/releasetools/common.py下的ZipWriteStr函数,common.ZipWriteStr会调用python的ZipFile模块的writestr函数,这个函数支持将二进制数据直接写入到压缩文档。

因此最终是在这里将每个firmware文件写入到 info.output_zip的 firmware-update/文件夹中,也就是最初在ota_from_target_files的main中创建的最终输出的升级包的firmware-update/下。

然后声明3个全局变量bootImages,binImages,fwImages,分别作为函数SplitFwTypes的返回值,

在SplitFwTypes中,将filesmap中第一列的所有文件中,去掉后缀名为.p或者.enc的文件, 剩下的分为3类,bootImages代表后缀名为.mbn或者.enc的文件, binImages代表后缀名为.bin的文件。

 

三  如何向升级脚本updater-script中加入控制firmware升级的语句

OTA_VerifyEnd执行完后,接下来返回FullOTA_InstallEnd_MMC,执行OTA_InstallEnd,在OTA_InstallEnd中,分别执行InstallBootImages,InstallBinImages,InstallFwImages,这三个函数中最终都通过script.AppendExtra,也就是edify_generator.EdifyGenerator的 AppendExtra函数,向控制升级过程的updater-script脚本中输出了形如

package_extract_file("firmware-update/rpm.mbn", "/dev/block/bootdevice/by-name/rpm");

等在升级过程中升级firewater的语句。


四 增量升级包相比全量包不同的步骤

target-files.zip 在编译增量包之前就已经生成,因此第一步firmware如何添加进target-files.zip和全量包的过程相同。

1 以手动编译增量包为例,因为手动编译增量包通过直接调用build/tools/releasetools/ota_from_target_files.py,因此首先从这里分析


build/tools/releasetools/ota_from_target_files.py


def            main(argv):          


                      def            option_handler(o, a):          


                      if            o            =           =            "--board_config"           :          


                      pass              # deprecated          


                      elif            o            in            (           "-k"           ,            "--package_key"           ):          


                      OPTIONS.package_key            =            a          


                      elif            o            in            (           "-i"           ,            "--incremental_from"           ):          


                      OPTIONS.incremental_source            =            a


因为生成增量包时调用ota_from_target_files.py的参数中肯定包含 -i,因此OPTIONS.incremental_source = a

2 假设前后两次生成的target-files.zip分别称为source target-files.zip,target target-files.zip,对target target-files.zip的解压与全量包完全相同:


build/tools/releasetools/ota_from_target_files.py


print            "unzipping target target-files..."          


           OPTIONS.input_tmp, input_zip            =            common.UnzipTemp(args[           0           ])          


           OPTIONS.target_tmp            =            OPTIONS.input_tmp          


           OPTIONS.info_dict            =            common.LoadInfoDict(input_zip)


不论全量包还是增量包,对要生成的升级包文件output_zip的创建方法相同:


build/tools/releasetools/ota_from_target_files.py


if            OPTIONS.no_signing:          


                      if            os.path.exists(args[           1           ]):          


                      os.unlink(args[           1           ])          


                      output_zip            =            zipfile.ZipFile(args[           1           ],            "w"           ,          


                      compression           =           zipfile.ZIP_DEFLATED)          


           else           :          


                      temp_zip_file            =            tempfile.NamedTemporaryFile()          


                      output_zip            =            zipfile.ZipFile(temp_zip_file,            "w"           ,          


                      compression           =           zipfile.ZIP_DEFLATED)


之后根据OPTIONS.incremental_source来决定执行WriteFullOTAPackage还是WriteIncrementalOTAPackage,


build/tools/releasetools/ota_from_target_files.py


if            OPTIONS.incremental_source            is            None           :          


                      WriteFullOTAPackage(input_zip, output_zip)          


                      if            OPTIONS.package_key            is            None           :          


                      OPTIONS.package_key            =            OPTIONS.info_dict.get(          


                      "default_system_dev_certificate"           ,          


                      "build/target/product/security/testkey"           )          


                      common.ZipClose(output_zip)          


                      break          


           else           :          


                      print            "unzipping source target-files..."          


                      OPTIONS.source_tmp, source_zip            =            common.UnzipTemp(          


                      OPTIONS.incremental_source)          


                      OPTIONS.target_info_dict            =            OPTIONS.info_dict          


                      OPTIONS.source_info_dict            =            common.LoadInfoDict(source_zip)          


                      if            "selinux_fc"            in            OPTIONS.source_info_dict:          


                      OPTIONS.source_info_dict[           "selinux_fc"           ]            =            os.path.join(          


                      OPTIONS.source_tmp,            "BOOT"           ,            "RAMDISK"           ,            "file_contexts"           )          


                      if            OPTIONS.package_key            is            None           :          


                      OPTIONS.package_key            =            OPTIONS.source_info_dict.get(          


                      "default_system_dev_certificate"           ,          


                      "build/target/product/security/testkey"           )          


                      if            OPTIONS.verbose:          


                      print            "--- source info ---"          


                      common.DumpInfoDict(OPTIONS.source_info_dict)          


                      try           :          


                      WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)          


                      common.ZipClose(output_zip)          


                      break          


                      except            ValueError:          


                      if            not            OPTIONS.fallback_to_full:          


                      raise          


                      print            "--- failed to build incremental; falling back to full ---"          


                      OPTIONS.incremental_source            =            None          


                      common.ZipClose(output_zip)


对于增量包:

用同样的方法先后读取两个target-files.zip中的META/misc_info.txt,分别保存为OPTIONS.source_info_dict,OPTIONS.target_info_dict,如果调用脚本时有-v参数,就会打印出OPTIONS.source_info_dict

要生成的升级包文件

3  在WriteIncrementalOTAPackage中,


build/tools/releasetools/ota_from_target_files.py

def            WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):          


                      target_has_recovery_patch            =            HasRecoveryPatch(target_zip)          


                      source_has_recovery_patch            =            HasRecoveryPatch(source_zip)          


                      if            (OPTIONS.block_based            and          


                      target_has_recovery_patch            and          


                      source_has_recovery_patch):          


                      return            WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)


如果生成增量包时的参数中有--block,并且两个target-files.zip都包含SYSTEM/recovery-from-boot.p,那么实际调用WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)。如果条件不满足,执行WriteIncrementalOTAPackage(target_zip, source_zip, output_zip)

下来以WriteBlockIncrementalOTAPackage为例分析。

4 对于增量包,在WriteBlockIncrementalOTAPackage中对class DeviceSpecificParams类型的device_specific变量的初始化与全量包一致,都是在common.py的__init__中加载对应路径下的releasetools.py

5 接下来,执行device_specific.IncrementalOTA_VerifyEnd(),与增量包相同,还是通过DeviceSpecificParams的_DoCall跳转到releasetools.py的IncrementalOTA_VerifyEnd,在IncrementalOTA_VerifyEnd同样通过调用OTA_VerifyEnd来实现将firmware添加进增量包中。有所区别的是,全量包是OTA_VerifyEnd(info, info.input_version, info.input_zip),增量包调用OTA_VerifyEnd是OTA_VerifyEnd(info, info.target_version, info.target_zip, info.source_zip),相比全量包多一个参数,导致在OTA_VerifyEnd内部的执行流程稍有不同:


releasetools.py的OTA_VerifyEnd


src_files            =            None          


           if            source_zip            is            not            None           :          


                      print            "Loading radio source..."          


                      src_files            =            GetRadioFiles(source_zip)          


           update_list            =            {}          


           largest_source_size            =            0          


           print            "Preparing radio-update files..."          


           for            fn            in            tgt_files:          


                      dest, destBak            =            GetFileDestination(fn, filesmap)          


                      if            dest            is            None           :          


                      continue          


                      tf            =            tgt_files[fn]          


                      sf            =            None          


                      if            src_files            is            not            None           :          


                      sf            =            src_files.get(fn,            None           )          


                      full            =            sf            is            None            or            fn.endswith(           '.enc'           )          


                      if            not            full:          


                      # no difference - skip this file          


                      if            tf.sha1            =           =            sf.sha1:          


                      continue          


                      d            =            common.Difference(tf, sf)          


                      _, _, d            =            d.ComputePatch()          


                      # no difference - skip this file          


                      if            d            is            None           :          


                      continue          


                      # if patch is almost as big as the file - don't bother patching          


                      full            =            len           (d) > tf.size            *            common.OPTIONS.patch_threshold          


                      if            not            full:          


                      f            =            "patch/firmware-update/"            +            fn            +            ".p"          


                      common.ZipWriteStr(info.output_zip, f, d)          


                      update_list[f]            =            (dest, destBak, tf, sf)          


                      largest_source_size            =            max           (largest_source_size, sf.size)          


                      if            full:          


                      f            =            "firmware-update/"            +            fn          


                      common.ZipWriteStr(info.output_zip, f, tf.data)          


                      update_list[f]            =            (dest, destBak,            None           ,            None           )


对于全量包,source_zip不再取默认值none,因此会同样取出source target_files.zip中RADIO下对应的文件。

将从两个target_files.zip中取出的文件分别作为sf,tf,因此full这时为flase,然后在for循环中,先比较它们的sha1,相同就跳过这个文件,否则对比sf和tf的差异并生成补丁d。

如果补丁d的大小与文件大小接近,也就是full = len(d) > tf.size * common.OPTIONS.patch_threshold 这个判断让full重置为true,因此这种情况下之后的流程和全量包的处理完全一样。

如果补丁d的大小没有超过阈值,这种情况下,会将产生的补丁文件写入到升级包的patch/firmware-update目录下。

6 接下来,执行device_specific.IncrementalOTA_InstallEnd(),与增量包相同,还是通过DeviceSpecificParams的_DoCall跳转到releasetools.py的IncrementalOTA_InstallEnd,IncrementalOTA_InstallEnd中继续调用OTA_InstallEnd,由于调用时并没有参数的区别,因此在OTA_InstallEnd中的处理过程与全量包没有什么大的差别,增量包的updater-script中控制firmware升级的语句也是在InstallBootImages,InstallBinImages,InstallFwImages中生成。

 

五 结论及修复方案

综上所述,如果我们现在要将firmware添加到ota升级包中,要做的主要有两步:

一 在编译系统中建立代表所有firmware的target INSTALLED_RADIOIMAGE_TARGET,它依赖于所有升级需要的firmware,同时编译target-files.zip依赖的object也包含它。在Makefile中编写生成这个target的规则,因为在releasetools.py中是通过读取target-files.zip的RADIO/filesmap文件来获取要升级哪些fimware的,所以具体需要的firmware要参考filesmap文件。

二 让编译系统能够找到 真正完成将firmware写入到升级包和在updater-script中输出升级语句的脚本releasetools.py。

以mxxx为例,一种具体可行的解决方案是:

1 在device/x*****/{机型名}/AndroidBoard.mk中添加如下语句:

ifeq ($(ADD_RADIO_FILES), true)
radio_dir := $(LOCAL_PATH)/radio
RADIO_FILES := $(shell cd $(radio_dir) ; ls)
$(foreach f, $(RADIO_FILES), \
$(call add-radio-file,radio/$(f)))
endif

首先调用函数add-radio-file将当前路径下radio下的filesmap文件作为INSTALLED_RADIOIMAGE_TARGET的依赖,这样在生成target-files.zip时,在将INSTALLED_RADIOIMAGE_TARGET依赖的所有文件拷贝到 $(zip_root)/RADIO,也就是out/target/product/mxxx/obj/PACKAGING/target_files_intermediates/mxxx-target_files-eng.username/RADIO下时,不光会拷贝firmware,还会同时将filesmap文件也拷贝过去,这样才能保证以后在releasetools.py中读取target-files.zip的RADIO/filesmap不会出错。

TARGET_BOOTLOADER_EMMC_INTERNAL := $(PRODUCT_OUT)/emmc_appsboot.mbn
$(TARGET_BOOTLOADER_EMMC_INTERNAL): $(INSTALLED_BOOTLOADER_MODULE)
INSTALLED_RADIOIMAGE_TARGET += $(TARGET_BOOTLOADER_EMMC_INTERNAL)


$(call add-radio-file,images/NON-HLOS.bin)
$(call add-radio-file,images/sbl1.mbn)
$(call add-radio-file,images/rpm.mbn)
$(call add-radio-file,images/tz.mbn)
$(call add-radio-file,images/devcfg.mbn)
$(call add-radio-file,images/adspso.bin)
$(call add-radio-file,images/sec.dat)
$(call add-radio-file,images/splash.img)
$(call add-radio-file,images/lksecapp.mbn)
$(call add-radio-file,images/cmnlib.mbn)
$(call add-radio-file,images/cmnlib64.mbn)

然后将当前路径下images下的相关文件,以及  $(PRODUCT_OUT)/emmc_appsboot.mbn 作为INSTALLED_RADIOIMAGE_TARGET的依赖。

 

2  因为在生成target-files.zip中的规则中有:


build/core/Makefile



$(hide)            if            test            -e $(tool_extensions)           /releasetools           .py;            then            $(ACP) $(tool_extensions)           /releasetools           .py $(zip_root)           /META/           ;            fi


所以target-files.zip中的releasetools.py其实也来自于$(tool_extensions)下的releasetools.py。因此,如果TARGET_RELEASETOOLS_EXTENSIONS没有定义,那么我们就要检查 $(TARGET_DEVICE_DIR)/../common这个路径是否存在,且这个路径下是否存在正确的releasetools.py。否则我们可以自定义TARGET_RELEASETOOLS_EXTENSIONS的值,将它设为正确的releasetools.py所在的目录。

根据在 OPTIONS.info_dict的tool_extensions取值过程分析 中的分析,我们可以在device/x*****/mxxx下的BoardConfig.mk中设置它的值,

如 TARGET_RELEASETOOLS_EXTENSIONS := device/x*****/mxxx 或者 TARGET_RELEASETOOLS_EXTENSIONS := device/qcom/common

标签:files,target,zip,ota,firmware,升级包,OPTIONS,TARGET
From: https://blog.51cto.com/u_16248677/7385150

相关文章

  • Android官方资料--Reducing OTA Size
    ReducingOTASizeINTHISDOCUMENTThebuilddifftoolChangestoreduceOTAsizeFileOrderBuildDirectoryTimestampsVersionStringsConsistentbuildtoolsAcommonproblemwithAndroidover-the-airupdates(OTAs)isthattheycontainchangedfilesthatdo......
  • Android官方资料--Block-Based OTAs
    Block-BasedOTAsINTHISDOCUMENTRecommendationsFilevs.BlockOTAsUpdatingunmodifiedsystemsUpdatingmodifiedsystemsYoucanenableblock-basedover-the-air(OTA)updatesfornewdevicesrunningAndroid5.0.OTAisthemechanismbywhichOEMsremote......
  • 输出编译ota升级包时的打包参数
    build / tools/releasetools/common.pydefParseOptions(argv,docstring,extra_opts="",extra_long_opts=(),extra_option_handler=None):"""Parsetheoptionsinargvandreturn......
  • ota升级包中update-script脚本的生成
    控制升级流程的主要逻辑,实际控制着升级过程中大部分重要操作的实施细节,而处于升级包中同目录下的update-bianry负责真正执行update-script记录的操作。 在负责生成升级包的脚本ota_from_target_files.py中,不论我们调用这个脚本来产生全量包,增量包,或者是安卓新加入的block方式的......
  • 20230529 java.lang.reflect.AnnotatedElement
    介绍java.lang.reflect.AnnotatedElementpublicinterfaceAnnotatedElementAPIisAnnotationPresentgetAnnotationgetAnnotationsgetAnnotationsByTypegetDeclaredAnnotationgetDeclaredAnnotationsByTypegetDeclaredAnnotations......
  • Linux日志管理经验总结(crontab+logrotate)
    Linux系统-部署-运维系列导航 日志管理目标日志的管理,一般包括两大部分日志内容,合理的日志内容(日志锚点,内容格式,等)可以为应用服务的执行记录、问题排查提供最有力的帮助日志存档规则,包括日志分割方式(按日期、按文件大小,等),日志存档数量,如只保存最近一个月,等对于自行开发的......
  • idea启动项目报错Error:(5, 52) java: 程序包org.springframework.beans.factory.anno
    idea启动项目报错Error:(5,52)java:程序包org.springframework.beans.factory.annotation不存在IDEA启动项目报错ERROR:(5,52)JAVA:程序包ORG.SPRINGFRAMEWORK.BEANS.FACTORY.ANNOTATION不存在去IDEA下查找maven选项:如果还不行,则继续选择下面的runner,勾选deleteIDEbuild......
  • <mvc:annotation-driven />, <context:annotation-config/>, <context:component-scan
    <mvc:annotation-driven/> 会做以下几件事: 向spring容器中注册DefaultAnnotationHandlerMapping。向spring容器中注册AnnotationMethodHandlerAdapter。配置一些messageconverter。解决了@Controller注解的使用前提配置,即HandlerMapping能够知道谁来处理请求。<cont......
  • <mvc:annotation-driven>和DefaultAnnotationHandlerMapping
    spring配置拦截器有两种方式: <mvc:annotation-driven/><!--*************openSessionInViewInterceptor*************--><beanid="openSessionInViewInterceptor"class="org.springframework.orm.hibernate3.support.OpenSessionInViewInt......
  • DWR的注释(annotations)使用及反向调用(Reverse Ajax)
    先说说注释语法,省掉dwr.xml。(自从用了java5之后,现在越看一堆堆的配置文件越烦,越来越喜欢注释方式来的直接简单了)  首先下载最新的稳定版本的dwr.jar文件放到你的工程中。(还有需要其它的吗?不需要了,dwr就是这么简单)然后在web.xml中添加如下一段<!--DWRServlet--><servle......