otp.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. #!/usr/bin/env python3
  2. import datetime
  3. import logging
  4. import os
  5. import re
  6. import struct
  7. from flipper.app import App
  8. from flipper.utils.programmer_openocd import OpenOCDProgrammer, OpenOCDProgrammerResult
  9. OTP_MAGIC = 0xBABE
  10. OTP_VERSION = 0x02
  11. OTP_RESERVED = 0x00
  12. OTP_COLORS = {
  13. "unknown": 0x00,
  14. "black": 0x01,
  15. "white": 0x02,
  16. }
  17. OTP_REGIONS = {
  18. "unknown": 0x00,
  19. "eu_ru": 0x01,
  20. "us_ca_au": 0x02,
  21. "jp": 0x03,
  22. "world": 0x04,
  23. }
  24. OTP_DISPLAYS = {
  25. "unknown": 0x00,
  26. "erc": 0x01,
  27. "mgg": 0x02,
  28. }
  29. class OTPException(Exception):
  30. def __init__(self, message: str, result: OpenOCDProgrammerResult):
  31. self.message = message
  32. self.result = result
  33. def get_exit_code(self) -> int:
  34. return int(self.result.value)
  35. class Main(App):
  36. def init(self):
  37. # SubParsers
  38. self.subparsers = self.parser.add_subparsers(help="sub-command help")
  39. # Generate All
  40. self.parser_generate_all = self.subparsers.add_parser(
  41. "generate", help="Generate OTP binary"
  42. )
  43. self._addFirstArgs(self.parser_generate_all)
  44. self._addSecondArgs(self.parser_generate_all)
  45. self.parser_generate_all.add_argument("file", help="Output file")
  46. self.parser_generate_all.set_defaults(func=self.generate_all)
  47. # Flash First
  48. self.parser_flash_first = self.subparsers.add_parser(
  49. "flash_first", help="Flash first block of OTP to device"
  50. )
  51. self._addArgsOpenOCD(self.parser_flash_first)
  52. self._addFirstArgs(self.parser_flash_first)
  53. self.parser_flash_first.set_defaults(func=self.flash_first)
  54. # Flash Second
  55. self.parser_flash_second = self.subparsers.add_parser(
  56. "flash_second", help="Flash second block of OTP to device"
  57. )
  58. self._addArgsOpenOCD(self.parser_flash_second)
  59. self._addSecondArgs(self.parser_flash_second)
  60. self.parser_flash_second.set_defaults(func=self.flash_second)
  61. # Flash All
  62. self.parser_flash_all = self.subparsers.add_parser(
  63. "flash_all", help="Flash OTP to device"
  64. )
  65. self._addArgsOpenOCD(self.parser_flash_all)
  66. self._addFirstArgs(self.parser_flash_all)
  67. self._addSecondArgs(self.parser_flash_all)
  68. self.parser_flash_all.set_defaults(func=self.flash_all)
  69. # logging
  70. self.logger = logging.getLogger()
  71. self.timestamp = datetime.datetime.now().timestamp()
  72. def _addArgsOpenOCD(self, parser):
  73. parser.add_argument(
  74. "--port-base", type=int, help="OpenOCD port base", default=3333
  75. )
  76. parser.add_argument(
  77. "--interface",
  78. type=str,
  79. help="OpenOCD interface",
  80. default="interface/cmsis-dap.cfg",
  81. )
  82. parser.add_argument(
  83. "--serial", type=str, help="OpenOCD interface serial number"
  84. )
  85. def _addFirstArgs(self, parser):
  86. parser.add_argument("--version", type=int, help="Version", required=True)
  87. parser.add_argument("--firmware", type=int, help="Firmware", required=True)
  88. parser.add_argument("--body", type=int, help="Body", required=True)
  89. parser.add_argument("--connect", type=int, help="Connect", required=True)
  90. parser.add_argument("--display", type=str, help="Display", required=True)
  91. def _addSecondArgs(self, parser):
  92. parser.add_argument("--color", type=str, help="Color", required=True)
  93. parser.add_argument("--region", type=str, help="Region", required=True)
  94. parser.add_argument("--name", type=str, help="Name", required=True)
  95. def _processFirstArgs(self):
  96. if self.args.display not in OTP_DISPLAYS:
  97. self.parser.error(f"Invalid display. Use one of {OTP_DISPLAYS.keys()}")
  98. self.args.display = OTP_DISPLAYS[self.args.display]
  99. def _processSecondArgs(self):
  100. if self.args.color not in OTP_COLORS:
  101. self.parser.error(f"Invalid color. Use one of {OTP_COLORS.keys()}")
  102. self.args.color = OTP_COLORS[self.args.color]
  103. if self.args.region not in OTP_REGIONS:
  104. self.parser.error(f"Invalid region. Use one of {OTP_REGIONS.keys()}")
  105. self.args.region = OTP_REGIONS[self.args.region]
  106. if len(self.args.name) > 8:
  107. self.parser.error("Name is too long. Max 8 symbols.")
  108. if re.match(r"^[a-zA-Z0-9.]+$", self.args.name) is None:
  109. self.parser.error(
  110. "Name contains incorrect symbols. Only a-zA-Z0-9 allowed."
  111. )
  112. def _packFirst(self):
  113. return struct.pack(
  114. "<" "HBBL" "BBBBBBH",
  115. OTP_MAGIC,
  116. OTP_VERSION,
  117. OTP_RESERVED,
  118. int(self.timestamp),
  119. self.args.version,
  120. self.args.firmware,
  121. self.args.body,
  122. self.args.connect,
  123. self.args.display,
  124. OTP_RESERVED,
  125. OTP_RESERVED,
  126. )
  127. def _packSecond(self):
  128. return struct.pack(
  129. "<" "BBHL" "8s",
  130. self.args.color,
  131. self.args.region,
  132. OTP_RESERVED,
  133. OTP_RESERVED,
  134. self.args.name.encode("ascii"),
  135. )
  136. def generate_all(self):
  137. self.logger.info("Generating OTP")
  138. self._processFirstArgs()
  139. self._processSecondArgs()
  140. with open(f"{self.args.file}_first.bin", "wb") as file:
  141. file.write(self._packFirst())
  142. with open(f"{self.args.file}_second.bin", "wb") as file:
  143. file.write(self._packSecond())
  144. self.logger.info(
  145. f"Generated files: {self.args.file}_first.bin and {self.args.file}_second.bin"
  146. )
  147. return 0
  148. def flash_first(self):
  149. self.logger.info("Flashing first block of OTP")
  150. self._processFirstArgs()
  151. filename = f"otp_unknown_first_{self.timestamp}.bin"
  152. try:
  153. self.logger.info("Packing binary data")
  154. with open(filename, "wb") as file:
  155. file.write(self._packFirst())
  156. self.logger.info("Flashing OTP")
  157. openocd = OpenOCDProgrammer(
  158. self.args.interface,
  159. self.args.port_base,
  160. self.args.serial,
  161. )
  162. programmer_result = openocd.otp_write(0x1FFF7000, filename)
  163. if programmer_result != OpenOCDProgrammerResult.Success:
  164. raise OTPException("Failed to flash OTP", programmer_result)
  165. self.logger.info("Flashed Successfully")
  166. except OTPException as e:
  167. self.logger.exception(e)
  168. return e.get_exit_code()
  169. finally:
  170. os.remove(filename)
  171. return 0
  172. def flash_second(self):
  173. self.logger.info("Flashing second block of OTP")
  174. self._processSecondArgs()
  175. filename = f"otp_{self.args.name}_second_{self.timestamp}.bin"
  176. try:
  177. self.logger.info("Packing binary data")
  178. with open(filename, "wb") as file:
  179. file.write(self._packSecond())
  180. self.logger.info("Flashing OTP")
  181. openocd = OpenOCDProgrammer(
  182. self.args.interface,
  183. self.args.port_base,
  184. self.args.serial,
  185. )
  186. programmer_result = openocd.otp_write(0x1FFF7010, filename)
  187. if programmer_result != OpenOCDProgrammerResult.Success:
  188. raise OTPException("Failed to flash OTP", programmer_result)
  189. self.logger.info("Flashed Successfully")
  190. except OTPException as e:
  191. self.logger.exception(e)
  192. return e.get_exit_code()
  193. finally:
  194. os.remove(filename)
  195. return 0
  196. def flash_all(self):
  197. self.logger.info("Flashing OTP")
  198. self._processFirstArgs()
  199. self._processSecondArgs()
  200. filename = f"otp_{self.args.name}_whole_{self.timestamp}.bin"
  201. try:
  202. self.logger.info("Packing binary data")
  203. with open(filename, "wb") as file:
  204. file.write(self._packFirst())
  205. file.write(self._packSecond())
  206. self.logger.info("Flashing OTP")
  207. openocd = OpenOCDProgrammer(
  208. self.args.interface,
  209. self.args.port_base,
  210. self.args.serial,
  211. )
  212. programmer_result = openocd.otp_write(0x1FFF7000, filename)
  213. if programmer_result != OpenOCDProgrammerResult.Success:
  214. raise OTPException("Failed to flash OTP", programmer_result)
  215. self.logger.info("Flashed Successfully")
  216. except OTPException as e:
  217. self.logger.exception(e)
  218. return e.get_exit_code()
  219. finally:
  220. os.remove(filename)
  221. return 0
  222. if __name__ == "__main__":
  223. Main()()