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

整个分析过程中,机型名以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,  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
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,  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

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值