[PATCH] ASoC: dapm: Export new 'graph.dot' file in debugfs

From: Martin Povišer
Date: Mon Aug 22 2022 - 05:52:56 EST


Provide a DOT summary of the DAPM graph in a newly added 'graph.dot'
file in debugfs, placed in the card's DAPM directory.

Signed-off-by: Martin Povišer <povik+lin@xxxxxxxxxxx>
---

Sample output: https://cutebit.org/macaudio-j274.svg
(With unupstreamed sound drivers on Mac mini (2020))

The helper bufprintf macro triggers checkpath.pl:

ERROR: Macros with complex values should be enclosed in parentheses
#47: FILE: sound/soc/soc-dapm.c:2235:
+#define bufprintf(...) \
+ ret += scnprintf(buf + ret, bufsize - ret, __VA_ARGS__)

but adding in {} to the macro body interferes with the if/else
constructions later, so I left it as-is.

sound/soc/soc-dapm.c | 141 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 141 insertions(+)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 73b8bd452ca7..86524908c3fd 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -2210,6 +2210,143 @@ static const struct file_operations dapm_bias_fops = {
.llseek = default_llseek,
};

+static ssize_t dapm_graph_read_file(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct snd_soc_card *card = file->private_data;
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_dapm_path *p;
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dapm_widget *wdone[16];
+ struct snd_soc_dai *dai;
+ int i, num_wdone = 0, cluster = 0;
+ char *buf;
+ ssize_t bufsize;
+ ssize_t ret = 0;
+
+ bufsize = 1024 * card->num_dapm_widgets;
+ buf = kmalloc(bufsize, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mutex_lock(&card->dapm_mutex);
+
+#define bufprintf(...) \
+ ret += scnprintf(buf + ret, bufsize - ret, __VA_ARGS__)
+
+ bufprintf("digraph dapm {\n");
+ bufprintf("label=\"%s\";\n", card->name);
+
+ /*
+ * Print the user-visible PCM devices of the card.
+ */
+ bufprintf("subgraph cluster_%d {\n", cluster++);
+ bufprintf("label=\"PCM devices\";style=filled;fillcolor=lightgray;\n");
+ for_each_card_rtds(card, rtd) {
+ if (rtd->dai_link->no_pcm)
+ continue;
+
+ bufprintf("w%pK [label=\"%d: %s\"];\n", rtd,
+ rtd->pcm->device, rtd->dai_link->name);
+ }
+ bufprintf("};\n");
+
+ /*
+ * Print the playback/capture widgets of CPU-side DAIs, and link
+ * them to the PCM devices. Keep a list of already printed
+ * widgets in 'wdone', so they will be skipped later. Do not put
+ * these widgets in a component cluster like we will do with
+ * the other widgets later, since that just clutters the graph.
+ */
+ for_each_card_rtds(card, rtd) {
+ for_each_rtd_cpu_dais(rtd, i, dai) {
+ if (dai->playback_widget) {
+ w = dai->playback_widget;
+ bufprintf("w%pK [label=\"%s\"];\n", w, w->name);
+ if (!rtd->dai_link->no_pcm)
+ bufprintf("w%pK -> w%pK;\n", rtd, w);
+ wdone[num_wdone] = w;
+ if (num_wdone < ARRAY_SIZE(wdone))
+ num_wdone++;
+ }
+
+ if (dai->capture_widget) {
+ w = dai->capture_widget;
+ bufprintf("w%pK [label=\"%s\"];\n", w, w->name);
+ if (!rtd->dai_link->no_pcm)
+ bufprintf("w%pK -> w%pK;\n", w, rtd);
+ wdone[num_wdone] = w;
+ if (num_wdone < ARRAY_SIZE(wdone))
+ num_wdone++;
+ }
+ }
+ }
+
+ for_each_card_dapms(card, dapm) {
+ const char *prefix = soc_dapm_prefix(dapm);
+
+ if (dapm != &card->dapm) {
+ bufprintf("subgraph cluster_%d {\n", cluster++);
+ if (prefix && dapm->component)
+ bufprintf("label=\"%s (%s)\";\n", prefix,
+ dapm->component->name);
+ else if (dapm->component)
+ bufprintf("label=\"%s\";\n",
+ dapm->component->name);
+ }
+
+ for_each_card_widgets(dapm->card, w) {
+ const char *name = w->name;
+ bool skip = false;
+
+ if (w->dapm != dapm)
+ continue;
+
+ if (list_empty(&w->edges[0]) && list_empty(&w->edges[1]))
+ continue;
+
+ for (i = 0; i < num_wdone; i++)
+ if (wdone[i] == w)
+ skip = true;
+ if (skip)
+ continue;
+
+ if (prefix && strlen(name) > strlen(prefix) + 1)
+ name += strlen(prefix) + 1;
+
+ bufprintf("w%pK [label=\"%s\"];\n", w, name);
+ }
+
+ if (dapm != &card->dapm)
+ bufprintf("}\n");
+ }
+
+ list_for_each_entry(p, &card->paths, list) {
+ if (p->name)
+ bufprintf("w%pK -> w%pK [label=\"%s\"];\n",
+ p->source, p->sink, p->name);
+ else
+ bufprintf("w%pK -> w%pK;\n", p->source, p->sink);
+ }
+
+ bufprintf("}\n");
+#undef bufprintf
+
+ mutex_unlock(&card->dapm_mutex);
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ kfree(buf);
+ return ret;
+}
+
+static const struct file_operations dapm_graph_fops = {
+ .open = simple_open,
+ .read = dapm_graph_read_file,
+ .llseek = default_llseek,
+};
+
void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
struct dentry *parent)
{
@@ -2220,6 +2357,10 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,

debugfs_create_file("bias_level", 0444, dapm->debugfs_dapm, dapm,
&dapm_bias_fops);
+
+ if (dapm == &dapm->card->dapm)
+ debugfs_create_file("graph.dot", 0444, dapm->debugfs_dapm,
+ dapm->card, &dapm_graph_fops);
}

static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w)
--
2.33.0