Skip to content

Commit e7bb438

Browse files
lucaceresolibroonie
authored andcommitted
ASoC: dapm-graph: new tool to visualize DAPM state
Add a tool to generate a picture of the current DAPM state for a sound card. dapm-graph is inspired by vizdapm which used to be published on a Wolfson Micro git repository now disappeared, and has a few forks around: https://github.com/mihais/asoc-tools https://github.com/alexandrebelloni/asoc-tools dapm-graph is a full reimplementation with several improvements while still being a self-contained shell script: Improvements to rendered output: - shows the entire card, not one component hierarchy only - each component is rendered in a separate box - shows widget on/off status based on widget information alone (the original vizdapm propagates the "on" green colour to the first input widget) - use bold line and gray background and not only green/red line to show on/off status (for the color blind) Improvements for embedded system developers: - remote mode: get state of remote device (possibly with minimal rootfs) via SSH, but parsing locally for faster operation - compatible with BusyBox shell, not only bash Usability improvements: - flexible command line (uses getopts for parsing) - detailed help text - flag to enable detailed debug logging - graphviz output format detected from file extension, not hard coded - a self-contained shell script Usage is designed to be simple: dapm-grpah -c CARD - get state from debugfs for CARD dapm-grpah -c CARD -r REMOTE_TARGET - same, but remotely via SSH dapm-grpah -d STATE_DIR - from a local copy of the debugfs tree for a card Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Link: https://lore.kernel.org/r/20240416-vizdapm-ng-v1-3-5d33c0b57bc5@bootlin.com Signed-off-by: Mark Brown <broonie@kernel.org>
1 parent 5b1047d commit e7bb438

2 files changed

Lines changed: 309 additions & 0 deletions

File tree

MAINTAINERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20669,6 +20669,12 @@ F: include/trace/events/sof*.h
2066920669
F: include/uapi/sound/asoc.h
2067020670
F: sound/soc/
2067120671

20672+
SOUND - SOC LAYER / dapm-graph
20673+
M: Luca Ceresoli <luca.ceresoli@bootlin.com>
20674+
L: linux-sound@vger.kernel.org
20675+
S: Maintained
20676+
F: tools/sound/dapm-graph
20677+
2067220678
SOUND - SOUND OPEN FIRMWARE (SOF) DRIVERS
2067320679
M: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
2067420680
M: Liam Girdwood <lgirdwood@gmail.com>

