# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import contextlib import pexpect import re import string import subprocess import sys import waflib from waflib import Logs JTAG_OPTIONS = {'olimex': 'source [find interface/ftdi/olimex-arm-usb-ocd-h.cfg]', 'fixture': 'source [find interface/flossjtag-noeeprom.cfg]', 'bb2': 'source waftools/openocd_bb2_ftdi.cfg', 'bb2-legacy': 'source waftools/openocd_bb2_ft2232.cfg', 'jtag_ftdi': 'source waftools/openocd_jtag_ftdi.cfg', 'swd_ftdi': 'source waftools/openocd_swd_ftdi.cfg', 'swd_jlink': 'source waftools/openocd_swd_jlink.cfg', 'swd_stlink': 'source [find interface/stlink-v2.cfg]', } OPENOCD_TELNET_PORT = 4444 @contextlib.contextmanager def daemon(ctx, cfg_file, use_swd=False): if _is_openocd_running(): yield else: if use_swd: expect_str = "SWD IDCODE" else: expect_str = "device found" proc = pexpect.spawn('openocd', ['-f', cfg_file], logfile=sys.stdout) # Wait for OpenOCD to connect to the board: result = proc.expect([expect_str, pexpect.TIMEOUT], timeout=10) if result == 0: yield else: raise Exception("Timed out connecting OpenOCD to development board...") proc.close() def _has_openocd(ctx): try: ctx.cmd_and_log(['which', 'openocd'], quiet=waflib.Context.BOTH) return True except: return False def _is_openocd_running(): import socket import errno s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.bind(('', OPENOCD_TELNET_PORT)) s.close() except socket.error as e: s.close() return e[0] == errno.EADDRINUSE return False def run_command(ctx, cmd, ignore_fail=False, expect=[], timeout=40, shutdown=True, enforce_expect=False, cfg_file="openocd.cfg"): if _is_openocd_running(): import telnetlib t = telnetlib.Telnet('', OPENOCD_TELNET_PORT) Logs.info("Sending commands to OpenOCD daemon:\n%s\n..." % cmd) t.write("%s\n" % cmd) for regex in expect: idx, match, text = t.expect([regex], timeout) if enforce_expect and idx == -1: # They'll see the full story in another window ctx.fatal("OpenOCD expectation '%s' unfulfilled" % regex) t.close() else: fail_handling = ' || true ' if ignore_fail else '' if shutdown: # append 'shutdown' to make openocd exit: cmd = "%s ; shutdown" % cmd ctx.exec_command('openocd -f %s -c "%s" 2>&1 | tee .waf.openocd.log %s' % (cfg_file, cmd, fail_handling), stdout=None, stderr=None) if enforce_expect: # Read the result with open(".waf.openocd.log", "r") as result_file: result = result_file.read() match_start = 0 for regex in expect: expect_match = re.search(regex, result[match_start:]) if not expect_match: ctx.fatal("OpenOCD expectation '%s' unfulfilled" % regex) match_start = expect_match.end() def _get_supported_interfaces(ctx): if not _has_openocd(ctx): return [] # Ugh, openocd exits with status 1 when not specifying an interface... try: ctx.cmd_and_log(['openocd', '-c', '"interface_list"'], quiet=waflib.Context.BOTH, output=waflib.Context.STDERR) except Exception as e: # Ugh, openocd prints the output to stderr... out = e.stderr out_lines = out.splitlines() interfaces = [] for line in out_lines: matches = re.search("\d+: (\w+)", line) if matches: interfaces.append(matches.groups()[0]) return interfaces def get_flavor(conf): """ Returns a 2-tuple (is_newer_than_0_7_0, is_pebble_flavor) """ try: version_string = conf.cmd_and_log(['openocd', '--version'], quiet=waflib.Context.BOTH, output=waflib.Context.STDERR) version_string = version_string.splitlines()[0] matches = re.search("(\d+)\.(\d+)\.(\d+)", version_string) version = map(int, matches.groups()) return (version[0] >= 0 and version[1] >= 7, 'pebble' in version_string) except Exception: Logs.error("Couldn't parse openocd version") return (False, False) def _get_reset_conf(conf, is_newer_than_0_7_0, should_connect_assert_srst): if is_newer_than_0_7_0: options = ['trst_and_srst', 'srst_nogate'] if should_connect_assert_srst: options.append('connect_assert_srst') return ' '.join(options) else: return 'trst_and_srst' def write_cfg(conf): jtag = conf.env.JTAG if jtag == 'bb2': if 'ftdi' not in _get_supported_interfaces(conf): jtag = 'bb2-legacy' Logs.warn('OpenOCD is not compiled with --enable-ftdi, falling' ' back to legacy ft2232 driver.') if conf.env.MICRO_FAMILY == 'STM32F2': target = 'stm32f2x.cfg' elif conf.env.MICRO_FAMILY == 'STM32F4': target = 'stm32f4x.cfg' elif conf.env.MICRO_FAMILY == 'STM32F7': target = 'stm32f7x.cfg' (is_newer_than_0_7_0, is_pebble_flavor) = get_flavor(conf) reset_config = _get_reset_conf(conf, is_newer_than_0_7_0, False) Logs.info("reset_config: %s" % reset_config) if is_pebble_flavor: Logs.info("openocd is Pebble flavored!") os_name = 'Pebble_FreeRTOS' else: os_name = 'FreeRTOS' openocd_cfg = OPENOCD_CFG_TEMPLATE.substitute(jtag=JTAG_OPTIONS[jtag], target=target, reset_config=reset_config, os_name=os_name) waflib.Utils.writef('./openocd.cfg', openocd_cfg) OPENOCD_CFG_TEMPLATE = string.Template(""" # THIS IS A GENERATED FILE: See waftools/openocd.py for details ${jtag} source [find target/${target}] reset_config ${reset_config} $$_TARGETNAME configure -rtos ${os_name} $$_TARGETNAME configure -event gdb-attach { echo "Halting target because GDB is attaching..." halt } $$_TARGETNAME configure -event gdb-detach { echo "Resuming target because GDB is detaching..." resume } """)