Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 1 | import common |
| 2 | import struct |
| 3 | |
Andrew Wheeler | 38266c4 | 2014-06-13 17:07:31 -0500 | [diff] [blame] | 4 | # The target does not support OTA-flashing |
| 5 | # the partition table, so blacklist it. |
| 6 | DEFAULT_BOOTLOADER_OTA_BLACKLIST = [ 'partition' ] |
| 7 | |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 8 | class 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 | # }; |
| 26 | HEADER_SIZE = 1024 |
| 27 | SECTOR_SIZE = 512 |
| 28 | NUM_MAX_IMAGES = 20 |
| 29 | MAGIC = "MBOOTV1\0" |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 30 | class MotobootImage(object): |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 31 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 32 | def __init__(self, data, name = None): |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 33 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 34 | self.name = name |
| 35 | self._unpack(data) |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 36 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 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]) |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 43 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 44 | img_info_format = "<24sLL" |
| 45 | img_info_size = struct.calcsize(img_info_format) |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 46 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 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) ] |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 48 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 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 |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 72 | |
| 73 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 74 | def FindRadio(zipfile): |
| 75 | try: |
| 76 | return zipfile.read("RADIO/radio.img") |
| 77 | except KeyError: |
| 78 | return None |
| 79 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 80 | def 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 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 94 | def 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 |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 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) |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 105 | info.script.PatchCheck("%s:%s:%d:%s:%d:%s" % ( |
| 106 | radio_type, radio_device, |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 107 | len(source_modem_img.data), source_modem_img.sha1, |
| 108 | len(target_modem_img.data), target_modem_img.sha1)) |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 109 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 110 | def 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" |
Andrew Wheeler | 38266c4 | 2014-06-13 17:07:31 -0500 | [diff] [blame] | 120 | elif source_bootloader_img == None: |
| 121 | print "no bootloader.img in source target_files; installing complete image" |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 122 | WriteBootloader(info, target_bootloader_img) |
Andrew Wheeler | 38266c4 | 2014-06-13 17:07:31 -0500 | [diff] [blame] | 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) |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 127 | except KeyError: |
| 128 | print "no bootloader.img in target target_files; skipping install" |
| 129 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 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. |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 141 | print "no radio image in source target_files; installing complete image" |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 142 | WriteRadio(info, tf.data) |
| 143 | else: |
| 144 | sf = common.File("radio.img", sf) |
| 145 | |
Andrew Wheeler | 38266c4 | 2014-06-13 17:07:31 -0500 | [diff] [blame] | 146 | if tf.size == sf.size and tf.sha1 == sf.sha1: |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 147 | print "radio image unchanged; skipping" |
| 148 | else: |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 149 | WriteIncrementalRadio(info, tf, sf) |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 150 | |
Andrew Wheeler | 38266c4 | 2014-06-13 17:07:31 -0500 | [diff] [blame] | 151 | def 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 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 179 | def 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. |
Andrew Wheeler | 38266c4 | 2014-06-13 17:07:31 -0500 | [diff] [blame] | 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. |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 205 | blacklist = [] |
| 206 | if not write_full_modem: |
| 207 | blacklist.append('modem') |
| 208 | WriteMotobootPartitionImages(info, target_radio_img, blacklist) |
| 209 | |
| 210 | def WriteIncrementalModemPartition(info, target_modem_image, source_modem_image): |
| 211 | tf = target_modem_image |
| 212 | sf = source_modem_image |
| 213 | |
Doug Zongker | c0a12ca | 2014-08-26 10:54:40 -0700 | [diff] [blame] | 214 | b = common.BlockDifference("modem", common.DataImage(tf.data), |
| 215 | common.DataImage(sf.data)) |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 216 | |
Doug Zongker | c0a12ca | 2014-08-26 10:54:40 -0700 | [diff] [blame] | 217 | b.WriteScript(info.script, info.output_zip) |
| 218 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 219 | |
| 220 | def WriteRadio(info, radio_img): |
| 221 | info.script.Print("Writing radio...") |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 222 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 223 | try: |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 224 | motoboot_image = MotobootImage(radio_img, "radio") |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 225 | except BadMagicError: |
| 226 | assert False, "radio.img bad magic value" |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 227 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 228 | WriteMotobootPartitionImages(info, motoboot_image) |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 229 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 230 | def WriteMotobootPartitionImages(info, motoboot_image, blacklist = []): |
| 231 | WriteGroupedImages(info, motoboot_image.name, motoboot_image.unpacked_images, blacklist) |
| 232 | |
| 233 | def WriteGroupedImages(info, group_name, images, blacklist = []): |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 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.""" |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 238 | |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 239 | for i in images: |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 240 | if i.name not in blacklist: |
| 241 | WritePartitionImage(info, i, group_name) |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 242 | |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 243 | def 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)) |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 259 | |
Andrew Wheeler | 38266c4 | 2014-06-13 17:07:31 -0500 | [diff] [blame] | 260 | def WriteBootloader(info, bootloader, blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST): |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 261 | info.script.Print("Writing bootloader...") |
| 262 | |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 263 | try: |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 264 | motoboot_image = MotobootImage(bootloader,"bootloader") |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 265 | except BadMagicError: |
| 266 | assert False, "bootloader.img bad magic value" |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 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 | |
Andrew Wheeler | eb1b7a8 | 2014-06-06 13:25:12 -0500 | [diff] [blame] | 278 | # OTA does not support partition changes, so |
| 279 | # do not bundle the partition image in the OTA package. |
Andrew Wheeler | ae64035 | 2014-06-12 15:10:16 -0500 | [diff] [blame] | 280 | WriteMotobootPartitionImages(info, motoboot_image, blacklist) |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 281 | |
| 282 | info.script.AppendExtra( |
| 283 | 'package_extract_file("bootloader-flag-clear.txt", "%s");' % |
| 284 | (misc_device,)) |
| 285 | |
Ed Tam | abb4375 | 2014-06-10 15:09:35 -0700 | [diff] [blame] | 286 | def trunc_to_null(s): |
| 287 | if '\0' in s: |
| 288 | return s[:s.index('\0')] |
| 289 | else: |
| 290 | return s |