openocd.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. import contextlib
  15. import pexpect
  16. import re
  17. import string
  18. import subprocess
  19. import sys
  20. import waflib
  21. from waflib import Logs
  22. JTAG_OPTIONS = {'olimex': 'source [find interface/ftdi/olimex-arm-usb-ocd-h.cfg]',
  23. 'fixture': 'source [find interface/flossjtag-noeeprom.cfg]',
  24. 'bb2': 'source waftools/openocd_bb2_ftdi.cfg',
  25. 'bb2-legacy': 'source waftools/openocd_bb2_ft2232.cfg',
  26. 'jtag_ftdi': 'source waftools/openocd_jtag_ftdi.cfg',
  27. 'swd_ftdi': 'source waftools/openocd_swd_ftdi.cfg',
  28. 'swd_jlink': 'source waftools/openocd_swd_jlink.cfg',
  29. 'swd_stlink': 'source [find interface/stlink-v2.cfg]',
  30. 'cmsis-dap': 'source [find interface/cmsis-dap.cfg]',
  31. }
  32. OPENOCD_TELNET_PORT = 4444
  33. @contextlib.contextmanager
  34. def daemon(ctx, cfg_file, use_swd=False):
  35. if _is_openocd_running():
  36. yield
  37. else:
  38. expect_str = "Listening on port"
  39. proc = pexpect.spawn('openocd', ['-f', cfg_file], encoding='utf-8', logfile=sys.stdout)
  40. # Wait for OpenOCD to connect to the board:
  41. result = proc.expect([expect_str, pexpect.TIMEOUT], timeout=10)
  42. if result == 0:
  43. yield
  44. else:
  45. raise Exception("Timed out connecting OpenOCD to development board...")
  46. proc.close()
  47. def _has_openocd(ctx):
  48. try:
  49. ctx.cmd_and_log(['which', 'openocd'], quiet=waflib.Context.BOTH)
  50. return True
  51. except:
  52. return False
  53. def _is_openocd_running():
  54. import socket
  55. import errno
  56. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  57. try:
  58. s.bind(('', OPENOCD_TELNET_PORT))
  59. s.close()
  60. except socket.error as e:
  61. s.close()
  62. return e.errno == errno.EADDRINUSE
  63. return False
  64. def run_command(ctx, cmd, ignore_fail=False, expect=[], timeout=40,
  65. shutdown=True, enforce_expect=False, cfg_file="openocd.cfg"):
  66. if _is_openocd_running():
  67. import telnetlib
  68. t = telnetlib.Telnet('', OPENOCD_TELNET_PORT)
  69. Logs.info("Sending commands to OpenOCD daemon:\n%s\n..." % cmd)
  70. t.write("%s\n" % cmd)
  71. for regex in expect:
  72. idx, match, text = t.expect([regex], timeout)
  73. if enforce_expect and idx == -1:
  74. # They'll see the full story in another window
  75. ctx.fatal("OpenOCD expectation '%s' unfulfilled" % regex)
  76. t.close()
  77. else:
  78. fail_handling = ' || true ' if ignore_fail else ''
  79. if shutdown:
  80. # append 'shutdown' to make openocd exit:
  81. cmd = "%s ; shutdown" % cmd
  82. ctx.exec_command('openocd -f %s -c "%s" 2>&1 | tee .waf.openocd.log %s' %
  83. (cfg_file, cmd, fail_handling), stdout=None, stderr=None)
  84. if enforce_expect:
  85. # Read the result
  86. with open(".waf.openocd.log", "r") as result_file:
  87. result = result_file.read()
  88. match_start = 0
  89. for regex in expect:
  90. expect_match = re.search(regex, result[match_start:])
  91. if not expect_match:
  92. ctx.fatal("OpenOCD expectation '%s' unfulfilled" % regex)
  93. match_start = expect_match.end()
  94. def _get_supported_interfaces(ctx):
  95. if not _has_openocd(ctx):
  96. return []
  97. # Ugh, openocd exits with status 1 when not specifying an interface...
  98. try:
  99. ctx.cmd_and_log(['openocd', '-c', '"interface_list"'],
  100. quiet=waflib.Context.BOTH,
  101. output=waflib.Context.STDERR)
  102. except Exception as e:
  103. # Ugh, openocd prints the output to stderr...
  104. out = e.stderr
  105. out_lines = out.splitlines()
  106. interfaces = []
  107. for line in out_lines:
  108. matches = re.search(r"\d+: (\w+)", line)
  109. if matches:
  110. interfaces.append(matches.groups()[0])
  111. return interfaces
  112. def get_flavor(conf):
  113. """ Returns a if OpenOCD is Pebble flavor """
  114. try:
  115. version_string = conf.cmd_and_log(['openocd', '--version'],
  116. quiet=waflib.Context.BOTH,
  117. output=waflib.Context.STDERR)
  118. version_string = version_string.splitlines()[0]
  119. return 'pebble' in version_string
  120. except Exception:
  121. Logs.error("Couldn't parse openocd version")
  122. return (False, False)
  123. def _get_reset_conf(conf, should_connect_assert_srst):
  124. if conf.env.MICRO_FAMILY.startswith('STM32'):
  125. options = ['trst_and_srst', 'srst_nogate']
  126. if should_connect_assert_srst:
  127. options.append('connect_assert_srst')
  128. return ' '.join(options)
  129. elif conf.env.MICRO_FAMILY.startswith('NRF52'):
  130. return 'none'
  131. else:
  132. raise Exception("Unsupported microcontroller family: %s" % conf.env.MICRO_FAMILY)
  133. def write_cfg(conf):
  134. jtag = conf.env.JTAG
  135. if jtag == 'bb2':
  136. if 'ftdi' not in _get_supported_interfaces(conf):
  137. jtag = 'bb2-legacy'
  138. Logs.warn('OpenOCD is not compiled with --enable-ftdi, falling'
  139. ' back to legacy ft2232 driver.')
  140. if conf.env.MICRO_FAMILY == 'STM32F2':
  141. target = 'stm32f2x.cfg'
  142. elif conf.env.MICRO_FAMILY == 'STM32F4':
  143. target = 'stm32f4x.cfg'
  144. elif conf.env.MICRO_FAMILY == 'STM32F7':
  145. target = 'stm32f7x.cfg'
  146. elif conf.env.MICRO_FAMILY == 'NRF52840':
  147. target = 'nrf52.cfg'
  148. is_pebble_flavor = get_flavor(conf)
  149. reset_config = _get_reset_conf(conf, False)
  150. Logs.info("reset_config: %s" % reset_config)
  151. if not is_pebble_flavor:
  152. Logs.warn("openocd is not Pebble flavored!")
  153. openocd_cfg = OPENOCD_CFG_TEMPLATE.substitute(jtag=JTAG_OPTIONS[jtag],
  154. target=target,
  155. reset_config=reset_config)
  156. waflib.Utils.writef('./openocd.cfg', openocd_cfg)
  157. OPENOCD_CFG_TEMPLATE = string.Template("""
  158. # THIS IS A GENERATED FILE: See waftools/openocd.py for details
  159. ${jtag}
  160. source [find target/${target}]
  161. reset_config ${reset_config}
  162. $$_TARGETNAME configure -rtos FreeRTOS
  163. $$_TARGETNAME configure -event gdb-attach {
  164. echo "Halting target because GDB is attaching..."
  165. halt
  166. }
  167. $$_TARGETNAME configure -event gdb-detach {
  168. echo "Resuming target because GDB is detaching..."
  169. resume
  170. }
  171. """)