programmer_openocd.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import logging
  2. import os
  3. import typing
  4. from flipper.utils.programmer import Programmer
  5. from flipper.utils.openocd import OpenOCD
  6. from flipper.utils.stm32wb55 import STM32WB55
  7. from flipper.assets.obdata import OptionBytesData
  8. class OpenOCDProgrammer(Programmer):
  9. def __init__(
  10. self,
  11. interface: str = "interface/cmsis-dap.cfg",
  12. port_base: typing.Union[int, None] = None,
  13. serial: typing.Union[str, None] = None,
  14. ):
  15. super().__init__()
  16. config = {}
  17. config["interface"] = interface
  18. config["target"] = "target/stm32wbx.cfg"
  19. if not serial is None:
  20. if interface == "interface/cmsis-dap.cfg":
  21. config["serial"] = f"cmsis_dap_serial {serial}"
  22. elif "stlink" in interface:
  23. config["serial"] = f"stlink_serial {serial}"
  24. if not port_base is None:
  25. config["port_base"] = port_base
  26. self.openocd = OpenOCD(config)
  27. self.logger = logging.getLogger()
  28. def reset(self, mode: Programmer.RunMode = Programmer.RunMode.Run) -> bool:
  29. stm32 = STM32WB55()
  30. if mode == Programmer.RunMode.Run:
  31. stm32.reset(self.openocd, stm32.RunMode.Run)
  32. elif mode == Programmer.RunMode.Stop:
  33. stm32.reset(self.openocd, stm32.RunMode.Init)
  34. else:
  35. raise Exception("Unknown mode")
  36. return True
  37. def flash(self, address: int, file_path: str, verify: bool = True) -> bool:
  38. if not os.path.exists(file_path):
  39. raise Exception(f"File {file_path} not found")
  40. self.openocd.start()
  41. self.openocd.send_tcl(f"init")
  42. self.openocd.send_tcl(
  43. f"program {file_path} 0x{address:08x}{' verify' if verify else ''} reset exit"
  44. )
  45. self.openocd.stop()
  46. return True
  47. def _ob_print_diff_table(self, ob_reference: bytes, ob_read: bytes, print_fn):
  48. print_fn(
  49. f'{"Reference": <20} {"Device": <20} {"Diff Reference": <20} {"Diff Device": <20}'
  50. )
  51. # Split into 8 byte, word + word
  52. for i in range(0, len(ob_reference), 8):
  53. ref = ob_reference[i : i + 8]
  54. read = ob_read[i : i + 8]
  55. diff_str1 = ""
  56. diff_str2 = ""
  57. for j in range(0, len(ref.hex()), 2):
  58. byte_str_1 = ref.hex()[j : j + 2]
  59. byte_str_2 = read.hex()[j : j + 2]
  60. if byte_str_1 == byte_str_2:
  61. diff_str1 += "__"
  62. diff_str2 += "__"
  63. else:
  64. diff_str1 += byte_str_1
  65. diff_str2 += byte_str_2
  66. print_fn(
  67. f"{ref.hex(): <20} {read.hex(): <20} {diff_str1: <20} {diff_str2: <20}"
  68. )
  69. def option_bytes_validate(self, file_path: str) -> bool:
  70. # Registers
  71. stm32 = STM32WB55()
  72. # OpenOCD
  73. self.openocd.start()
  74. stm32.reset(self.openocd, stm32.RunMode.Init)
  75. # Generate Option Bytes data
  76. ob_data = OptionBytesData(file_path)
  77. ob_values = ob_data.gen_values().export()
  78. ob_reference = ob_values.reference
  79. ob_compare_mask = ob_values.compare_mask
  80. ob_length = len(ob_reference)
  81. ob_words = int(ob_length / 4)
  82. # Read Option Bytes
  83. ob_read = bytes()
  84. for i in range(ob_words):
  85. addr = stm32.OPTION_BYTE_BASE + i * 4
  86. value = self.openocd.read_32(addr)
  87. ob_read += value.to_bytes(4, "little")
  88. # Compare Option Bytes with reference by mask
  89. ob_compare = bytes()
  90. for i in range(ob_length):
  91. ob_compare += bytes([ob_read[i] & ob_compare_mask[i]])
  92. # Compare Option Bytes
  93. return_code = False
  94. if ob_reference == ob_compare:
  95. self.logger.info("Option Bytes are valid")
  96. return_code = True
  97. else:
  98. self.logger.error("Option Bytes are invalid")
  99. self._ob_print_diff_table(ob_reference, ob_compare, self.logger.error)
  100. # Stop OpenOCD
  101. stm32.reset(self.openocd, stm32.RunMode.Run)
  102. self.openocd.stop()
  103. return return_code
  104. def _unpack_u32(self, data: bytes, offset: int):
  105. return int.from_bytes(data[offset : offset + 4], "little")
  106. def option_bytes_set(self, file_path: str) -> bool:
  107. # Registers
  108. stm32 = STM32WB55()
  109. # OpenOCD
  110. self.openocd.start()
  111. stm32.reset(self.openocd, stm32.RunMode.Init)
  112. # Generate Option Bytes data
  113. ob_data = OptionBytesData(file_path)
  114. ob_values = ob_data.gen_values().export()
  115. ob_reference_bytes = ob_values.reference
  116. ob_compare_mask_bytes = ob_values.compare_mask
  117. ob_write_mask_bytes = ob_values.write_mask
  118. ob_length = len(ob_reference_bytes)
  119. ob_dwords = int(ob_length / 8)
  120. # Clear flash errors
  121. stm32.clear_flash_errors(self.openocd)
  122. # Unlock Flash and Option Bytes
  123. stm32.flash_unlock(self.openocd)
  124. stm32.option_bytes_unlock(self.openocd)
  125. ob_need_to_apply = False
  126. for i in range(ob_dwords):
  127. device_addr = stm32.OPTION_BYTE_BASE + i * 8
  128. device_value = self.openocd.read_32(device_addr)
  129. ob_write_mask = self._unpack_u32(ob_write_mask_bytes, i * 8)
  130. ob_compare_mask = self._unpack_u32(ob_compare_mask_bytes, i * 8)
  131. ob_value_ref = self._unpack_u32(ob_reference_bytes, i * 8)
  132. ob_value_masked = device_value & ob_compare_mask
  133. need_patch = ((ob_value_masked ^ ob_value_ref) & ob_write_mask) != 0
  134. if need_patch:
  135. ob_need_to_apply = True
  136. self.logger.info(
  137. f"Need to patch: {device_addr:08X}: {ob_value_masked:08X} != {ob_value_ref:08X}, REG[{i}]"
  138. )
  139. # Check if this option byte (dword) is mapped to a register
  140. device_reg_addr = stm32.option_bytes_id_to_address(i)
  141. # Construct new value for the OB register
  142. ob_value = device_value & (~ob_write_mask)
  143. ob_value |= ob_value_ref & ob_write_mask
  144. self.logger.info(f"Writing {ob_value:08X} to {device_reg_addr:08X}")
  145. self.openocd.write_32(device_reg_addr, ob_value)
  146. if ob_need_to_apply:
  147. stm32.option_bytes_apply(self.openocd)
  148. else:
  149. self.logger.info(f"Option Bytes are already correct")
  150. # Load Option Bytes
  151. # That will reset and also lock the Option Bytes and the Flash
  152. stm32.option_bytes_load(self.openocd)
  153. # Stop OpenOCD
  154. stm32.reset(self.openocd, stm32.RunMode.Run)
  155. self.openocd.stop()
  156. return True
  157. def otp_write(self, address: int, file_path: str) -> bool:
  158. # Open file, check that it aligned to 8 bytes
  159. with open(file_path, "rb") as f:
  160. data = f.read()
  161. if len(data) % 8 != 0:
  162. self.logger.error(f"File {file_path} is not aligned to 8 bytes")
  163. return False
  164. # Check that address is aligned to 8 bytes
  165. if address % 8 != 0:
  166. self.logger.error(f"Address {address} is not aligned to 8 bytes")
  167. return False
  168. # Get size of data
  169. data_size = len(data)
  170. # Check that data size is aligned to 8 bytes
  171. if data_size % 8 != 0:
  172. self.logger.error(f"Data size {data_size} is not aligned to 8 bytes")
  173. return False
  174. self.logger.debug(f"Writing {data_size} bytes to OTP at {address:08X}")
  175. self.logger.debug(f"Data: {data.hex().upper()}")
  176. # Start OpenOCD
  177. oocd = self.openocd
  178. oocd.start()
  179. # Registers
  180. stm32 = STM32WB55()
  181. try:
  182. # Check that OTP is empty for the given address
  183. # Also check that data is already written
  184. already_written = True
  185. for i in range(0, data_size, 4):
  186. file_word = int.from_bytes(data[i : i + 4], "little")
  187. device_word = oocd.read_32(address + i)
  188. if device_word != 0xFFFFFFFF and device_word != file_word:
  189. self.logger.error(
  190. f"OTP memory at {address + i:08X} is not empty: {device_word:08X}"
  191. )
  192. raise Exception("OTP memory is not empty")
  193. if device_word != file_word:
  194. already_written = False
  195. if already_written:
  196. self.logger.info(f"OTP memory is already written with the given data")
  197. return True
  198. self.reset(self.RunMode.Stop)
  199. stm32.clear_flash_errors(oocd)
  200. # Write OTP memory by 8 bytes
  201. for i in range(0, data_size, 8):
  202. word_1 = int.from_bytes(data[i : i + 4], "little")
  203. word_2 = int.from_bytes(data[i + 4 : i + 8], "little")
  204. self.logger.debug(
  205. f"Writing {word_1:08X} {word_2:08X} to {address + i:08X}"
  206. )
  207. stm32.write_flash_64(oocd, address + i, word_1, word_2)
  208. # Validate OTP memory
  209. validation_result = True
  210. for i in range(0, data_size, 4):
  211. file_word = int.from_bytes(data[i : i + 4], "little")
  212. device_word = oocd.read_32(address + i)
  213. if file_word != device_word:
  214. self.logger.error(
  215. f"Validation failed: {file_word:08X} != {device_word:08X} at {address + i:08X}"
  216. )
  217. validation_result = False
  218. finally:
  219. # Stop OpenOCD
  220. stm32.reset(oocd, stm32.RunMode.Run)
  221. oocd.stop()
  222. return validation_result