linux/sound/soc/sof/trace.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
  11#include <linux/debugfs.h>
  12#include <linux/sched/signal.h>
  13#include "sof-priv.h"
  14#include "ops.h"
  15
  16#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4
  17#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024
  18
  19static int trace_filter_append_elem(struct snd_sof_dev *sdev, uint32_t key, uint32_t value,
  20                                    struct sof_ipc_trace_filter_elem *elem_list,
  21                                    int capacity, int *counter)
  22{
  23        if (*counter >= capacity)
  24                return -ENOMEM;
  25
  26        elem_list[*counter].key = key;
  27        elem_list[*counter].value = value;
  28        ++*counter;
  29
  30        return 0;
  31}
  32
  33static int trace_filter_parse_entry(struct snd_sof_dev *sdev, const char *line,
  34                                    struct sof_ipc_trace_filter_elem *elem,
  35                                    int capacity, int *counter)
  36{
  37        int len = strlen(line);
  38        int cnt = *counter;
  39        uint32_t uuid_id;
  40        int log_level;
  41        int pipe_id;
  42        int comp_id;
  43        int read;
  44        int ret;
  45
  46        /* ignore empty content */
  47        ret = sscanf(line, " %n", &read);
  48        if (!ret && read == len)
  49                return len;
  50
  51        ret = sscanf(line, " %d %x %d %d %n", &log_level, &uuid_id, &pipe_id, &comp_id, &read);
  52        if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) {
  53                dev_err(sdev->dev, "error: invalid trace filter entry '%s'\n", line);
  54                return -EINVAL;
  55        }
  56
  57        if (uuid_id > 0) {
  58                ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_UUID,
  59                                               uuid_id, elem, capacity, &cnt);
  60                if (ret)
  61                        return ret;
  62        }
  63        if (pipe_id >= 0) {
  64                ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE,
  65                                               pipe_id, elem, capacity, &cnt);
  66                if (ret)
  67                        return ret;
  68        }
  69        if (comp_id >= 0) {
  70                ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_COMP,
  71                                               comp_id, elem, capacity, &cnt);
  72                if (ret)
  73                        return ret;
  74        }
  75
  76        ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL |
  77                                       SOF_IPC_TRACE_FILTER_ELEM_FIN,
  78                                       log_level, elem, capacity, &cnt);
  79        if (ret)
  80                return ret;
  81
  82        /* update counter only when parsing whole entry passed */
  83        *counter = cnt;
  84
  85        return len;
  86}
  87
  88static int trace_filter_parse(struct snd_sof_dev *sdev, char *string,
  89                              int *out_elem_cnt,
  90                              struct sof_ipc_trace_filter_elem **out)
  91{
  92        static const char entry_delimiter[] = ";";
  93        char *entry = string;
  94        int capacity = 0;
  95        int entry_len;
  96        int cnt = 0;
  97
  98        /*
  99         * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY
 100         * IPC elements, depending on content. Calculate IPC elements capacity
 101         * for the input string where each element is set.
 102         */
 103        while (entry) {
 104                capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY;
 105                entry = strchr(entry + 1, entry_delimiter[0]);
 106        }
 107        *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL);
 108        if (!*out)
 109                return -ENOMEM;
 110
 111        /* split input string by ';', and parse each entry separately in trace_filter_parse_entry */
 112        while ((entry = strsep(&string, entry_delimiter))) {
 113                entry_len = trace_filter_parse_entry(sdev, entry, *out, capacity, &cnt);
 114                if (entry_len < 0) {
 115                        dev_err(sdev->dev, "error: %s failed for '%s', %d\n", __func__, entry,
 116                                entry_len);
 117                        return -EINVAL;
 118                }
 119        }
 120
 121        *out_elem_cnt = cnt;
 122
 123        return 0;
 124}
 125
 126static int sof_ipc_trace_update_filter(struct snd_sof_dev *sdev, int num_elems,
 127                                       struct sof_ipc_trace_filter_elem *elems)
 128{
 129        struct sof_ipc_trace_filter *msg;
 130        struct sof_ipc_reply reply;
 131        size_t size;
 132        int ret;
 133
 134        size = struct_size(msg, elems, num_elems);
 135        if (size > SOF_IPC_MSG_MAX_SIZE)
 136                return -EINVAL;
 137
 138        msg = kmalloc(size, GFP_KERNEL);
 139        if (!msg)
 140                return -ENOMEM;
 141
 142        msg->hdr.size = size;
 143        msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE;
 144        msg->elem_cnt = num_elems;
 145        memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems));
 146
 147        ret = pm_runtime_get_sync(sdev->dev);
 148        if (ret < 0 && ret != -EACCES) {
 149                pm_runtime_put_noidle(sdev->dev);
 150                dev_err(sdev->dev, "error: enabling device failed: %d\n", ret);
 151                goto error;
 152        }
 153        ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size,
 154                                 &reply, sizeof(reply));
 155        pm_runtime_mark_last_busy(sdev->dev);
 156        pm_runtime_put_autosuspend(sdev->dev);
 157
 158error:
 159        kfree(msg);
 160        return ret ? ret : reply.error;
 161}
 162
 163static ssize_t sof_dfsentry_trace_filter_write(struct file *file, const char __user *from,
 164                                               size_t count, loff_t *ppos)
 165{
 166        struct snd_sof_dfsentry *dfse = file->private_data;
 167        struct sof_ipc_trace_filter_elem *elems = NULL;
 168        struct snd_sof_dev *sdev = dfse->sdev;
 169        loff_t pos = 0;
 170        int num_elems;
 171        char *string;
 172        int ret;
 173
 174        if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) {
 175                dev_err(sdev->dev, "%s too long input, %zu > %d\n", __func__, count,
 176                        TRACE_FILTER_MAX_CONFIG_STRING_LENGTH);
 177                return -EINVAL;
 178        }
 179
 180        string = kmalloc(count + 1, GFP_KERNEL);
 181        if (!string)
 182                return -ENOMEM;
 183
 184        /* assert null termination */
 185        string[count] = 0;
 186        ret = simple_write_to_buffer(string, count, &pos, from, count);
 187        if (ret < 0)
 188                goto error;
 189
 190        ret = trace_filter_parse(sdev, string, &num_elems, &elems);
 191        if (ret < 0) {
 192                dev_err(sdev->dev, "error: fail in trace_filter_parse, %d\n", ret);
 193                goto error;
 194        }
 195
 196        if (num_elems) {
 197                ret = sof_ipc_trace_update_filter(sdev, num_elems, elems);
 198                if (ret < 0) {
 199                        dev_err(sdev->dev, "error: fail in sof_ipc_trace_update_filter %d\n", ret);
 200                        goto error;
 201                }
 202        }
 203        ret = count;
 204error:
 205        kfree(string);
 206        kfree(elems);
 207        return ret;
 208}
 209
 210static const struct file_operations sof_dfs_trace_filter_fops = {
 211        .open = simple_open,
 212        .write = sof_dfsentry_trace_filter_write,
 213        .llseek = default_llseek,
 214};
 215
 216static int trace_debugfs_filter_create(struct snd_sof_dev *sdev)
 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->sdev = sdev;
 225        dfse->type = SOF_DFSENTRY_TYPE_BUF;
 226
 227        debugfs_create_file("filter", 0200, sdev->debugfs_root, dfse,
 228                            &sof_dfs_trace_filter_fops);
 229        /* add to dfsentry list */
 230        list_add(&dfse->list, &sdev->dfsentry_list);
 231
 232        return 0;
 233}
 234
 235static size_t sof_trace_avail(struct snd_sof_dev *sdev,
 236                              loff_t pos, size_t buffer_size)
 237{
 238        loff_t host_offset = READ_ONCE(sdev->host_offset);
 239
 240        /*
 241         * If host offset is less than local pos, it means write pointer of
 242         * host DMA buffer has been wrapped. We should output the trace data
 243         * at the end of host DMA buffer at first.
 244         */
 245        if (host_offset < pos)
 246                return buffer_size - pos;
 247
 248        /* If there is available trace data now, it is unnecessary to wait. */
 249        if (host_offset > pos)
 250                return host_offset - pos;
 251
 252        return 0;
 253}
 254
 255static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev,
 256                                   loff_t pos, size_t buffer_size)
 257{
 258        wait_queue_entry_t wait;
 259        size_t ret = sof_trace_avail(sdev, pos, buffer_size);
 260
 261        /* data immediately available */
 262        if (ret)
 263                return ret;
 264
 265        if (!sdev->dtrace_is_enabled && sdev->dtrace_draining) {
 266                /*
 267                 * tracing has ended and all traces have been
 268                 * read by client, return EOF
 269                 */
 270                sdev->dtrace_draining = false;
 271                return 0;
 272        }
 273
 274        /* wait for available trace data from FW */
 275        init_waitqueue_entry(&wait, current);
 276        set_current_state(TASK_INTERRUPTIBLE);
 277        add_wait_queue(&sdev->trace_sleep, &wait);
 278
 279        if (!signal_pending(current)) {
 280                /* set timeout to max value, no error code */
 281                schedule_timeout(MAX_SCHEDULE_TIMEOUT);
 282        }
 283        remove_wait_queue(&sdev->trace_sleep, &wait);
 284
 285        return sof_trace_avail(sdev, pos, buffer_size);
 286}
 287
 288static ssize_t sof_dfsentry_trace_read(struct file *file, char __user *buffer,
 289                                       size_t count, loff_t *ppos)
 290{
 291        struct snd_sof_dfsentry *dfse = file->private_data;
 292        struct snd_sof_dev *sdev = dfse->sdev;
 293        unsigned long rem;
 294        loff_t lpos = *ppos;
 295        size_t avail, buffer_size = dfse->size;
 296        u64 lpos_64;
 297
 298        /* make sure we know about any failures on the DSP side */
 299        sdev->dtrace_error = false;
 300
 301        /* check pos and count */
 302        if (lpos < 0)
 303                return -EINVAL;
 304        if (!count)
 305                return 0;
 306
 307        /* check for buffer wrap and count overflow */
 308        lpos_64 = lpos;
 309        lpos = do_div(lpos_64, buffer_size);
 310
 311        if (count > buffer_size - lpos) /* min() not used to avoid sparse warnings */
 312                count = buffer_size - lpos;
 313
 314        /* get available count based on current host offset */
 315        avail = sof_wait_trace_avail(sdev, lpos, buffer_size);
 316        if (sdev->dtrace_error) {
 317                dev_err(sdev->dev, "error: trace IO error\n");
 318                return -EIO;
 319        }
 320
 321        /* make sure count is <= avail */
 322        count = avail > count ? count : avail;
 323
 324        /* copy available trace data to debugfs */
 325        rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count);
 326        if (rem)
 327                return -EFAULT;
 328
 329        *ppos += count;
 330
 331        /* move debugfs reading position */
 332        return count;
 333}
 334
 335static int sof_dfsentry_trace_release(struct inode *inode, struct file *file)
 336{
 337        struct snd_sof_dfsentry *dfse = inode->i_private;
 338        struct snd_sof_dev *sdev = dfse->sdev;
 339
 340        /* avoid duplicate traces at next open */
 341        if (!sdev->dtrace_is_enabled)
 342                sdev->host_offset = 0;
 343
 344        return 0;
 345}
 346
 347static const struct file_operations sof_dfs_trace_fops = {
 348        .open = simple_open,
 349        .read = sof_dfsentry_trace_read,
 350        .llseek = default_llseek,
 351        .release = sof_dfsentry_trace_release,
 352};
 353
 354static int trace_debugfs_create(struct snd_sof_dev *sdev)
 355{
 356        struct snd_sof_dfsentry *dfse;
 357        int ret;
 358
 359        if (!sdev)
 360                return -EINVAL;
 361
 362        ret = trace_debugfs_filter_create(sdev);
 363        if (ret < 0)
 364                dev_err(sdev->dev, "error: fail in %s, %d", __func__, ret);
 365
 366        dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
 367        if (!dfse)
 368                return -ENOMEM;
 369
 370        dfse->type = SOF_DFSENTRY_TYPE_BUF;
 371        dfse->buf = sdev->dmatb.area;
 372        dfse->size = sdev->dmatb.bytes;
 373        dfse->sdev = sdev;
 374
 375        dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root,
 376                                             dfse, &sof_dfs_trace_fops);
 377        if (!dfse->dfsentry) {
 378                /* can't rely on debugfs, only log error and keep going */
 379                dev_err(sdev->dev,
 380                        "error: cannot create debugfs entry for trace\n");
 381        }
 382
 383        return 0;
 384}
 385
 386int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev)
 387{
 388        struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
 389        struct sof_ipc_fw_version *v = &ready->version;
 390        struct sof_ipc_dma_trace_params_ext params;
 391        struct sof_ipc_reply ipc_reply;
 392        int ret;
 393
 394        if (!sdev->dtrace_is_supported)
 395                return 0;
 396
 397        if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages)
 398                return -EINVAL;
 399
 400        /* set IPC parameters */
 401        params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG;
 402        /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */
 403        if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) {
 404                params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext);
 405                params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT;
 406                params.timestamp_ns = ktime_get(); /* in nanosecond */
 407        } else {
 408                params.hdr.size = sizeof(struct sof_ipc_dma_trace_params);
 409                params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS;
 410        }
 411        params.buffer.phy_addr = sdev->dmatp.addr;
 412        params.buffer.size = sdev->dmatb.bytes;
 413        params.buffer.pages = sdev->dma_trace_pages;
 414        params.stream_tag = 0;
 415
 416        sdev->host_offset = 0;
 417        sdev->dtrace_draining = false;
 418
 419        ret = snd_sof_dma_trace_init(sdev, &params.stream_tag);
 420        if (ret < 0) {
 421                dev_err(sdev->dev,
 422                        "error: fail in snd_sof_dma_trace_init %d\n", ret);
 423                return ret;
 424        }
 425        dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag);
 426
 427        /* send IPC to the DSP */
 428        ret = sof_ipc_tx_message(sdev->ipc,
 429                                 params.hdr.cmd, &params, sizeof(params),
 430                                 &ipc_reply, sizeof(ipc_reply));
 431        if (ret < 0) {
 432                dev_err(sdev->dev,
 433                        "error: can't set params for DMA for trace %d\n", ret);
 434                goto trace_release;
 435        }
 436
 437        ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START);
 438        if (ret < 0) {
 439                dev_err(sdev->dev,
 440                        "error: snd_sof_dma_trace_trigger: start: %d\n", ret);
 441                goto trace_release;
 442        }
 443
 444        sdev->dtrace_is_enabled = true;
 445
 446        return 0;
 447
 448trace_release:
 449        snd_sof_dma_trace_release(sdev);
 450        return ret;
 451}
 452
 453int snd_sof_init_trace(struct snd_sof_dev *sdev)
 454{
 455        int ret;
 456
 457        if (!sdev->dtrace_is_supported)
 458                return 0;
 459
 460        /* set false before start initialization */
 461        sdev->dtrace_is_enabled = false;
 462
 463        /* allocate trace page table buffer */
 464        ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev,
 465                                  PAGE_SIZE, &sdev->dmatp);
 466        if (ret < 0) {
 467                dev_err(sdev->dev,
 468                        "error: can't alloc page table for trace %d\n", ret);
 469                return ret;
 470        }
 471
 472        /* allocate trace data buffer */
 473        ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev,
 474                                  DMA_BUF_SIZE_FOR_TRACE, &sdev->dmatb);
 475        if (ret < 0) {
 476                dev_err(sdev->dev,
 477                        "error: can't alloc buffer for trace %d\n", ret);
 478                goto page_err;
 479        }
 480
 481        /* create compressed page table for audio firmware */
 482        ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb,
 483                                        sdev->dmatp.area, sdev->dmatb.bytes);
 484        if (ret < 0)
 485                goto table_err;
 486
 487        sdev->dma_trace_pages = ret;
 488        dev_dbg(sdev->dev, "dma_trace_pages: %d\n", sdev->dma_trace_pages);
 489
 490        if (sdev->first_boot) {
 491                ret = trace_debugfs_create(sdev);
 492                if (ret < 0)
 493                        goto table_err;
 494        }
 495
 496        init_waitqueue_head(&sdev->trace_sleep);
 497
 498        ret = snd_sof_init_trace_ipc(sdev);
 499        if (ret < 0)
 500                goto table_err;
 501
 502        return 0;
 503table_err:
 504        sdev->dma_trace_pages = 0;
 505        snd_dma_free_pages(&sdev->dmatb);
 506page_err:
 507        snd_dma_free_pages(&sdev->dmatp);
 508        return ret;
 509}
 510EXPORT_SYMBOL(snd_sof_init_trace);
 511
 512int snd_sof_trace_update_pos(struct snd_sof_dev *sdev,
 513                             struct sof_ipc_dma_trace_posn *posn)
 514{
 515        if (!sdev->dtrace_is_supported)
 516                return 0;
 517
 518        if (sdev->dtrace_is_enabled && sdev->host_offset != posn->host_offset) {
 519                sdev->host_offset = posn->host_offset;
 520                wake_up(&sdev->trace_sleep);
 521        }
 522
 523        if (posn->overflow != 0)
 524                dev_err(sdev->dev,
 525                        "error: DSP trace buffer overflow %u bytes. Total messages %d\n",
 526                        posn->overflow, posn->messages);
 527
 528        return 0;
 529}
 530
 531/* an error has occurred within the DSP that prevents further trace */
 532void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev)
 533{
 534        if (!sdev->dtrace_is_supported)
 535                return;
 536
 537        if (sdev->dtrace_is_enabled) {
 538                dev_err(sdev->dev, "error: waking up any trace sleepers\n");
 539                sdev->dtrace_error = true;
 540                wake_up(&sdev->trace_sleep);
 541        }
 542}
 543EXPORT_SYMBOL(snd_sof_trace_notify_for_error);
 544
 545void snd_sof_release_trace(struct snd_sof_dev *sdev)
 546{
 547        int ret;
 548
 549        if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled)
 550                return;
 551
 552        ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP);
 553        if (ret < 0)
 554                dev_err(sdev->dev,
 555                        "error: snd_sof_dma_trace_trigger: stop: %d\n", ret);
 556
 557        ret = snd_sof_dma_trace_release(sdev);
 558        if (ret < 0)
 559                dev_err(sdev->dev,
 560                        "error: fail in snd_sof_dma_trace_release %d\n", ret);
 561
 562        sdev->dtrace_is_enabled = false;
 563        sdev->dtrace_draining = true;
 564        wake_up(&sdev->trace_sleep);
 565}
 566EXPORT_SYMBOL(snd_sof_release_trace);
 567
 568void snd_sof_free_trace(struct snd_sof_dev *sdev)
 569{
 570        if (!sdev->dtrace_is_supported)
 571                return;
 572
 573        snd_sof_release_trace(sdev);
 574
 575        if (sdev->dma_trace_pages) {
 576                snd_dma_free_pages(&sdev->dmatb);
 577                snd_dma_free_pages(&sdev->dmatp);
 578                sdev->dma_trace_pages = 0;
 579        }
 580}
 581EXPORT_SYMBOL(snd_sof_free_trace);
 582