330 lines · plain
1#!/bin/sh2# SPDX-License-Identifier: GPL-2.03#4# Generate a graph of the current DAPM state for an audio card5#6# Copyright 2024 Bootlin7# Author: Luca Ceresoli <luca.ceresol@bootlin.com>8 9set -eu10 11STYLE_COMPONENT_ON="color=dodgerblue;style=bold"12STYLE_COMPONENT_OFF="color=gray40;style=filled;fillcolor=gray90"13STYLE_NODE_ON="shape=box,style=bold,color=green4"14STYLE_NODE_OFF="shape=box,style=filled,color=gray30,fillcolor=gray95"15 16# Print usage and exit17#18# $1 = exit return value19# $2 = error string (required if $1 != 0)20usage()21{22 if [ "${1}" -ne 0 ]; then23 echo "${2}" >&224 fi25 26 echo "27Generate a graph of the current DAPM state for an audio card.28 29The DAPM state can be obtained via debugfs for a card on the local host or30a remote target, or from a local copy of the debugfs tree for the card.31 32Usage:33 $(basename $0) [options] -c CARD - Local sound card34 $(basename $0) [options] -c CARD -r REMOTE_TARGET - Card on remote system35 $(basename $0) [options] -d STATE_DIR - Local directory36 37Options:38 -c CARD Sound card to get DAPM state of39 -r REMOTE_TARGET Get DAPM state from REMOTE_TARGET via SSH and SCP40 instead of using a local sound card41 -d STATE_DIR Get DAPM state from a local copy of a debugfs tree42 -o OUT_FILE Output file (default: dapm.dot)43 -D Show verbose debugging info44 -h Print this help and exit45 46The output format is implied by the extension of OUT_FILE:47 48 * Use the .dot extension to generate a text graph representation in49 graphviz dot syntax.50 * Any other extension is assumed to be a format supported by graphviz for51 rendering, e.g. 'png', 'svg', and will produce both the .dot file and a52 picture from it. This requires the 'dot' program from the graphviz53 package.54"55 56 exit ${1}57}58 59# Connect to a remote target via SSH, collect all DAPM files from debufs60# into a tarball and get the tarball via SCP into $3/dapm.tar61#62# $1 = target as used by ssh and scp, e.g. "root@192.168.1.1"63# $2 = sound card name64# $3 = temp dir path (present on the host, created on the target)65# $4 = local directory to extract the tarball into66#67# Requires an ssh+scp server, find and tar+gz on the target68#69# Note: the tarball is needed because plain 'scp -r' from debugfs would70# copy only empty files71grab_remote_files()72{73 echo "Collecting DAPM state from ${1}"74 dbg_echo "Collected DAPM state in ${3}"75 76 ssh "${1}" "77set -eu &&78cd \"/sys/kernel/debug/asoc/${2}\" &&79find * -type d -exec mkdir -p ${3}/dapm-tree/{} \; &&80find * -type f -exec cp \"{}\" \"${3}/dapm-tree/{}\" \; &&81cd ${3}/dapm-tree &&82tar cf ${3}/dapm.tar ."83 scp -q "${1}:${3}/dapm.tar" "${3}"84 85 mkdir -p "${4}"86 tar xf "${tmp_dir}/dapm.tar" -C "${4}"87}88 89# Parse a widget file and generate graph description in graphviz dot format90#91# Skips any file named "bias_level".92#93# $1 = temporary work dir94# $2 = component name95# $3 = widget filename96process_dapm_widget()97{98 local tmp_dir="${1}"99 local c_name="${2}"100 local w_file="${3}"101 local dot_file="${tmp_dir}/main.dot"102 local links_file="${tmp_dir}/links.dot"103 104 local w_name="$(basename "${w_file}")"105 local w_tag="${c_name}_${w_name}"106 107 if [ "${w_name}" = "bias_level" ]; then108 return 0109 fi110 111 dbg_echo " + Widget: ${w_name}"112 113 cat "${w_file}" | (114 read line115 116 if echo "${line}" | grep -q ': On '117 then local node_style="${STYLE_NODE_ON}"118 else local node_style="${STYLE_NODE_OFF}"119 fi120 121 local w_type=""122 while read line; do123 # Collect widget type if present124 if echo "${line}" | grep -q '^widget-type '; then125 local w_type_raw="$(echo "$line" | cut -d ' ' -f 2)"126 dbg_echo " - Widget type: ${w_type_raw}"127 128 # Note: escaping '\n' is tricky to get working with both129 # bash and busybox ash, so use a '%' here and replace it130 # later131 local w_type="%n[${w_type_raw}]"132 fi133 134 # Collect any links. We could use "in" links or "out" links,135 # let's use "in" links136 if echo "${line}" | grep -q '^in '; then137 local w_route=$(echo "$line" | awk -F\" '{print $2}')138 local w_src=$(echo "$line" |139 awk -F\" '{print $6 "_" $4}' |140 sed 's/^(null)_/ROOT_/')141 dbg_echo " - Input route from: ${w_src}"142 dbg_echo " - Route: ${w_route}"143 local w_edge_attrs=""144 if [ "${w_route}" != "static" ]; then145 w_edge_attrs=" [label=\"${w_route}\"]"146 fi147 echo " \"${w_src}\" -> \"$w_tag\"${w_edge_attrs}" >> "${links_file}"148 fi149 done150 151 echo " \"${w_tag}\" [label=\"${w_name}${w_type}\",${node_style}]" |152 tr '%' '\\' >> "${dot_file}"153 )154}155 156# Parse the DAPM tree for a sound card component and generate graph157# description in graphviz dot format158#159# $1 = temporary work dir160# $2 = component directory161# $3 = "ROOT" for the root card directory, empty otherwise162process_dapm_component()163{164 local tmp_dir="${1}"165 local c_dir="${2}"166 local c_name="${3}"167 local is_component=0168 local dot_file="${tmp_dir}/main.dot"169 local links_file="${tmp_dir}/links.dot"170 local c_attribs=""171 172 if [ -z "${c_name}" ]; then173 is_component=1174 175 # Extract directory name into component name:176 # "./cs42l51.0-004a/dapm" -> "cs42l51.0-004a"177 c_name="$(basename $(dirname "${c_dir}"))"178 fi179 180 dbg_echo " * Component: ${c_name}"181 182 if [ ${is_component} = 1 ]; then183 if [ -f "${c_dir}/bias_level" ]; then184 c_onoff=$(sed -n -e 1p "${c_dir}/bias_level" | awk '{print $1}')185 dbg_echo " - bias_level: ${c_onoff}"186 if [ "$c_onoff" = "On" ]; then187 c_attribs="${STYLE_COMPONENT_ON}"188 elif [ "$c_onoff" = "Off" ]; then189 c_attribs="${STYLE_COMPONENT_OFF}"190 fi191 fi192 193 echo "" >> "${dot_file}"194 echo " subgraph \"${c_name}\" {" >> "${dot_file}"195 echo " cluster = true" >> "${dot_file}"196 echo " label = \"${c_name}\"" >> "${dot_file}"197 echo " ${c_attribs}" >> "${dot_file}"198 fi199 200 # Create empty file to ensure it will exist in all cases201 >"${links_file}"202 203 # Iterate over widgets in the component dir204 for w_file in ${c_dir}/*; do205 process_dapm_widget "${tmp_dir}" "${c_name}" "${w_file}"206 done207 208 if [ ${is_component} = 1 ]; then209 echo " }" >> "${dot_file}"210 fi211 212 cat "${links_file}" >> "${dot_file}"213}214 215# Parse the DAPM tree for a sound card and generate graph description in216# graphviz dot format217#218# $1 = temporary work dir219# $2 = directory tree with DAPM state (either in debugfs or a mirror)220process_dapm_tree()221{222 local tmp_dir="${1}"223 local dapm_dir="${2}"224 local dot_file="${tmp_dir}/main.dot"225 226 echo "digraph G {" > "${dot_file}"227 echo " fontname=\"sans-serif\"" >> "${dot_file}"228 echo " node [fontname=\"sans-serif\"]" >> "${dot_file}"229 echo " edge [fontname=\"sans-serif\"]" >> "${dot_file}"230 231 # Process root directory (no component)232 process_dapm_component "${tmp_dir}" "${dapm_dir}/dapm" "ROOT"233 234 # Iterate over components235 for c_dir in "${dapm_dir}"/*/dapm236 do237 process_dapm_component "${tmp_dir}" "${c_dir}" ""238 done239 240 echo "}" >> "${dot_file}"241}242 243main()244{245 # Parse command line246 local out_file="dapm.dot"247 local card_name=""248 local remote_target=""249 local dapm_tree=""250 local dbg_on=""251 while getopts "c:r:d:o:Dh" arg; do252 case $arg in253 c) card_name="${OPTARG}" ;;254 r) remote_target="${OPTARG}" ;;255 d) dapm_tree="${OPTARG}" ;;256 o) out_file="${OPTARG}" ;;257 D) dbg_on="1" ;;258 h) usage 0 ;;259 *) usage 1 ;;260 esac261 done262 shift $(($OPTIND - 1))263 264 if [ -n "${dapm_tree}" ]; then265 if [ -n "${card_name}${remote_target}" ]; then266 usage 1 "Cannot use -c and -r with -d"267 fi268 echo "Using local tree: ${dapm_tree}"269 elif [ -n "${remote_target}" ]; then270 if [ -z "${card_name}" ]; then271 usage 1 "-r requires -c"272 fi273 echo "Using card ${card_name} from remote target ${remote_target}"274 elif [ -n "${card_name}" ]; then275 echo "Using local card: ${card_name}"276 else277 usage 1 "Please choose mode using -c, -r or -d"278 fi279 280 # Define logging function281 if [ "${dbg_on}" ]; then282 dbg_echo() {283 echo "$*" >&2284 }285 else286 dbg_echo() {287 :288 }289 fi290 291 # Filename must have a dot in order the infer the format from the292 # extension293 if ! echo "${out_file}" | grep -qE '\.'; then294 echo "Missing extension in output filename ${out_file}" >&2295 usage296 exit 1297 fi298 299 local out_fmt="${out_file##*.}"300 local dot_file="${out_file%.*}.dot"301 302 dbg_echo "dot file: $dot_file"303 dbg_echo "Output file: $out_file"304 dbg_echo "Output format: $out_fmt"305 306 tmp_dir="$(mktemp -d /tmp/$(basename $0).XXXXXX)"307 trap "{ rm -fr ${tmp_dir}; }" INT TERM EXIT308 309 if [ -z "${dapm_tree}" ]310 then311 dapm_tree="/sys/kernel/debug/asoc/${card_name}"312 fi313 if [ -n "${remote_target}" ]; then314 dapm_tree="${tmp_dir}/dapm-tree"315 grab_remote_files "${remote_target}" "${card_name}" "${tmp_dir}" "${dapm_tree}"316 fi317 # In all cases now ${dapm_tree} contains the DAPM state318 319 process_dapm_tree "${tmp_dir}" "${dapm_tree}"320 cp "${tmp_dir}/main.dot" "${dot_file}"321 322 if [ "${out_file}" != "${dot_file}" ]; then323 dot -T"${out_fmt}" "${dot_file}" -o "${out_file}"324 fi325 326 echo "Generated file ${out_file}"327}328 329main "${@}"330