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  if common.OPTIONS.full_radio:
97    if not target_radio_img:
98      assert False, "full radio option specified but no radio img found"
99    else:
100      return
101  source_radio_img = FindRadio(info.source_zip)
102  if not target_radio_img or not source_radio_img:
103    return
104  target_modem_img = MotobootImage(target_radio_img).GetUnpackedImage("modem")
105  if not target_modem_img: return
106  source_modem_img = MotobootImage(source_radio_img).GetUnpackedImage("modem")
107  if not source_modem_img: return
108  if target_modem_img.sha1 != source_modem_img.sha1:
109    info.script.CacheFreeSpaceCheck(len(source_modem_img.data))
110    radio_type, radio_device = common.GetTypeAndDevice("/modem", info.info_dict)
111    info.script.PatchCheck("%s:%s:%d:%s:%d:%s" % (
112        radio_type, radio_device,
113        len(source_modem_img.data), source_modem_img.sha1,
114        len(target_modem_img.data), target_modem_img.sha1))
115
116def IncrementalOTA_InstallEnd(info):
117  try:
118    target_bootloader_img = info.target_zip.read("RADIO/bootloader.img")
119    try:
120      source_bootloader_img = info.source_zip.read("RADIO/bootloader.img")
121    except KeyError:
122      source_bootloader_img = None
123
124    if source_bootloader_img == target_bootloader_img:
125      print "bootloader unchanged; skipping"
126    elif source_bootloader_img == None:
127      print "no bootloader.img in source target_files; installing complete image"
128      WriteBootloader(info, target_bootloader_img)
129    else:
130      tf = common.File("bootloader.img", target_bootloader_img)
131      sf = common.File("bootloader.img", source_bootloader_img)
132      WriteIncrementalBootloader(info, tf, sf)
133  except KeyError:
134    print "no bootloader.img in target target_files; skipping install"
135
136  tf = FindRadio(info.target_zip)
137  if not tf:
138    # failed to read TARGET radio image: don't include any radio in update.
139    print "no radio.img in target target_files; skipping install"
140    # we have checked the existence of the radio image in
141    # IncrementalOTA_VerifyEnd(), so it won't reach here.
142    assert common.OPTIONS.full_radio == False
143  else:
144    tf = common.File("radio.img", tf)
145
146    sf = FindRadio(info.source_zip)
147    if not sf or common.OPTIONS.full_radio:
148      # failed to read SOURCE radio image or one has specified the option to
149      # include the whole target radio image.
150      print("no radio image in source target_files or full_radio specified; "
151            "installing complete image")
152      WriteRadio(info, tf.data)
153    else:
154      sf = common.File("radio.img", sf)
155
156      if tf.size == sf.size and tf.sha1 == sf.sha1:
157        print "radio image unchanged; skipping"
158      else:
159        WriteIncrementalRadio(info, tf, sf)
160
161def WriteIncrementalBootloader(info, target_imagefile, source_imagefile):
162  try:
163    tm = MotobootImage(target_imagefile.data, "bootloader")
164  except BadMagicError:
165    assert False, "bootloader.img bad magic value"
166  try:
167    sm = MotobootImage(source_imagefile.data, "bootloader")
168  except BadMagicError:
169    print "source bootloader image is not a motoboot image. Installing complete image."
170    return WriteBootloader(info, target_imagefile.data)
171
172  # blacklist any partitions that match the source image
173  blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST
174  for ti in tm.unpacked_images:
175    if ti not in blacklist:
176      si = sm.GetUnpackedImage(ti.name)
177      if not si:
178        continue
179      if ti.size == si.size and ti.sha1 == si.sha1:
180        print "target bootloader partition image %s matches source; skipping" % ti.name
181        blacklist.append(ti.name)
182
183  # If there are any images to then write them
184  whitelist = [ i.name for i in tm.unpacked_images if i.name not in blacklist ]
185  if len(whitelist):
186    # Install the bootloader, skipping any matching partitions
187    WriteBootloader(info, target_imagefile.data, blacklist)
188
189def WriteIncrementalRadio(info, target_imagefile, source_imagefile):
190  try:
191    target_radio_img = MotobootImage(target_imagefile.data, "radio")
192  except BadMagicError:
193    assert False, "radio.img bad magic value"
194
195  try:
196    source_radio_img = MotobootImage(source_imagefile.data, "radio")
197  except BadMagicError:
198    source_radio_img = None
199
200  write_full_modem = True
201  if source_radio_img:
202    target_modem_img = target_radio_img.GetUnpackedImage("modem")
203    if target_modem_img:
204      source_modem_img = source_radio_img.GetUnpackedImage("modem")
205      if source_modem_img:
206        WriteIncrementalModemPartition(info, target_modem_img, source_modem_img)
207        write_full_modem = False
208
209  # Write the full images, skipping modem if so directed.
210  #
211  # NOTE: Some target flex radio images are zero-filled, and must
212  #       be flashed to trigger the flex update "magic".  Do not
213  #       skip installing target partition images that are identical
214  #       to its corresponding source partition image.
215  blacklist = []
216  if not write_full_modem:
217    blacklist.append('modem')
218  WriteMotobootPartitionImages(info, target_radio_img, blacklist)
219
220def WriteIncrementalModemPartition(info, target_modem_image, source_modem_image):
221  tf = target_modem_image
222  sf = source_modem_image
223
224  b = common.BlockDifference("modem", common.DataImage(tf.data),
225                             common.DataImage(sf.data))
226
227  b.WriteScript(info.script, info.output_zip)
228
229
230def WriteRadio(info, radio_img):
231  info.script.Print("Writing radio...")
232
233  try:
234    motoboot_image = MotobootImage(radio_img, "radio")
235  except BadMagicError:
236      assert False, "radio.img bad magic value"
237
238  WriteMotobootPartitionImages(info, motoboot_image)
239
240def WriteMotobootPartitionImages(info, motoboot_image, blacklist = []):
241  WriteGroupedImages(info, motoboot_image.name, motoboot_image.unpacked_images, blacklist)
242
243def WriteGroupedImages(info, group_name, images, blacklist = []):
244  """ Write a group of partition images to the OTA package,
245  and add the corresponding flash instructions to the recovery
246  script.  Skip any images that do not have a corresponding
247  entry in recovery.fstab."""
248
249  for i in images:
250    if i.name not in blacklist:
251      WritePartitionImage(info, i, group_name)
252
253def WritePartitionImage(info, image, group_name = None):
254
255  filename = "%s.img" % image.name
256  if group_name:
257    filename = "%s.%s" % (group_name,filename)
258
259  try:
260    _, device = common.GetTypeAndDevice("/"+image.name, info.info_dict)
261  except KeyError:
262    print "skipping flash of %s; not in recovery.fstab" % (image.name,)
263    return
264
265  common.ZipWriteStr(info.output_zip, filename, image.data)
266
267  info.script.AppendExtra('package_extract_file("%s", "%s");' %
268                          (filename, device))
269
270def WriteBootloader(info, bootloader, blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST):
271  info.script.Print("Writing bootloader...")
272
273  try:
274    motoboot_image = MotobootImage(bootloader,"bootloader")
275  except BadMagicError:
276      assert False, "bootloader.img bad magic value"
277
278  common.ZipWriteStr(info.output_zip, "bootloader-flag.txt",
279                     "updating-bootloader" + "\0" * 13)
280  common.ZipWriteStr(info.output_zip, "bootloader-flag-clear.txt", "\0" * 32)
281
282  _, misc_device = common.GetTypeAndDevice("/misc", info.info_dict)
283
284  info.script.AppendExtra(
285      'package_extract_file("bootloader-flag.txt", "%s");' %
286      (misc_device,))
287
288  # OTA does not support partition changes, so
289  # do not bundle the partition image in the OTA package.
290  WriteMotobootPartitionImages(info, motoboot_image, blacklist)
291
292  info.script.AppendExtra(
293      'package_extract_file("bootloader-flag-clear.txt", "%s");' %
294      (misc_device,))
295
296def trunc_to_null(s):
297  if '\0' in s:
298    return s[:s.index('\0')]
299  else:
300    return s
301