xref: /openbmc/linux/sound/soc/sof/debug.c (revision 78bb17f7)
1 // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
2 //
3 // This file is provided under a dual BSD/GPLv2 license.  When using or
4 // redistributing this file, you may do so under either license.
5 //
6 // Copyright(c) 2018 Intel Corporation. All rights reserved.
7 //
8 // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
9 //
10 // Generic debug routines used to export DSP MMIO and memories to userspace
11 // for firmware debugging.
12 //
13 
14 #include <linux/debugfs.h>
15 #include <linux/io.h>
16 #include <linux/pm_runtime.h>
17 #include "sof-priv.h"
18 #include "ops.h"
19 
20 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
21 #include "probe.h"
22 
23 /**
24  * strsplit_u32 - Split string into sequence of u32 tokens
25  * @buf:	String to split into tokens.
26  * @delim:	String containing delimiter characters.
27  * @tkns:	Returned u32 sequence pointer.
28  * @num_tkns:	Returned number of tokens obtained.
29  */
30 static int
31 strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns)
32 {
33 	char *s;
34 	u32 *data, *tmp;
35 	size_t count = 0;
36 	size_t cap = 32;
37 	int ret = 0;
38 
39 	*tkns = NULL;
40 	*num_tkns = 0;
41 	data = kcalloc(cap, sizeof(*data), GFP_KERNEL);
42 	if (!data)
43 		return -ENOMEM;
44 
45 	while ((s = strsep(buf, delim)) != NULL) {
46 		ret = kstrtouint(s, 0, data + count);
47 		if (ret)
48 			goto exit;
49 		if (++count >= cap) {
50 			cap *= 2;
51 			tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL);
52 			if (!tmp) {
53 				ret = -ENOMEM;
54 				goto exit;
55 			}
56 			data = tmp;
57 		}
58 	}
59 
60 	if (!count)
61 		goto exit;
62 	*tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL);
63 	if (*tkns == NULL) {
64 		ret = -ENOMEM;
65 		goto exit;
66 	}
67 	*num_tkns = count;
68 
69 exit:
70 	kfree(data);
71 	return ret;
72 }
73 
74 static int tokenize_input(const char __user *from, size_t count,
75 		loff_t *ppos, u32 **tkns, size_t *num_tkns)
76 {
77 	char *buf;
78 	int ret;
79 
80 	buf = kmalloc(count + 1, GFP_KERNEL);
81 	if (!buf)
82 		return -ENOMEM;
83 
84 	ret = simple_write_to_buffer(buf, count, ppos, from, count);
85 	if (ret != count) {
86 		ret = ret >= 0 ? -EIO : ret;
87 		goto exit;
88 	}
89 
90 	buf[count] = '\0';
91 	ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns);
92 exit:
93 	kfree(buf);
94 	return ret;
95 }
96 
97 static ssize_t probe_points_read(struct file *file,
98 		char __user *to, size_t count, loff_t *ppos)
99 {
100 	struct snd_sof_dfsentry *dfse = file->private_data;
101 	struct snd_sof_dev *sdev = dfse->sdev;
102 	struct sof_probe_point_desc *desc;
103 	size_t num_desc, len = 0;
104 	char *buf;
105 	int i, ret;
106 
107 	if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
108 		dev_warn(sdev->dev, "no extractor stream running\n");
109 		return -ENOENT;
110 	}
111 
112 	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
113 	if (!buf)
114 		return -ENOMEM;
115 
116 	ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc);
117 	if (ret < 0)
118 		goto exit;
119 
120 	for (i = 0; i < num_desc; i++) {
121 		ret = snprintf(buf + len, PAGE_SIZE - len,
122 			"Id: %#010x  Purpose: %d  Node id: %#x\n",
123 			desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag);
124 		if (ret < 0)
125 			goto free_desc;
126 		len += ret;
127 	}
128 
129 	ret = simple_read_from_buffer(to, count, ppos, buf, len);
130 free_desc:
131 	kfree(desc);
132 exit:
133 	kfree(buf);
134 	return ret;
135 }
136 
137 static ssize_t probe_points_write(struct file *file,
138 		const char __user *from, size_t count, loff_t *ppos)
139 {
140 	struct snd_sof_dfsentry *dfse = file->private_data;
141 	struct snd_sof_dev *sdev = dfse->sdev;
142 	struct sof_probe_point_desc *desc;
143 	size_t num_tkns, bytes;
144 	u32 *tkns;
145 	int ret;
146 
147 	if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
148 		dev_warn(sdev->dev, "no extractor stream running\n");
149 		return -ENOENT;
150 	}
151 
152 	ret = tokenize_input(from, count, ppos, &tkns, &num_tkns);
153 	if (ret < 0)
154 		return ret;
155 	bytes = sizeof(*tkns) * num_tkns;
156 	if (!num_tkns || (bytes % sizeof(*desc))) {
157 		ret = -EINVAL;
158 		goto exit;
159 	}
160 
161 	desc = (struct sof_probe_point_desc *)tkns;
162 	ret = sof_ipc_probe_points_add(sdev,
163 			desc, bytes / sizeof(*desc));
164 	if (!ret)
165 		ret = count;
166 exit:
167 	kfree(tkns);
168 	return ret;
169 }
170 
171 static const struct file_operations probe_points_fops = {
172 	.open = simple_open,
173 	.read = probe_points_read,
174 	.write = probe_points_write,
175 	.llseek = default_llseek,
176 };
177 
178 static ssize_t probe_points_remove_write(struct file *file,
179 		const char __user *from, size_t count, loff_t *ppos)
180 {
181 	struct snd_sof_dfsentry *dfse = file->private_data;
182 	struct snd_sof_dev *sdev = dfse->sdev;
183 	size_t num_tkns;
184 	u32 *tkns;
185 	int ret;
186 
187 	if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
188 		dev_warn(sdev->dev, "no extractor stream running\n");
189 		return -ENOENT;
190 	}
191 
192 	ret = tokenize_input(from, count, ppos, &tkns, &num_tkns);
193 	if (ret < 0)
194 		return ret;
195 	if (!num_tkns) {
196 		ret = -EINVAL;
197 		goto exit;
198 	}
199 
200 	ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns);
201 	if (!ret)
202 		ret = count;
203 exit:
204 	kfree(tkns);
205 	return ret;
206 }
207 
208 static const struct file_operations probe_points_remove_fops = {
209 	.open = simple_open,
210 	.write = probe_points_remove_write,
211 	.llseek = default_llseek,
212 };
213 
214 static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev,
215 				 const char *name, mode_t mode,
216 				 const struct file_operations *fops)
217 {
218 	struct snd_sof_dfsentry *dfse;
219 
220 	dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
221 	if (!dfse)
222 		return -ENOMEM;
223 
224 	dfse->type = SOF_DFSENTRY_TYPE_BUF;
225 	dfse->sdev = sdev;
226 
227 	debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops);
228 	/* add to dfsentry list */
229 	list_add(&dfse->list, &sdev->dfsentry_list);
230 
231 	return 0;
232 }
233 #endif
234 
235 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
236 #define MAX_IPC_FLOOD_DURATION_MS 1000
237 #define MAX_IPC_FLOOD_COUNT 10000
238 #define IPC_FLOOD_TEST_RESULT_LEN 512
239 
240 static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev,
241 				    struct snd_sof_dfsentry *dfse,
242 				    bool flood_duration_test,
243 				    unsigned long ipc_duration_ms,
244 				    unsigned long ipc_count)
245 {
246 	struct sof_ipc_cmd_hdr hdr;
247 	struct sof_ipc_reply reply;
248 	u64 min_response_time = U64_MAX;
249 	ktime_t start, end, test_end;
250 	u64 avg_response_time = 0;
251 	u64 max_response_time = 0;
252 	u64 ipc_response_time;
253 	int i = 0;
254 	int ret;
255 
256 	/* configure test IPC */
257 	hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD;
258 	hdr.size = sizeof(hdr);
259 
260 	/* set test end time for duration flood test */
261 	if (flood_duration_test)
262 		test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC;
263 
264 	/* send test IPC's */
265 	while (1) {
266 		start = ktime_get();
267 		ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size,
268 					 &reply, sizeof(reply));
269 		end = ktime_get();
270 
271 		if (ret < 0)
272 			break;
273 
274 		/* compute min and max response times */
275 		ipc_response_time = ktime_to_ns(ktime_sub(end, start));
276 		min_response_time = min(min_response_time, ipc_response_time);
277 		max_response_time = max(max_response_time, ipc_response_time);
278 
279 		/* sum up response times */
280 		avg_response_time += ipc_response_time;
281 		i++;
282 
283 		/* test complete? */
284 		if (flood_duration_test) {
285 			if (ktime_to_ns(end) >= test_end)
286 				break;
287 		} else {
288 			if (i == ipc_count)
289 				break;
290 		}
291 	}
292 
293 	if (ret < 0)
294 		dev_err(sdev->dev,
295 			"error: ipc flood test failed at %d iterations\n", i);
296 
297 	/* return if the first IPC fails */
298 	if (!i)
299 		return ret;
300 
301 	/* compute average response time */
302 	do_div(avg_response_time, i);
303 
304 	/* clear previous test output */
305 	memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN);
306 
307 	if (flood_duration_test) {
308 		dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n",
309 			ipc_duration_ms);
310 		snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN,
311 			 "IPC Flood test duration: %lums\n", ipc_duration_ms);
312 	}
313 
314 	dev_dbg(sdev->dev,
315 		"IPC Flood count: %d, Avg response time: %lluns\n",
316 		i, avg_response_time);
317 	dev_dbg(sdev->dev, "Max response time: %lluns\n",
318 		max_response_time);
319 	dev_dbg(sdev->dev, "Min response time: %lluns\n",
320 		min_response_time);
321 
322 	/* format output string */
323 	snprintf(dfse->cache_buf + strlen(dfse->cache_buf),
324 		 IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf),
325 		 "IPC Flood count: %d\nAvg response time: %lluns\n",
326 		 i, avg_response_time);
327 
328 	snprintf(dfse->cache_buf + strlen(dfse->cache_buf),
329 		 IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf),
330 		 "Max response time: %lluns\nMin response time: %lluns\n",
331 		 max_response_time, min_response_time);
332 
333 	return ret;
334 }
335 #endif
336 
337 static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer,
338 				  size_t count, loff_t *ppos)
339 {
340 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
341 	struct snd_sof_dfsentry *dfse = file->private_data;
342 	struct snd_sof_dev *sdev = dfse->sdev;
343 	unsigned long ipc_duration_ms = 0;
344 	bool flood_duration_test = false;
345 	unsigned long ipc_count = 0;
346 	struct dentry *dentry;
347 	int err;
348 #endif
349 	size_t size;
350 	char *string;
351 	int ret;
352 
353 	string = kzalloc(count, GFP_KERNEL);
354 	if (!string)
355 		return -ENOMEM;
356 
357 	size = simple_write_to_buffer(string, count, ppos, buffer, count);
358 	ret = size;
359 
360 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
361 	/*
362 	 * write op is only supported for ipc_flood_count or
363 	 * ipc_flood_duration_ms debugfs entries atm.
364 	 * ipc_flood_count floods the DSP with the number of IPC's specified.
365 	 * ipc_duration_ms test floods the DSP for the time specified
366 	 * in the debugfs entry.
367 	 */
368 	dentry = file->f_path.dentry;
369 	if (strcmp(dentry->d_name.name, "ipc_flood_count") &&
370 	    strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) {
371 		ret = -EINVAL;
372 		goto out;
373 	}
374 
375 	if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))
376 		flood_duration_test = true;
377 
378 	/* test completion criterion */
379 	if (flood_duration_test)
380 		ret = kstrtoul(string, 0, &ipc_duration_ms);
381 	else
382 		ret = kstrtoul(string, 0, &ipc_count);
383 	if (ret < 0)
384 		goto out;
385 
386 	/* limit max duration/ipc count for flood test */
387 	if (flood_duration_test) {
388 		if (!ipc_duration_ms) {
389 			ret = size;
390 			goto out;
391 		}
392 
393 		/* find the minimum. min() is not used to avoid warnings */
394 		if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)
395 			ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;
396 	} else {
397 		if (!ipc_count) {
398 			ret = size;
399 			goto out;
400 		}
401 
402 		/* find the minimum. min() is not used to avoid warnings */
403 		if (ipc_count > MAX_IPC_FLOOD_COUNT)
404 			ipc_count = MAX_IPC_FLOOD_COUNT;
405 	}
406 
407 	ret = pm_runtime_get_sync(sdev->dev);
408 	if (ret < 0) {
409 		dev_err_ratelimited(sdev->dev,
410 				    "error: debugfs write failed to resume %d\n",
411 				    ret);
412 		pm_runtime_put_noidle(sdev->dev);
413 		goto out;
414 	}
415 
416 	/* flood test */
417 	ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test,
418 				       ipc_duration_ms, ipc_count);
419 
420 	pm_runtime_mark_last_busy(sdev->dev);
421 	err = pm_runtime_put_autosuspend(sdev->dev);
422 	if (err < 0)
423 		dev_err_ratelimited(sdev->dev,
424 				    "error: debugfs write failed to idle %d\n",
425 				    err);
426 
427 	/* return size if test is successful */
428 	if (ret >= 0)
429 		ret = size;
430 out:
431 #endif
432 	kfree(string);
433 	return ret;
434 }
435 
436 static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer,
437 				 size_t count, loff_t *ppos)
438 {
439 	struct snd_sof_dfsentry *dfse = file->private_data;
440 	struct snd_sof_dev *sdev = dfse->sdev;
441 	loff_t pos = *ppos;
442 	size_t size_ret;
443 	int skip = 0;
444 	int size;
445 	u8 *buf;
446 
447 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
448 	struct dentry *dentry;
449 
450 	dentry = file->f_path.dentry;
451 	if ((!strcmp(dentry->d_name.name, "ipc_flood_count") ||
452 	     !strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) &&
453 	    dfse->cache_buf) {
454 		if (*ppos)
455 			return 0;
456 
457 		count = strlen(dfse->cache_buf);
458 		size_ret = copy_to_user(buffer, dfse->cache_buf, count);
459 		if (size_ret)
460 			return -EFAULT;
461 
462 		*ppos += count;
463 		return count;
464 	}
465 #endif
466 	size = dfse->size;
467 
468 	/* validate position & count */
469 	if (pos < 0)
470 		return -EINVAL;
471 	if (pos >= size || !count)
472 		return 0;
473 	/* find the minimum. min() is not used since it adds sparse warnings */
474 	if (count > size - pos)
475 		count = size - pos;
476 
477 	/* align io read start to u32 multiple */
478 	pos = ALIGN_DOWN(pos, 4);
479 
480 	/* intermediate buffer size must be u32 multiple */
481 	size = ALIGN(count, 4);
482 
483 	/* if start position is unaligned, read extra u32 */
484 	if (unlikely(pos != *ppos)) {
485 		skip = *ppos - pos;
486 		if (pos + size + 4 < dfse->size)
487 			size += 4;
488 	}
489 
490 	buf = kzalloc(size, GFP_KERNEL);
491 	if (!buf)
492 		return -ENOMEM;
493 
494 	if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) {
495 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
496 		/*
497 		 * If the DSP is active: copy from IO.
498 		 * If the DSP is suspended:
499 		 *	- Copy from IO if the memory is always accessible.
500 		 *	- Otherwise, copy from cached buffer.
501 		 */
502 		if (pm_runtime_active(sdev->dev) ||
503 		    dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) {
504 			memcpy_fromio(buf, dfse->io_mem + pos, size);
505 		} else {
506 			dev_info(sdev->dev,
507 				 "Copying cached debugfs data\n");
508 			memcpy(buf, dfse->cache_buf + pos, size);
509 		}
510 #else
511 		/* if the DSP is in D3 */
512 		if (!pm_runtime_active(sdev->dev) &&
513 		    dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {
514 			dev_err(sdev->dev,
515 				"error: debugfs entry cannot be read in DSP D3\n");
516 			kfree(buf);
517 			return -EINVAL;
518 		}
519 
520 		memcpy_fromio(buf, dfse->io_mem + pos, size);
521 #endif
522 	} else {
523 		memcpy(buf, ((u8 *)(dfse->buf) + pos), size);
524 	}
525 
526 	/* copy to userspace */
527 	size_ret = copy_to_user(buffer, buf + skip, count);
528 
529 	kfree(buf);
530 
531 	/* update count & position if copy succeeded */
532 	if (size_ret)
533 		return -EFAULT;
534 
535 	*ppos = pos + count;
536 
537 	return count;
538 }
539 
540 static const struct file_operations sof_dfs_fops = {
541 	.open = simple_open,
542 	.read = sof_dfsentry_read,
543 	.llseek = default_llseek,
544 	.write = sof_dfsentry_write,
545 };
546 
547 /* create FS entry for debug files that can expose DSP memories, registers */
548 int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,
549 			    void __iomem *base, size_t size,
550 			    const char *name,
551 			    enum sof_debugfs_access_type access_type)
552 {
553 	struct snd_sof_dfsentry *dfse;
554 
555 	if (!sdev)
556 		return -EINVAL;
557 
558 	dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
559 	if (!dfse)
560 		return -ENOMEM;
561 
562 	dfse->type = SOF_DFSENTRY_TYPE_IOMEM;
563 	dfse->io_mem = base;
564 	dfse->size = size;
565 	dfse->sdev = sdev;
566 	dfse->access_type = access_type;
567 
568 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
569 	/*
570 	 * allocate cache buffer that will be used to save the mem window
571 	 * contents prior to suspend
572 	 */
573 	if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {
574 		dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL);
575 		if (!dfse->cache_buf)
576 			return -ENOMEM;
577 	}
578 #endif
579 
580 	debugfs_create_file(name, 0444, sdev->debugfs_root, dfse,
581 			    &sof_dfs_fops);
582 
583 	/* add to dfsentry list */
584 	list_add(&dfse->list, &sdev->dfsentry_list);
585 
586 	return 0;
587 }
588 EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item);
589 
590 /* create FS entry for debug files to expose kernel memory */
591 int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,
592 			     void *base, size_t size,
593 			     const char *name, mode_t mode)
594 {
595 	struct snd_sof_dfsentry *dfse;
596 
597 	if (!sdev)
598 		return -EINVAL;
599 
600 	dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
601 	if (!dfse)
602 		return -ENOMEM;
603 
604 	dfse->type = SOF_DFSENTRY_TYPE_BUF;
605 	dfse->buf = base;
606 	dfse->size = size;
607 	dfse->sdev = sdev;
608 
609 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
610 	/*
611 	 * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries.
612 	 * So, use it to save the results of the last IPC flood test.
613 	 */
614 	dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN,
615 				       GFP_KERNEL);
616 	if (!dfse->cache_buf)
617 		return -ENOMEM;
618 #endif
619 
620 	debugfs_create_file(name, mode, sdev->debugfs_root, dfse,
621 			    &sof_dfs_fops);
622 	/* add to dfsentry list */
623 	list_add(&dfse->list, &sdev->dfsentry_list);
624 
625 	return 0;
626 }
627 EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item);
628 
629 int snd_sof_dbg_init(struct snd_sof_dev *sdev)
630 {
631 	const struct snd_sof_dsp_ops *ops = sof_ops(sdev);
632 	const struct snd_sof_debugfs_map *map;
633 	int i;
634 	int err;
635 
636 	/* use "sof" as top level debugFS dir */
637 	sdev->debugfs_root = debugfs_create_dir("sof", NULL);
638 
639 	/* init dfsentry list */
640 	INIT_LIST_HEAD(&sdev->dfsentry_list);
641 
642 	/* create debugFS files for platform specific MMIO/DSP memories */
643 	for (i = 0; i < ops->debug_map_count; i++) {
644 		map = &ops->debug_map[i];
645 
646 		err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] +
647 					      map->offset, map->size,
648 					      map->name, map->access_type);
649 		/* errors are only due to memory allocation, not debugfs */
650 		if (err < 0)
651 			return err;
652 	}
653 
654 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
655 	err = snd_sof_debugfs_probe_item(sdev, "probe_points",
656 			0644, &probe_points_fops);
657 	if (err < 0)
658 		return err;
659 	err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove",
660 			0200, &probe_points_remove_fops);
661 	if (err < 0)
662 		return err;
663 #endif
664 
665 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
666 	/* create read-write ipc_flood_count debugfs entry */
667 	err = snd_sof_debugfs_buf_item(sdev, NULL, 0,
668 				       "ipc_flood_count", 0666);
669 
670 	/* errors are only due to memory allocation, not debugfs */
671 	if (err < 0)
672 		return err;
673 
674 	/* create read-write ipc_flood_duration_ms debugfs entry */
675 	err = snd_sof_debugfs_buf_item(sdev, NULL, 0,
676 				       "ipc_flood_duration_ms", 0666);
677 
678 	/* errors are only due to memory allocation, not debugfs */
679 	if (err < 0)
680 		return err;
681 #endif
682 
683 	return 0;
684 }
685 EXPORT_SYMBOL_GPL(snd_sof_dbg_init);
686 
687 void snd_sof_free_debug(struct snd_sof_dev *sdev)
688 {
689 	debugfs_remove_recursive(sdev->debugfs_root);
690 }
691 EXPORT_SYMBOL_GPL(snd_sof_free_debug);
692 
693 void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev)
694 {
695 	if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||
696 	    (sof_core_debug & SOF_DBG_RETAIN_CTX)) {
697 		/* should we prevent DSP entering D3 ? */
698 		dev_info(sdev->dev, "info: preventing DSP entering D3 state to preserve context\n");
699 		pm_runtime_get_noresume(sdev->dev);
700 	}
701 
702 	/* dump vital information to the logs */
703 	snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX);
704 	snd_sof_ipc_dump(sdev);
705 	snd_sof_trace_notify_for_error(sdev);
706 }
707 EXPORT_SYMBOL(snd_sof_handle_fw_exception);
708