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>
This commit is contained in:
parent
5b1047dcf8
commit
e7bb43898b
|
@ -20669,6 +20669,12 @@ F: include/trace/events/sof*.h
|
|||
F: include/uapi/sound/asoc.h
|
||||
F: sound/soc/
|
||||
|
||||
SOUND - SOC LAYER / dapm-graph
|
||||
M: Luca Ceresoli <luca.ceresoli@bootlin.com>
|
||||
L: linux-sound@vger.kernel.org
|
||||
S: Maintained
|
||||
F: tools/sound/dapm-graph
|
||||
|
||||
SOUND - SOUND OPEN FIRMWARE (SOF) DRIVERS
|
||||
M: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
|
||||
M: Liam Girdwood <lgirdwood@gmail.com>
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
#!/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_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_src=$(echo "$line" |
|
||||
awk -F\" '{print $6 "_" $4}' |
|
||||
sed 's/^(null)_/ROOT_/')
|
||||
dbg_echo " - Input route from: ${w_src}"
|
||||
echo " \"${w_src}\" -> \"$w_tag\"" >> "${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 = forced component name (extracted for path if empty)
|
||||
process_dapm_component()
|
||||
{
|
||||
local tmp_dir="${1}"
|
||||
local c_dir="${2}"
|
||||
local c_name="${3}"
|
||||
local dot_file="${tmp_dir}/main.dot"
|
||||
local links_file="${tmp_dir}/links.dot"
|
||||
|
||||
if [ -z "${c_name}" ]; then
|
||||
# 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}"
|
||||
|
||||
echo "" >> "${dot_file}"
|
||||
echo " subgraph \"${c_name}\" {" >> "${dot_file}"
|
||||
echo " cluster = true" >> "${dot_file}"
|
||||
echo " label = \"${c_name}\"" >> "${dot_file}"
|
||||
echo " color=dodgerblue" >> "${dot_file}"
|
||||
|
||||
# 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
|
||||
|
||||
echo " }" >> "${dot_file}"
|
||||
|
||||
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}"
|
||||
|
||||
|
||||
# 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 "${@}"
|
Loading…
Reference in New Issue