linux-yocto/tools/sound/dapm-graph
Luca Ceresoli a14b278a47
ASoC: dapm-graph: show path name for non-static routes
Many routes are just static, not modifiable at runtime. Show the route name
for all the other routes as an edge label in the generated graph.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Link: https://patch.msgid.link/20240823-dapm-graph-v1-3-989a47308c4c@bootlin.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2024-08-23 11:03:00 +01:00

9.1 KiB
Executable File

#!/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:dDh" 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 "${@}"