mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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>
		
			
				
	
	
		
			303 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Bash
		
	
	
		
			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_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 "${@}"
 |