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