brintos

brintos / linux-shallow public Read only

0
0
Text · 9.1 KiB · f14bdfe Raw
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