axolotl-shiftos: import qcom releasetools as extension
This allows us to update radio images via OTA.
Change-Id: Icfe51a2d9861e69e44afa97b1b11d73c15f11847
Signed-off-by: Alexander Martinz <amartinz@shiftphones.com>
diff --git a/BoardConfig.mk b/BoardConfig.mk
index 0270328..9ae2331 100644
--- a/BoardConfig.mk
+++ b/BoardConfig.mk
@@ -4,6 +4,7 @@
# SPDX-License-Identifier: Apache-2.0
#
+# Kernel
ifeq ($(TARGET_BUILD_VARIANT),eng)
TARGET_KERNEL_CONFIG := axolotl_eng_defconfig
else
@@ -13,3 +14,26 @@
TARGET_KERNEL_CONFIG := axolotl_user_defconfig
endif
endif
+
+# Radio
+ADD_RADIO_FILES := true
+AB_OTA_PARTITIONS += \
+ ImageFv \
+ abl \
+ aop \
+ bluetooth \
+ cmnlib \
+ cmnlib64 \
+ devcfg \
+ dsp \
+ hyp \
+ keymaster \
+ modem \
+ qupfw \
+ storsec \
+ tz \
+ xbl \
+ xbl_config \
+
+# Releasetools
+TARGET_RELEASETOOLS_EXTENSIONS := device/shift/axolotl/shiftos
diff --git a/releasetools.py b/releasetools.py
new file mode 100755
index 0000000..6e93852
--- /dev/null
+++ b/releasetools.py
@@ -0,0 +1,412 @@
+# Copyright (C) 2009 The Android Open Source Project
+# Copyright (c) 2011-2013, 2020 The Linux Foundation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Emit commands needed for QCOM devices during OTA installation
+(installing the radio image)."""
+
+import common
+import os
+import re
+
+
+bootImages = {}
+binImages = {}
+fwImages = {}
+
+# List of images that we pick from IMAGES/ in target-files.
+target_files_IMAGES_list = ["dtbo.img", "vbmeta.img", "vbmeta_system.img", "vbmeta_vendor.img"]
+
+# The joined list of user image partitions of source and target builds.
+# - Items should be added to the list if new dynamic partitions are added.
+# - Items should not be removed from the list even if dynamic partitions are
+# deleted. When generating an incremental OTA package, this script needs to
+# know that an image is present in source build but not in target build.
+USERIMAGE_PARTITIONS = [
+ "product",
+ "system_ext",
+ "odm",
+]
+
+# Parse filesmap file containing firmware residing places
+def LoadFilesMap(zip, name="RADIO/filesmap"):
+ try:
+ data = zip.read(name)
+ except KeyError:
+ print "Warning: could not find %s in %s." % (name, zip)
+ data = ""
+ d = {}
+ for line in data.split("\n"):
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+ pieces = line.split()
+ if not (len(pieces) == 2):
+ raise ValueError("malformed filesmap line: \"%s\"" % (line,))
+ d[pieces[0]] = pieces[1]
+ return d
+
+
+# Read firmware images from target files zip
+def GetRadioFiles(z):
+ out = {}
+ for info in z.infolist():
+ f = info.filename
+ if f.startswith("RADIO/") and (f.__len__() > len("RADIO/")):
+ fn = f[6:]
+ if fn.startswith("filesmap"):
+ continue
+ data = z.read(f)
+ out[fn] = common.File(f, data)
+
+ # This is to include vbmeta,dtbo images from IMAGES/ folder.
+ if f.startswith("IMAGES/") and (f.__len__() > len("IMAGES/")):
+ fn = f[7:]
+ if (fn in target_files_IMAGES_list):
+ data = z.read(f)
+ out[fn] = common.File(f, data)
+
+ return out
+
+
+# Get firmware residing place from filesmap
+def GetFileDestination(fn, filesmap):
+ # if file is encoded disregard the .enc extention
+ if fn.endswith('.enc'):
+ fn = fn[:-4]
+
+ # get backup destination as well if present
+ backup = None
+ if fn + ".bak" in filesmap:
+ backup = filesmap[fn + ".bak"]
+
+ # Assert if an image belonging to target_files_IMAGES_list is not found in filesmap
+ # but found in IMAGES/ as these are critical images like vbmeta/dtbo etc.
+ if fn in target_files_IMAGES_list and fn not in filesmap:
+ raise common.ExternalError("Filesmap entry for "+ fn +" missing !!")
+
+ # If full filename is not specified in filesmap get only the name part
+ # and look for this token
+ if fn not in filesmap:
+ fn = fn.split(".")[0] + ".*"
+ if fn not in filesmap:
+ print "warning radio-update: '%s' not found in filesmap" % (fn)
+ return None, backup
+ return filesmap[fn], backup
+
+
+# Separate image types as each type needs different handling
+def SplitFwTypes(files):
+ boot = {}
+ bin = {}
+ fw = {}
+
+ for f in files:
+ extIdx = -1
+ dotSeparated = f.split(".")
+ while True:
+ if dotSeparated[extIdx] != 'p' and dotSeparated[extIdx] != 'enc':
+ break
+ extIdx -= 1
+
+ if dotSeparated[extIdx] == 'mbn' or dotSeparated[extIdx] == 'elf' or dotSeparated[extIdx] == 'img':
+ boot[f] = files[f]
+ elif dotSeparated[extIdx] == 'bin':
+ bin[f] = files[f]
+ else:
+ fw[f] = files[f]
+ return boot, bin, fw
+
+
+# Prepare radio-update files and verify them
+def OTA_VerifyEnd(info, api_version, target_zip, source_zip=None):
+ if api_version < 3:
+ print "warning radio-update: no support for api_version less than 3"
+ return False
+
+ print "Loading radio filesmap..."
+ filesmap = LoadFilesMap(target_zip)
+ if filesmap == {}:
+ print "warning radio-update: no or invalid filesmap file found"
+ return False
+
+ print "Loading radio target..."
+ tgt_files = GetRadioFiles(target_zip)
+ if tgt_files == {}:
+ print "warning radio-update: no radio images in input target_files"
+ return False
+
+ 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)
+
+ global bootImages
+ global binImages
+ global fwImages
+ bootImages, binImages, fwImages = SplitFwTypes(update_list)
+
+ # If there are incremental patches verify them
+ if largest_source_size != 0:
+ info.script.Comment("---- radio update verification ----")
+ info.script.Print("Verifying radio-update...")
+
+ for f in bootImages:
+ dest, destBak, tf, sf = bootImages[f]
+ # Not incremental
+ if sf is None:
+ continue
+ info.script.PatchCheck("EMMC:%s:%d:%s:%d:%s" %
+ (dest, sf.size, sf.sha1, tf.size, tf.sha1))
+ if destBak is not None:
+ info.script.PatchCheck("EMMC:%s:%d:%s:%d:%s" %
+ (destBak, sf.size, sf.sha1, tf.size, tf.sha1))
+ for f in binImages:
+ dest, destBak, tf, sf = binImages[f]
+ # Not incremental
+ if sf is None:
+ continue
+ info.script.PatchCheck("EMMC:%s:%d:%s:%d:%s" %
+ (dest, sf.size, sf.sha1, tf.size, tf.sha1))
+
+ last_mounted = ""
+ for f in fwImages:
+ dest, destBak, tf, sf = fwImages[f]
+ # Not incremental
+ if sf is None:
+ continue
+ # Get the filename without the path and the patch (.p) extention
+ f = f.split("/")[-1][:-2]
+ # Parse filesmap destination paths for "/dev/" pattern in the beginng.
+ # This would mean that the file must be written to block device -
+ # fs mount needed
+ if dest.startswith("/dev/"):
+ if last_mounted != dest:
+ info.script.AppendExtra('unmount("/firmware");')
+ info.script.AppendExtra('mount("vfat", "EMMC", "%s", "/firmware");' %
+ (dest))
+ last_mounted = dest
+ dest = "/firmware/image/" + f
+ else:
+ dest = dest + "/" + f
+ info.script.PatchCheck(dest, tf.sha1, sf.sha1)
+
+ info.script.CacheFreeSpaceCheck(largest_source_size)
+ return True
+
+
+def FullOTA_Assertions(info):
+ #TODO: Implement device specific asserstions.
+ return
+
+
+def IncrementalOTA_Assertions(info):
+ #TODO: Implement device specific asserstions.
+ return
+
+
+def IncrementalOTA_VerifyEnd(info):
+ OTA_VerifyEnd(info, info.target_version, info.target_zip, info.source_zip)
+ return
+
+
+# This function handles only non-HLOS whole partition images
+def InstallRawImage(script, f, dest, tf, sf):
+ if f.endswith('.p'):
+ script.ApplyPatch("EMMC:%s:%d:%s:%d:%s" %
+ (dest, sf.size, sf.sha1, tf.size, tf.sha1),
+ "-", tf.size, tf.sha1, sf.sha1, f)
+ elif f.endswith('.enc'):
+ # Get the filename without the path
+ fn = f.split("/")[-1]
+ script.AppendExtra('package_extract_file("%s", "/tmp/%s");' % (f, fn))
+ script.AppendExtra('msm.decrypt("/tmp/%s", "%s");' % (fn, dest))
+ else:
+ script.AppendExtra('package_extract_file("%s", "%s");' % (f, dest))
+ return
+
+
+# This function handles only non-HLOS boot images - files list must contain
+# only such images (aboot, tz, etc)
+def InstallBootImages(script, files):
+ bakExists = False
+ # update main partitions
+ script.AppendExtra('ifelse(msm.boot_update("main"), (')
+ for f in files:
+ dest, destBak, tf, sf = files[f]
+ if destBak is not None:
+ bakExists = True
+ InstallRawImage(script, f, dest, tf, sf)
+ script.AppendExtra('), "");')
+
+ # update backup partitions
+ if bakExists:
+ script.AppendExtra('ifelse(msm.boot_update("backup"), (')
+ for f in files:
+ dest, destBak, tf, sf = files[f]
+ if destBak is not None:
+ InstallRawImage(script, f, destBak, tf, sf)
+ script.AppendExtra('), "");')
+ # just finalize primary update stage
+ else:
+ script.AppendExtra('msm.boot_update("backup");')
+
+ # finalize partitions update
+ script.AppendExtra('msm.boot_update("finalize");')
+ return
+
+
+# This function handles only non-HLOS bin images
+def InstallBinImages(script, files):
+ for f in files:
+ dest, _, tf, sf = files[f]
+ InstallRawImage(script, f, dest, tf, sf)
+ return
+
+
+# This function handles only non-HLOS firmware files that are not whole
+# partition images (modem, dsp, etc)
+def InstallFwImages(script, files):
+ last_mounted = ""
+
+ for f in files:
+ dest, _, tf, sf = files[f]
+ # Get the filename without the path
+ fn = f.split("/")[-1]
+ # Parse filesmap destination paths for "/dev/" pattern in the beginng.
+ # This would mean that the file must be written to block device -
+ # fs mount needed
+ if dest.startswith("/dev/"):
+ if last_mounted != dest:
+ script.AppendExtra('unmount("/firmware");')
+ script.AppendExtra('mount("vfat", "EMMC", "%s", "/firmware");' %
+ (dest))
+ last_mounted = dest
+ dest = "/firmware/image/" + fn
+ else:
+ dest = dest + "/" + fn
+
+ if f.endswith('.p'):
+ script.ApplyPatch(dest[:-2], "-", tf.size, tf.sha1, sf.sha1, f)
+ elif f.endswith('.enc'):
+ script.AppendExtra('package_extract_file("%s", "/tmp/%s");' % (f, fn))
+ script.AppendExtra('msm.decrypt("/tmp/%s", "%s");' % (fn, dest[:-4]))
+ else:
+ script.AppendExtra('package_extract_file("%s", "%s");' % (f, dest))
+
+ if last_mounted != "":
+ script.AppendExtra('unmount("/firmware");')
+ return
+
+
+def OTA_InstallEnd(info):
+ print "Applying radio-update script modifications..."
+ info.script.Comment("---- radio update tasks ----")
+ info.script.Print("Patching firmware images...")
+
+ if bootImages != {}:
+ InstallBootImages(info.script, bootImages)
+ if binImages != {}:
+ InstallBinImages(info.script, binImages)
+ if fwImages != {}:
+ InstallFwImages(info.script, fwImages)
+ return
+
+
+def FullOTA_InstallEnd_MMC(info):
+ if OTA_VerifyEnd(info, info.input_version, info.input_zip):
+ OTA_InstallEnd(info)
+ return
+
+
+def FullOTA_InstallEnd_MTD(info):
+ print "warning radio-update: radio update for NAND devices not supported"
+ return
+
+
+def FullOTA_InstallEnd(info):
+ FullOTA_InstallEnd_MMC(info)
+ return
+
+def IncrementalOTA_InstallEnd_MMC(info):
+ OTA_InstallEnd(info)
+ return
+
+
+def IncrementalOTA_InstallEnd_MTD(info):
+ print "warning radio-update: radio update for NAND devices not supported"
+ return
+
+def IncrementalOTA_InstallEnd(info):
+ IncrementalOTA_InstallEnd_MMC(info)
+ return
+
+def GetUserImages(input_tmp, input_zip):
+ return {partition: common.GetUserImage(partition, input_tmp, input_zip)
+ for partition in USERIMAGE_PARTITIONS
+ if os.path.exists(os.path.join(input_tmp,
+ "IMAGES", partition + ".img"))}
+
+def FullOTA_GetBlockDifferences(info):
+ images = GetUserImages(info.input_tmp, info.input_zip)
+ return [common.BlockDifference(partition, image)
+ for partition, image in images.items()]
+
+def IncrementalOTA_GetBlockDifferences(info):
+ source_images = GetUserImages(info.source_tmp, info.source_zip)
+ target_images = GetUserImages(info.target_tmp, info.target_zip)
+
+ # Use EmptyImage() as a placeholder for partitions that will be deleted.
+ for partition in source_images:
+ target_images.setdefault(partition, common.EmptyImage())
+
+ # Use source_images.get() because new partitions are not in source_images.
+ return [common.BlockDifference(partition, target_image, source_images.get(partition))
+ for partition, target_image in target_images.items()]