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