build_ota_image.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #!/usr/bin/env python3
  2. #
  3. # Build Actions SoC OTA firmware
  4. #
  5. # Copyright (c) 2019 Actions Semiconductor Co., Ltd
  6. #
  7. # SPDX-License-Identifier: Apache-2.0
  8. #
  9. import os
  10. import sys
  11. import time
  12. import struct
  13. import argparse
  14. import platform
  15. import subprocess
  16. import array
  17. import hashlib
  18. import shutil
  19. import zipfile
  20. import xml.etree.ElementTree as ET
  21. import zlib
  22. script_path = os.path.split(os.path.realpath(__file__))[0]
  23. def align_down(data, alignment):
  24. return data & ~(alignment - 1)
  25. def align_up(data, alignment):
  26. return align_down(data + alignment - 1, alignment)
  27. def calc_pad_length(length, alignment):
  28. return align_up(length, alignment) - length
  29. def crc32_file(filename):
  30. if os.path.isfile(filename):
  31. with open(filename, 'rb') as f:
  32. crc = zlib.crc32(f.read(), 0) & 0xffffffff
  33. return crc
  34. return 0
  35. def panic(err_msg):
  36. print('\033[1;31;40m')
  37. print('FW: Error: %s\n' %err_msg)
  38. print('\033[0m')
  39. sys.exit(1)
  40. def print_notice(msg):
  41. print('\033[1;32;40m%s\033[0m' %msg)
  42. '''
  43. /* OTA image */
  44. typedef struct
  45. {
  46. uint8 filename[12];
  47. uint8 reserved1[4];
  48. uint32 offset;
  49. uint32 length;
  50. uint8 reserved2[4];
  51. uint32 checksum;
  52. }atf_dir_t;
  53. struct ota_fw_hdr {
  54. u32_t magic;
  55. u32_t header_checksum;
  56. u16_t header_version;
  57. u16_t header_size;
  58. u16_t file_cnt;
  59. u16_t flag;
  60. u16_t dir_offset;
  61. u16_t data_offset;
  62. u32_t data_size;
  63. u32_t data_checksum;
  64. u8_t reserved[8];
  65. } __attribute__((packed));
  66. struct fw_version {
  67. u32_t magic;
  68. u32_t version_code;
  69. u32_t version_res;
  70. u32_t system_version_code;
  71. char version_name[64];
  72. char board_name[32];
  73. u8_t reserved1[12];
  74. u32_t checksum;
  75. };
  76. struct ota_fw_head {
  77. struct ota_fw_hdr hdr;
  78. u8_t reserved1[32];
  79. /* offset: 0x40 */
  80. struct ota_fw_ver new_ver;
  81. /* offset: 0xa0 */
  82. struct ota_fw_ver old_ver;
  83. u8_t reserved2[32];
  84. /* offset: 0x200 */
  85. struct ota_fw_dir dir;
  86. };
  87. '''
  88. class fw_version(object):
  89. def __init__(self, xml_file, tag_name):
  90. self.valid = False
  91. print('FWVER: Parse xml file: %s tag %s' %(xml_file, tag_name))
  92. tree = ET.ElementTree(file=xml_file)
  93. root = tree.getroot()
  94. if (root.tag != 'ota_firmware'):
  95. panic('OTA: invalid OTA xml file')
  96. ver = root.find(tag_name)
  97. if ver == None:
  98. return
  99. self.version_name = ver.find('version_name').text.strip()
  100. self.version_code = int(ver.find('version_code').text.strip(), 0)
  101. self.version_res = int(ver.find('version_res').text.strip(), 0)
  102. self.board_name = ver.find('board_name').text.strip()
  103. self.valid = True
  104. def get_data(self):
  105. return struct.pack('<32s24s4xIxI', bytearray(self.version_name, 'utf8'), \
  106. bytearray(self.board_name, 'utf8'), self.version_code, self.version_res)
  107. def dump_info(self):
  108. print('\t' + 'version name: ' + self.version_code)
  109. print('\t' + 'version name: ' + self.version_res)
  110. print('\t' + 'version code: ' + self.version_name)
  111. print('\t' + ' board name: ' + self.board_name)
  112. class ota_file(object):
  113. def __init__(self, fpath):
  114. if (not os.path.isfile(fpath)):
  115. panic('invalid file: ' + fpath)
  116. self.file_path = fpath
  117. self.file_name = bytearray(os.path.basename(fpath), 'utf8')
  118. if len(self.file_name) > 12:
  119. print('OTA: file %s name is too long' %(self.file_name))
  120. return
  121. self.length = os.path.getsize(fpath)
  122. self.checksum = int(crc32_file(fpath))
  123. self.data = bytearray(0)
  124. with open(fpath, 'rb') as f:
  125. self.data = f.read()
  126. def dump_info(self):
  127. print(' name: ' + self.file_name.decode('utf8').rstrip('\0'))
  128. print(' length: ' + str(self.length))
  129. print(' checksum: ' + str(self.checksum))
  130. FIRMWARE_VERSION_MAGIC = 0x52455646 #FVER
  131. OTA_FIRMWARE_MAGIC = b'AOTA'
  132. OTA_FIRMWARE_DIR_OFFSET = 0x200
  133. OTA_FIRMWARE_DIR_ENTRY_SIZE = 0x20
  134. OTA_FIRMWARE_HEADER_SIZE = 0x400
  135. OTA_FIRMWARE_DATA_OFFSET = 0x400
  136. OTA_FIRMWARE_VERSION = 0x0100
  137. class ota_fw(object):
  138. def __init__(self, path_list):
  139. self.old_version = None
  140. self.new_version = None
  141. self.length = 0
  142. self.offset = 0
  143. self.ota_files = []
  144. self.file_data = bytearray(0)
  145. self.head_data = bytearray(0)
  146. self.dir_data = bytearray(0)
  147. if len(path_list) > 14:
  148. panic('OTA: too much input files')
  149. if len(path_list) == 1 and os.path.isdir(path_list[0]):
  150. files = [name for name in os.listdir(path_list[0]) \
  151. if os.path.isfile(os.path.join(path_list[0], name))]
  152. else:
  153. files = path_list
  154. for file in files:
  155. self.ota_files.append(ota_file(file))
  156. def dump_info(self):
  157. i = 0
  158. for file in self.ota_files:
  159. print('[%d]' %(i))
  160. i = i + 1
  161. file.dump_info()
  162. def pack(self, output_file):
  163. data_offset = OTA_FIRMWARE_DATA_OFFSET
  164. dir_entry = bytearray(0)
  165. for file in self.ota_files:
  166. dir_entry = struct.pack("<12s4xII4xI", file.file_name, data_offset, file.length, file.checksum)
  167. pad_len = calc_pad_length(file.length, 512)
  168. data_offset = data_offset + file.length + pad_len
  169. self.file_data = self.file_data + file.data + bytearray(pad_len)
  170. self.dir_data = self.dir_data + dir_entry
  171. data_crc = zlib.crc32(self.file_data, 0) & 0xffffffff
  172. pad_len = calc_pad_length(len(self.dir_data), 512)
  173. self.dir_data = self.dir_data + bytearray(pad_len)
  174. self.head_data = struct.pack("<4sIHHHHHHII36x", OTA_FIRMWARE_MAGIC, 0, \
  175. OTA_FIRMWARE_VERSION, OTA_FIRMWARE_HEADER_SIZE, \
  176. len(self.ota_files), 0, \
  177. OTA_FIRMWARE_DIR_OFFSET, OTA_FIRMWARE_DATA_OFFSET, \
  178. data_offset, data_crc)
  179. # add fw version
  180. xml_fpath = ''
  181. for file in self.ota_files:
  182. if file.file_name == b'ota.xml':
  183. xml_fpath = file.file_path
  184. if xml_fpath == '':
  185. panic('cannot found ota.xml file')
  186. fw_ver = fw_version(xml_fpath, 'firmware_version')
  187. if not fw_ver.valid:
  188. panic('cannot found ota.xml file')
  189. self.head_data = self.head_data + fw_ver.get_data()
  190. old_fw_ver = fw_version(xml_fpath, 'old_firmware_version')
  191. if old_fw_ver.valid:
  192. self.head_data = self.head_data + old_fw_ver.get_data()
  193. self.head_data = self.head_data + bytearray(calc_pad_length(len(self.head_data), 512))
  194. header_crc = zlib.crc32(self.head_data[8:] + self.dir_data, 0) & 0xffffffff
  195. self.head_data = self.head_data[0:4] + struct.pack('<I', header_crc) + \
  196. self.head_data[8:]
  197. with open(output_file, 'wb') as f:
  198. f.write(self.head_data)
  199. f.write(self.dir_data)
  200. f.write(self.file_data)
  201. def unpack_fw(fpath, out_dir):
  202. f = open(fpath, 'rb')
  203. if f == None:
  204. panic('cannot open file')
  205. ota_data = f.read()
  206. magic, checksum, header_version, header_size, file_cnt, header_flag, \
  207. dir_offs, data_offs, data_len, data_crc \
  208. = struct.unpack("<4sIHHHHHHII36x", ota_data[0:64])
  209. if magic != OTA_FIRMWARE_MAGIC:
  210. panic('invalid magic')
  211. if not os.path.exists(out_dir):
  212. os.makedirs(out_dir)
  213. for i in range(file_cnt):
  214. entry_offs = dir_offs + i * OTA_FIRMWARE_DIR_ENTRY_SIZE
  215. name, offset, file_length, checksum = \
  216. struct.unpack("<12s4xII4xI", ota_data[entry_offs : entry_offs + OTA_FIRMWARE_DIR_ENTRY_SIZE])
  217. if name[0] != 0:
  218. with open(os.path.join(out_dir, name.decode('utf8').rstrip('\0')), 'wb') as wf:
  219. wf.write(ota_data[offset : offset + file_length])
  220. def main(argv):
  221. parser = argparse.ArgumentParser(
  222. description='Build OTA firmware image',
  223. )
  224. parser.add_argument('-o', dest = 'output_file')
  225. parser.add_argument('-i', dest = 'image_file')
  226. parser.add_argument('-x', dest = 'extract_out_dir')
  227. parser.add_argument('input_files', nargs = '*')
  228. args = parser.parse_args();
  229. print('ATF: Build OTA firmware image: %s' %args.output_file)
  230. if args.extract_out_dir:
  231. unpack_fw(args.image_file, args.extract_out_dir)
  232. else:
  233. if (not args.output_file) :
  234. panic('no file')
  235. fw = ota_fw(args.input_files)
  236. fw.dump_info()
  237. fw.pack(args.output_file)
  238. return 0
  239. if __name__ == '__main__':
  240. main(sys.argv[1:])