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

主要可分为

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

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

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

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

五 结论及修复方案

编译前的准备 INSTALLED_RADIOIMAGE_TARGET:

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取值分析》,TARGET_DEVICE_DIR的取值最终是device/******/xxxx

于是在build/target/board/Android.mk中就会将device/******/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取值过程分析》

 

最后调用zipfile.ZipFile创建要最终输出的升级包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(),在其中有

1 2 3 4 5 6 7 8 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 == ".py":           = 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"

在__init__中首先通过kwargs,按照key-value的方式,将 input_zip, input_version等参数的值传给class DeviceSpecificParams对应的同名成员变量

接下来self.extras = OPTIONS.extras, 打印出OPTIONS.extras值为空 在_init_mxxx_module中执行一些mxxx添加的功能

path = OPTIONS.device_specific 将之前在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内置的impfind_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

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

然后在OTA_VerifyEnd中调用 GetRadioFiles,在 GetRadioFiles中读取target-files.zipRADIO下除了filesmap的所有文件并保存到数组中返回给OTA_VerifyEnd中的tgt_files

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

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

= "firmware-update/" + fn common.ZipWriteStr(info.output_zip, f, tf.data) update_list[f] = (dest, destBak, NoneNone)

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

1679 1680 1681 1682 1683 1684 1685 1686 def main(argv):   def option_handler(o, a):     if == "--board_config":       pass   # deprecated     elif in ("-k""--package_key"):       OPTIONS.package_key = a     elif 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

1763 1764 1765 1766 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

1811 1812 1813 1814 1815 1816 1817 1818 1819 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

1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 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

input_zip是后来生成的target target-files.zip,source_zip是之前生成的source target-files.zip,output_zip是要生成的升级包文件

3  在WriteIncrementalOTAPackage中,

build/tools/releasetools/ota_from_target_files.py

1225 1226 1227 1228 1229 1230 1231 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

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 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     = common.Difference(tf, sf)     _, _, d = d.ComputePatch()     # no difference - skip this file     if 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:       = "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:     = "firmware-update/" + fn     common.ZipWriteStr(info.output_zip, f, tf.data)     update_list[f] = (dest, destBak, NoneNone)

对于全量包,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