Merge branch 'enable_esp32s31_jtag_tests' into 'master'

change(ci): enable esp32s31 jtag tests

Closes IDF-14691, IDF-14692, IDF-14942, and IDFCI-11113

See merge request espressif/esp-idf!50149
This commit is contained in:
Erhan Kurubas
2026-07-14 11:39:22 +02:00
14 changed files with 205 additions and 149 deletions

View File

@@ -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 ----------------------- */

View File

@@ -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

View File

@@ -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,9 @@ 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')
@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)
@@ -64,7 +65,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:

View File

@@ -102,7 +102,8 @@ 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')
@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)

View File

@@ -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,9 @@ 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'])
@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)
@@ -226,6 +227,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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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];

View File

@@ -16,28 +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}'
with open(trace_log, 'rb') as f:
content = f.read()
# 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'
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:
@@ -49,56 +56,71 @@ 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...'
# Wait some time to let data accumulate in target's ring buffer
time.sleep(0.2)
# Send Stop command
ser.write(STOP_CMD)
# Capture until target flushed data or timeout (3 seconds)
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:
# 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)
@@ -124,9 +146,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
@@ -140,10 +162,11 @@ 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')
@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)
@@ -152,15 +175,15 @@ 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)
@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,11 +199,12 @@ 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')
@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
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)

View File

@@ -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.

View File

@@ -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

View File

@@ -8,28 +8,25 @@ 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
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)
@@ -51,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)
@@ -76,8 +74,10 @@ 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'],
)
@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)

View File

@@ -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...')

View File

@@ -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)