1 # Copyright (C) 2009 The Android Open Source Project 2 # Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 """Emit commands needed for QCOM devices during OTA installation 17 (installing the radio image).""" 18 19 import common 20 import re 21 22 23 bootImages = {} 24 binImages = {} 25 fwImages = {} 26 27 28 # Parse filesmap file containing firmware residing places 29 def LoadFilesMap(zip, name="RADIO/filesmap"): 30 try: 31 data = zip.read(name) 32 except KeyError: 33 print "Warning: could not find %s in %s." % (name, zip) 34 data = "" 35 d = {} 36 for line in data.split("\n"): 37 line = line.strip() 38 if not line or line.startswith("#"): 39 continue 40 pieces = line.split() 41 if not (len(pieces) == 2): 42 raise ValueError("malformed filesmap line: \"%s\"" % (line,)) 43 d[pieces[0]] = pieces[1] 44 return d 45 46 47 # Read firmware images from target files zip 48 def GetRadioFiles(z): 49 out = {} 50 for info in z.infolist(): 51 f = info.filename 52 if f.startswith("RADIO/") and (f.__len__() > len("RADIO/")): 53 fn = f[6:] 54 if fn.startswith("filesmap"): 55 continue 56 data = z.read(f) 57 out[fn] = common.File(f, data) 58 return out 59 60 61 # Get firmware residing place from filesmap 62 def GetFileDestination(fn, filesmap): 63 # if file is encoded disregard the .enc extention 64 if fn.endswith('.enc'): 65 fn = fn[:-4] 66 67 # get backup destination as well if present 68 backup = None 69 if fn + ".bak" in filesmap: 70 backup = filesmap[fn + ".bak"] 71 72 # If full filename is not specified in filesmap get only the name part 73 # and look for this token 74 if fn not in filesmap: 75 fn = fn.split(".")[0] + ".*" 76 if fn not in filesmap: 77 print "warning radio-update: '%s' not found in filesmap" % (fn) 78 return None, backup 79 return filesmap[fn], backup 80 81 82 # Separate image types as each type needs different handling 83 def SplitFwTypes(files): 84 boot = {} 85 bin = {} 86 fw = {} 87 88 for f in files: 89 extIdx = -1 90 dotSeparated = f.split(".") 91 while True: 92 if dotSeparated[extIdx] != 'p' and dotSeparated[extIdx] != 'enc': 93 break 94 extIdx -= 1 95 96 if dotSeparated[extIdx] == 'mbn' or dotSeparated[extIdx] == 'elf': 97 boot[f] = files[f] 98 elif dotSeparated[extIdx] == 'bin': 99 bin[f] = files[f] 100 else: 101 fw[f] = files[f] 102 return boot, bin, fw 103 104 105 # Prepare radio-update files and verify them 106 def OTA_VerifyEnd(info, api_version, target_zip, source_zip=None): 107 if api_version < 3: 108 print "warning radio-update: no support for api_version less than 3" 109 return False 110 111 print "Loading radio filesmap..." 112 filesmap = LoadFilesMap(target_zip) 113 if filesmap == {}: 114 print "warning radio-update: no or invalid filesmap file found" 115 return False 116 117 print "Loading radio target..." 118 tgt_files = GetRadioFiles(target_zip) 119 if tgt_files == {}: 120 print "warning radio-update: no radio images in input target_files" 121 return False 122 123 src_files = None 124 if source_zip is not None: 125 print "Loading radio source..." 126 src_files = GetRadioFiles(source_zip) 127 128 update_list = {} 129 largest_source_size = 0 130 131 print "Preparing radio-update files..." 132 for fn in tgt_files: 133 dest, destBak = GetFileDestination(fn, filesmap) 134 if dest is None: 135 continue 136 137 tf = tgt_files[fn] 138 sf = None 139 if src_files is not None: 140 sf = src_files.get(fn, None) 141 142 full = sf is None or fn.endswith('.enc') 143 if not full: 144 # no difference - skip this file 145 if tf.sha1 == sf.sha1: 146 continue 147 d = common.Difference(tf, sf) 148 _, _, d = d.ComputePatch() 149 # no difference - skip this file 150 if d is None: 151 continue 152 # if patch is almost as big as the file - don't bother patching 153 full = len(d) > tf.size * common.OPTIONS.patch_threshold 154 if not full: 155 f = "patch/firmware-update/" + fn + ".p" 156 common.ZipWriteStr(info.output_zip, f, d) 157 update_list[f] = (dest, destBak, tf, sf) 158 largest_source_size = max(largest_source_size, sf.size) 159 if full: 160 f = "firmware-update/" + fn 161 common.ZipWriteStr(info.output_zip, f, tf.data) 162 update_list[f] = (dest, destBak, None, None) 163 164 global bootImages 165 global binImages 166 global fwImages 167 bootImages, binImages, fwImages = SplitFwTypes(update_list) 168 169 # If there are incremental patches verify them 170 if largest_source_size != 0: 171 info.script.Comment("---- radio update verification ----") 172 info.script.Print("Verifying radio-update...") 173 174 for f in bootImages: 175 dest, destBak, tf, sf = bootImages[f] 176 # Not incremental 177 if sf is None: 178 continue 179 info.script.PatchCheck("EMMC:%s:%d:%s:%d:%s" % 180 (dest, sf.size, sf.sha1, tf.size, tf.sha1)) 181 if destBak is not None: 182 info.script.PatchCheck("EMMC:%s:%d:%s:%d:%s" % 183 (destBak, sf.size, sf.sha1, tf.size, tf.sha1)) 184 for f in binImages: 185 dest, destBak, tf, sf = binImages[f] 186 # Not incremental 187 if sf is None: 188 continue 189 info.script.PatchCheck("EMMC:%s:%d:%s:%d:%s" % 190 (dest, sf.size, sf.sha1, tf.size, tf.sha1)) 191 192 last_mounted = "" 193 for f in fwImages: 194 dest, destBak, tf, sf = fwImages[f] 195 # Not incremental 196 if sf is None: 197 continue 198 # Get the filename without the path and the patch (.p) extention 199 f = f.split("/")[-1][:-2] 200 # Parse filesmap destination paths for "/dev/" pattern in the beginng. 201 # This would mean that the file must be written to block device - 202 # fs mount needed 203 if dest.startswith("/dev/"): 204 if last_mounted != dest: 205 info.script.AppendExtra('unmount("/firmware");') 206 info.script.AppendExtra('mount("vfat", "EMMC", "%s", "/firmware");' % 207 (dest)) 208 last_mounted = dest 209 dest = "/firmware/image/" + f 210 else: 211 dest = dest + "/" + f 212 info.script.PatchCheck(dest, tf.sha1, sf.sha1) 213 214 info.script.CacheFreeSpaceCheck(largest_source_size) 215 return True 216 217 218 def FullOTA_Assertions(info): 219 #TODO: Implement device specific asserstions. 220 return 221 222 223 def IncrementalOTA_Assertions(info): 224 #TODO: Implement device specific asserstions. 225 return 226 227 228 def IncrementalOTA_VerifyEnd(info): 229 OTA_VerifyEnd(info, info.target_version, info.target_zip, info.source_zip) 230 return 231 232 233 # This function handles only non-HLOS whole partition images 234 def InstallRawImage(script, f, dest, tf, sf): 235 if f.endswith('.p'): 236 script.ApplyPatch("EMMC:%s:%d:%s:%d:%s" % 237 (dest, sf.size, sf.sha1, tf.size, tf.sha1), 238 "-", tf.size, tf.sha1, sf.sha1, f) 239 elif f.endswith('.enc'): 240 # Get the filename without the path 241 fn = f.split("/")[-1] 242 script.AppendExtra('package_extract_file("%s", "/tmp/%s");' % (f, fn)) 243 script.AppendExtra('msm.decrypt("/tmp/%s", "%s");' % (fn, dest)) 244 else: 245 script.AppendExtra('package_extract_file("%s", "%s");' % (f, dest)) 246 return 247 248 249 # This function handles only non-HLOS boot images - files list must contain 250 # only such images (aboot, tz, etc) 251 def InstallBootImages(script, files): 252 bakExists = False 253 # update main partitions 254 script.AppendExtra('ifelse(msm.boot_update("main"), (') 255 for f in files: 256 dest, destBak, tf, sf = files[f] 257 if destBak is not None: 258 bakExists = True 259 InstallRawImage(script, f, dest, tf, sf) 260 script.AppendExtra('), "");') 261 262 # update backup partitions 263 if bakExists: 264 script.AppendExtra('ifelse(msm.boot_update("backup"), (') 265 for f in files: 266 dest, destBak, tf, sf = files[f] 267 if destBak is not None: 268 InstallRawImage(script, f, destBak, tf, sf) 269 script.AppendExtra('), "");') 270 # just finalize primary update stage 271 else: 272 script.AppendExtra('msm.boot_update("backup");') 273 274 # finalize partitions update 275 script.AppendExtra('msm.boot_update("finalize");') 276 return 277 278 279 # This function handles only non-HLOS bin images 280 def InstallBinImages(script, files): 281 for f in files: 282 dest, _, tf, sf = files[f] 283 InstallRawImage(script, f, dest, tf, sf) 284 return 285 286 287 # This function handles only non-HLOS firmware files that are not whole 288 # partition images (modem, dsp, etc) 289 def InstallFwImages(script, files): 290 last_mounted = "" 291 292 for f in files: 293 dest, _, tf, sf = files[f] 294 # Get the filename without the path 295 fn = f.split("/")[-1] 296 # Parse filesmap destination paths for "/dev/" pattern in the beginng. 297 # This would mean that the file must be written to block device - 298 # fs mount needed 299 if dest.startswith("/dev/"): 300 if last_mounted != dest: 301 script.AppendExtra('unmount("/firmware");') 302 script.AppendExtra('mount("vfat", "EMMC", "%s", "/firmware");' % 303 (dest)) 304 last_mounted = dest 305 dest = "/firmware/image/" + fn 306 else: 307 dest = dest + "/" + fn 308 309 if f.endswith('.p'): 310 script.ApplyPatch(dest[:-2], "-", tf.size, tf.sha1, sf.sha1, f) 311 elif f.endswith('.enc'): 312 script.AppendExtra('package_extract_file("%s", "/tmp/%s");' % (f, fn)) 313 script.AppendExtra('msm.decrypt("/tmp/%s", "%s");' % (fn, dest[:-4])) 314 else: 315 script.AppendExtra('package_extract_file("%s", "%s");' % (f, dest)) 316 317 if last_mounted != "": 318 script.AppendExtra('unmount("/firmware");') 319 return 320 321 322 def OTA_InstallEnd(info): 323 print "Applying radio-update script modifications..." 324 info.script.Comment("---- radio update tasks ----") 325 info.script.Print("Patching firmware images...") 326 327 if bootImages != {}: 328 InstallBootImages(info.script, bootImages) 329 if binImages != {}: 330 InstallBinImages(info.script, binImages) 331 if fwImages != {}: 332 InstallFwImages(info.script, fwImages) 333 return 334 335 336 def FullOTA_InstallEnd_MMC(info): 337 if OTA_VerifyEnd(info, info.input_version, info.input_zip): 338 OTA_InstallEnd(info) 339 return 340 341 342 def FullOTA_InstallEnd_MTD(info): 343 print "warning radio-update: radio update for NAND devices not supported" 344 return 345 346 347 def FullOTA_InstallEnd(info): 348 FullOTA_InstallEnd_MMC(info) 349 return 350 351 def IncrementalOTA_InstallEnd_MMC(info): 352 OTA_InstallEnd(info) 353 return 354 355 356 def IncrementalOTA_InstallEnd_MTD(info): 357 print "warning radio-update: radio update for NAND devices not supported" 358 return 359 360 def IncrementalOTA_InstallEnd(info): 361 IncrementalOTA_InstallEnd_MMC(info) 362 return 363