1#
2# Copyright (C) 2015 The Android Open-Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import common
18import struct
19
20# The target does not support OTA-flashing
21# the partition table, so blacklist it.
22DEFAULT_BOOTLOADER_OTA_BLACKLIST = ['partition']
23
24
25class BadMagicError(Exception):
26  __str__ = "bad magic value"
27
28#
29# Huawei Bootloader packed image format
30#
31# typedef struct meta_header {
32#  u32   magic;             /* 0xce1ad63c */
33#  u16   major_version;     /* (0x1)-reject images with higher major versions */
34#  u16   minor_version;     /* (0x0)-allow images with higer minor versions */
35#  char  img_version[64];   /* Top level version for images in this meta */
36#  u16   meta_hdr_sz;       /* size of this header */
37#  u16   img_hdr_sz;        /* size of img_header_entry list */
38# } meta_header_t;
39
40# typedef struct img_header_entry {
41#  char   ptn_name[MAX_GPT_NAME_SIZE];
42#  u32    start_offset;
43#  u32    size;
44# } img_header_entry_t
45
46
47MAGIC = 0xce1ad63c
48
49
50class HuaweiBootImage(object):
51
52  def __init__(self, data, name=None):
53    self.name = name
54    self.unpacked_images = None
55    self._unpack(data)
56
57  def _unpack(self, data):
58    """Unpack the data blob as a Huawei boot image and return the list
59    of contained image objects"""
60    num_imgs_fmt = struct.Struct("<IHH64sHH")
61    header = data[0:num_imgs_fmt.size]
62    info = {}
63    (info["magic"], info["major_version"],
64     info["minor_version"], info["img_version"],
65     info["meta_hdr_size"], info["img_hdr_size"]) = num_imgs_fmt.unpack(header)
66
67    img_info_format = "<72sLL"
68    img_info_size = struct.calcsize(img_info_format)
69    num = info["img_hdr_size"] / img_info_size
70    size = num_imgs_fmt.size
71    imgs = [
72         struct.unpack(
73             img_info_format,
74             data[size + i * img_info_size:size + (i + 1) * img_info_size])
75         for i in range(num)
76    ]
77
78    if info["magic"] != MAGIC:
79      raise BadMagicError
80
81    img_objs = {}
82    for name, start, end in imgs:
83      if TruncToNull(name):
84        img = common.File(TruncToNull(name), data[start:start + end])
85        img_objs[img.name] = img
86
87    self.unpacked_images = img_objs
88
89  def GetUnpackedImage(self, name):
90    return self.unpacked_images.get(name)
91
92
93def FindRadio(zipfile):
94  try:
95    return zipfile.read("RADIO/radio.img")
96  except KeyError:
97    return None
98
99
100def FullOTA_InstallEnd(info):
101  try:
102    bootloader_img = info.input_zip.read("RADIO/bootloader.img")
103  except KeyError:
104    print "no bootloader.img in target_files; skipping install"
105  else:
106    WriteBootloader(info, bootloader_img)
107
108  radio_img = FindRadio(info.input_zip)
109  if radio_img:
110    WriteRadio(info, radio_img)
111  else:
112    print "no radio.img in target_files; skipping install"
113
114
115def IncrementalOTA_VerifyEnd(info):
116  target_radio_img = FindRadio(info.target_zip)
117  source_radio_img = FindRadio(info.source_zip)
118  if not target_radio_img or not source_radio_img:
119    return
120  target_modem_img = HuaweiBootImage(target_radio_img).GetUnpackedImage("modem")
121  if not target_modem_img:
122    return
123  source_modem_img = HuaweiBootImage(source_radio_img).GetUnpackedImage("modem")
124  if not source_modem_img:
125    return
126  if target_modem_img.sha1 != source_modem_img.sha1:
127    info.script.CacheFreeSpaceCheck(len(source_modem_img.data))
128    radio_type, radio_device = common.GetTypeAndDevice("/modem", info.info_dict)
129    info.script.PatchCheck("%s:%s:%d:%s:%d:%s" % (
130        radio_type, radio_device,
131        len(source_modem_img.data), source_modem_img.sha1,
132        len(target_modem_img.data), target_modem_img.sha1))
133
134
135def IncrementalOTA_InstallEnd(info):
136  try:
137    target_bootloader_img = info.target_zip.read("RADIO/bootloader.img")
138    try:
139      source_bootloader_img = info.source_zip.read("RADIO/bootloader.img")
140    except KeyError:
141      source_bootloader_img = None
142
143    if source_bootloader_img == target_bootloader_img:
144      print "bootloader unchanged; skipping"
145    elif source_bootloader_img == None:
146      print "no bootloader in source target_files; installing complete image"
147      WriteBootloader(info, target_bootloader_img)
148    else:
149      tf = common.File("bootloader.img", target_bootloader_img)
150      sf = common.File("bootloader.img", source_bootloader_img)
151      WriteIncrementalBootloader(info, tf, sf)
152  except KeyError:
153    print "no bootloader.img in target target_files; skipping install"
154
155  target_radio_image = FindRadio(info.target_zip)
156  if not target_radio_image:
157    # failed to read TARGET radio image: don't include any radio in update.
158    print "no radio.img in target target_files; skipping install"
159  else:
160    tf = common.File("radio.img", target_radio_image)
161
162    source_radio_image = FindRadio(info.source_zip)
163    if not source_radio_image:
164      # failed to read SOURCE radio image: include the whole target
165      # radio image.
166      print "no radio image in source target_files; installing complete image"
167      WriteRadio(info, tf.data)
168    else:
169      sf = common.File("radio.img", source_radio_image)
170
171      if tf.size == sf.size and tf.sha1 == sf.sha1:
172        print "radio image unchanged; skipping"
173      else:
174        WriteIncrementalRadio(info, tf, sf)
175
176
177def WriteIncrementalBootloader(info, target_imagefile, source_imagefile):
178  try:
179    tm = HuaweiBootImage(target_imagefile.data, "bootloader")
180  except BadMagicError:
181    raise ValueError("bootloader.img bad magic value")
182  try:
183    sm = HuaweiBootImage(source_imagefile.data, "bootloader")
184  except BadMagicError:
185    print "source bootloader is not a Huawei boot img; installing complete img."
186    return WriteBootloader(info, target_imagefile.data)
187
188  # blacklist any partitions that match the source image
189  blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST
190  for ti in tm.unpacked_images.values():
191    if ti not in blacklist:
192      si = sm.GetUnpackedImage(ti.name)
193      if not si:
194        continue
195      if ti.size == si.size and ti.sha1 == si.sha1:
196        print "target bootloader partition img %s matches source; skipping" % (
197            ti.name)
198        blacklist.append(ti.name)
199
200  # If there are any images to then write them
201  whitelist = [i.name for i in tm.unpacked_images.values()
202               if i.name not in blacklist]
203  if len(whitelist):
204    # Install the bootloader, skipping any matching partitions
205    WriteBootloader(info, target_imagefile.data, blacklist)
206
207
208def WriteIncrementalRadio(info, target_imagefile, source_imagefile):
209  try:
210    target_radio_img = HuaweiBootImage(target_imagefile.data, "radio")
211  except BadMagicError:
212    print "Magic number mismatch in target radio image"
213    raise ValueError("radio.img bad magic value")
214
215  try:
216    source_radio_img = HuaweiBootImage(source_imagefile.data, "radio")
217  except BadMagicError:
218    print "Magic number mismatch in source radio image"
219    source_radio_img = None
220
221  write_full_modem = True
222  if source_radio_img:
223    target_modem_img = target_radio_img.GetUnpackedImage("modem")
224    if target_modem_img:
225      source_modem_img = source_radio_img.GetUnpackedImage("modem")
226      if source_modem_img:
227        WriteIncrementalModemPartition(info, target_modem_img, source_modem_img)
228        write_full_modem = False
229
230  # Write the full images, skipping modem if so directed.
231  #
232  # NOTE: Some target flex radio images are zero-filled, and must
233  #       be flashed to trigger the flex update "magic".  Do not
234  #       skip installing target partition images that are identical
235  #       to its corresponding source partition image.
236  blacklist = []
237  if not write_full_modem:
238    blacklist.append("modem")
239  WriteHuaweiBootPartitionImages(info, target_radio_img, blacklist)
240
241
242def WriteIncrementalModemPartition(info, target_modem_image,
243                                   source_modem_image):
244  tf = target_modem_image
245  sf = source_modem_image
246  pad_tf = False
247  pad_sf = False
248  blocksize = 4096
249
250  partial_tf = len(tf.data) % blocksize
251  partial_sf = len(sf.data) % blocksize
252
253  if partial_tf:
254    pad_tf = True
255  if partial_sf:
256    pad_sf = True
257  b = common.BlockDifference("modem", common.DataImage(tf.data, False, pad_tf),
258                             common.DataImage(sf.data, False, pad_sf))
259  b.WriteScript(info.script, info.output_zip)
260
261
262def WriteRadio(info, radio_img):
263  info.script.Print("Writing radio...")
264
265  try:
266    huawei_boot_image = HuaweiBootImage(radio_img, "radio")
267  except BadMagicError:
268    raise ValueError("radio.img bad magic value")
269
270  WriteHuaweiBootPartitionImages(info, huawei_boot_image)
271
272
273def WriteHuaweiBootPartitionImages(info, huawei_boot_image, blacklist=None):
274  if blacklist is None:
275    blacklist = []
276  WriteGroupedImages(info, huawei_boot_image.name,
277                     huawei_boot_image.unpacked_images.values(), blacklist)
278
279
280def WriteGroupedImages(info, group_name, images, blacklist=None):
281  """Write a group of partition images to the OTA package,
282  and add the corresponding flash instructions to the recovery
283  script.  Skip any images that do not have a corresponding
284  entry in recovery.fstab."""
285  if blacklist is None:
286    blacklist = []
287  for i in images:
288    if i.name not in blacklist:
289      WritePartitionImage(info, i, group_name)
290
291
292def WritePartitionImage(info, image, group_name=None):
293  filename = "%s.img" % image.name
294  if group_name:
295    filename = "%s.%s" % (group_name, filename)
296
297  try:
298    info.script.Print("writing partition image %s" % image.name)
299    _, device = common.GetTypeAndDevice("/" + image.name, info.info_dict)
300  except KeyError:
301    print "skipping flash of %s; not in recovery.fstab" % image.name
302    return
303
304  common.ZipWriteStr(info.output_zip, filename, image.data)
305
306  info.script.AppendExtra('package_extract_file("%s", "%s");' %
307                          (filename, device))
308
309
310def WriteBootloader(info, bootloader, blacklist=None):
311  if blacklist is None:
312    blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST
313  info.script.Print("Writing bootloader...")
314  try:
315    huawei_boot_image = HuaweiBootImage(bootloader, "bootloader")
316  except BadMagicError:
317    raise ValueError("bootloader.img bad magic value")
318
319  common.ZipWriteStr(info.output_zip, "bootloader-flag.txt",
320                     "updating-bootloader" + "\0" * 13)
321  common.ZipWriteStr(info.output_zip, "bootloader-flag-clear.txt", "\0" * 32)
322
323  _, misc_device = common.GetTypeAndDevice("/misc", info.info_dict)
324
325  info.script.AppendExtra(
326      'package_extract_file("bootloader-flag.txt", "%s");' % misc_device)
327
328  # OTA does not support partition changes, so
329  # do not bundle the partition image in the OTA package.
330  WriteHuaweiBootPartitionImages(info, huawei_boot_image, blacklist)
331
332  info.script.AppendExtra(
333      'package_extract_file("bootloader-flag-clear.txt", "%s");' % misc_device)
334
335
336def TruncToNull(s):
337  if '\0' in s:
338    return s[:s.index('\0')]
339  else:
340    return s
341