mirror of
https://github.com/espressif/esp-idf.git
synced 2026-07-16 16:03:08 +03:00
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:
@@ -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 ----------------------- */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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...')
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user