Skip to content

Commit 1a93afa

Browse files
committed
[nrf fromlist] samples: boards: nordic: coresight_stm: Test STM dictionary mode
Extend STM logger test. Use nrfutil trace to decode STM logs in dictionary mode. Upstream PR #: 78947 Signed-off-by: Sebastian Głąb <[email protected]>
1 parent e49e65b commit 1a93afa

File tree

3 files changed

+382
-24
lines changed

3 files changed

+382
-24
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
.. zephyr:code-sample:: coresight_stm_sample
2+
:name: Coresight STM benchmark
3+
:relevant-api: log_api
4+
5+
Overview
6+
********
7+
8+
This sample presents how to enable STM logging on nRF54H20 platform.
9+
10+
Also, it prints timing for different log messages.
11+
Thus, performance of different loggers can be compared.
12+
13+
There are three sample configurations in the :file:`sample.yaml`.
14+
15+
* **sample.boards.nrf.coresight_stm.local_uart**
16+
17+
This configuration doesn't use STM.
18+
Logs are printed on local console.
19+
20+
* **sample.boards.nrf.coresight_stm**
21+
22+
This configuration use STM.
23+
Application, Radio, PPR and FLPR cores send logs to an ETR buffer.
24+
Proxy (Application core) gets logs from the ETR buffer, decodes STPv2 data
25+
and prints human readable logs on UART.
26+
27+
* **sample.boards.nrf.coresight_stm.dict**
28+
29+
This sample uses STM logging in dictionary mode.
30+
Application, Radio, PPR and FLPR cores send logs to the ETR buffer.
31+
Proxy (Application core) forwards data from the ETR to the host using UART.
32+
Host tool is needed to decode the logs.
33+
34+
Requirements
35+
************
36+
37+
This application uses nRF54H20 DK board for the demo.
38+
39+
**sample.boards.nrf.coresight_stm.dict** requires host tool like ``nrfutil trace``
40+
to decode the traces.
41+
42+
Building and running
43+
********************
44+
45+
To build the sample, use configuration setups from the :file:`sample.yaml` using the ``-T`` option.
46+
See the example:
47+
48+
nRF54H20 DK
49+
50+
.. code-block:: console
51+
52+
west build -p -b nrf54h20dk/nrf54h20/cpuapp -T sample.boards.nrf.coresight_stm .
53+
54+
Sample Output
55+
=============
56+
57+
.. code-block:: console
58+
59+
*** Using Zephyr OS v3.6.99-5bb7bb0af17c ***
60+
(...)
61+
[00:00:00.227,264] <inf> app/app: test with one argument 100
62+
[00:00:00.227,265] <inf> app/app: test with one argument 100
63+
(...)
64+
[00:00:00.585,558] <inf> rad/app: test with one argument 100
65+
[00:00:00.585,569] <inf> rad/app: test with one argument 100
66+
(...)
67+
[00:00:00.624,408] <inf> ppr/app: test with one argument 100
68+
[00:00:00.624,433] <inf> ppr/app: test with one argument 100
69+
(...)
70+
[00:00:00.625,249] <inf> flpr/app: test with one argument 100
71+
[00:00:00.625,251] <inf> flpr/app: test with one argument 100
72+
(...)
73+
rad: Timing for log message with 0 arguments: 5.10us
74+
rad: Timing for log message with 1 argument: 6.10us
75+
rad: Timing for log message with 2 arguments: 6.0us
76+
rad: Timing for log message with 3 arguments: 6.40us
77+
rad: Timing for log_message with string: 7.10us
78+
rad: Timing for tracepoint: 0.5us
79+
rad: Timing for tracepoint_d32: 0.5us
80+
flpr: Timing for log message with 0 arguments: 1.20us
81+
flpr: Timing for log message with 1 argument: 1.20us
82+
flpr: Timing for log message with 2 arguments: 1.20us
83+
flpr: Timing for log message with 3 arguments: 1.30us
84+
flpr: Timing for log_message with string: 3.0us
85+
flpr: Timing for tracepoint: 0.0us
86+
flpr: Timing for tracepoint_d32: 0.0us
87+
app: Timing for log message with 0 arguments: 1.80us
88+
app: Timing for log message with 1 argument: 2.0us
89+
app: Timing for log message with 2 arguments: 2.0us
90+
app: Timing for log message with 3 arguments: 2.10us
91+
app: Timing for log_message with string: 4.40us
92+
app: Timing for tracepoint: 0.10us
93+
app: Timing for tracepoint_d32: 0.10us
94+
ppr: Timing for log message with 0 arguments: 25.20us
95+
ppr: Timing for log message with 1 argument: 26.20us
96+
ppr: Timing for log message with 2 arguments: 26.90us
97+
ppr: Timing for log message with 3 arguments: 27.40us
98+
ppr: Timing for log_message with string: 64.80us
99+
ppr: Timing for tracepoint: 0.30us
100+
ppr: Timing for tracepoint_d32: 0.25us
101+
102+
For logging on NRF54H20 using ARM Coresight STM see :ref:`logging_cs_stm`.
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#
2+
# Copyright (c) 2024 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
import logging
8+
import re
9+
import subprocess
10+
from pathlib import Path
11+
from time import sleep
12+
13+
import psutil
14+
from twister_harness import DeviceAdapter
15+
16+
logger = logging.getLogger(__name__)
17+
18+
SB_CONFIG_APP_CPUPPR_RUN = None
19+
SB_CONFIG_APP_CPUFLPR_RUN = None
20+
21+
# https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/misc/coresight/nrf_etr.c#L102
22+
STM_M_ID = {
23+
"sec": 33,
24+
"app": 34,
25+
"rad": 35,
26+
"mod": 36,
27+
"sys": 44,
28+
"flpr": 45,
29+
"ppr": 46,
30+
"hw": 128,
31+
}
32+
33+
34+
def _analyse_autoconf(filepath: str) -> None:
35+
global SB_CONFIG_APP_CPUPPR_RUN
36+
global SB_CONFIG_APP_CPUFLPR_RUN
37+
38+
SB_CONFIG_APP_CPUPPR_RUN = False
39+
SB_CONFIG_APP_CPUFLPR_RUN = False
40+
41+
# Parse contents of {BUILD_DIR}/_sysbuild/autoconf.h
42+
with open(f"{filepath}", errors="ignore") as autoconf:
43+
for line in autoconf:
44+
if "SB_CONFIG_APP_CPUPPR_RUN 1" in line:
45+
SB_CONFIG_APP_CPUPPR_RUN = True
46+
continue
47+
if "SB_CONFIG_APP_CPUFLPR_RUN 1" in line:
48+
SB_CONFIG_APP_CPUFLPR_RUN = True
49+
logger.debug(f"{SB_CONFIG_APP_CPUPPR_RUN=}")
50+
logger.debug(f"{SB_CONFIG_APP_CPUFLPR_RUN=}")
51+
52+
53+
def _check_benchmark_results(output: str, core: str) -> None:
54+
"""
55+
Use regular expressions to parse 'output' string.
56+
Search for benchmark results related to 'core' coprocessor.
57+
"""
58+
59+
latency_msg_0_str = re.search(
60+
rf"{core}: Timing for log message with 0 arguments: (.+)us", output
61+
).group(1)
62+
assert latency_msg_0_str is not None, "Timing for log message with 0 arguments NOT found"
63+
64+
latency_msg_1_str = re.search(
65+
rf"{core}: Timing for log message with 1 argument: (.+)us", output
66+
).group(1)
67+
assert latency_msg_1_str is not None, "Timing for log message with 1 argument NOT found"
68+
69+
latency_msg_2_str = re.search(
70+
rf"{core}: Timing for log message with 2 arguments: (.+)us", output
71+
).group(1)
72+
assert latency_msg_2_str is not None, "Timing for log message with 2 arguments NOT found"
73+
74+
latency_msg_3_str = re.search(
75+
rf"{core}: Timing for log message with 3 arguments: (.+)us", output
76+
).group(1)
77+
assert latency_msg_3_str is not None, "Timing for log message with 3 arguments NOT found"
78+
79+
latency_msg_string_str = re.search(
80+
rf"{core}: Timing for log_message with string: (.+)us", output
81+
).group(1)
82+
assert latency_msg_string_str is not None, "Timing for log_message with string NOT found"
83+
84+
latency_tracepoint_str = re.search(
85+
rf"{core}: Timing for tracepoint: (.+)us", output
86+
).group(1)
87+
assert latency_tracepoint_str is not None, "Timing for tracepoint NOT found"
88+
89+
latency_tracepoint_d32_str = re.search(
90+
rf"{core}: Timing for tracepoint_d32: (.+)us", output
91+
).group(1)
92+
assert latency_tracepoint_d32_str is not None, "Timing for tracepoint_d32 NOT found"
93+
94+
95+
# nrfutil starts children processes
96+
# when subprocess.terminate(nrfutil_process) is executed, only the parent terminates
97+
# this blocks serial port for other uses
98+
def _kill(proc):
99+
try:
100+
for child in psutil.Process(proc.pid).children(recursive=True):
101+
child.kill()
102+
proc.kill()
103+
except Exception as e:
104+
logger.exception(f'Could not kill nrfutil - {e}')
105+
106+
107+
def _nrfutil_dictionary_from_serial(
108+
dut: DeviceAdapter,
109+
decoded_file_name: str = "output.txt",
110+
collect_time: float = 60.0,
111+
) -> None:
112+
UART_PATH = dut.device_config.serial
113+
UART_BAUDRATE = dut.device_config.baud
114+
dut.close()
115+
116+
logger.debug(f"Using serial: {UART_PATH}")
117+
118+
if Path(f"{decoded_file_name}").exists():
119+
logger.warning("Output file already exists!")
120+
121+
# prepare database config string
122+
BUILD_DIR = str(dut.device_config.build_dir)
123+
logger.debug(f"{BUILD_DIR=}")
124+
config_str = f"{STM_M_ID['app']}:{BUILD_DIR}/coresight_stm/zephyr/log_dictionary.json"
125+
config_str = config_str + f",{STM_M_ID['rad']}:{BUILD_DIR}/remote_rad/zephyr/log_dictionary.json"
126+
if SB_CONFIG_APP_CPUPPR_RUN:
127+
config_str = config_str + f",{STM_M_ID['ppr']}:{BUILD_DIR}/remote_ppr/zephyr/log_dictionary.json"
128+
if SB_CONFIG_APP_CPUFLPR_RUN:
129+
config_str = config_str + f",{STM_M_ID['flpr']}:{BUILD_DIR}/remote_flpr/zephyr/log_dictionary.json"
130+
logger.debug(f"{config_str=}")
131+
132+
cmd = (
133+
"nrfutil trace stm --database-config "
134+
f"{config_str} "
135+
f"--input-serialport {UART_PATH} --baudrate {UART_BAUDRATE} "
136+
f"--output-ascii {decoded_file_name}"
137+
)
138+
try:
139+
# run nrfutil trace in background non-blocking
140+
logger.info(f"Executing:\n{cmd}")
141+
proc = subprocess.Popen(cmd.split(), stdout=subprocess.DEVNULL)
142+
except OSError as exc:
143+
logger.error(f"Unable to start nrfutil trace:\n{cmd}\n{exc}")
144+
try:
145+
proc.wait(collect_time)
146+
except subprocess.TimeoutExpired:
147+
pass
148+
finally:
149+
_kill(proc)
150+
151+
152+
def test_STM_decoded(dut: DeviceAdapter):
153+
"""
154+
Run sample.boards.nrf.coresight_stm from samples/boards/nrf/coresight_stm sample.
155+
Both Application and Radio cores use STM for logging.
156+
STM proxy (Application core) decodes logs from all domains.
157+
After reset, coprocessors execute code in expected way and Application core
158+
outputs STM traces on UART port.
159+
"""
160+
BUILD_DIR = str(dut.device_config.build_dir)
161+
autoconf_file = f"{BUILD_DIR}/_sysbuild/autoconf.h"
162+
163+
# nrf54h20 prints immediately after it is flashed.
164+
# Wait a bit to skipp logs from previous test.
165+
sleep(4)
166+
167+
# Get output from serial port
168+
output = "\n".join(dut.readlines())
169+
170+
# set SB_CONFIG_APP_CPUPPR_RUN, SB_CONFIG_APP_CPUFLPR_RUN
171+
_analyse_autoconf(autoconf_file)
172+
173+
# check that LOGs from Application core are present
174+
_check_benchmark_results(
175+
output=output,
176+
core='app',
177+
)
178+
179+
# check that LOGs from Radio core are present
180+
_check_benchmark_results(
181+
output=output,
182+
core='rad',
183+
)
184+
185+
if SB_CONFIG_APP_CPUPPR_RUN:
186+
# check that LOGs from PPR core are present
187+
_check_benchmark_results(
188+
output=output,
189+
core='ppr',
190+
)
191+
192+
if SB_CONFIG_APP_CPUFLPR_RUN:
193+
# check that LOGs from FLPR core are present
194+
_check_benchmark_results(
195+
output=output,
196+
core='flpr',
197+
)
198+
199+
200+
def test_STM_dictionary_mode(dut: DeviceAdapter):
201+
"""
202+
Run sample.boards.nrf.coresight_stm.dict from samples/boards/nrf/coresight_stm sample.
203+
Both Application and Radio cores use STM for logging.
204+
STM proxy (Application core) prints on serial port raw logs from all domains.
205+
Nrfutil trace is used to decode STM logs.
206+
After reset, coprocessors execute code in expected way and Application core
207+
outputs STM traces on UART port.
208+
"""
209+
BUILD_DIR = str(dut.device_config.build_dir)
210+
test_filename = f"{BUILD_DIR}/coresight_stm_dictionary.txt"
211+
autoconf_file = f"{BUILD_DIR}/_sysbuild/autoconf.h"
212+
COLLECT_TIMEOUT = 10.0
213+
214+
# set SB_CONFIG_APP_CPUPPR_RUN, SB_CONFIG_APP_CPUFLPR_RUN
215+
# this information is needed to build nrfutil database-config
216+
_analyse_autoconf(autoconf_file)
217+
218+
# use nrfutil trace to decode logs
219+
_nrfutil_dictionary_from_serial(
220+
dut=dut,
221+
decoded_file_name=f"{test_filename}",
222+
collect_time=COLLECT_TIMEOUT,
223+
)
224+
225+
# read decoded logs
226+
with open(f"{test_filename}", errors="ignore") as decoded_file:
227+
decoded_file_content = decoded_file.read()
228+
229+
# if nothing in decoded_file, stop test
230+
assert(
231+
len(decoded_file_content) > 0
232+
), f"File {test_filename} is empty"
233+
234+
# check that LOGs from Application core are present
235+
_check_benchmark_results(
236+
output=decoded_file_content,
237+
core='app',
238+
)
239+
240+
# check that LOGs from Radio core are present
241+
_check_benchmark_results(
242+
output=decoded_file_content,
243+
core='rad',
244+
)
245+
246+
if SB_CONFIG_APP_CPUPPR_RUN:
247+
# check that LOGs from PPR core are present
248+
_check_benchmark_results(
249+
output=decoded_file_content,
250+
core='ppr',
251+
)
252+
253+
if SB_CONFIG_APP_CPUFLPR_RUN:
254+
# check that LOGs from FLPR core are present
255+
_check_benchmark_results(
256+
output=decoded_file_content,
257+
core='flpr',
258+
)

0 commit comments

Comments
 (0)