#
# Copyright (C) 2015 The Android Open-Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
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"
#
# Huawei Bootloader packed image format
#
# typedef struct meta_header {
# u32 magic; /* 0xce1ad63c */
# u16 major_version; /* (0x1)-reject images with higher major versions */
# u16 minor_version; /* (0x0)-allow images with higer minor versions */
# char img_version[64]; /* Top level version for images in this meta */
# u16 meta_hdr_sz; /* size of this header */
# u16 img_hdr_sz; /* size of img_header_entry list */
# } meta_header_t;
# typedef struct img_header_entry {
# char ptn_name[MAX_GPT_NAME_SIZE];
# u32 start_offset;
# u32 size;
# } img_header_entry_t
MAGIC = 0xce1ad63c
class HuaweiBootImage(object):
def __init__(self, data, name=None):
self.name = name
self.unpacked_images = None
self._unpack(data)
def _unpack(self, data):
"""Unpack the data blob as a Huawei boot image and return the list
of contained image objects"""
num_imgs_fmt = struct.Struct("<IHH64sHH")
header = data[0:num_imgs_fmt.size]
info = {}
(info["magic"], info["major_version"],
info["minor_version"], info["img_version"],
info["meta_hdr_size"], info["img_hdr_size"]) = num_imgs_fmt.unpack(header)
img_info_format = "<72sLL"
img_info_size = struct.calcsize(img_info_format)
num = info["img_hdr_size"] / img_info_size
size = num_imgs_fmt.size
imgs = [
struct.unpack(
img_info_format,
data[size + i * img_info_size:size + (i + 1) * img_info_size])
for i in range(num)
]
if info["magic"] != MAGIC:
raise BadMagicError
img_objs = {}
for name, start, end in imgs:
if TruncToNull(name):
img = common.File(TruncToNull(name), data[start:start + end])
img_objs[img.name] = img
self.unpacked_images = img_objs
def GetUnpackedImage(self, name):
return self.unpacked_images.get(name)
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)
source_radio_img = FindRadio(info.source_zip)
if not target_radio_img or not source_radio_img:
return
target_modem_img = HuaweiBootImage(target_radio_img).GetUnpackedImage("modem")
if not target_modem_img:
return
source_modem_img = HuaweiBootImage(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 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"
target_radio_image = FindRadio(info.target_zip)
if not target_radio_image:
# failed to read TARGET radio image: don't include any radio in update.
print "no radio.img in target target_files; skipping install"
else:
tf = common.File("radio.img", target_radio_image)
source_radio_image = FindRadio(info.source_zip)
if not source_radio_image:
# failed to read SOURCE radio image: include the whole target
# radio image.
print "no radio image in source target_files; installing complete image"
WriteRadio(info, tf.data)
else:
sf = common.File("radio.img", source_radio_image)
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 = HuaweiBootImage(target_imagefile.data, "bootloader")
except BadMagicError:
raise ValueError("bootloader.img bad magic value")
try:
sm = HuaweiBootImage(source_imagefile.data, "bootloader")
except BadMagicError:
print "source bootloader is not a Huawei boot img; installing complete img."
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.values():
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 img %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.values()
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 = HuaweiBootImage(target_imagefile.data, "radio")
except BadMagicError:
print "Magic number mismatch in target radio image"
raise ValueError("radio.img bad magic value")
try:
source_radio_img = HuaweiBootImage(source_imagefile.data, "radio")
except BadMagicError:
print "Magic number mismatch in source radio image"
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")
WriteHuaweiBootPartitionImages(info, target_radio_img, blacklist)
def WriteIncrementalModemPartition(info, target_modem_image,
source_modem_image):
tf = target_modem_image
sf = source_modem_image
pad_tf = False
pad_sf = False
blocksize = 4096
partial_tf = len(tf.data) % blocksize
partial_sf = len(sf.data) % blocksize
if partial_tf:
pad_tf = True
if partial_sf:
pad_sf = True
b = common.BlockDifference("modem", common.DataImage(tf.data, False, pad_tf),
common.DataImage(sf.data, False, pad_sf))
b.WriteScript(info.script, info.output_zip)
def WriteRadio(info, radio_img):
info.script.Print("Writing radio...")
try:
huawei_boot_image = HuaweiBootImage(radio_img, "radio")
except BadMagicError:
raise ValueError("radio.img bad magic value")
WriteHuaweiBootPartitionImages(info, huawei_boot_image)
def WriteHuaweiBootPartitionImages(info, huawei_boot_image, blacklist=None):
if blacklist is None:
blacklist = []
WriteGroupedImages(info, huawei_boot_image.name,
huawei_boot_image.unpacked_images.values(), blacklist)
def WriteGroupedImages(info, group_name, images, blacklist=None):
"""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."""
if blacklist is None:
blacklist = []
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:
info.script.Print("writing partition image %s" % image.name)
_, 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=None):
if blacklist is None:
blacklist = DEFAULT_BOOTLOADER_OTA_BLACKLIST
info.script.Print("Writing bootloader...")
try:
huawei_boot_image = HuaweiBootImage(bootloader, "bootloader")
except BadMagicError:
raise ValueError("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.
WriteHuaweiBootPartitionImages(info, huawei_boot_image, blacklist)
info.script.AppendExtra(
'package_extract_file("bootloader-flag-clear.txt", "%s");' % misc_device)
def TruncToNull(s):
if '\0' in s:
return s[:s.index('\0')]
else:
return s