linux/sound/soc/sof/debug.c
<<
>>
Prefs
   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 "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 */
  30static int
  31strsplit_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
  69exit:
  70        kfree(data);
  71        return ret;
  72}
  73
  74static 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);
  92exit:
  93        kfree(buf);
  94        return ret;
  95}
  96
  97static 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);
 130free_desc:
 131        kfree(desc);
 132exit:
 133        kfree(buf);
 134        return ret;
 135}
 136
 137static 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;
 166exit:
 167        kfree(tkns);
 168        return ret;
 169}
 170
 171static 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
 178static 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;
 203exit:
 204        kfree(tkns);
 205        return ret;
 206}
 207
 208static const struct file_operations probe_points_remove_fops = {
 209        .open = simple_open,
 210        .write = probe_points_remove_write,
 211        .llseek = default_llseek,
 212};
 213
 214static 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
 240static 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
 337static 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;
 430out:
 431#endif
 432        kfree(string);
 433        return ret;
 434}
 435
 436static 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
 540static 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 */
 548int 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}
 588EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item);
 589
 590/* create FS entry for debug files to expose kernel memory */
 591int 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}
 627EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item);
 628
 629int 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}
 685EXPORT_SYMBOL_GPL(snd_sof_dbg_init);
 686
 687void snd_sof_free_debug(struct snd_sof_dev *sdev)
 688{
 689        debugfs_remove_recursive(sdev->debugfs_root);
 690}
 691EXPORT_SYMBOL_GPL(snd_sof_free_debug);
 692
 693void 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}
 707EXPORT_SYMBOL(snd_sof_handle_fw_exception);
 708