1import common 2import struct 3 4# The target does not support OTA-flashing 5# the partition table, so blacklist it. 6DEFAULT_BOOTLOADER_OTA_BLACKLIST = [ 'partition' ] 7 8class BadMagicError(Exception): 9 __str__ = "bad magic value" 10 11# 12# Motoboot packed image format 13# 14# #define BOOTLDR_MAGIC "MBOOTV1" 15# #define HEADER_SIZE 1024 16# #define SECTOR_SIZE 512 17# struct packed_images_header { 18# unsigned int num_images; 19# struct { 20# char name[24]; 21# unsigned int start; // start offset = HEADER_SIZE + start * SECTOR_SIZE 22# unsigned int end; // end offset = HEADER_SIZE + (end + 1) * SECTOR_SIZE - 1 23# } img_info[20]; 24# char magic[8]; // set to BOOTLDR_MAGIC 25# }; 26HEADER_SIZE = 1024 27SECTOR_SIZE = 512 28NUM_MAX_IMAGES = 20 29MAGIC = "MBOOTV1\0" 30class MotobootImage(object): 31 32 def __init__(self, data, name = None): 33 34 self.name = name 35 self._unpack(data) 36 37 def _unpack(self, data): 38 """ Unpack the data blob as a motoboot image and return the list 39 of contained image objects""" 40 num_imgs_fmt = "<L" 41 num_imgs_size = struct.calcsize(num_imgs_fmt) 42 num_imgs, = struct.unpack(num_imgs_fmt, data[:num_imgs_size]) 43 44 img_info_format = "<24sLL" 45 img_info_size = struct.calcsize(img_info_format) 46 47 imgs = [ struct.unpack(img_info_format, data[num_imgs_size + i*img_info_size:num_imgs_size + (i+1)*img_info_size]) for i in range(num_imgs) ] 48 49 magic_format = "<8s" 50 magic_size = struct.calcsize(magic_format) 51 magic, = struct.unpack(magic_format, data[num_imgs_size + NUM_MAX_IMAGES*img_info_size:num_imgs_size + NUM_MAX_IMAGES*img_info_size + magic_size]) 52 if magic != MAGIC: 53 raise BadMagicError 54 55 img_objs = [] 56 for name, start, end in imgs: 57 start_offset = HEADER_SIZE + start * SECTOR_SIZE 58 end_offset = HEADER_SIZE + (end + 1) * SECTOR_SIZE - 1 59 img = common.File(trunc_to_null(name), data[start_offset:end_offset+1]) 60 img_objs.append(img) 61 62 self.unpacked_images = img_objs 63 64 def GetUnpackedImage(self, name): 65 66 found_image = None 67 for image in self.unpacked_images: 68 if image.name == name: 69 found_image = image 70 break 71 return found_image 72 73 74def FindRadio(zipfile): 75 try: 76 return zipfile.read("RADIO/radio.img") 77 except KeyError: 78 return None 79 80def FullOTA_InstallEnd(info): 81 try: 82 bootloader_img = info.input_zip.read("RADIO/bootloader.img") 83 except KeyError: 84 print "no bootloader.img in target_files; skipping install" 85 else: 86 WriteBootloader(info, bootloader_img) 87 88 radio_img = FindRadio(info.input_zip) 89 if radio_img: 90 WriteRadio(info, radio_img) 91 else: 92 print "no radio.img in target_files; skipping install" 93 94def IncrementalOTA_VerifyEnd(info): 95 target_radio_img = FindRadio(info.target_zip) 96 source_radio_img = FindRadio(info.source_zip) 97 if not target_radio_img or not source_radio_img: return 98 target_modem_img = MotobootImage(target_radio_img).GetUnpackedImage("modem") 99 if not target_modem_img: return 100 source_modem_img = MotobootImage(source_radio_img).GetUnpackedImage("modem") 101 if not source_modem_img: return 102 if target_modem_img.sha1 != source_modem_img.sha1: 103 info.script.CacheFreeSpaceCheck(len(source_modem_img.data)) 104 radio_type, radio_device = common.GetTypeAndDevice("/modem", info.info_dict) 105 info.script.PatchCheck("%s:%s:%d:%s:%d:%s" % ( 106 radio_type, radio_device, 107 len(source_modem_img.data), source_modem_img.sha1, 108 len(target_modem_img.data), target_modem_img.sha1)) 109 110def IncrementalOTA_InstallEnd(info): 111 try: 112 target_bootloader_img = info.target_zip.read("RADIO/bootloader.img") 113 try: 114 source_bootloader_img = info.source_zip.read("RADIO/bootloader.img") 115 except KeyError: 116 source_bootloader_img = None 117 118 if source_bootloader_img == target_bootloader_img: 119 print "bootloader unchanged; skipping" 120 elif source_bootloader_img == None: 121 print "no bootloader.img in source target_files; installing complete image" 122 WriteBootloader(info, target_bootloader_img) 123 else: 124 tf = common.File("bootloader.img", target_bootloader_img) 125 sf = common.File("bootloader.img", source_bootloader_img) 126 WriteIncrementalBootloader(info, tf, sf) 127 except KeyError: 128 print "no bootloader.img in target target_files; skipping install" 129 130 tf = FindRadio(info.target_zip) 131 if not tf: 132 # failed to read TARGET radio image: don't include any radio in update. 133 print "no radio.img in target target_files; skipping install" 134 else: 135 tf = common.File("radio.img", tf) 136 137 sf = FindRadio(info.source_zip) 138 if not sf: 139 # failed to read SOURCE radio image: include the whole target 140 # radio image. 141 print "no radio image in source target_files; installing complete image" 142 WriteRadio(info, tf.data) 143 else: 144 sf = common.File("radio.img", sf) 145 146 if tf.size == sf.size and tf.sha1 == sf.sha1: 147 print "radio image unchanged; skipping" 148 else: 149 WriteIncrementalRadio(info, tf, sf) 150 151def WriteIncrementalBootloader(info, target_imagefile, source_imagefile): 152 try: 153 tm = MotobootImage(target_imagefile.data, "bootloader") 154 except BadMagicError: 155 assert False, "bootloader.img bad magic value" 156 try: 157 sm = MotobootImage(source_imagefile.data, "bootloader") 158 except BadMagicError: 159 print "source bootloader image is not a motoboot image. Installing complete image." 160 return WriteBootloader(info, target_imagefile.data) 161 162 # blacklist any partitions that match the source image 163 blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST 164 for ti in tm.unpacked_images: 165 if ti not in blacklist: 166 si = sm.GetUnpackedImage(ti.name) 167 if not si: 168 continue 169 if ti.size == si.size and ti.sha1 == si.sha1: 170 print "target bootloader partition image %s matches source; skipping" % ti.name 171 blacklist.append(ti.name) 172 173 # If there are any images to then write them 174 whitelist = [ i.name for i in tm.unpacked_images if i.name not in blacklist ] 175 if len(whitelist): 176 # Install the bootloader, skipping any matching partitions 177 WriteBootloader(info, target_imagefile.data, blacklist) 178 179def WriteIncrementalRadio(info, target_imagefile, source_imagefile): 180 try: 181 target_radio_img = MotobootImage(target_imagefile.data, "radio") 182 except BadMagicError: 183 assert False, "radio.img bad magic value" 184 185 try: 186 source_radio_img = MotobootImage(source_imagefile.data, "radio") 187 except BadMagicError: 188 source_radio_img = None 189 190 write_full_modem = True 191 if source_radio_img: 192 target_modem_img = target_radio_img.GetUnpackedImage("modem") 193 if target_modem_img: 194 source_modem_img = source_radio_img.GetUnpackedImage("modem") 195 if source_modem_img: 196 WriteIncrementalModemPartition(info, target_modem_img, source_modem_img) 197 write_full_modem = False 198 199 # Write the full images, skipping modem if so directed. 200 # 201 # NOTE: Some target flex radio images are zero-filled, and must 202 # be flashed to trigger the flex update "magic". Do not 203 # skip installing target partition images that are identical 204 # to its corresponding source partition image. 205 blacklist = [] 206 if not write_full_modem: 207 blacklist.append('modem') 208 WriteMotobootPartitionImages(info, target_radio_img, blacklist) 209 210def WriteIncrementalModemPartition(info, target_modem_image, source_modem_image): 211 tf = target_modem_image 212 sf = source_modem_image 213 214 b = common.BlockDifference("modem", common.DataImage(tf.data), 215 common.DataImage(sf.data)) 216 217 b.WriteScript(info.script, info.output_zip) 218 219 220def WriteRadio(info, radio_img): 221 info.script.Print("Writing radio...") 222 223 try: 224 motoboot_image = MotobootImage(radio_img, "radio") 225 except BadMagicError: 226 assert False, "radio.img bad magic value" 227 228 WriteMotobootPartitionImages(info, motoboot_image) 229 230def WriteMotobootPartitionImages(info, motoboot_image, blacklist = []): 231 WriteGroupedImages(info, motoboot_image.name, motoboot_image.unpacked_images, blacklist) 232 233def WriteGroupedImages(info, group_name, images, blacklist = []): 234 """ Write a group of partition images to the OTA package, 235 and add the corresponding flash instructions to the recovery 236 script. Skip any images that do not have a corresponding 237 entry in recovery.fstab.""" 238 239 for i in images: 240 if i.name not in blacklist: 241 WritePartitionImage(info, i, group_name) 242 243def WritePartitionImage(info, image, group_name = None): 244 245 filename = "%s.img" % image.name 246 if group_name: 247 filename = "%s.%s" % (group_name,filename) 248 249 try: 250 _, device = common.GetTypeAndDevice("/"+image.name, info.info_dict) 251 except KeyError: 252 print "skipping flash of %s; not in recovery.fstab" % (image.name,) 253 return 254 255 common.ZipWriteStr(info.output_zip, filename, image.data) 256 257 info.script.AppendExtra('package_extract_file("%s", "%s");' % 258 (filename, device)) 259 260def WriteBootloader(info, bootloader, blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST): 261 info.script.Print("Writing bootloader...") 262 263 try: 264 motoboot_image = MotobootImage(bootloader,"bootloader") 265 except BadMagicError: 266 assert False, "bootloader.img bad magic value" 267 268 common.ZipWriteStr(info.output_zip, "bootloader-flag.txt", 269 "updating-bootloader" + "\0" * 13) 270 common.ZipWriteStr(info.output_zip, "bootloader-flag-clear.txt", "\0" * 32) 271 272 _, misc_device = common.GetTypeAndDevice("/misc", info.info_dict) 273 274 info.script.AppendExtra( 275 'package_extract_file("bootloader-flag.txt", "%s");' % 276 (misc_device,)) 277 278 # OTA does not support partition changes, so 279 # do not bundle the partition image in the OTA package. 280 WriteMotobootPartitionImages(info, motoboot_image, blacklist) 281 282 info.script.AppendExtra( 283 'package_extract_file("bootloader-flag-clear.txt", "%s");' % 284 (misc_device,)) 285 286def trunc_to_null(s): 287 if '\0' in s: 288 return s[:s.index('\0')] 289 else: 290 return s 291