#!/usr/bin/env python3 # # Build Actions SoC OTA firmware # # Copyright (c) 2019 Actions Semiconductor Co., Ltd # # SPDX-License-Identifier: Apache-2.0 # import os import sys import time import struct import argparse import platform import subprocess import array import hashlib import shutil import zipfile import xml.etree.ElementTree as ET import zlib script_path = os.path.split(os.path.realpath(__file__))[0] def align_down(data, alignment): return data & ~(alignment - 1) def align_up(data, alignment): return align_down(data + alignment - 1, alignment) def calc_pad_length(length, alignment): return align_up(length, alignment) - length def crc32_file(filename): if os.path.isfile(filename): with open(filename, 'rb') as f: crc = zlib.crc32(f.read(), 0) & 0xffffffff return crc return 0 def panic(err_msg): print('\033[1;31;40m') print('FW: Error: %s\n' %err_msg) print('\033[0m') sys.exit(1) def print_notice(msg): print('\033[1;32;40m%s\033[0m' %msg) ''' /* OTA image */ typedef struct { uint8 filename[12]; uint8 reserved1[4]; uint32 offset; uint32 length; uint8 reserved2[4]; uint32 checksum; }atf_dir_t; struct ota_fw_hdr { u32_t magic; u32_t header_checksum; u16_t header_version; u16_t header_size; u16_t file_cnt; u16_t flag; u16_t dir_offset; u16_t data_offset; u32_t data_size; u32_t data_checksum; u8_t reserved[8]; } __attribute__((packed)); struct fw_version { u32_t magic; u32_t version_code; u32_t version_res; u32_t system_version_code; char version_name[64]; char board_name[32]; u8_t reserved1[12]; u32_t checksum; }; struct ota_fw_head { struct ota_fw_hdr hdr; u8_t reserved1[32]; /* offset: 0x40 */ struct ota_fw_ver new_ver; /* offset: 0xa0 */ struct ota_fw_ver old_ver; u8_t reserved2[32]; /* offset: 0x200 */ struct ota_fw_dir dir; }; ''' class fw_version(object): def __init__(self, xml_file, tag_name): self.valid = False print('FWVER: Parse xml file: %s tag %s' %(xml_file, tag_name)) tree = ET.ElementTree(file=xml_file) root = tree.getroot() if (root.tag != 'ota_firmware'): panic('OTA: invalid OTA xml file') ver = root.find(tag_name) if ver == None: return self.version_name = ver.find('version_name').text.strip() self.version_code = int(ver.find('version_code').text.strip(), 0) self.version_res = int(ver.find('version_res').text.strip(), 0) self.board_name = ver.find('board_name').text.strip() self.valid = True def get_data(self): return struct.pack('<32s24s4xIxI', bytearray(self.version_name, 'utf8'), \ bytearray(self.board_name, 'utf8'), self.version_code, self.version_res) def dump_info(self): print('\t' + 'version name: ' + self.version_code) print('\t' + 'version name: ' + self.version_res) print('\t' + 'version code: ' + self.version_name) print('\t' + ' board name: ' + self.board_name) class ota_file(object): def __init__(self, fpath): if (not os.path.isfile(fpath)): panic('invalid file: ' + fpath) self.file_path = fpath self.file_name = bytearray(os.path.basename(fpath), 'utf8') if len(self.file_name) > 12: print('OTA: file %s name is too long' %(self.file_name)) return self.length = os.path.getsize(fpath) self.checksum = int(crc32_file(fpath)) self.data = bytearray(0) with open(fpath, 'rb') as f: self.data = f.read() def dump_info(self): print(' name: ' + self.file_name.decode('utf8').rstrip('\0')) print(' length: ' + str(self.length)) print(' checksum: ' + str(self.checksum)) FIRMWARE_VERSION_MAGIC = 0x52455646 #FVER OTA_FIRMWARE_MAGIC = b'AOTA' OTA_FIRMWARE_DIR_OFFSET = 0x200 OTA_FIRMWARE_DIR_ENTRY_SIZE = 0x20 OTA_FIRMWARE_HEADER_SIZE = 0x400 OTA_FIRMWARE_DATA_OFFSET = 0x400 OTA_FIRMWARE_VERSION = 0x0100 class ota_fw(object): def __init__(self, path_list): self.old_version = None self.new_version = None self.length = 0 self.offset = 0 self.ota_files = [] self.file_data = bytearray(0) self.head_data = bytearray(0) self.dir_data = bytearray(0) if len(path_list) > 14: panic('OTA: too much input files') if len(path_list) == 1 and os.path.isdir(path_list[0]): files = [name for name in os.listdir(path_list[0]) \ if os.path.isfile(os.path.join(path_list[0], name))] else: files = path_list for file in files: self.ota_files.append(ota_file(file)) def dump_info(self): i = 0 for file in self.ota_files: print('[%d]' %(i)) i = i + 1 file.dump_info() def pack(self, output_file): data_offset = OTA_FIRMWARE_DATA_OFFSET dir_entry = bytearray(0) for file in self.ota_files: dir_entry = struct.pack("<12s4xII4xI", file.file_name, data_offset, file.length, file.checksum) pad_len = calc_pad_length(file.length, 512) data_offset = data_offset + file.length + pad_len self.file_data = self.file_data + file.data + bytearray(pad_len) self.dir_data = self.dir_data + dir_entry data_crc = zlib.crc32(self.file_data, 0) & 0xffffffff pad_len = calc_pad_length(len(self.dir_data), 512) self.dir_data = self.dir_data + bytearray(pad_len) self.head_data = struct.pack("<4sIHHHHHHII36x", OTA_FIRMWARE_MAGIC, 0, \ OTA_FIRMWARE_VERSION, OTA_FIRMWARE_HEADER_SIZE, \ len(self.ota_files), 0, \ OTA_FIRMWARE_DIR_OFFSET, OTA_FIRMWARE_DATA_OFFSET, \ data_offset, data_crc) # add fw version xml_fpath = '' for file in self.ota_files: if file.file_name == b'ota.xml': xml_fpath = file.file_path if xml_fpath == '': panic('cannot found ota.xml file') fw_ver = fw_version(xml_fpath, 'firmware_version') if not fw_ver.valid: panic('cannot found ota.xml file') self.head_data = self.head_data + fw_ver.get_data() old_fw_ver = fw_version(xml_fpath, 'old_firmware_version') if old_fw_ver.valid: self.head_data = self.head_data + old_fw_ver.get_data() self.head_data = self.head_data + bytearray(calc_pad_length(len(self.head_data), 512)) header_crc = zlib.crc32(self.head_data[8:] + self.dir_data, 0) & 0xffffffff self.head_data = self.head_data[0:4] + struct.pack('