1e7bb4389SLuca Ceresoli#!/bin/sh 2e7bb4389SLuca Ceresoli# SPDX-License-Identifier: GPL-2.0 3e7bb4389SLuca Ceresoli# 4e7bb4389SLuca Ceresoli# Generate a graph of the current DAPM state for an audio card 5e7bb4389SLuca Ceresoli# 6e7bb4389SLuca Ceresoli# Copyright 2024 Bootlin 7e7bb4389SLuca Ceresoli# Author: Luca Ceresoli <luca.ceresol@bootlin.com> 8e7bb4389SLuca Ceresoli 9e7bb4389SLuca Ceresoliset -eu 10e7bb4389SLuca Ceresoli 1164a1e3ddSLuca CeresoliSTYLE_COMPONENT_ON="color=dodgerblue;style=bold" 1264a1e3ddSLuca CeresoliSTYLE_COMPONENT_OFF="color=gray40;style=filled;fillcolor=gray90" 13*d31babd7SNicolas FrattaroliSTYLE_NODE_ON="shape=box,style=bold,color=green4,fillcolor=white" 14e7bb4389SLuca CeresoliSTYLE_NODE_OFF="shape=box,style=filled,color=gray30,fillcolor=gray95" 15e7bb4389SLuca Ceresoli 16e7bb4389SLuca Ceresoli# Print usage and exit 17e7bb4389SLuca Ceresoli# 18e7bb4389SLuca Ceresoli# $1 = exit return value 19e7bb4389SLuca Ceresoli# $2 = error string (required if $1 != 0) 20e7bb4389SLuca Ceresoliusage() 21e7bb4389SLuca Ceresoli{ 22e7bb4389SLuca Ceresoli if [ "${1}" -ne 0 ]; then 23e7bb4389SLuca Ceresoli echo "${2}" >&2 24e7bb4389SLuca Ceresoli fi 25e7bb4389SLuca Ceresoli 26e7bb4389SLuca Ceresoli echo " 27e7bb4389SLuca CeresoliGenerate a graph of the current DAPM state for an audio card. 28e7bb4389SLuca Ceresoli 29e7bb4389SLuca CeresoliThe DAPM state can be obtained via debugfs for a card on the local host or 30e7bb4389SLuca Ceresolia remote target, or from a local copy of the debugfs tree for the card. 31e7bb4389SLuca Ceresoli 32e7bb4389SLuca CeresoliUsage: 33e7bb4389SLuca Ceresoli $(basename $0) [options] -c CARD - Local sound card 34e7bb4389SLuca Ceresoli $(basename $0) [options] -c CARD -r REMOTE_TARGET - Card on remote system 35e7bb4389SLuca Ceresoli $(basename $0) [options] -d STATE_DIR - Local directory 36e7bb4389SLuca Ceresoli 37e7bb4389SLuca CeresoliOptions: 38e7bb4389SLuca Ceresoli -c CARD Sound card to get DAPM state of 39e7bb4389SLuca Ceresoli -r REMOTE_TARGET Get DAPM state from REMOTE_TARGET via SSH and SCP 40e7bb4389SLuca Ceresoli instead of using a local sound card 41e7bb4389SLuca Ceresoli -d STATE_DIR Get DAPM state from a local copy of a debugfs tree 42e7bb4389SLuca Ceresoli -o OUT_FILE Output file (default: dapm.dot) 43e7bb4389SLuca Ceresoli -D Show verbose debugging info 44e7bb4389SLuca Ceresoli -h Print this help and exit 45e7bb4389SLuca Ceresoli 46e7bb4389SLuca CeresoliThe output format is implied by the extension of OUT_FILE: 47e7bb4389SLuca Ceresoli 48e7bb4389SLuca Ceresoli * Use the .dot extension to generate a text graph representation in 49e7bb4389SLuca Ceresoli graphviz dot syntax. 50e7bb4389SLuca Ceresoli * Any other extension is assumed to be a format supported by graphviz for 51e7bb4389SLuca Ceresoli rendering, e.g. 'png', 'svg', and will produce both the .dot file and a 52e7bb4389SLuca Ceresoli picture from it. This requires the 'dot' program from the graphviz 53e7bb4389SLuca Ceresoli package. 54e7bb4389SLuca Ceresoli" 55e7bb4389SLuca Ceresoli 56e7bb4389SLuca Ceresoli exit ${1} 57e7bb4389SLuca Ceresoli} 58e7bb4389SLuca Ceresoli 59e7bb4389SLuca Ceresoli# Connect to a remote target via SSH, collect all DAPM files from debufs 60e7bb4389SLuca Ceresoli# into a tarball and get the tarball via SCP into $3/dapm.tar 61e7bb4389SLuca Ceresoli# 62e7bb4389SLuca Ceresoli# $1 = target as used by ssh and scp, e.g. "root@192.168.1.1" 63e7bb4389SLuca Ceresoli# $2 = sound card name 64e7bb4389SLuca Ceresoli# $3 = temp dir path (present on the host, created on the target) 65e7bb4389SLuca Ceresoli# $4 = local directory to extract the tarball into 66e7bb4389SLuca Ceresoli# 67e7bb4389SLuca Ceresoli# Requires an ssh+scp server, find and tar+gz on the target 68e7bb4389SLuca Ceresoli# 69e7bb4389SLuca Ceresoli# Note: the tarball is needed because plain 'scp -r' from debugfs would 70e7bb4389SLuca Ceresoli# copy only empty files 71e7bb4389SLuca Ceresoligrab_remote_files() 72e7bb4389SLuca Ceresoli{ 73e7bb4389SLuca Ceresoli echo "Collecting DAPM state from ${1}" 74e7bb4389SLuca Ceresoli dbg_echo "Collected DAPM state in ${3}" 75e7bb4389SLuca Ceresoli 76e7bb4389SLuca Ceresoli ssh "${1}" " 77e7bb4389SLuca Ceresoliset -eu && 78e7bb4389SLuca Ceresolicd \"/sys/kernel/debug/asoc/${2}\" && 79e7bb4389SLuca Ceresolifind * -type d -exec mkdir -p ${3}/dapm-tree/{} \; && 80e7bb4389SLuca Ceresolifind * -type f -exec cp \"{}\" \"${3}/dapm-tree/{}\" \; && 81e7bb4389SLuca Ceresolicd ${3}/dapm-tree && 82e7bb4389SLuca Ceresolitar cf ${3}/dapm.tar ." 83e7bb4389SLuca Ceresoli scp -q "${1}:${3}/dapm.tar" "${3}" 84e7bb4389SLuca Ceresoli 85e7bb4389SLuca Ceresoli mkdir -p "${4}" 86e7bb4389SLuca Ceresoli tar xf "${tmp_dir}/dapm.tar" -C "${4}" 87e7bb4389SLuca Ceresoli} 88e7bb4389SLuca Ceresoli 89e7bb4389SLuca Ceresoli# Parse a widget file and generate graph description in graphviz dot format 90e7bb4389SLuca Ceresoli# 91e7bb4389SLuca Ceresoli# Skips any file named "bias_level". 92e7bb4389SLuca Ceresoli# 93e7bb4389SLuca Ceresoli# $1 = temporary work dir 94e7bb4389SLuca Ceresoli# $2 = component name 95e7bb4389SLuca Ceresoli# $3 = widget filename 96e7bb4389SLuca Ceresoliprocess_dapm_widget() 97e7bb4389SLuca Ceresoli{ 98e7bb4389SLuca Ceresoli local tmp_dir="${1}" 99e7bb4389SLuca Ceresoli local c_name="${2}" 100e7bb4389SLuca Ceresoli local w_file="${3}" 101e7bb4389SLuca Ceresoli local dot_file="${tmp_dir}/main.dot" 102e7bb4389SLuca Ceresoli local links_file="${tmp_dir}/links.dot" 103e7bb4389SLuca Ceresoli 104e7bb4389SLuca Ceresoli local w_name="$(basename "${w_file}")" 105e7bb4389SLuca Ceresoli local w_tag="${c_name}_${w_name}" 106e7bb4389SLuca Ceresoli 107e7bb4389SLuca Ceresoli if [ "${w_name}" = "bias_level" ]; then 108e7bb4389SLuca Ceresoli return 0 109e7bb4389SLuca Ceresoli fi 110e7bb4389SLuca Ceresoli 111e7bb4389SLuca Ceresoli dbg_echo " + Widget: ${w_name}" 112e7bb4389SLuca Ceresoli 113e7bb4389SLuca Ceresoli cat "${w_file}" | ( 114e7bb4389SLuca Ceresoli read line 115e7bb4389SLuca Ceresoli 116e7bb4389SLuca Ceresoli if echo "${line}" | grep -q ': On ' 117e7bb4389SLuca Ceresoli then local node_style="${STYLE_NODE_ON}" 118e7bb4389SLuca Ceresoli else local node_style="${STYLE_NODE_OFF}" 119e7bb4389SLuca Ceresoli fi 120e7bb4389SLuca Ceresoli 121e7bb4389SLuca Ceresoli local w_type="" 122e7bb4389SLuca Ceresoli while read line; do 123e7bb4389SLuca Ceresoli # Collect widget type if present 124e7bb4389SLuca Ceresoli if echo "${line}" | grep -q '^widget-type '; then 125e7bb4389SLuca Ceresoli local w_type_raw="$(echo "$line" | cut -d ' ' -f 2)" 126e7bb4389SLuca Ceresoli dbg_echo " - Widget type: ${w_type_raw}" 127e7bb4389SLuca Ceresoli 128e7bb4389SLuca Ceresoli # Note: escaping '\n' is tricky to get working with both 129e7bb4389SLuca Ceresoli # bash and busybox ash, so use a '%' here and replace it 130e7bb4389SLuca Ceresoli # later 131e7bb4389SLuca Ceresoli local w_type="%n[${w_type_raw}]" 132e7bb4389SLuca Ceresoli fi 133e7bb4389SLuca Ceresoli 134e7bb4389SLuca Ceresoli # Collect any links. We could use "in" links or "out" links, 135e7bb4389SLuca Ceresoli # let's use "in" links 136e7bb4389SLuca Ceresoli if echo "${line}" | grep -q '^in '; then 137a14b278aSLuca Ceresoli local w_route=$(echo "$line" | awk -F\" '{print $2}') 138e7bb4389SLuca Ceresoli local w_src=$(echo "$line" | 139e7bb4389SLuca Ceresoli awk -F\" '{print $6 "_" $4}' | 140e7bb4389SLuca Ceresoli sed 's/^(null)_/ROOT_/') 141e7bb4389SLuca Ceresoli dbg_echo " - Input route from: ${w_src}" 142a14b278aSLuca Ceresoli dbg_echo " - Route: ${w_route}" 143a14b278aSLuca Ceresoli local w_edge_attrs="" 144a14b278aSLuca Ceresoli if [ "${w_route}" != "static" ]; then 145a14b278aSLuca Ceresoli w_edge_attrs=" [label=\"${w_route}\"]" 146a14b278aSLuca Ceresoli fi 147a14b278aSLuca Ceresoli echo " \"${w_src}\" -> \"$w_tag\"${w_edge_attrs}" >> "${links_file}" 148e7bb4389SLuca Ceresoli fi 149e7bb4389SLuca Ceresoli done 150e7bb4389SLuca Ceresoli 151e7bb4389SLuca Ceresoli echo " \"${w_tag}\" [label=\"${w_name}${w_type}\",${node_style}]" | 152e7bb4389SLuca Ceresoli tr '%' '\\' >> "${dot_file}" 153e7bb4389SLuca Ceresoli ) 154e7bb4389SLuca Ceresoli} 155e7bb4389SLuca Ceresoli 156e7bb4389SLuca Ceresoli# Parse the DAPM tree for a sound card component and generate graph 157e7bb4389SLuca Ceresoli# description in graphviz dot format 158e7bb4389SLuca Ceresoli# 159e7bb4389SLuca Ceresoli# $1 = temporary work dir 160e7bb4389SLuca Ceresoli# $2 = component directory 1615a98c2e5SLuca Ceresoli# $3 = "ROOT" for the root card directory, empty otherwise 162e7bb4389SLuca Ceresoliprocess_dapm_component() 163e7bb4389SLuca Ceresoli{ 164e7bb4389SLuca Ceresoli local tmp_dir="${1}" 165e7bb4389SLuca Ceresoli local c_dir="${2}" 166e7bb4389SLuca Ceresoli local c_name="${3}" 1675a98c2e5SLuca Ceresoli local is_component=0 168e7bb4389SLuca Ceresoli local dot_file="${tmp_dir}/main.dot" 169e7bb4389SLuca Ceresoli local links_file="${tmp_dir}/links.dot" 17064a1e3ddSLuca Ceresoli local c_attribs="" 171e7bb4389SLuca Ceresoli 172e7bb4389SLuca Ceresoli if [ -z "${c_name}" ]; then 1735a98c2e5SLuca Ceresoli is_component=1 1745a98c2e5SLuca Ceresoli 175e7bb4389SLuca Ceresoli # Extract directory name into component name: 176e7bb4389SLuca Ceresoli # "./cs42l51.0-004a/dapm" -> "cs42l51.0-004a" 177e7bb4389SLuca Ceresoli c_name="$(basename $(dirname "${c_dir}"))" 17864a1e3ddSLuca Ceresoli fi 17964a1e3ddSLuca Ceresoli 18064a1e3ddSLuca Ceresoli dbg_echo " * Component: ${c_name}" 18164a1e3ddSLuca Ceresoli 18264a1e3ddSLuca Ceresoli if [ ${is_component} = 1 ]; then 18364a1e3ddSLuca Ceresoli if [ -f "${c_dir}/bias_level" ]; then 18464a1e3ddSLuca Ceresoli c_onoff=$(sed -n -e 1p "${c_dir}/bias_level" | awk '{print $1}') 18564a1e3ddSLuca Ceresoli dbg_echo " - bias_level: ${c_onoff}" 18664a1e3ddSLuca Ceresoli if [ "$c_onoff" = "On" ]; then 18764a1e3ddSLuca Ceresoli c_attribs="${STYLE_COMPONENT_ON}" 18864a1e3ddSLuca Ceresoli elif [ "$c_onoff" = "Off" ]; then 18964a1e3ddSLuca Ceresoli c_attribs="${STYLE_COMPONENT_OFF}" 19064a1e3ddSLuca Ceresoli fi 19164a1e3ddSLuca Ceresoli fi 192e7bb4389SLuca Ceresoli 193e7bb4389SLuca Ceresoli echo "" >> "${dot_file}" 194e7bb4389SLuca Ceresoli echo " subgraph \"${c_name}\" {" >> "${dot_file}" 195e7bb4389SLuca Ceresoli echo " cluster = true" >> "${dot_file}" 196e7bb4389SLuca Ceresoli echo " label = \"${c_name}\"" >> "${dot_file}" 19764a1e3ddSLuca Ceresoli echo " ${c_attribs}" >> "${dot_file}" 1985a98c2e5SLuca Ceresoli fi 1995a98c2e5SLuca Ceresoli 200e7bb4389SLuca Ceresoli # Create empty file to ensure it will exist in all cases 201e7bb4389SLuca Ceresoli >"${links_file}" 202e7bb4389SLuca Ceresoli 203e7bb4389SLuca Ceresoli # Iterate over widgets in the component dir 204e7bb4389SLuca Ceresoli for w_file in ${c_dir}/*; do 205e7bb4389SLuca Ceresoli process_dapm_widget "${tmp_dir}" "${c_name}" "${w_file}" 206e7bb4389SLuca Ceresoli done 207e7bb4389SLuca Ceresoli 2085a98c2e5SLuca Ceresoli if [ ${is_component} = 1 ]; then 209e7bb4389SLuca Ceresoli echo " }" >> "${dot_file}" 2105a98c2e5SLuca Ceresoli fi 211e7bb4389SLuca Ceresoli 212e7bb4389SLuca Ceresoli cat "${links_file}" >> "${dot_file}" 213e7bb4389SLuca Ceresoli} 214e7bb4389SLuca Ceresoli 215e7bb4389SLuca Ceresoli# Parse the DAPM tree for a sound card and generate graph description in 216e7bb4389SLuca Ceresoli# graphviz dot format 217e7bb4389SLuca Ceresoli# 218e7bb4389SLuca Ceresoli# $1 = temporary work dir 219e7bb4389SLuca Ceresoli# $2 = directory tree with DAPM state (either in debugfs or a mirror) 220e7bb4389SLuca Ceresoliprocess_dapm_tree() 221e7bb4389SLuca Ceresoli{ 222e7bb4389SLuca Ceresoli local tmp_dir="${1}" 223e7bb4389SLuca Ceresoli local dapm_dir="${2}" 224e7bb4389SLuca Ceresoli local dot_file="${tmp_dir}/main.dot" 225e7bb4389SLuca Ceresoli 226e7bb4389SLuca Ceresoli echo "digraph G {" > "${dot_file}" 227e7bb4389SLuca Ceresoli echo " fontname=\"sans-serif\"" >> "${dot_file}" 228e7bb4389SLuca Ceresoli echo " node [fontname=\"sans-serif\"]" >> "${dot_file}" 229a14b278aSLuca Ceresoli echo " edge [fontname=\"sans-serif\"]" >> "${dot_file}" 230e7bb4389SLuca Ceresoli 231e7bb4389SLuca Ceresoli # Process root directory (no component) 232e7bb4389SLuca Ceresoli process_dapm_component "${tmp_dir}" "${dapm_dir}/dapm" "ROOT" 233e7bb4389SLuca Ceresoli 234e7bb4389SLuca Ceresoli # Iterate over components 235e7bb4389SLuca Ceresoli for c_dir in "${dapm_dir}"/*/dapm 236e7bb4389SLuca Ceresoli do 237e7bb4389SLuca Ceresoli process_dapm_component "${tmp_dir}" "${c_dir}" "" 238e7bb4389SLuca Ceresoli done 239e7bb4389SLuca Ceresoli 240e7bb4389SLuca Ceresoli echo "}" >> "${dot_file}" 241e7bb4389SLuca Ceresoli} 242e7bb4389SLuca Ceresoli 243e7bb4389SLuca Ceresolimain() 244e7bb4389SLuca Ceresoli{ 245e7bb4389SLuca Ceresoli # Parse command line 246e7bb4389SLuca Ceresoli local out_file="dapm.dot" 247e7bb4389SLuca Ceresoli local card_name="" 248e7bb4389SLuca Ceresoli local remote_target="" 249e7bb4389SLuca Ceresoli local dapm_tree="" 250e7bb4389SLuca Ceresoli local dbg_on="" 251e7bb4389SLuca Ceresoli while getopts "c:r:d:o:Dh" arg; do 252e7bb4389SLuca Ceresoli case $arg in 253e7bb4389SLuca Ceresoli c) card_name="${OPTARG}" ;; 254e7bb4389SLuca Ceresoli r) remote_target="${OPTARG}" ;; 255e7bb4389SLuca Ceresoli d) dapm_tree="${OPTARG}" ;; 256e7bb4389SLuca Ceresoli o) out_file="${OPTARG}" ;; 257e7bb4389SLuca Ceresoli D) dbg_on="1" ;; 258e7bb4389SLuca Ceresoli h) usage 0 ;; 259e7bb4389SLuca Ceresoli *) usage 1 ;; 260e7bb4389SLuca Ceresoli esac 261e7bb4389SLuca Ceresoli done 262e7bb4389SLuca Ceresoli shift $(($OPTIND - 1)) 263e7bb4389SLuca Ceresoli 264e7bb4389SLuca Ceresoli if [ -n "${dapm_tree}" ]; then 265e7bb4389SLuca Ceresoli if [ -n "${card_name}${remote_target}" ]; then 266e7bb4389SLuca Ceresoli usage 1 "Cannot use -c and -r with -d" 267e7bb4389SLuca Ceresoli fi 268e7bb4389SLuca Ceresoli echo "Using local tree: ${dapm_tree}" 269e7bb4389SLuca Ceresoli elif [ -n "${remote_target}" ]; then 270e7bb4389SLuca Ceresoli if [ -z "${card_name}" ]; then 271e7bb4389SLuca Ceresoli usage 1 "-r requires -c" 272e7bb4389SLuca Ceresoli fi 273e7bb4389SLuca Ceresoli echo "Using card ${card_name} from remote target ${remote_target}" 274e7bb4389SLuca Ceresoli elif [ -n "${card_name}" ]; then 275e7bb4389SLuca Ceresoli echo "Using local card: ${card_name}" 276e7bb4389SLuca Ceresoli else 277e7bb4389SLuca Ceresoli usage 1 "Please choose mode using -c, -r or -d" 278e7bb4389SLuca Ceresoli fi 279e7bb4389SLuca Ceresoli 280e7bb4389SLuca Ceresoli # Define logging function 281e7bb4389SLuca Ceresoli if [ "${dbg_on}" ]; then 282e7bb4389SLuca Ceresoli dbg_echo() { 283e7bb4389SLuca Ceresoli echo "$*" >&2 284e7bb4389SLuca Ceresoli } 285e7bb4389SLuca Ceresoli else 286e7bb4389SLuca Ceresoli dbg_echo() { 287e7bb4389SLuca Ceresoli : 288e7bb4389SLuca Ceresoli } 289e7bb4389SLuca Ceresoli fi 290e7bb4389SLuca Ceresoli 291e7bb4389SLuca Ceresoli # Filename must have a dot in order the infer the format from the 292e7bb4389SLuca Ceresoli # extension 293e7bb4389SLuca Ceresoli if ! echo "${out_file}" | grep -qE '\.'; then 294e7bb4389SLuca Ceresoli echo "Missing extension in output filename ${out_file}" >&2 295e7bb4389SLuca Ceresoli usage 296e7bb4389SLuca Ceresoli exit 1 297e7bb4389SLuca Ceresoli fi 298e7bb4389SLuca Ceresoli 299e7bb4389SLuca Ceresoli local out_fmt="${out_file##*.}" 300e7bb4389SLuca Ceresoli local dot_file="${out_file%.*}.dot" 301e7bb4389SLuca Ceresoli 302e7bb4389SLuca Ceresoli dbg_echo "dot file: $dot_file" 303e7bb4389SLuca Ceresoli dbg_echo "Output file: $out_file" 304e7bb4389SLuca Ceresoli dbg_echo "Output format: $out_fmt" 305e7bb4389SLuca Ceresoli 306e7bb4389SLuca Ceresoli tmp_dir="$(mktemp -d /tmp/$(basename $0).XXXXXX)" 307e7bb4389SLuca Ceresoli trap "{ rm -fr ${tmp_dir}; }" INT TERM EXIT 308e7bb4389SLuca Ceresoli 309e7bb4389SLuca Ceresoli if [ -z "${dapm_tree}" ] 310e7bb4389SLuca Ceresoli then 311e7bb4389SLuca Ceresoli dapm_tree="/sys/kernel/debug/asoc/${card_name}" 312e7bb4389SLuca Ceresoli fi 313e7bb4389SLuca Ceresoli if [ -n "${remote_target}" ]; then 314e7bb4389SLuca Ceresoli dapm_tree="${tmp_dir}/dapm-tree" 315e7bb4389SLuca Ceresoli grab_remote_files "${remote_target}" "${card_name}" "${tmp_dir}" "${dapm_tree}" 316e7bb4389SLuca Ceresoli fi 317e7bb4389SLuca Ceresoli # In all cases now ${dapm_tree} contains the DAPM state 318e7bb4389SLuca Ceresoli 319e7bb4389SLuca Ceresoli process_dapm_tree "${tmp_dir}" "${dapm_tree}" 320e7bb4389SLuca Ceresoli cp "${tmp_dir}/main.dot" "${dot_file}" 321e7bb4389SLuca Ceresoli 322e7bb4389SLuca Ceresoli if [ "${out_file}" != "${dot_file}" ]; then 323e7bb4389SLuca Ceresoli dot -T"${out_fmt}" "${dot_file}" -o "${out_file}" 324e7bb4389SLuca Ceresoli fi 325e7bb4389SLuca Ceresoli 326e7bb4389SLuca Ceresoli echo "Generated file ${out_file}" 327e7bb4389SLuca Ceresoli} 328e7bb4389SLuca Ceresoli 329e7bb4389SLuca Ceresolimain "${@}" 330