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