From 27dc9c956f7c965ee4158cdbce65e9b5586c35ee Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Mon, 29 Jun 2026 13:57:27 +0200 Subject: [PATCH 1/8] change(ci): enable esp32s31 jtag tests --- examples/system/.build-test-rules.yml | 22 +++++-------------- .../app_trace_basic/pytest_app_trace_basic.py | 8 +++---- examples/system/esp_trace/pytest_esp_trace.py | 2 +- examples/system/gcov/pytest_gcov.py | 8 +++---- .../sysview_tracing/pytest_sysview_tracing.py | 6 ++--- .../pytest_sysview_tracing_heap_log.py | 4 +++- 6 files changed, 20 insertions(+), 30 deletions(-) diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index b73b4039af4..a80dceaec02 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -7,10 +7,7 @@ examples/system/app_trace_basic: reason: lack of runners - if: IDF_TARGET == "esp32h4" temporary: true - reason: not supported yet #TODO: OCD-1137 - - if: IDF_TARGET == "esp32s31" - temporary: true - reason: not support yet #TODO: OCD-1290 + reason: lack of runners depends_components: - esp_trace - app_trace @@ -80,9 +77,6 @@ examples/system/esp_trace: - if: IDF_TARGET == "esp32h4" temporary: true reason: lack of runners - - if: IDF_TARGET == "esp32s31" - temporary: true - reason: lack of runners depends_components: - esp_trace - freertos @@ -108,10 +102,7 @@ examples/system/gcov: reason: lack of runners - if: IDF_TARGET == "esp32h4" temporary: true - reason: not supported yet #TODO: OCD-1138 - - if: IDF_TARGET == "esp32s31" - temporary: true - reason: not support yet #TODO: OCD-1291 + reason: lack of runners depends_components: - esp_trace - app_trace @@ -294,9 +285,9 @@ examples/system/sysview_tracing: - if: IDF_TARGET == "esp32h21" temporary: true reason: lack of runners - - if: IDF_TARGET == "esp32s31" + - if: IDF_TARGET == "esp32h4" temporary: true - reason: not support yet #TODO: OCD-1289 + reason: lack of runners depends_components: - esp_trace - app_trace @@ -310,10 +301,7 @@ examples/system/sysview_tracing_heap_log: reason: lack of runners - if: IDF_TARGET == "esp32h4" temporary: true - reason: not supported yet #TODO: OCD-1136 - - if: IDF_TARGET == "esp32s31" - temporary: true - reason: not support yet #TODO: OCD-1289 + reason: lack of runners depends_components: - esp_trace - app_trace diff --git a/examples/system/app_trace_basic/pytest_app_trace_basic.py b/examples/system/app_trace_basic/pytest_app_trace_basic.py index 5d8b5a94759..ef7a7df9336 100644 --- a/examples/system/app_trace_basic/pytest_app_trace_basic.py +++ b/examples/system/app_trace_basic/pytest_app_trace_basic.py @@ -8,6 +8,7 @@ import pytest import serial from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets if typing.TYPE_CHECKING: from conftest import OpenOCD @@ -54,9 +55,8 @@ def test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag @idf_parametrize('config', ['apptrace_jtag'], indirect=['config']) -@idf_parametrize( - 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'], indirect=['target'] -) +@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_examples_app_trace_basic_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_examples_app_trace_basic(openocd_dut, dut) @@ -64,7 +64,7 @@ def test_examples_app_trace_basic_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> No @pytest.mark.generic @idf_parametrize('config', ['apptrace_uart'], indirect=['config']) @idf_parametrize('target', ['supported_targets'], indirect=['target']) -@pytest.mark.temp_skip_ci(targets=['esp32s31', 'esp32h4'], reason='bringup on this module is not done') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_examples_app_trace_basic_uart(dut: IdfDut) -> None: dut.serial.close() with serial.Serial(dut.serial.port, baudrate=1000000, timeout=3) as ser: diff --git a/examples/system/esp_trace/pytest_esp_trace.py b/examples/system/esp_trace/pytest_esp_trace.py index e5bc2a0af85..d1f031f5201 100644 --- a/examples/system/esp_trace/pytest_esp_trace.py +++ b/examples/system/esp_trace/pytest_esp_trace.py @@ -102,7 +102,7 @@ def _capture_trace(ser: serial.Serial, trace_log_path: str, capture_s: float = 5 @pytest.mark.usb_serial_jtag @idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) @pytest.mark.parametrize('config', [pytest.param('default')], indirect=True) -@pytest.mark.temp_skip_ci(targets=['esp32h4', 'esp32s31'], reason='lack of runner # TODO: IDFCI-10703') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_esp_trace_ext_lib_usj(dut: IdfDut) -> None: dut.expect('Start of trace session', timeout=5) diff --git a/examples/system/gcov/pytest_gcov.py b/examples/system/gcov/pytest_gcov.py index fa67e5b28d1..042a91e4b32 100644 --- a/examples/system/gcov/pytest_gcov.py +++ b/examples/system/gcov/pytest_gcov.py @@ -8,6 +8,7 @@ import typing import pytest from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets if typing.TYPE_CHECKING: from conftest import OpenOCD @@ -176,9 +177,8 @@ def test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag @idf_parametrize('config', ['gcov_jtag'], indirect=['config']) -@idf_parametrize( - 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'], indirect=['target'] -) +@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_gcov_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_gcov(openocd_dut, dut) @@ -226,6 +226,6 @@ def _test_gcov_uart(dut: IdfDut) -> None: @pytest.mark.generic @idf_parametrize('config', ['gcov_uart'], indirect=['config']) @idf_parametrize('target', ['supported_targets'], indirect=['target']) -@pytest.mark.temp_skip_ci(targets=['esp32s31', 'esp32h4'], reason='bringup on this module is not done') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_gcov_uart(dut: IdfDut) -> None: _test_gcov_uart(dut) diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index ac8d9b52d70..f60091384d4 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -140,7 +140,7 @@ def test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @idf_parametrize('config', ['sysview_jtag'], indirect=['config']) @idf_parametrize( 'target', - ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h4'], + soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target'], ) @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') @@ -160,7 +160,7 @@ def _test_sysview_tracing_uart(dut: IdfDut) -> None: @pytest.mark.generic @idf_parametrize('config', ['sysview_uart'], indirect=['config']) @idf_parametrize('target', ['supported_targets'], indirect=['target']) -@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='bringup on this module is not done') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_sysview_tracing_uart(dut: IdfDut) -> None: _test_sysview_tracing_uart(dut) @@ -176,7 +176,7 @@ def test_sysview_tracing_uart_c2(dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag @idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) @pytest.mark.parametrize('config', [pytest.param('sysview_usj')], indirect=True) -@pytest.mark.temp_skip_ci(targets=['esp32h4', 'esp32s31'], reason='lack of runner # TODO: IDFCI-10703') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_sysview_tracing_usj_serial(dut: IdfDut) -> None: time.sleep(1) # wait for USJ port to be ready usj_port = '/dev/serial_ports/ttyACM-esp32' diff --git a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py index 9a1f1312040..1ee4ebcfd86 100644 --- a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py +++ b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py @@ -8,6 +8,7 @@ import pexpect.fdpexpect import pytest from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets if typing.TYPE_CHECKING: from conftest import OpenOCD @@ -76,8 +77,9 @@ def test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str @pytest.mark.usb_serial_jtag @idf_parametrize( 'target', - ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'], + soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target'], ) +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_examples_sysview_tracing_heap_log_usj(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None: _test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut) From f44867619eaff209be3a82932afd5a4f0b43cff4 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Tue, 30 Jun 2026 00:55:01 +0200 Subject: [PATCH 2/8] fix(sysview): handle variable-length timestamp in TRACE_STOP validation --- .../sysview_tracing/pytest_sysview_tracing.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index f60091384d4..30224135b2b 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -31,13 +31,16 @@ def _validate_trace_data(trace_log: list[str], target: str, is_uart: bool = Fals content = f.read() search_str = f'N=FreeRTOS Application,D={target},C=core{idx},O=FreeRTOS'.encode() assert search_str in content, f'SysView trace data not found in {log}' - - # For UART transport, validate STOP record at end of file - # TODO: Adapt this to JTAG as well. - if is_uart: - size = len(content) - assert size >= 2, 'Trace file too small to contain STOP record' - assert content[-2] == STOP_EVENT_ID, 'STOP record does not start with STOP eventID' + # The file must end with a TRACE_STOP record: the STOP event ID + # followed by a variable-length timestamp delta. Walk back + # over the trailing continuation bytes (0x80 bit set) + # to find the event ID, since its offset is not fixed. + size = len(content) + assert size >= 2, 'Trace file too small to contain STOP record' + i = size - 2 + while i >= 0 and (content[i] & 0x80): + i -= 1 + assert i >= 0 and content[i] == STOP_EVENT_ID, 'STOP record does not start with STOP eventID' def _capture_sysview_trace(ser: serial.Serial, trace_log_path: str) -> None: From 4e845e262ae90058b86414cd7beab6d6a5e267da Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Tue, 30 Jun 2026 11:25:21 +0200 Subject: [PATCH 3/8] fix(sysview): add prefix to the example sysview module desc --- examples/system/sysview_tracing/main/sysview_tracing.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/examples/system/sysview_tracing/main/sysview_tracing.c b/examples/system/sysview_tracing/main/sysview_tracing.c index b988471ba27..18a2413b2ca 100644 --- a/examples/system/sysview_tracing/main/sysview_tracing.c +++ b/examples/system/sysview_tracing/main/sysview_tracing.c @@ -45,19 +45,11 @@ static const char *TAG = "example"; #define SYSVIEW_EXAMPLE_WAIT_EVENT_START() example_sysview_event_send(SYSVIEW_EXAMPLE_WAIT_EVENT_START_ID, 0) #define SYSVIEW_EXAMPLE_WAIT_EVENT_END(_val_) example_sysview_event_send(SYSVIEW_EXAMPLE_WAIT_EVENT_END_ID, _val_) -static void example_sysview_module_send_desc(void); - static SEGGER_SYSVIEW_MODULE s_example_sysview_module = { - .sModule = "example_sysview_module", + .sModule = "M=Example SystemView User Module", .NumEvents = SYSVIEW_EXAMPLE_EVENT_MAX, - .pfSendModuleDesc = example_sysview_module_send_desc, }; -static void example_sysview_module_send_desc(void) -{ - SEGGER_SYSVIEW_RecordModuleDescription(&s_example_sysview_module, "Example SystemView User Module"); -} - static void example_sysview_event_send(uint32_t id, uint32_t val) { U8 aPacket[SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32]; From 4a5a50e95af1f7bd1daab15fc6f2e6d2f86c211f Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Tue, 30 Jun 2026 12:47:50 +0200 Subject: [PATCH 4/8] change(sysview): use esp sysview_mcore command for sysview_tracing JTAG capture --- examples/system/sysview_tracing/README.md | 10 ++- examples/system/sysview_tracing/gdbinit | 4 +- .../sysview_tracing/pytest_sysview_tracing.py | 69 ++++++++++--------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/examples/system/sysview_tracing/README.md b/examples/system/sysview_tracing/README.md index 0431e14a496..381d09a1d6b 100644 --- a/examples/system/sysview_tracing/README.md +++ b/examples/system/sysview_tracing/README.md @@ -111,15 +111,13 @@ NOTE: In order to run this example you need OpenOCD version `v0.10.0-esp32-20181 mon reset halt b app_main commands - mon esp sysview start file:///tmp/sysview_example.svdat - # For dual-core mode uncomment the line below and comment the line above - # mon esp sysview start file:///tmp/sysview_example0.svdat file:///tmp/sysview_example1.svdat + mon esp sysview_mcore start file:///tmp/sysview_example.svdat c end c ``` - Using this file GDB will connect to the target, reset it, and start tracing when it hit breakpoint at `app_main`. Trace data will be saved to `/tmp/sysview_example.svdat`. + Using this file GDB will connect to the target, reset it, and start tracing when it hit breakpoint at `app_main`. Trace data will be saved to `/tmp/sysview_example.svdat`. The `esp sysview_mcore` command uses the SEGGER SystemView multi-core format, so a single file holds the trace for both single- and dual-core targets. 6. Run GDB using the following command from the project root directory: @@ -132,12 +130,12 @@ NOTE: In order to run this example you need OpenOCD version `v0.10.0-esp32-20181 7. When program prints the last message, interrupt its execution (e.g. by pressing `CTRL+C`) and type the following command in GDB console to stop tracing: ``` - mon esp sysview stop + mon esp sysview_mcore stop ``` You can also use another breakpoint to stop tracing and add respective lines to `gdbinit` at step 5. -8. Open trace data file in SystemView tool. +8. Open trace data file in SystemView tool. The multi-core format produced by `esp sysview_mcore` requires SEGGER SystemView v3.60 or later. 9. Right-click on any event in `Events` view and select: diff --git a/examples/system/sysview_tracing/gdbinit b/examples/system/sysview_tracing/gdbinit index 114bcb23c45..f2179a451d9 100644 --- a/examples/system/sysview_tracing/gdbinit +++ b/examples/system/sysview_tracing/gdbinit @@ -6,9 +6,7 @@ maintenance flush register-cache b app_main commands -mon esp sysview start file:///tmp/sysview_example.svdat -# For dual-core mode uncomment the line below and comment the line above -# mon esp sysview start file:///tmp/sysview_example0.svdat file:///tmp/sysview_example1.svdat +mon esp sysview_mcore start file:///tmp/sysview_example.svdat c end diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index 30224135b2b..8dc876165da 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -16,31 +16,35 @@ if typing.TYPE_CHECKING: from conftest import OpenOCD -def _validate_trace_data(trace_log: list[str], target: str, is_uart: bool = False) -> None: - """Validate SysView trace data in log file(s). +def _validate_trace_data(trace_log: str, target: str, dual_core: bool = False, is_uart: bool = False) -> None: + """Validate SysView trace data in a single trace log file. Args: - trace_log: List of trace log paths + trace_log: Path to the trace log file target: Target chip name (e.g., 'esp32', 'esp32s3') + dual_core: If True, expect a per-core description block for both cores + (the ``esp sysview_mcore`` multi-core capture embeds one per core) is_uart: If True, also validate STOP record at end of file """ STOP_EVENT_ID = 0x0B # SYSVIEW_EVTID_TRACE_STOP - for idx, log in enumerate(trace_log): - with open(log, 'rb') as f: - content = f.read() - search_str = f'N=FreeRTOS Application,D={target},C=core{idx},O=FreeRTOS'.encode() - assert search_str in content, f'SysView trace data not found in {log}' - # The file must end with a TRACE_STOP record: the STOP event ID - # followed by a variable-length timestamp delta. Walk back - # over the trailing continuation bytes (0x80 bit set) - # to find the event ID, since its offset is not fixed. - size = len(content) - assert size >= 2, 'Trace file too small to contain STOP record' - i = size - 2 - while i >= 0 and (content[i] & 0x80): - i -= 1 - assert i >= 0 and content[i] == STOP_EVENT_ID, 'STOP record does not start with STOP eventID' + with open(trace_log, 'rb') as f: + content = f.read() + + for idx in range(2 if dual_core else 1): + search_str = f'N=FreeRTOS Application,D={target},C=core{idx},O=FreeRTOS'.encode() + assert search_str in content, f'SysView core{idx} trace data not found in {trace_log}' + + # The file must end with a TRACE_STOP record: the STOP event ID + # followed by a variable-length timestamp delta. Walk back + # over the trailing continuation bytes (0x80 bit set) + # to find the event ID, since its offset is not fixed. + size = len(content) + assert size >= 2, 'Trace file too small to contain STOP record' + i = size - 2 + while i >= 0 and (content[i] & 0x80): + i -= 1 + assert i >= 0 and content[i] == STOP_EVENT_ID, 'STOP record does not start with STOP eventID' def _capture_sysview_trace(ser: serial.Serial, trace_log_path: str) -> None: @@ -86,22 +90,19 @@ def _capture_sysview_trace(ser: serial.Serial, trace_log_path: str) -> None: def _test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: - # Construct trace log paths - trace_log = [ - os.path.join(dut.logdir, 'sys_log0.svdat') # pylint: disable=protected-access - ] - if not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3': - trace_log.append(os.path.join(dut.logdir, 'sys_log1.svdat')) # pylint: disable=protected-access - trace_files = ' '.join([f'file://{log}' for log in trace_log]) + # Single multi-core capture file (esp sysview_mcore): one file is enough for + # both single- and dual-core targets. + trace_log = os.path.join(dut.logdir, 'sys_log.svdat') # pylint: disable=protected-access + dual_core = not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3' - # Prepare gdbinit file + # Prepare gdbinit file pointing at this run's capture file gdb_logfile = os.path.join(dut.logdir, 'gdb.txt') gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit') gdbinit = os.path.join(dut.logdir, 'gdbinit') with open(gdbinit_orig) as f_r, open(gdbinit, 'w') as f_w: for line in f_r: - if line.startswith('mon esp sysview start'): - f_w.write(f'mon esp sysview start {trace_files}\n') + if line.startswith('mon esp sysview_mcore start'): + f_w.write(f'mon esp sysview_mcore start file://{trace_log}\n') else: f_w.write(line) @@ -127,9 +128,9 @@ def _test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: # Do a sleep while sysview samples are captured. time.sleep(3) - openocd.write('esp sysview stop') + openocd.write('esp sysview_mcore stop') - _validate_trace_data(trace_log, dut.target) + _validate_trace_data(trace_log, dut.target, dual_core=dual_core) @pytest.mark.jtag @@ -155,8 +156,8 @@ def _test_sysview_tracing_uart(dut: IdfDut) -> None: dut.serial.close() time.sleep(2) # Wait for the DUT to reboot with serial.Serial(dut.serial.port, baudrate=dut.app.sdkconfig.get('APPTRACE_UART_BAUDRATE'), timeout=3) as ser: - trace_log = [os.path.join(dut.logdir, 'sys_log_uart.svdat')] # pylint: disable=protected-access - _capture_sysview_trace(ser, trace_log[0]) + trace_log = os.path.join(dut.logdir, 'sys_log_uart.svdat') # pylint: disable=protected-access + _capture_sysview_trace(ser, trace_log) _validate_trace_data(trace_log, dut.target, is_uart=True) @@ -184,6 +185,6 @@ def test_sysview_tracing_usj_serial(dut: IdfDut) -> None: time.sleep(1) # wait for USJ port to be ready usj_port = '/dev/serial_ports/ttyACM-esp32' ser = serial.Serial(usj_port, baudrate=1000000, timeout=10) - trace_log = [os.path.join(dut.logdir, 'sys_log_usj.svdat')] # pylint: disable=protected-access - _capture_sysview_trace(ser, trace_log[0]) + trace_log = os.path.join(dut.logdir, 'sys_log_usj.svdat') # pylint: disable=protected-access + _capture_sysview_trace(ser, trace_log) _validate_trace_data(trace_log, dut.target, is_uart=True) From d22741aa12715e544b1d683638ea801ec8e9ab05 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Tue, 30 Jun 2026 14:29:09 +0200 Subject: [PATCH 5/8] change(sysview_heap_log): use esp sysview_mcore command for the JTAG capture --- .../system/sysview_tracing_heap_log/README.md | 13 ++++-------- .../system/sysview_tracing_heap_log/gdbinit | 6 ++---- .../pytest_sysview_tracing_heap_log.py | 21 ++++++++----------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/examples/system/sysview_tracing_heap_log/README.md b/examples/system/sysview_tracing_heap_log/README.md index d0cce54afc7..4e9813a718a 100644 --- a/examples/system/sysview_tracing_heap_log/README.md +++ b/examples/system/sysview_tracing_heap_log/README.md @@ -21,11 +21,9 @@ NOTE: In order to run this example you need OpenOCD version `v0.10.0-esp32-20190 The gdbinit file includes commands to start tracing: ``` - mon esp sysview start file:///tmp/heap_log.svdat - # For dual-core mode uncomment the line below and comment the line above - # mon esp sysview start file:///tmp/heap_log0.svdat file:///tmp/heap_log1.svdat + mon esp sysview_mcore start file:///tmp/heap_log.svdat ``` - When program hits breakpoint at `heap_trace_start`, in single core mode, trace data will be saved to `/tmp/heap_log.svdat`. In dual core mode, there will be two trace data files, one for each core. `/tmp/heap_log0.svdat` and `/tmp/heap_log1.svdat` + When program hits breakpoint at `heap_trace_start`, trace data will be saved to `/tmp/heap_log.svdat`. The `esp sysview_mcore` command uses the SEGGER SystemView multi-core format, so a single file holds the trace for both single- and dual-core targets. Tracing will be stopped when program hits breakpoint at `heap_trace_stop`. 3. [SEGGER SystemView tool](https://www.segger.com/products/development-tools/systemview/). By default SystemView shows only numeric values of IDs and parameters for IDF's heap messages in `Events` view. To make them pretty-looking you need to copy `SYSVIEW_FreeRTOS.txt` from the project's root directory to SystemView installation one. @@ -54,7 +52,7 @@ To run the example and collect trace data: 2. When program stops at `heap_trace_stop` quit GDB. -3. Open trace data file in SystemView tool. +3. Open trace data file in SystemView tool. The multi-core format produced by `esp sysview_mcore` requires SEGGER SystemView v3.60 or later. 4. Now you can inspect all collected events. Log messages are shown in `Terminal` view. @@ -67,10 +65,7 @@ Since SystemView tool is mostly intended for OS level analysis. It allows just t ``` $IDF_PATH/tools/esp_app_trace/sysviewtrace_proc.py -p -b build/sysview_tracing_heap_log.elf /tmp/heap_log.svdat ``` -And in dual core mode -``` -$IDF_PATH/tools/esp_app_trace/sysviewtrace_proc.py -p -b build/sysview_tracing_heap_log.elf /tmp/heap_log0.svdat /tmp/heap_log1.svdat -``` +The script auto-detects the multi-core capture format and processes both cores from the single file, so the same command works for single- and dual-core targets. Below is the sample scripts output. diff --git a/examples/system/sysview_tracing_heap_log/gdbinit b/examples/system/sysview_tracing_heap_log/gdbinit index 049b60c1bb9..09c36b8a341 100644 --- a/examples/system/sysview_tracing_heap_log/gdbinit +++ b/examples/system/sysview_tracing_heap_log/gdbinit @@ -6,15 +6,13 @@ maintenance flush register-cache tb heap_trace_start commands -mon esp sysview start file:///tmp/heap_log.svdat -# For dual-core mode uncomment the line below and comment the line above -# mon esp sysview start file:///tmp/heap_log0.svdat file:///tmp/heap_log1.svdat +mon esp sysview_mcore start file:///tmp/heap_log.svdat c end tb heap_trace_stop commands -mon esp sysview stop +mon esp sysview_mcore stop end c diff --git a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py index 1ee4ebcfd86..7cea65a7784 100644 --- a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py +++ b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py @@ -15,22 +15,18 @@ if typing.TYPE_CHECKING: def _test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None: - # Construct trace log paths - trace_log = [ - os.path.join(dut.logdir, 'heap_log0.svdat') # pylint: disable=protected-access - ] - if not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3': - trace_log.append(os.path.join(dut.logdir, 'heap_log1.svdat')) # pylint: disable=protected-access - trace_files = ' '.join([f'file://{log}' for log in trace_log]) + # Single multi-core capture file (esp sysview_mcore): one file is enough for + # both single- and dual-core targets. + trace_log = os.path.join(dut.logdir, 'heap_log.svdat') # pylint: disable=protected-access - # Prepare gdbinit file + # Prepare gdbinit file pointing at this run's capture file gdb_logfile = os.path.join(dut.logdir, 'gdb.txt') gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit') gdbinit = os.path.join(dut.logdir, 'gdbinit') with open(gdbinit_orig) as f_r, open(gdbinit, 'w') as f_w: for line in f_r: - if line.startswith('mon esp sysview start'): - f_w.write(f'mon esp sysview start {trace_files}\n') + if line.startswith('mon esp sysview_mcore start'): + f_w.write(f'mon esp sysview_mcore start file://{trace_log}\n') else: f_w.write(line) @@ -52,8 +48,9 @@ def _test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: st # Wait for sysview files to be generated p.expect_exact('Tracing is STOPPED') - # Process sysview trace logs - command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p'] + trace_log + # Process sysview trace log (sysviewtrace_proc.py auto-detects and splits the + # multi-core capture, so a single file works for single- and dual-core) + command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p', trace_log] with pexpect.spawn(' '.join(command)) as sysviewtrace: sysviewtrace.expect(r'Found \d+ leaked bytes in \d+ blocks.', timeout=120) From 67e42bfaecfef7ca33b466af59bad3a98759c690 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Mon, 6 Jul 2026 14:51:22 +0200 Subject: [PATCH 6/8] fix(esp_trace): limit USB-Serial-JTAG trace flush timeout --- .../adapter_transport_usb_serial_jtag.c | 97 +++++++++++++++---- .../sysview_tracing/pytest_sysview_tracing.py | 4 +- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c b/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c index b6d2415b854..da3e366bfe5 100644 --- a/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c +++ b/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c @@ -41,19 +41,40 @@ static const char *TAG = "usj_transport"; +/* + * USB CDC IN transfers finish with a short packet. A 64-byte USJ packet fills + * the endpoint, so the host may keep the transfer open until it receives another + * packet shorter than 64 bytes. If no later short packet is sent, flush must send + * a ZLP (zero-length packet) to finish the transfer. + * + * See usb_serial_jtag_ll_txfifo_flush() for the USJ FIFO/full-packet behavior. + */ +typedef enum { + USJ_TX_IDLE, + USJ_TX_SHORT_PENDING, + USJ_TX_ZLP_PENDING, +} usj_tx_state_t; + /* Transport context */ typedef struct { int inited; ///< Initialization flag (bitmask per core) esp_trace_rb_t tx_ring; ///< TX ring buffer esp_trace_rb_t rx_ring; ///< RX ring buffer + usj_tx_state_t tx_state; ///< Pending TX packet finalization state /* Flush configuration */ uint32_t flush_tmo; ///< Flush timeout in microseconds uint32_t flush_thresh; ///< Flush threshold in bytes } usj_ctx_t; -#define USJ_FLUSH_TIMEOUT_US (1000000) // 1 second -#define USJ_FLUSH_THRESH_BYTES (0) // 0 bytes +/* + * flush_nolock() runs with interrupts masked, so normal flushes use a short + * fixed timeout even if an encoder requests a longer one. + */ +#define USJ_FLUSH_TIMEOUT_US (1000) // 1 ms +#define USJ_FLUSH_THRESH_BYTES (0) // 0 bytes +#define USJ_FLUSH_MAX_INTR_MASKED_US (2000) // 2 ms +#define USJ_FLUSH_POLL_STEP_US (100) // delay between no-progress polls /* USB Serial JTAG hardware FIFO size (RX and TX) is 64 bytes (USB FS bulk endpoint max packet size) */ #define USJ_HW_FIFO_SIZE (64) @@ -76,12 +97,32 @@ static uint32_t usj_write_fifo(usj_ctx_t *ctx, esp_trace_rb_t *rb) uint32_t written = usb_serial_jtag_ll_write_txfifo(ptr, to_send); esp_trace_rb_consume(rb, written); - /* Flush to send data or zero-byte packet to end USB transfer */ - usb_serial_jtag_ll_txfifo_flush(); + ctx->tx_state = usb_serial_jtag_ll_txfifo_writable() ? USJ_TX_SHORT_PENDING : USJ_TX_ZLP_PENDING; return written; } +static uint32_t usj_fill_txfifo(usj_ctx_t *ctx, bool commit_short) +{ + esp_trace_rb_t *rb = &ctx->tx_ring; + uint32_t total_written = 0; + + while (esp_trace_rb_data_len(rb) > 0 && usb_serial_jtag_ll_txfifo_writable()) { + uint32_t written = usj_write_fifo(ctx, rb); + if (written == 0) { + break; + } + total_written += written; + } + + if (commit_short && ctx->tx_state == USJ_TX_SHORT_PENDING) { + usb_serial_jtag_ll_txfifo_flush(); + ctx->tx_state = USJ_TX_IDLE; + } + + return total_written; +} + static void usj_read_rx_fifo(usj_ctx_t *ctx) { uint8_t tmp[USJ_HW_FIFO_SIZE]; @@ -229,12 +270,8 @@ static esp_err_t usj_write(esp_trace_transport_t *tp, const void *data, size_t s /* Add data to TX ring buffer */ esp_trace_rb_put(rb, (const uint8_t *)data, size); - /* Try to flush some data to HW FIFO immediately (non-blocking) */ - while (esp_trace_rb_data_len(rb) > 0) { - if (usj_write_fifo(ctx, rb) == 0) { - break; /* FIFO full, will be drained on next write or flush */ - } - } + /* Try to move data to HW FIFO immediately, without forcing a short packet. */ + usj_fill_txfifo(ctx, false); return ESP_OK; } @@ -250,31 +287,48 @@ static esp_err_t usj_down_buffer_config(esp_trace_transport_t *tp, uint8_t *buf, return ESP_OK; } -static esp_err_t usj_flush_nolock(esp_trace_transport_t *tp) +static esp_err_t usj_flush_with_timeout(usj_ctx_t *ctx, uint32_t tmo_us) { - usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; esp_trace_rb_t *rb = &ctx->tx_ring; uint32_t pending = esp_trace_rb_data_len(rb); - if (pending < ctx->flush_thresh) { + if (pending < ctx->flush_thresh && ctx->tx_state == USJ_TX_IDLE) { return ESP_OK; } esp_trace_tmo_t timeout; - esp_trace_tmo_init(&timeout, ctx->flush_tmo); + esp_trace_tmo_init(&timeout, tmo_us); - /* Drain ring buffer to HW FIFO */ - while (esp_trace_rb_data_len(rb) > 0) { - usj_write_fifo(ctx, rb); - if (esp_trace_tmo_check(&timeout) != ESP_OK) { - return ESP_ERR_TIMEOUT; + while (esp_trace_rb_data_len(rb) > 0 || ctx->tx_state != USJ_TX_IDLE) { + uint32_t written = usj_fill_txfifo(ctx, true); + + if (ctx->tx_state == USJ_TX_ZLP_PENDING && usb_serial_jtag_ll_txfifo_writable()) { + usb_serial_jtag_ll_txfifo_flush(); + ctx->tx_state = USJ_TX_IDLE; + continue; + } + + if (written == 0) { + if (esp_trace_tmo_check(&timeout) != ESP_OK) { + return ESP_ERR_TIMEOUT; + } + esp_rom_delay_us(USJ_FLUSH_POLL_STEP_US); } - esp_rom_delay_us(100); } return ESP_OK; } +static esp_err_t usj_flush_nolock(esp_trace_transport_t *tp) +{ + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + + /* Interrupts are masked here, so never spin longer than the int_wdt-safe limit. */ + uint32_t tmo = (ctx->flush_tmo < USJ_FLUSH_MAX_INTR_MASKED_US) + ? ctx->flush_tmo : USJ_FLUSH_MAX_INTR_MASKED_US; + return usj_flush_with_timeout(ctx, tmo); +} + static bool usj_is_host_connected(esp_trace_transport_t *tp) { (void)tp; @@ -341,7 +395,8 @@ static esp_err_t usj_get_config(esp_trace_transport_t *tp, esp_trace_transport_c static void usj_panic_handler(esp_trace_transport_t *tp, const void *info) { (void)info; - usj_flush_nolock(tp); + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + usj_flush_with_timeout(ctx, ctx->flush_tmo); } /* ----------------------- Transport Registration ----------------------- */ diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index 8dc876165da..da469568f43 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -71,13 +71,13 @@ def _capture_sysview_trace(ser: serial.Serial, trace_log_path: str) -> None: except serial.SerialTimeoutException: assert False, 'Timeout reached while reading from serial port, exiting...' - # Wait some time to let data accumulate in target's ring buffer + # Give pending trace data a short window to reach the host before requesting STOP. time.sleep(0.2) # Send Stop command ser.write(STOP_CMD) - # Capture until target flushed data or timeout (3 seconds) + # Capture the final data produced by STOP and the transport flush. end_time = time.time() + 3.0 last_data_time = time.time() while time.time() < end_time and (time.time() - last_data_time) <= 1.0: From 4cf35efa35c01cbde6715938a3d7a3281f545094 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Fri, 3 Jul 2026 16:07:28 +0200 Subject: [PATCH 7/8] test(usb_serial_jtag): pin console uart to ttyUSBx port --- examples/system/app_trace_basic/pytest_app_trace_basic.py | 1 + examples/system/esp_trace/pytest_esp_trace.py | 1 + examples/system/gcov/pytest_gcov.py | 1 + examples/system/sysview_tracing/pytest_sysview_tracing.py | 2 ++ .../pytest_sysview_tracing_heap_log.py | 1 + .../system/ulp/lp_core/debugging/pytest_lp_core_debugging.py | 1 + tools/test_apps/system/gdb/pytest_gdb.py | 4 ++++ 7 files changed, 11 insertions(+) diff --git a/examples/system/app_trace_basic/pytest_app_trace_basic.py b/examples/system/app_trace_basic/pytest_app_trace_basic.py index ef7a7df9336..dd2435a4a63 100644 --- a/examples/system/app_trace_basic/pytest_app_trace_basic.py +++ b/examples/system/app_trace_basic/pytest_app_trace_basic.py @@ -57,6 +57,7 @@ def test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @idf_parametrize('config', ['apptrace_jtag'], indirect=['config']) @idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) def test_examples_app_trace_basic_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_examples_app_trace_basic(openocd_dut, dut) diff --git a/examples/system/esp_trace/pytest_esp_trace.py b/examples/system/esp_trace/pytest_esp_trace.py index d1f031f5201..7ad9136a845 100644 --- a/examples/system/esp_trace/pytest_esp_trace.py +++ b/examples/system/esp_trace/pytest_esp_trace.py @@ -102,6 +102,7 @@ def _capture_trace(ser: serial.Serial, trace_log_path: str, capture_s: float = 5 @pytest.mark.usb_serial_jtag @idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) @pytest.mark.parametrize('config', [pytest.param('default')], indirect=True) +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_esp_trace_ext_lib_usj(dut: IdfDut) -> None: dut.expect('Start of trace session', timeout=5) diff --git a/examples/system/gcov/pytest_gcov.py b/examples/system/gcov/pytest_gcov.py index 042a91e4b32..b9c4ef3486a 100644 --- a/examples/system/gcov/pytest_gcov.py +++ b/examples/system/gcov/pytest_gcov.py @@ -178,6 +178,7 @@ def test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag @idf_parametrize('config', ['gcov_jtag'], indirect=['config']) @idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_gcov_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_gcov(openocd_dut, dut) diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index da469568f43..ef3b056706d 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -148,6 +148,7 @@ def test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: indirect=['target'], ) @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) def test_sysview_tracing_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_sysview_tracing_jtag(openocd_dut, dut) @@ -180,6 +181,7 @@ def test_sysview_tracing_uart_c2(dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag @idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) @pytest.mark.parametrize('config', [pytest.param('sysview_usj')], indirect=True) +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_sysview_tracing_usj_serial(dut: IdfDut) -> None: time.sleep(1) # wait for USJ port to be ready diff --git a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py index 7cea65a7784..2c98f560fb4 100644 --- a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py +++ b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py @@ -77,6 +77,7 @@ def test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target'], ) +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) @pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') def test_examples_sysview_tracing_heap_log_usj(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None: _test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut) diff --git a/examples/system/ulp/lp_core/debugging/pytest_lp_core_debugging.py b/examples/system/ulp/lp_core/debugging/pytest_lp_core_debugging.py index 433db2cc0a8..2a17fc948d4 100644 --- a/examples/system/ulp/lp_core/debugging/pytest_lp_core_debugging.py +++ b/examples/system/ulp/lp_core/debugging/pytest_lp_core_debugging.py @@ -15,6 +15,7 @@ if typing.TYPE_CHECKING: @idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4', 'esp32s31'], indirect=['target']) @pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='s31-lpcore not supported in latest OpenOCD release yet') +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) @pytest.mark.usb_serial_jtag def test_lp_core_debugging(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: dut.expect('Do some work on HP core...') diff --git a/tools/test_apps/system/gdb/pytest_gdb.py b/tools/test_apps/system/gdb/pytest_gdb.py index 6ddf6ff5ec0..fd62f74ce15 100644 --- a/tools/test_apps/system/gdb/pytest_gdb.py +++ b/tools/test_apps/system/gdb/pytest_gdb.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Unlicense OR CC0-1.0 import os import re +import time import typing import pexpect @@ -21,6 +22,8 @@ def _test_idf_gdb(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: # Don't need to have output from UART anymore dut.serial.stop_redirect_thread() + time.sleep(1) # Wait for the USJ port to be ready + with ( openocd_dut.run(), open(os.path.join(dut.logdir, 'gdb.txt'), 'w') as gdb_log, @@ -47,5 +50,6 @@ def test_idf_gdb(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag @idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2'], indirect=['target']) +@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port']) def test_idf_gdb_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_idf_gdb(openocd_dut, dut) From 21b9eaf6ff4f12dd09a69a08924e36e3e005a334 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Wed, 8 Jul 2026 14:01:36 +0200 Subject: [PATCH 8/8] test(sysview): trim trace capture to the STOP record to drop post-reset UART noise --- .../sysview_tracing/pytest_sysview_tracing.py | 68 ++++++++++++------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index ef3b056706d..ed6905d2938 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -56,37 +56,55 @@ def _capture_sysview_trace(ser: serial.Serial, trace_log_path: str) -> None: """ START_CMD = b'\x01' STOP_CMD = b'\x02' + STOP_EVENT_ID = 0x0B # SYSVIEW_EVTID_TRACE_STOP ser.reset_input_buffer() # Send Start command to start SysView tracing ser.write(START_CMD) + data = bytearray() + + # Capture for 3 seconds + end_time = time.time() + 3.0 + while time.time() < end_time: + try: + if ser.in_waiting: + data += ser.read(ser.in_waiting) + except serial.SerialTimeoutException: + assert False, 'Timeout reached while reading from serial port, exiting...' + + # Give pending trace data a short window to reach the host before requesting STOP. + time.sleep(0.2) + + # Send Stop command + ser.write(STOP_CMD) + + # Capture the final data produced by STOP and the transport flush. + end_time = time.time() + 3.0 + last_data_time = time.time() + while time.time() < end_time and (time.time() - last_data_time) <= 1.0: + try: + if ser.in_waiting: + data += ser.read(ser.in_waiting) + last_data_time = time.time() + except serial.SerialTimeoutException: + assert False, 'Timeout reached while reading from serial port, exiting...' + + # Drop anything after the TRACE_STOP record (e.g. the ROM boot banner printed + # on the shared UART when pytest-embedded resets the DUT after the test). + stop_pos = data.rfind(STOP_EVENT_ID) + if stop_pos != -1: + end = stop_pos + 1 + # Consume the timestamp delta: continuation bytes have the 0x80 bit set, + # terminated by a single byte with 0x80 clear. + while end < len(data) and (data[end] & 0x80): + end += 1 + if end < len(data): + end += 1 + data = data[:end] + with open(trace_log_path, 'w+b') as f: - # Capture for 3 seconds - end_time = time.time() + 3.0 - while time.time() < end_time: - try: - if ser.in_waiting: - f.write(ser.read(ser.in_waiting)) - except serial.SerialTimeoutException: - assert False, 'Timeout reached while reading from serial port, exiting...' - - # Give pending trace data a short window to reach the host before requesting STOP. - time.sleep(0.2) - - # Send Stop command - ser.write(STOP_CMD) - - # Capture the final data produced by STOP and the transport flush. - end_time = time.time() + 3.0 - last_data_time = time.time() - while time.time() < end_time and (time.time() - last_data_time) <= 1.0: - try: - if ser.in_waiting: - f.write(ser.read(ser.in_waiting)) - last_data_time = time.time() - except serial.SerialTimeoutException: - assert False, 'Timeout reached while reading from serial port, exiting...' + f.write(data) def _test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: