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