# Copyright (C) 2009 The Android Open Source Project # Copyright (c) 2011-2013, 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 re bootImages = {} binImages = {} fwImages = {} # 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) 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"] # 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': boot[f] = files[f] elif dotSeparated[extIdx] == 'bin': dest, destBak, x, y = files[f] if dest is not None and dest.startswith("/"): fw[f] = files[f] else: bin[f] = files[f] elif dotSeparated[extIdx] == 'ubi': 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 if info.type == 'MMC': part = "EMMC" else: part = "MTD" 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("%s:%s:%d:%s:%d:%s" % (part, dest, sf.size, sf.sha1, tf.size, tf.sha1)) if destBak is not None: info.script.PatchCheck("%s:%s:%d:%s:%d:%s" % (part, 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("%s:%s:%d:%s:%d:%s" % (part, 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", "%s", "%s", "/firmware");' % (part, 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): if info.type == 'MMC': OTA_VerifyEnd(info, info.target_version, info.target_zip, info.source_zip) return # This function handles only non-HLOS whole partition images def InstallRawImage(type, script, f, dest, tf, sf): if f.endswith('.p'): if type == 'MMC': part = "EMMC" elif type == 'MTD': part = "MTD" script.ApplyPatch("%s:%s:%d:%s:%d:%s" % (part, 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: if type == 'MMC': script.AppendExtra('package_extract_file("%s", "%s");' % (f, dest)) elif type == 'MTD': script.AppendExtra('write_raw_image(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(type, script, files): if type == 'MMC': 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(type, 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(type, 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");') elif type == 'MTD': for f in files: dest, _, tf, sf = files[f] InstallRawImage(type, script, f, dest, tf, sf) return # This function handles only non-HLOS bin images def InstallBinImages(type, script, files): for f in files: dest, _, tf, sf = files[f] InstallRawImage(type, 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(type, script, files): last_mounted = "" fs_type = "vfat" if type == 'MMC': part = "EMMC" elif type == 'MTD': part = "MTD" 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("%s", "%s", "%s", "/firmware");' % (fs_type, part, 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.type, info.script, bootImages) if binImages != {}: InstallBinImages(info.type, info.script, binImages) if fwImages != {}: InstallFwImages(info.type, info.script, fwImages) return def FullOTA_InstallEnd(info): if OTA_VerifyEnd(info, info.input_version, info.input_zip): OTA_InstallEnd(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): if info.type == 'MMC': IncrementalOTA_InstallEnd_MMC(info) elif info.type == 'MTD': IncrementalOTA_InstallEnd_MTD(info) return