tools/sound/dapm-graph

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
#!/bin/sh
2+
# SPDX-License-Identifier: GPL-2.0
3+
#
4+
# Generate a graph of the current DAPM state for an audio card
5+
#
6+
# Copyright 2024 Bootlin
7+
# Author: Luca Ceresoli <luca.ceresol@bootlin.com>
8+
9+
set -eu
10+
11+
STYLE_NODE_ON="shape=box,style=bold,color=green4"
12+
STYLE_NODE_OFF="shape=box,style=filled,color=gray30,fillcolor=gray95"
13+
14+
# Print usage and exit
15+
#
16+
# $1 = exit return value
17+
# $2 = error string (required if $1 != 0)
18+
usage()
19+
{
20+
if [ "${1}" -ne 0 ]; then
21+
echo "${2}" >&2
22+
fi
23+
24+
echo "
25+
Generate a graph of the current DAPM state for an audio card.
26+
27+
The DAPM state can be obtained via debugfs for a card on the local host or
28+
a remote target, or from a local copy of the debugfs tree for the card.
29+
30+
Usage:
31+
$(basename $0) [options] -c CARD - Local sound card
32+
$(basename $0) [options] -c CARD -r REMOTE_TARGET - Card on remote system
33+
$(basename $0) [options] -d STATE_DIR - Local directory
34+
35+
Options:
36+
-c CARD Sound card to get DAPM state of
37+
-r REMOTE_TARGET Get DAPM state from REMOTE_TARGET via SSH and SCP
38+
instead of using a local sound card
39+
-d STATE_DIR Get DAPM state from a local copy of a debugfs tree
40+
-o OUT_FILE Output file (default: dapm.dot)
41+
-D Show verbose debugging info
42+
-h Print this help and exit
43+
44+
The output format is implied by the extension of OUT_FILE:
45+
46+
* Use the .dot extension to generate a text graph representation in
47+
graphviz dot syntax.
48+
* Any other extension is assumed to be a format supported by graphviz for
49+
rendering, e.g. 'png', 'svg', and will produce both the .dot file and a
50+
picture from it. This requires the 'dot' program from the graphviz
51+
package.
52+
"
53+
54+
exit ${1}
55+
}
56+
57+
# Connect to a remote target via SSH, collect all DAPM files from debufs
58+
# into a tarball and get the tarball via SCP into $3/dapm.tar
59+
#
60+
# $1 = target as used by ssh and scp, e.g. "root@192.168.1.1"
61+
# $2 = sound card name
62+
# $3 = temp dir path (present on the host, created on the target)
63+
# $4 = local directory to extract the tarball into
64+
#
65+
# Requires an ssh+scp server, find and tar+gz on the target
66+
#
67+
# Note: the tarball is needed because plain 'scp -r' from debugfs would
68+
# copy only empty files
69+
grab_remote_files()
70+
{
71+
echo "Collecting DAPM state from ${1}"
72+
dbg_echo "Collected DAPM state in ${3}"
73+
74+
ssh "${1}" "
75+
set -eu &&
76+
cd \"/sys/kernel/debug/asoc/${2}\" &&
77+
find * -type d -exec mkdir -p ${3}/dapm-tree/{} \; &&
78+
find * -type f -exec cp \"{}\" \"${3}/dapm-tree/{}\" \; &&
79+
cd ${3}/dapm-tree &&
80+
tar cf ${3}/dapm.tar ."
81+
scp -q "${1}:${3}/dapm.tar" "${3}"
82+
83+
mkdir -p "${4}"
84+
tar xf "${tmp_dir}/dapm.tar" -C "${4}"
85+
}
86+
87+
# Parse a widget file and generate graph description in graphviz dot format
88+
#
89+
# Skips any file named "bias_level".
90+
#
91+
# $1 = temporary work dir
92+
# $2 = component name
93+
# $3 = widget filename
94+
process_dapm_widget()
95+
{
96+
local tmp_dir="${1}"
97+
local c_name="${2}"
98+
local w_file="${3}"
99+
local dot_file="${tmp_dir}/main.dot"
100+
local links_file="${tmp_dir}/links.dot"
101+
102+
local w_name="$(basename "${w_file}")"
103+
local w_tag="${c_name}_${w_name}"
104+
105+
if [ "${w_name}" = "bias_level" ]; then
106+
return 0
107+
fi
108+
109+
dbg_echo " + Widget: ${w_name}"
110+
111+
cat "${w_file}" | (
112+
read line
113+
114+
if echo "${line}" | grep -q ': On '
115+
then local node_style="${STYLE_NODE_ON}"
116+
else local node_style="${STYLE_NODE_OFF}"
117+
fi
118+
119+
local w_type=""
120+
while read line; do
121+
# Collect widget type if present
122+
if echo "${line}" | grep -q '^widget-type '; then
123+
local w_type_raw="$(echo "$line" | cut -d ' ' -f 2)"
124+
dbg_echo " - Widget type: ${w_type_raw}"
125+
126+
# Note: escaping '\n' is tricky to get working with both
127+
# bash and busybox ash, so use a '%' here and replace it
128+
# later
129+
local w_type="%n[${w_type_raw}]"
130+
fi
131+
132+
# Collect any links. We could use "in" links or "out" links,
133+
# let's use "in" links
134+
if echo "${line}" | grep -q '^in '; then
135+
local w_src=$(echo "$line" |
136+
awk -F\" '{print $6 "_" $4}' |
137+
sed 's/^(null)_/ROOT_/')
138+
dbg_echo " - Input route from: ${w_src}"
139+
echo " \"${w_src}\" -> \"$w_tag\"" >> "${links_file}"
140+
fi
141+
done
142+
143+
echo " \"${w_tag}\" [label=\"${w_name}${w_type}\",${node_style}]" |
144+
tr '%' '\\' >> "${dot_file}"
145+
)
146+
}
147+
148+
# Parse the DAPM tree for a sound card component and generate graph
149+
# description in graphviz dot format
150+
#
151+
# $1 = temporary work dir
152+
# $2 = component directory
153+
# $3 = forced component name (extracted for path if empty)
154+
process_dapm_component()
155+
{
156+
local tmp_dir="${1}"
157+
local c_dir="${2}"
158+
local c_name="${3}"
159+
local dot_file="${tmp_dir}/main.dot"
160+
local links_file="${tmp_dir}/links.dot"
161+
162+
if [ -z "${c_name}" ]; then
163+
# Extract directory name into component name:
164+
# "./cs42l51.0-004a/dapm" -> "cs42l51.0-004a"
165+
c_name="$(basename $(dirname "${c_dir}"))"
166+
fi
167+
168+
dbg_echo " * Component: ${c_name}"
169+
170+
echo "" >> "${dot_file}"
171+
echo " subgraph \"${c_name}\" {" >> "${dot_file}"
172+
echo " cluster = true" >> "${dot_file}"
173+
echo " label = \"${c_name}\"" >> "${dot_file}"
174+
echo " color=dodgerblue" >> "${dot_file}"
175+
176+
# Create empty file to ensure it will exist in all cases
177+
>"${links_file}"
178+
179+
# Iterate over widgets in the component dir
180+
for w_file in ${c_dir}/*; do
181+
process_dapm_widget "${tmp_dir}" "${c_name}" "${w_file}"
182+
done
183+
184+
echo " }" >> "${dot_file}"
185+
186+
cat "${links_file}" >> "${dot_file}"
187+
}
188+
189+
# Parse the DAPM tree for a sound card and generate graph description in
190+
# graphviz dot format
191+
#
192+
# $1 = temporary work dir
193+
# $2 = directory tree with DAPM state (either in debugfs or a mirror)
194+
process_dapm_tree()
195+
{
196+
local tmp_dir="${1}"
197+
local dapm_dir="${2}"
198+
local dot_file="${tmp_dir}/main.dot"
199+
200+
echo "digraph G {" > "${dot_file}"
201+
echo " fontname=\"sans-serif\"" >> "${dot_file}"
202+
echo " node [fontname=\"sans-serif\"]" >> "${dot_file}"
203+
204+
205+
# Process root directory (no component)
206+
process_dapm_component "${tmp_dir}" "${dapm_dir}/dapm" "ROOT"
207+
208+
# Iterate over components
209+
for c_dir in "${dapm_dir}"/*/dapm
210+
do
211+
process_dapm_component "${tmp_dir}" "${c_dir}" ""
212+
done
213+
214+
echo "}" >> "${dot_file}"
215+
}
216+
217+
main()
218+
{
219+
# Parse command line
220+
local out_file="dapm.dot"
221+
local card_name=""
222+
local remote_target=""
223+
local dapm_tree=""
224+
local dbg_on=""
225+
while getopts "c:r:d:o:Dh" arg; do
226+
case $arg in
227+
c) card_name="${OPTARG}" ;;
228+
r) remote_target="${OPTARG}" ;;
229+
d) dapm_tree="${OPTARG}" ;;
230+
o) out_file="${OPTARG}" ;;
231+
D) dbg_on="1" ;;
232+
h) usage 0 ;;
233+
*) usage 1 ;;
234+
esac
235+
done
236+
shift $(($OPTIND - 1))
237+
238+
if [ -n "${dapm_tree}" ]; then
239+
if [ -n "${card_name}${remote_target}" ]; then
240+
usage 1 "Cannot use -c and -r with -d"
241+
fi
242+
echo "Using local tree: ${dapm_tree}"
243+
elif [ -n "${remote_target}" ]; then
244+
if [ -z "${card_name}" ]; then
245+
usage 1 "-r requires -c"
246+
fi
247+
echo "Using card ${card_name} from remote target ${remote_target}"
248+
elif [ -n "${card_name}" ]; then
249+
echo "Using local card: ${card_name}"
250+
else
251+
usage 1 "Please choose mode using -c, -r or -d"
252+
fi
253+
254+
# Define logging function
255+
if [ "${dbg_on}" ]; then
256+
dbg_echo() {
257+
echo "$*" >&2
258+
}
259+
else
260+
dbg_echo() {
261+
:
262+
}
263+
fi
264+
265+
# Filename must have a dot in order the infer the format from the
266+
# extension
267+
if ! echo "${out_file}" | grep -qE '\.'; then
268+
echo "Missing extension in output filename ${out_file}" >&2
269+
usage
270+
exit 1
271+
fi
272+
273+
local out_fmt="${out_file##*.}"
274+
local dot_file="${out_file%.*}.dot"
275+
276+
dbg_echo "dot file: $dot_file"
277+
dbg_echo "Output file: $out_file"
278+
dbg_echo "Output format: $out_fmt"
279+
280+
tmp_dir="$(mktemp -d /tmp/$(basename $0).XXXXXX)"
281+
trap "{ rm -fr ${tmp_dir}; }" INT TERM EXIT
282+
283+
if [ -z "${dapm_tree}" ]
284+
then
285+
dapm_tree="/sys/kernel/debug/asoc/${card_name}"
286+
fi
287+
if [ -n "${remote_target}" ]; then
288+
dapm_tree="${tmp_dir}/dapm-tree"
289+
grab_remote_files "${remote_target}" "${card_name}" "${tmp_dir}" "${dapm_tree}"
290+
fi
291+
# In all cases now ${dapm_tree} contains the DAPM state
292+
293+
process_dapm_tree "${tmp_dir}" "${dapm_tree}"
294+
cp "${tmp_dir}/main.dot" "${dot_file}"
295+
296+
if [ "${out_file}" != "${dot_file}" ]; then
297+
dot -T"${out_fmt}" "${dot_file}" -o "${out_file}"
298+
fi
299+
300+
echo "Generated file ${out_file}"
301+
}
302+
303+
main "${@}"

0 commit comments

Comments
 (0)