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