Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | #!/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 "${@}" |