import common import struct # The target does not support OTA-flashing # the partition table, so blacklist it. DEFAULT_BOOTLOADER_OTA_BLACKLIST = [ 'partition' ] class BadMagicError(Exception): __str__ = "bad magic value" # # Motoboot packed image format # # #define BOOTLDR_MAGIC "MBOOTV1" # #define HEADER_SIZE 1024 # #define SECTOR_SIZE 512 # struct packed_images_header { # unsigned int num_images; # struct { # char name[24]; # unsigned int start; // start offset = HEADER_SIZE + start * SECTOR_SIZE # unsigned int end; // end offset = HEADER_SIZE + (end + 1) * SECTOR_SIZE - 1 # } img_info[20]; # char magic[8]; // set to BOOTLDR_MAGIC # }; HEADER_SIZE = 1024 SECTOR_SIZE = 512 NUM_MAX_IMAGES = 20 MAGIC = "MBOOTV1\0" class MotobootImage(object): def __init__(self, data, name = None): self.name = name self._unpack(data) def _unpack(self, data): """ Unpack the data blob as a motoboot image and return the list of contained image objects""" num_imgs_fmt = "<L" num_imgs_size = struct.calcsize(num_imgs_fmt) num_imgs, = struct.unpack(num_imgs_fmt, data[:num_imgs_size]) img_info_format = "<24sLL" img_info_size = struct.calcsize(img_info_format) 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) ] magic_format = "<8s" magic_size = struct.calcsize(magic_format) 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]) if magic != MAGIC: raise BadMagicError img_objs = [] for name, start, end in imgs: start_offset = HEADER_SIZE + start * SECTOR_SIZE end_offset = HEADER_SIZE + (end + 1) * SECTOR_SIZE - 1 img = common.File(trunc_to_null(name), data[start_offset:end_offset+1]) img_objs.append(img) self.unpacked_images = img_objs def GetUnpackedImage(self, name): found_image = None for image in self.unpacked_images: if image.name == name: found_image = image break return found_image def FindRadio(zipfile): try: return zipfile.read("RADIO/radio.img") except KeyError: return None def FullOTA_InstallEnd(info): try: bootloader_img = info.input_zip.read("RADIO/bootloader.img") except KeyError: print "no bootloader.img in target_files; skipping install" else: WriteBootloader(info, bootloader_img) radio_img = FindRadio(info.input_zip) if radio_img: WriteRadio(info, radio_img) else: print "no radio.img in target_files; skipping install" def IncrementalOTA_VerifyEnd(info): target_radio_img = FindRadio(info.target_zip) if common.OPTIONS.full_radio: if not target_radio_img: assert False, "full radio option specified but no radio img found" else: return source_radio_img = FindRadio(info.source_zip) if not target_radio_img or not source_radio_img: return target_modem_img = MotobootImage(target_radio_img).GetUnpackedImage("modem") if not target_modem_img: return source_modem_img = MotobootImage(source_radio_img).GetUnpackedImage("modem") if not source_modem_img: return if target_modem_img.sha1 != source_modem_img.sha1: info.script.CacheFreeSpaceCheck(len(source_modem_img.data)) radio_type, radio_device = common.GetTypeAndDevice("/modem", info.info_dict) info.script.PatchCheck("%s:%s:%d:%s:%d:%s" % ( radio_type, radio_device, len(source_modem_img.data), source_modem_img.sha1, len(target_modem_img.data), target_modem_img.sha1)) def IncrementalOTA_InstallEnd(info): try: target_bootloader_img = info.target_zip.read("RADIO/bootloader.img") try: source_bootloader_img = info.source_zip.read("RADIO/bootloader.img") except KeyError: source_bootloader_img = None if source_bootloader_img == target_bootloader_img: print "bootloader unchanged; skipping" elif source_bootloader_img == None: print "no bootloader.img in source target_files; installing complete image" WriteBootloader(info, target_bootloader_img) else: tf = common.File("bootloader.img", target_bootloader_img) sf = common.File("bootloader.img", source_bootloader_img) WriteIncrementalBootloader(info, tf, sf) except KeyError: print "no bootloader.img in target target_files; skipping install" tf = FindRadio(info.target_zip) if not tf: # failed to read TARGET radio image: don't include any radio in update. print "no radio.img in target target_files; skipping install" # we have checked the existence of the radio image in # IncrementalOTA_VerifyEnd(), so it won't reach here. assert common.OPTIONS.full_radio == False else: tf = common.File("radio.img", tf) sf = FindRadio(info.source_zip) if not sf or common.OPTIONS.full_radio: # failed to read SOURCE radio image or one has specified the option to # include the whole target radio image. print("no radio image in source target_files or full_radio specified; " "installing complete image") WriteRadio(info, tf.data) else: sf = common.File("radio.img", sf) if tf.size == sf.size and tf.sha1 == sf.sha1: print "radio image unchanged; skipping" else: WriteIncrementalRadio(info, tf, sf) def WriteIncrementalBootloader(info, target_imagefile, source_imagefile): try: tm = MotobootImage(target_imagefile.data, "bootloader") except BadMagicError: assert False, "bootloader.img bad magic value" try: sm = MotobootImage(source_imagefile.data, "bootloader") except BadMagicError: print "source bootloader image is not a motoboot image. Installing complete image." return WriteBootloader(info, target_imagefile.data) # blacklist any partitions that match the source image blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST for ti in tm.unpacked_images: if ti not in blacklist: si = sm.GetUnpackedImage(ti.name) if not si: continue if ti.size == si.size and ti.sha1 == si.sha1: print "target bootloader partition image %s matches source; skipping" % ti.name blacklist.append(ti.name) # If there are any images to then write them whitelist = [ i.name for i in tm.unpacked_images if i.name not in blacklist ] if len(whitelist): # Install the bootloader, skipping any matching partitions WriteBootloader(info, target_imagefile.data, blacklist) def WriteIncrementalRadio(info, target_imagefile, source_imagefile): try: target_radio_img = MotobootImage(target_imagefile.data, "radio") except BadMagicError: assert False, "radio.img bad magic value" try: source_radio_img = MotobootImage(source_imagefile.data, "radio") except BadMagicError: source_radio_img = None write_full_modem = True if source_radio_img: target_modem_img = target_radio_img.GetUnpackedImage("modem") if target_modem_img: source_modem_img = source_radio_img.GetUnpackedImage("modem") if source_modem_img: WriteIncrementalModemPartition(info, target_modem_img, source_modem_img) write_full_modem = False # Write the full images, skipping modem if so directed. # # NOTE: Some target flex radio images are zero-filled, and must # be flashed to trigger the flex update "magic". Do not # skip installing target partition images that are identical # to its corresponding source partition image. blacklist = [] if not write_full_modem: blacklist.append('modem') WriteMotobootPartitionImages(info, target_radio_img, blacklist) def WriteIncrementalModemPartition(info, target_modem_image, source_modem_image): tf = target_modem_image sf = source_modem_image b = common.BlockDifference("modem", common.DataImage(tf.data), common.DataImage(sf.data)) b.WriteScript(info.script, info.output_zip) def WriteRadio(info, radio_img): info.script.Print("Writing radio...") try: motoboot_image = MotobootImage(radio_img, "radio") except BadMagicError: assert False, "radio.img bad magic value" WriteMotobootPartitionImages(info, motoboot_image) def WriteMotobootPartitionImages(info, motoboot_image, blacklist = []): WriteGroupedImages(info, motoboot_image.name, motoboot_image.unpacked_images, blacklist) def WriteGroupedImages(info, group_name, images, blacklist = []): """ Write a group of partition images to the OTA package, and add the corresponding flash instructions to the recovery script. Skip any images that do not have a corresponding entry in recovery.fstab.""" for i in images: if i.name not in blacklist: WritePartitionImage(info, i, group_name) def WritePartitionImage(info, image, group_name = None): filename = "%s.img" % image.name if group_name: filename = "%s.%s" % (group_name,filename) try: _, device = common.GetTypeAndDevice("/"+image.name, info.info_dict) except KeyError: print "skipping flash of %s; not in recovery.fstab" % (image.name,) return common.ZipWriteStr(info.output_zip, filename, image.data) info.script.AppendExtra('package_extract_file("%s", "%s");' % (filename, device)) def WriteBootloader(info, bootloader, blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST): info.script.Print("Writing bootloader...") try: motoboot_image = MotobootImage(bootloader,"bootloader") except BadMagicError: assert False, "bootloader.img bad magic value" common.ZipWriteStr(info.output_zip, "bootloader-flag.txt", "updating-bootloader" + "\0" * 13) common.ZipWriteStr(info.output_zip, "bootloader-flag-clear.txt", "\0" * 32) _, misc_device = common.GetTypeAndDevice("/misc", info.info_dict) info.script.AppendExtra( 'package_extract_file("bootloader-flag.txt", "%s");' % (misc_device,)) # OTA does not support partition changes, so # do not bundle the partition image in the OTA package. WriteMotobootPartitionImages(info, motoboot_image, blacklist) info.script.AppendExtra( 'package_extract_file("bootloader-flag-clear.txt", "%s");' % (misc_device,)) def trunc_to_null(s): if '\0' in s: return s[:s.index('\0')] else: return s