linux-yocto/tools/sound/dapm-graph
Nicolas Frattaroli d31babd7e3
ASoC: dapm-graph: set fill colour of turned on nodes
Some tools like KGraphViewer interpret the "ON" nodes not having an
explicitly set fill colour as them being entirely black, which obscures
the text on them and looks funny. In fact, I thought they were off for
the longest time. Comparing to the output of the `dot` tool, I assume
they are supposed to be white.

Instead of speclawyering over who's in the wrong and must immediately
atone for their wickedness at the altar of RFC2119, just be explicit
about it, set the fillcolor to white, and nobody gets confused.

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Tested-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Link: https://patch.msgid.link/20250221-dapm-graph-node-colour-v1-1-514ed0aa7069@collabora.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2025-02-24 18:35:54 +00: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,fillcolor=white" 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 "${@}"