Skip to content

Commit 5419b42

Browse files
ranj063marc-hb
authored andcommitted
tools: Add a new script for firmware boot stress testing
This requires the kernel changes that expose the debugfs entries for executing the different DSP ops and reading the current DSP firmware and power states. The script takes the firmware name and the path as inputs along with the number of iterations of firmware boot. It takes care of putting the DSP in the D3 state and unloading any existing firmware before starting firmware boot. Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
1 parent f3109df commit 5419b42

1 file changed

Lines changed: 167 additions & 0 deletions

File tree

tools/sof-fw-boot-test.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env python3
2+
3+
"""Module to stress test firmware boot"""
4+
5+
import argparse
6+
import logging
7+
import sys
8+
from datetime import datetime
9+
from systemd import journal
10+
11+
logging.basicConfig(level=logging.INFO, format="%(message)s")
12+
13+
# set path to the depbugfs entries
14+
DEBUGFS_PATH = "/sys/kernel/debug/sof/fw_debug_ops"
15+
16+
def read_jctl_logs(start_time):
17+
"""Reads journalctl logs from a given start time and extracts the timestamp and log"""
18+
journal_reader = journal.Reader()
19+
journal_reader.this_boot()
20+
journal_reader.seek_realtime(datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S'))
21+
22+
entries = []
23+
for entry in journal_reader:
24+
timestamp = entry['__REALTIME_TIMESTAMP']
25+
message = entry.get('MESSAGE', '')
26+
if isinstance(message, bytes):
27+
message = message.decode('utf-8', errors='replace')
28+
entries.append((timestamp, message))
29+
30+
return entries
31+
32+
# define command line arguments
33+
def parse_cmdline():
34+
"""Function to parse the command line arguments"""
35+
parser = argparse.ArgumentParser(
36+
add_help=True,
37+
formatter_class=argparse.RawTextHelpFormatter,
38+
description="A script for stress testing firmware boot",
39+
)
40+
parser.add_argument(
41+
"-i", "--iter", type=int, default=100, help="number of firmware boot iterations"
42+
)
43+
parser.add_argument(
44+
"-f", "--firmware", type=str,
45+
help="firmware filename. If this is not set, the kernel will boot the default firmware"
46+
)
47+
parser.add_argument(
48+
"-p",
49+
"--fw_path",
50+
type=str,
51+
help="""path to the firmware file. If the path is not relative to /lib/firmware,
52+
use echo -n /path/to/fw_file > /sys/module/firmware_class/parameters/path""",
53+
)
54+
return parser.parse_args()
55+
56+
def dsp_set(node, value):
57+
"""Set the debugfs node"""
58+
open(f"{DEBUGFS_PATH}/{node}", "w").write(f"{value}\n")
59+
60+
def dsp_get(node):
61+
"""Get the value from a debugfs node"""
62+
return open(f"{DEBUGFS_PATH}/{node}").read().rstrip()
63+
64+
def boot_fw():
65+
"""Power down the DSP and boot firmware using previously set firmware path and filename"""
66+
# put the DSP in D3
67+
dsp_set("dsp_power_state", "D3")
68+
69+
# check if the DSP is in D3
70+
power_state = dsp_get("dsp_power_state")
71+
if power_state != "D3":
72+
sys.exit("Failed booting firmware. DSP is not in D3")
73+
74+
# unload current firmware
75+
dsp_set("unload_fw", "1")
76+
77+
# get current fw_state and continue to boot only if the current state is 'PREPARE'
78+
fw_state = dsp_get("fw_state")
79+
if "PREPARE" not in fw_state:
80+
sys.exit(f"Cannot boot firmware from current state {fw_state}")
81+
82+
# load and boot firmware
83+
dsp_set("boot_fw", "1")
84+
85+
# get current fw_state
86+
fw_state = dsp_get("fw_state")
87+
88+
return fw_state
89+
90+
def calculate_boot_time(journalctl_output):
91+
"""Calculate boot time from the journal ctl log entries"""
92+
boot_start_time = 0
93+
boot_end_time = 0
94+
for timestamp, message in journalctl_output:
95+
if 'booting DSP firmware' in message:
96+
boot_start_time = timestamp
97+
if 'firmware boot complete' in message:
98+
boot_end_time = timestamp
99+
if boot_start_time == 0 or boot_end_time == 0:
100+
sys.exit("Failed to calculate boot time")
101+
time_diff = boot_end_time - boot_start_time
102+
boot_time_ms = round(time_diff.total_seconds() * 1000, 2)
103+
104+
return boot_time_ms
105+
106+
def main():
107+
"""Main function for stress testing"""
108+
cmd_args = parse_cmdline()
109+
110+
# clear firmware filename/path
111+
dsp_set("fw_filename", "")
112+
dsp_set("fw_path", "")
113+
114+
# Get firmware file path if set
115+
if cmd_args.fw_path:
116+
fw_path = cmd_args.fw_path
117+
dsp_set("fw_path", fw_path)
118+
else:
119+
fw_path = "default"
120+
121+
# Get firmware file name if set
122+
if cmd_args.firmware:
123+
fw_filename = cmd_args.firmware
124+
dsp_set("fw_filename", fw_filename)
125+
else:
126+
fw_filename = "default"
127+
128+
num_iter = cmd_args.iter
129+
output = f"""==============================================================================
130+
Starting boot stress test with:
131+
Firmware filename: {fw_filename}
132+
Path to firmware file: {fw_path}
133+
Number of Iterations: {num_iter}
134+
=============================================================================="""
135+
logging.info(output)
136+
137+
total_boot_time_ms = 0
138+
min_boot_time_ms = sys.maxsize
139+
max_boot_time_ms = 0
140+
141+
for i in range(num_iter):
142+
start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
143+
fw_state = boot_fw()
144+
# check if fw_state is COMPLETE
145+
if "COMPLETE" not in fw_state:
146+
sys.exit(f"Firmware boot failed at iteration {i}")
147+
148+
journalctl_output = read_jctl_logs(start_time)
149+
150+
boot_time_ms = calculate_boot_time(journalctl_output)
151+
total_boot_time_ms += boot_time_ms
152+
min_boot_time_ms = min(min_boot_time_ms, boot_time_ms)
153+
max_boot_time_ms = max(max_boot_time_ms, boot_time_ms)
154+
155+
logging.info("Firmware boot iteration %d completed in %0.2f ms", i, boot_time_ms)
156+
157+
# print firmware boot stats
158+
avg_boot_time_ms = round(total_boot_time_ms / num_iter, 2)
159+
output = f"""==============================================================================
160+
Average firmware boot time {avg_boot_time_ms} ms
161+
Maximum firmware boot time {max_boot_time_ms} ms
162+
Minimum firmware boot time {min_boot_time_ms} ms
163+
=============================================================================="""
164+
logging.info(output)
165+
166+
if __name__ == "__main__":
167+
main()

0 commit comments

Comments
 (0)