1 // SPDX-License-Identifier: (GPL-2.0-only 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 <sound/sof/ext_manifest.h> 18 #include <sound/sof/debug.h> 19 #include "sof-priv.h" 20 #include "ops.h" 21 22 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) 23 #include "sof-probes.h" 24 25 /** 26 * strsplit_u32 - Split string into sequence of u32 tokens 27 * @buf: String to split into tokens. 28 * @delim: String containing delimiter characters. 29 * @tkns: Returned u32 sequence pointer. 30 * @num_tkns: Returned number of tokens obtained. 31 */ 32 static int 33 strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns) 34 { 35 char *s; 36 u32 *data, *tmp; 37 size_t count = 0; 38 size_t cap = 32; 39 int ret = 0; 40 41 *tkns = NULL; 42 *num_tkns = 0; 43 data = kcalloc(cap, sizeof(*data), GFP_KERNEL); 44 if (!data) 45 return -ENOMEM; 46 47 while ((s = strsep(buf, delim)) != NULL) { 48 ret = kstrtouint(s, 0, data + count); 49 if (ret) 50 goto exit; 51 if (++count >= cap) { 52 cap *= 2; 53 tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); 54 if (!tmp) { 55 ret = -ENOMEM; 56 goto exit; 57 } 58 data = tmp; 59 } 60 } 61 62 if (!count) 63 goto exit; 64 *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); 65 if (*tkns == NULL) { 66 ret = -ENOMEM; 67 goto exit; 68 } 69 *num_tkns = count; 70 71 exit: 72 kfree(data); 73 return ret; 74 } 75 76 static int tokenize_input(const char __user *from, size_t count, 77 loff_t *ppos, u32 **tkns, size_t *num_tkns) 78 { 79 char *buf; 80 int ret; 81 82 buf = kmalloc(count + 1, GFP_KERNEL); 83 if (!buf) 84 return -ENOMEM; 85 86 ret = simple_write_to_buffer(buf, count, ppos, from, count); 87 if (ret != count) { 88 ret = ret >= 0 ? -EIO : ret; 89 goto exit; 90 } 91 92 buf[count] = '\0'; 93 ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns); 94 exit: 95 kfree(buf); 96 return ret; 97 } 98 99 static ssize_t probe_points_read(struct file *file, 100 char __user *to, size_t count, loff_t *ppos) 101 { 102 struct snd_sof_dfsentry *dfse = file->private_data; 103 struct snd_sof_dev *sdev = dfse->sdev; 104 struct sof_probe_point_desc *desc; 105 size_t num_desc, len = 0; 106 char *buf; 107 int i, ret; 108 109 if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { 110 dev_warn(sdev->dev, "no extractor stream running\n"); 111 return -ENOENT; 112 } 113 114 buf = kzalloc(PAGE_SIZE, GFP_KERNEL); 115 if (!buf) 116 return -ENOMEM; 117 118 ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); 119 if (ret < 0) 120 goto exit; 121 122 for (i = 0; i < num_desc; i++) { 123 ret = snprintf(buf + len, PAGE_SIZE - len, 124 "Id: %#010x Purpose: %d Node id: %#x\n", 125 desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); 126 if (ret < 0) 127 goto free_desc; 128 len += ret; 129 } 130 131 ret = simple_read_from_buffer(to, count, ppos, buf, len); 132 free_desc: 133 kfree(desc); 134 exit: 135 kfree(buf); 136 return ret; 137 } 138 139 static ssize_t probe_points_write(struct file *file, 140 const char __user *from, size_t count, loff_t *ppos) 141 { 142 struct snd_sof_dfsentry *dfse = file->private_data; 143 struct snd_sof_dev *sdev = dfse->sdev; 144 struct sof_probe_point_desc *desc; 145 size_t num_tkns, bytes; 146 u32 *tkns; 147 int ret; 148 149 if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { 150 dev_warn(sdev->dev, "no extractor stream running\n"); 151 return -ENOENT; 152 } 153 154 ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); 155 if (ret < 0) 156 return ret; 157 bytes = sizeof(*tkns) * num_tkns; 158 if (!num_tkns || (bytes % sizeof(*desc))) { 159 ret = -EINVAL; 160 goto exit; 161 } 162 163 desc = (struct sof_probe_point_desc *)tkns; 164 ret = sof_ipc_probe_points_add(sdev, 165 desc, bytes / sizeof(*desc)); 166 if (!ret) 167 ret = count; 168 exit: 169 kfree(tkns); 170 return ret; 171 } 172 173 static const struct file_operations probe_points_fops = { 174 .open = simple_open, 175 .read = probe_points_read, 176 .write = probe_points_write, 177 .llseek = default_llseek, 178 }; 179 180 static ssize_t probe_points_remove_write(struct file *file, 181 const char __user *from, size_t count, loff_t *ppos) 182 { 183 struct snd_sof_dfsentry *dfse = file->private_data; 184 struct snd_sof_dev *sdev = dfse->sdev; 185 size_t num_tkns; 186 u32 *tkns; 187 int ret; 188 189 if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { 190 dev_warn(sdev->dev, "no extractor stream running\n"); 191 return -ENOENT; 192 } 193 194 ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); 195 if (ret < 0) 196 return ret; 197 if (!num_tkns) { 198 ret = -EINVAL; 199 goto exit; 200 } 201 202 ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns); 203 if (!ret) 204 ret = count; 205 exit: 206 kfree(tkns); 207 return ret; 208 } 209 210 static const struct file_operations probe_points_remove_fops = { 211 .open = simple_open, 212 .write = probe_points_remove_write, 213 .llseek = default_llseek, 214 }; 215 216 static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, 217 const char *name, mode_t mode, 218 const struct file_operations *fops) 219 { 220 struct snd_sof_dfsentry *dfse; 221 222 dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); 223 if (!dfse) 224 return -ENOMEM; 225 226 dfse->type = SOF_DFSENTRY_TYPE_BUF; 227 dfse->sdev = sdev; 228 229 debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); 230 /* add to dfsentry list */ 231 list_add(&dfse->list, &sdev->dfsentry_list); 232 233 return 0; 234 } 235 #endif 236 237 static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, 238 size_t count, loff_t *ppos) 239 { 240 size_t size; 241 char *string; 242 int ret; 243 244 string = kzalloc(count+1, GFP_KERNEL); 245 if (!string) 246 return -ENOMEM; 247 248 size = simple_write_to_buffer(string, count, ppos, buffer, count); 249 ret = size; 250 251 kfree(string); 252 return ret; 253 } 254 255 static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, 256 size_t count, loff_t *ppos) 257 { 258 struct snd_sof_dfsentry *dfse = file->private_data; 259 struct snd_sof_dev *sdev = dfse->sdev; 260 loff_t pos = *ppos; 261 size_t size_ret; 262 int skip = 0; 263 int size; 264 u8 *buf; 265 266 size = dfse->size; 267 268 /* validate position & count */ 269 if (pos < 0) 270 return -EINVAL; 271 if (pos >= size || !count) 272 return 0; 273 /* find the minimum. min() is not used since it adds sparse warnings */ 274 if (count > size - pos) 275 count = size - pos; 276 277 /* align io read start to u32 multiple */ 278 pos = ALIGN_DOWN(pos, 4); 279 280 /* intermediate buffer size must be u32 multiple */ 281 size = ALIGN(count, 4); 282 283 /* if start position is unaligned, read extra u32 */ 284 if (unlikely(pos != *ppos)) { 285 skip = *ppos - pos; 286 if (pos + size + 4 < dfse->size) 287 size += 4; 288 } 289 290 buf = kzalloc(size, GFP_KERNEL); 291 if (!buf) 292 return -ENOMEM; 293 294 if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) { 295 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) 296 /* 297 * If the DSP is active: copy from IO. 298 * If the DSP is suspended: 299 * - Copy from IO if the memory is always accessible. 300 * - Otherwise, copy from cached buffer. 301 */ 302 if (pm_runtime_active(sdev->dev) || 303 dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) { 304 memcpy_fromio(buf, dfse->io_mem + pos, size); 305 } else { 306 dev_info(sdev->dev, 307 "Copying cached debugfs data\n"); 308 memcpy(buf, dfse->cache_buf + pos, size); 309 } 310 #else 311 /* if the DSP is in D3 */ 312 if (!pm_runtime_active(sdev->dev) && 313 dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { 314 dev_err(sdev->dev, 315 "error: debugfs entry cannot be read in DSP D3\n"); 316 kfree(buf); 317 return -EINVAL; 318 } 319 320 memcpy_fromio(buf, dfse->io_mem + pos, size); 321 #endif 322 } else { 323 memcpy(buf, ((u8 *)(dfse->buf) + pos), size); 324 } 325 326 /* copy to userspace */ 327 size_ret = copy_to_user(buffer, buf + skip, count); 328 329 kfree(buf); 330 331 /* update count & position if copy succeeded */ 332 if (size_ret) 333 return -EFAULT; 334 335 *ppos = pos + count; 336 337 return count; 338 } 339 340 static const struct file_operations sof_dfs_fops = { 341 .open = simple_open, 342 .read = sof_dfsentry_read, 343 .llseek = default_llseek, 344 .write = sof_dfsentry_write, 345 }; 346 347 /* create FS entry for debug files that can expose DSP memories, registers */ 348 static int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, 349 void __iomem *base, size_t size, 350 const char *name, 351 enum sof_debugfs_access_type access_type) 352 { 353 struct snd_sof_dfsentry *dfse; 354 355 if (!sdev) 356 return -EINVAL; 357 358 dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); 359 if (!dfse) 360 return -ENOMEM; 361 362 dfse->type = SOF_DFSENTRY_TYPE_IOMEM; 363 dfse->io_mem = base; 364 dfse->size = size; 365 dfse->sdev = sdev; 366 dfse->access_type = access_type; 367 368 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) 369 /* 370 * allocate cache buffer that will be used to save the mem window 371 * contents prior to suspend 372 */ 373 if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { 374 dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL); 375 if (!dfse->cache_buf) 376 return -ENOMEM; 377 } 378 #endif 379 380 debugfs_create_file(name, 0444, sdev->debugfs_root, dfse, 381 &sof_dfs_fops); 382 383 /* add to dfsentry list */ 384 list_add(&dfse->list, &sdev->dfsentry_list); 385 386 return 0; 387 } 388 389 int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev, 390 enum snd_sof_fw_blk_type blk_type, u32 offset, 391 size_t size, const char *name, 392 enum sof_debugfs_access_type access_type) 393 { 394 int bar = snd_sof_dsp_get_bar_index(sdev, blk_type); 395 396 if (bar < 0) 397 return bar; 398 399 return snd_sof_debugfs_io_item(sdev, sdev->bar[bar] + offset, size, name, 400 access_type); 401 } 402 EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem); 403 404 /* create FS entry for debug files to expose kernel memory */ 405 int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, 406 void *base, size_t size, 407 const char *name, mode_t mode) 408 { 409 struct snd_sof_dfsentry *dfse; 410 411 if (!sdev) 412 return -EINVAL; 413 414 dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); 415 if (!dfse) 416 return -ENOMEM; 417 418 dfse->type = SOF_DFSENTRY_TYPE_BUF; 419 dfse->buf = base; 420 dfse->size = size; 421 dfse->sdev = sdev; 422 423 debugfs_create_file(name, mode, sdev->debugfs_root, dfse, 424 &sof_dfs_fops); 425 /* add to dfsentry list */ 426 list_add(&dfse->list, &sdev->dfsentry_list); 427 428 return 0; 429 } 430 EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item); 431 432 static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_size) 433 { 434 struct sof_ipc_cmd_hdr msg = { 435 .size = sizeof(struct sof_ipc_cmd_hdr), 436 .cmd = SOF_IPC_GLB_DEBUG | SOF_IPC_DEBUG_MEM_USAGE, 437 }; 438 struct sof_ipc_dbg_mem_usage *reply; 439 int len; 440 int ret; 441 int i; 442 443 reply = kmalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); 444 if (!reply) 445 return -ENOMEM; 446 447 ret = pm_runtime_get_sync(sdev->dev); 448 if (ret < 0 && ret != -EACCES) { 449 pm_runtime_put_noidle(sdev->dev); 450 dev_err(sdev->dev, "error: enabling device failed: %d\n", ret); 451 goto error; 452 } 453 454 ret = sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE); 455 pm_runtime_mark_last_busy(sdev->dev); 456 pm_runtime_put_autosuspend(sdev->dev); 457 if (ret < 0 || reply->rhdr.error < 0) { 458 ret = min(ret, reply->rhdr.error); 459 dev_err(sdev->dev, "error: reading memory info failed, %d\n", ret); 460 goto error; 461 } 462 463 if (struct_size(reply, elems, reply->num_elems) != reply->rhdr.hdr.size) { 464 dev_err(sdev->dev, "error: invalid memory info ipc struct size, %d\n", 465 reply->rhdr.hdr.size); 466 ret = -EINVAL; 467 goto error; 468 } 469 470 for (i = 0, len = 0; i < reply->num_elems; i++) { 471 ret = snprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n", 472 reply->elems[i].zone, reply->elems[i].id, 473 reply->elems[i].used, reply->elems[i].free); 474 if (ret < 0) 475 goto error; 476 len += ret; 477 } 478 479 ret = len; 480 error: 481 kfree(reply); 482 return ret; 483 } 484 485 static ssize_t memory_info_read(struct file *file, char __user *to, size_t count, loff_t *ppos) 486 { 487 struct snd_sof_dfsentry *dfse = file->private_data; 488 struct snd_sof_dev *sdev = dfse->sdev; 489 int data_length; 490 491 /* read memory info from FW only once for each file read */ 492 if (!*ppos) { 493 dfse->buf_data_size = 0; 494 data_length = memory_info_update(sdev, dfse->buf, dfse->size); 495 if (data_length < 0) 496 return data_length; 497 dfse->buf_data_size = data_length; 498 } 499 500 return simple_read_from_buffer(to, count, ppos, dfse->buf, dfse->buf_data_size); 501 } 502 503 static int memory_info_open(struct inode *inode, struct file *file) 504 { 505 struct snd_sof_dfsentry *dfse = inode->i_private; 506 struct snd_sof_dev *sdev = dfse->sdev; 507 508 file->private_data = dfse; 509 510 /* allocate buffer memory only in first open run, to save memory when unused */ 511 if (!dfse->buf) { 512 dfse->buf = devm_kmalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL); 513 if (!dfse->buf) 514 return -ENOMEM; 515 dfse->size = PAGE_SIZE; 516 } 517 518 return 0; 519 } 520 521 static const struct file_operations memory_info_fops = { 522 .open = memory_info_open, 523 .read = memory_info_read, 524 .llseek = default_llseek, 525 }; 526 527 int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev) 528 { 529 struct snd_sof_dfsentry *dfse; 530 531 dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); 532 if (!dfse) 533 return -ENOMEM; 534 535 /* don't allocate buffer before first usage, to save memory when unused */ 536 dfse->type = SOF_DFSENTRY_TYPE_BUF; 537 dfse->sdev = sdev; 538 539 debugfs_create_file("memory_info", 0444, sdev->debugfs_root, dfse, &memory_info_fops); 540 541 /* add to dfsentry list */ 542 list_add(&dfse->list, &sdev->dfsentry_list); 543 return 0; 544 } 545 EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init); 546 547 int snd_sof_dbg_init(struct snd_sof_dev *sdev) 548 { 549 const struct snd_sof_dsp_ops *ops = sof_ops(sdev); 550 const struct snd_sof_debugfs_map *map; 551 int i; 552 int err; 553 554 /* use "sof" as top level debugFS dir */ 555 sdev->debugfs_root = debugfs_create_dir("sof", NULL); 556 557 /* init dfsentry list */ 558 INIT_LIST_HEAD(&sdev->dfsentry_list); 559 560 /* create debugFS files for platform specific MMIO/DSP memories */ 561 for (i = 0; i < ops->debug_map_count; i++) { 562 map = &ops->debug_map[i]; 563 564 err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] + 565 map->offset, map->size, 566 map->name, map->access_type); 567 /* errors are only due to memory allocation, not debugfs */ 568 if (err < 0) 569 return err; 570 } 571 572 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) 573 err = snd_sof_debugfs_probe_item(sdev, "probe_points", 574 0644, &probe_points_fops); 575 if (err < 0) 576 return err; 577 err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove", 578 0200, &probe_points_remove_fops); 579 if (err < 0) 580 return err; 581 #endif 582 583 return 0; 584 } 585 EXPORT_SYMBOL_GPL(snd_sof_dbg_init); 586 587 void snd_sof_free_debug(struct snd_sof_dev *sdev) 588 { 589 debugfs_remove_recursive(sdev->debugfs_root); 590 } 591 EXPORT_SYMBOL_GPL(snd_sof_free_debug); 592 593 static const struct soc_fw_state_info { 594 enum sof_fw_state state; 595 const char *name; 596 } fw_state_dbg[] = { 597 {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"}, 598 {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"}, 599 {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"}, 600 {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"}, 601 {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"}, 602 {SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK"}, 603 {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"}, 604 {SOF_FW_CRASHED, "SOF_FW_CRASHED"}, 605 }; 606 607 static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level) 608 { 609 int i; 610 611 for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) { 612 if (sdev->fw_state == fw_state_dbg[i].state) { 613 dev_printk(level, sdev->dev, "fw_state: %s (%d)\n", 614 fw_state_dbg[i].name, i); 615 return; 616 } 617 } 618 619 dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state); 620 } 621 622 void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags) 623 { 624 char *level = flags & SOF_DBG_DUMP_OPTIONAL ? KERN_DEBUG : KERN_ERR; 625 bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS); 626 627 if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all) 628 return; 629 630 if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) { 631 dev_printk(level, sdev->dev, 632 "------------[ DSP dump start ]------------\n"); 633 if (msg) 634 dev_printk(level, sdev->dev, "%s\n", msg); 635 snd_sof_dbg_print_fw_state(sdev, level); 636 sof_ops(sdev)->dbg_dump(sdev, flags); 637 dev_printk(level, sdev->dev, 638 "------------[ DSP dump end ]------------\n"); 639 if (!print_all) 640 sdev->dbg_dump_printed = true; 641 } else if (msg) { 642 dev_printk(level, sdev->dev, "%s\n", msg); 643 } 644 } 645 EXPORT_SYMBOL(snd_sof_dsp_dbg_dump); 646 647 static void snd_sof_ipc_dump(struct snd_sof_dev *sdev) 648 { 649 if (sof_ops(sdev)->ipc_dump && !sdev->ipc_dump_printed) { 650 dev_err(sdev->dev, "------------[ IPC dump start ]------------\n"); 651 sof_ops(sdev)->ipc_dump(sdev); 652 dev_err(sdev->dev, "------------[ IPC dump end ]------------\n"); 653 if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS)) 654 sdev->ipc_dump_printed = true; 655 } 656 } 657 658 void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) 659 { 660 if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) || 661 sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) { 662 /* should we prevent DSP entering D3 ? */ 663 if (!sdev->ipc_dump_printed) 664 dev_info(sdev->dev, 665 "preventing DSP entering D3 state to preserve context\n"); 666 pm_runtime_get_noresume(sdev->dev); 667 } 668 669 /* dump vital information to the logs */ 670 snd_sof_ipc_dump(sdev); 671 snd_sof_dsp_dbg_dump(sdev, "Firmware exception", 672 SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); 673 snd_sof_trace_notify_for_error(sdev); 674 } 675 EXPORT_SYMBOL(snd_sof_handle_fw_exception); 676