summaryrefslogtreecommitdiff
path: root/test/hil/helper
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-08-28 14:16:02 +0700
committerGitHub <[email protected]>2026-08-28 14:16:02 +0700
commiteca6caf673452c8ec940e2acf5e46d0631fb72bf (patch)
treece197bf019b29712095c3768c0fc23b26499cc79 /test/hil/helper
parent4c7bb3ced98cb7262dd8eefd6c9f7df6e42ffcee (diff)
Add RTT console/capture tooling (tools/rtt.py), rtt skill, and HIL harness support (#3853)
Promote SEGGER RTT from an inline debugging technique to a standalone skill backed by one stdlib-only implementation in tools/rtt.py: a CLI and importable module for console/capture over J-Link (RTTTelnetPort) and OpenOCD (rtt server) probes, with probe selection by serial or VID:PID, control-block address via --elf or --addr, bidirectional console, post-mortem ring dump, and --reset-before-attach for boot-time capture. The HIL harness reads a board's console over RTT when its probe has no VCOM ("logger": "rtt" plus a LOGGER=rtt variant define), covering device_info, pool-check aliveness, and CI wiring. Validated on 22 boards across both backends; 26 unit tests run in pre-commit.
Diffstat (limited to 'test/hil/helper')
-rw-r--r--test/hil/helper/hil_pool_check.py42
-rw-r--r--test/hil/helper/hil_util.py26
2 files changed, 66 insertions, 2 deletions
diff --git a/test/hil/helper/hil_pool_check.py b/test/hil/helper/hil_pool_check.py
index b92f0aee0..4623ce45f 100644
--- a/test/hil/helper/hil_pool_check.py
+++ b/test/hil/helper/hil_pool_check.py
@@ -360,7 +360,47 @@ def check_host_serial(board: dict, do_reset: bool = True, want_hello: bool = Fal
do_reset=False listens to the firmware as-is: used right after a flash whose
own reset already started it — a second openocd/JLink session back-to-back on
- the same probe can fail transiently and leave the target halted."""
+ the same probe can fail transiently and leave the target halted.
+
+ "logger": "rtt" boards have no VCOM: the same check runs over the probe's RTT
+ console instead. The reset happens BEFORE the console opens (it owns the probe),
+ which also zeroes the .bss ring — so pre-reset backlog cannot count as life, and
+ without a reset Commander delivers the boot burst the preceding flash left."""
+ if board.get('logger') == 'rtt':
+ if do_reset:
+ # a failed reset leaves the previous run's ring intact: attaching anyway would
+ # score stale output as life, so bail to host_alive's board_test reflash ladder
+ rc, err = call_flasher(getattr(hil_flash, f'reset_{board["flasher"]["name"].lower()}'), board)
+ if rc:
+ say(f'{board["name"]:26} reset failed: {err}')
+ return None
+ try:
+ ser = hil_util.JlinkRtt(board, timeout=0.3)
+ except hil_util.RttError as e:
+ say(f'{board["name"]:26} no RTT console: {e}')
+ return None
+ try:
+ data = b''
+ deadline = time.monotonic() + SERIAL_WAIT
+ while time.monotonic() < deadline:
+ ser.write(b'U')
+ data += ser.read(256)
+ # JLinkExe's banner arrives whether or not the target is alive --
+ # judged unfiltered it scores a dead board 'alive'. Same shared filter
+ # as test_host_device_info; complete_only drops a trailing partial
+ # line, so a banner FRAGMENT split by this read boundary cannot count
+ # as target output either.
+ td = hil_util.strip_banner(data, complete_only=True)
+ if want_hello:
+ if b'Hello from TinyUSB' in td:
+ return td
+ elif td and not boardtest_output(td):
+ return td
+ return hil_util.strip_banner(data)
+ except hil_util.RttError:
+ return None # console died mid-poll (server exited, probe dropped)
+ finally:
+ ser.close()
import serial
try:
port = hil_util.get_serial_dev(board['flasher']['uid'], None, None, 0)
diff --git a/test/hil/helper/hil_util.py b/test/hil/helper/hil_util.py
index 03d01270f..f279cfa77 100644
--- a/test/hil/helper/hil_util.py
+++ b/test/hil/helper/hil_util.py
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# Bottom layer of the HIL harness: the bounded command runner plus the shared helpers and
-# data every other module needs. Stays stdlib-only and imports nothing local -- everything
+# data every other module needs. Stays stdlib-only; its one local dependency is
+# tools/rtt.py (the RTT console, loaded by path below) -- everything
# else imports this, including the unit tests on GitHub's bare runner; never import them
# from here. Callers set the module global `verbose`.
@@ -499,6 +500,29 @@ def run_alongside(argv: list, work, timeout: int) -> subprocess.CompletedProcess
return _reap()
+# The RTT console implementation lives in tools/rtt.py (importable classes + CLI,
+# stdlib-only, harness-critical — see its module docstring). Loaded by file path so
+# no sys.path entry for tools/ can shadow other imports; re-exported here so the
+# harness keeps addressing hil_util.JlinkRtt.
+import importlib.util as _ilu
+
+_rtt_path = TINYUSB_ROOT / 'tools' / 'rtt.py'
+if not _rtt_path.exists():
+ # name the real cause: a bare FileNotFoundError out of an exec_module here reads
+ # as a harness bug, when the actual problem is an incompletely staged tree
+ raise ImportError(f'{_rtt_path} is missing — the RTT console lives there and the '
+ f'harness depends on it; stage it alongside test/hil (hil_ci.sh does)')
+_rtt_spec = _ilu.spec_from_file_location('tinyusb_tools_rtt', _rtt_path)
+_rtt = _ilu.module_from_spec(_rtt_spec)
+sys.modules[_rtt_spec.name] = _rtt # registered: RttError must be picklable across the fork Pool
+_rtt_spec.loader.exec_module(_rtt)
+JlinkRtt = _rtt.JlinkRtt
+OpenocdRtt = _rtt.OpenocdRtt
+RttError = _rtt.RttError
+RTT_BANNER_RE = _rtt.RTT_BANNER_RE
+strip_banner = _rtt.strip_banner
+
+
def _cmd_label(cmd) -> str:
"""A one-line name for a banner. An argv whose payload is a `python3 -c` program would
otherwise dump the whole body into the CI log, where run_cmd's banners are already the