imaging.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # Copyright 2024 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import print_function
  15. from binascii import crc32
  16. import os
  17. import struct
  18. import sys
  19. import traceback
  20. from functools import reduce
  21. import pebble.pulse2.exceptions
  22. from .. import PebbleCommander, exceptions, parsers
  23. from ..util import stm32_crc
  24. class PebbleFirmwareBinaryInfo(object):
  25. V1_STRUCT_VERSION = 1
  26. V1_STRUCT_DEFINTION = [
  27. ('20s', 'build_id'),
  28. ('L', 'version_timestamp'),
  29. ('32s', 'version_tag'),
  30. ('8s', 'version_short'),
  31. ('?', 'is_recovery_firmware'),
  32. ('B', 'hw_platform'),
  33. ('B', 'metadata_version')
  34. ]
  35. # The platforms which use a legacy defective crc32
  36. LEGACY_CRC_PLATFORMS = [
  37. 0, # unknown (assume legacy)
  38. 1, # OneEV1
  39. 2, # OneEV2
  40. 3, # OneEV2_3
  41. 4, # OneEV2_4
  42. 5, # OnePointFive
  43. 6, # TwoPointFive
  44. 7, # SnowyEVT2
  45. 8, # SnowyDVT
  46. 9, # SpaldingEVT
  47. 10, # BobbyDVT
  48. 11, # Spalding
  49. 0xff, # OneBigboard
  50. 0xfe, # OneBigboard2
  51. 0xfd, # SnowyBigboard
  52. 0xfc, # SnowyBigboard2
  53. 0xfb, # SpaldingBigboard
  54. ]
  55. def get_crc(self):
  56. _, ext = os.path.splitext(self.path)
  57. assert ext == '.bin', 'Can only calculate crc for .bin files'
  58. with open(self.path, 'rb') as f:
  59. image = f.read()
  60. if self.hw_platform in self.LEGACY_CRC_PLATFORMS:
  61. # use the legacy defective crc
  62. return stm32_crc.crc32(image)
  63. else:
  64. # use a regular crc
  65. return crc32(image) & 0xFFFFFFFF
  66. def _get_footer_struct(self):
  67. fmt = '<' + reduce(lambda s, t: s + t[0],
  68. PebbleFirmwareBinaryInfo.V1_STRUCT_DEFINTION, '')
  69. return struct.Struct(fmt)
  70. def _get_footer_data_from_bin(self, path):
  71. with open(path, 'rb') as f:
  72. struct_size = self.struct.size
  73. f.seek(-struct_size, 2)
  74. footer_data = f.read()
  75. return footer_data
  76. def _parse_footer_data(self, footer_data):
  77. z = zip(PebbleFirmwareBinaryInfo.V1_STRUCT_DEFINTION,
  78. self.struct.unpack(footer_data))
  79. return {entry[1]: data for entry, data in z}
  80. def __init__(self, bin_path):
  81. self.path = bin_path
  82. self.struct = self._get_footer_struct()
  83. _, ext = os.path.splitext(bin_path)
  84. if ext != '.bin':
  85. raise ValueError('Unexpected extension. Must be ".bin"')
  86. footer_data = self._get_footer_data_from_bin(bin_path)
  87. self.info = self._parse_footer_data(footer_data)
  88. # Trim leading NULLS on the strings:
  89. for k in ["version_tag", "version_short"]:
  90. self.info[k] = self.info[k].rstrip(b"\x00")
  91. def __str__(self):
  92. return str(self.info)
  93. def __repr__(self):
  94. return self.info.__repr__()
  95. def __getattr__(self, name):
  96. if name in self.info:
  97. return self.info[name]
  98. raise AttributeError
  99. # typedef struct ATTR_PACKED FirmwareDescription {
  100. # uint32_t description_length;
  101. # uint32_t firmware_length;
  102. # uint32_t checksum;
  103. # } FirmwareDescription;
  104. FW_DESCR_FORMAT = '<III'
  105. FW_DESCR_SIZE = struct.calcsize(FW_DESCR_FORMAT)
  106. def _generate_firmware_description_struct(firmware_length, firmware_crc):
  107. return struct.pack(FW_DESCR_FORMAT, FW_DESCR_SIZE, firmware_length, firmware_crc)
  108. def insert_firmware_description_struct(input_binary, output_binary=None):
  109. fw_bin_info = PebbleFirmwareBinaryInfo(input_binary)
  110. with open(input_binary, 'rb') as inf:
  111. fw_bin = inf.read()
  112. fw_crc = fw_bin_info.get_crc()
  113. return _generate_firmware_description_struct(len(fw_bin), fw_crc) + fw_bin
  114. def _load(connection, image, progress, verbose, address):
  115. image_crc = stm32_crc.crc32(image)
  116. progress_cb = None
  117. if progress or verbose:
  118. def progress_cb(acked):
  119. print('.' if acked else 'R', end='')
  120. sys.stdout.flush()
  121. if progress or verbose:
  122. print('Erasing... ', end='')
  123. sys.stdout.flush()
  124. try:
  125. connection.flash.erase(address, len(image))
  126. except pebble.pulse2.exceptions.PulseException as e:
  127. detail = ''.join(traceback.format_exception_only(type(e), e))
  128. if verbose:
  129. detail = '\n' + traceback.format_exc()
  130. print('Erase failed! ' + detail)
  131. return False
  132. if progress or verbose:
  133. print('done.')
  134. sys.stdout.flush()
  135. try:
  136. retries = connection.flash.write(address, image,
  137. progress_cb=progress_cb)
  138. except pebble.pulse2.exceptions.PulseException as e:
  139. detail = ''.join(traceback.format_exception_only(type(e), e))
  140. if verbose:
  141. detail = '\n' + traceback.format_exc()
  142. print('Write failed! ' + detail)
  143. return False
  144. result_crc = connection.flash.crc(address, len(image))
  145. if progress or verbose:
  146. print()
  147. if verbose:
  148. print('Retries: %d' % retries)
  149. if result_crc != image_crc:
  150. print('CRC mismatch, got 0x%08X but expected %08X' % (result_crc, image_crc))
  151. return result_crc == image_crc
  152. def load_firmware(connection, fin, progress, verbose, address=None):
  153. if address is None:
  154. # If address is unspecified, assume we want the prf address
  155. _, address, length = connection.flash.query_region_geometry(
  156. connection.flash.REGION_PRF)
  157. address = int(address)
  158. image = insert_firmware_description_struct(fin)
  159. if _load(connection, image, progress, verbose, address):
  160. connection.flash.finalize_region(
  161. connection.flash.REGION_PRF)
  162. return True
  163. return False
  164. def load_resources(connection, fin, progress, verbose):
  165. _, address, length = connection.flash.query_region_geometry(
  166. connection.flash.REGION_SYSTEM_RESOURCES)
  167. with open(fin, 'rb') as f:
  168. data = f.read()
  169. assert len(data) <= length
  170. if _load(connection, data, progress, verbose, address):
  171. connection.flash.finalize_region(
  172. connection.flash.REGION_SYSTEM_RESOURCES)
  173. return True
  174. return False
  175. @PebbleCommander.command()
  176. def image_resources(cmdr, pack='build/system_resources.pbpack'):
  177. """ Image resources.
  178. """
  179. load_resources(cmdr.connection, pack,
  180. progress=cmdr.interactive, verbose=cmdr.interactive)
  181. @PebbleCommander.command()
  182. def image_firmware(cmdr, firm='build/prf/src/fw/tintin_fw.bin', address=None):
  183. """ Image recovery firmware.
  184. """
  185. if address is not None:
  186. address = int(str(address), 0)
  187. load_firmware(cmdr.connection, firm, progress=cmdr.interactive,
  188. verbose=cmdr.interactive, address=address)