471 lines · c
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 or4// redistributing this file, you may do so under either license.5//6// Copyright(c) 2018 Intel Corporation7//8// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>9//10// Generic debug routines used to export DSP MMIO and memories to userspace11// 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 22static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer,23 size_t count, loff_t *ppos)24{25 struct snd_sof_dfsentry *dfse = file->private_data;26 struct snd_sof_dev *sdev = dfse->sdev;27 loff_t pos = *ppos;28 size_t size_ret;29 int skip = 0;30 int size;31 u8 *buf;32 33 size = dfse->size;34 35 /* validate position & count */36 if (pos < 0)37 return -EINVAL;38 if (pos >= size || !count)39 return 0;40 /* find the minimum. min() is not used since it adds sparse warnings */41 if (count > size - pos)42 count = size - pos;43 44 /* align io read start to u32 multiple */45 pos = ALIGN_DOWN(pos, 4);46 47 /* intermediate buffer size must be u32 multiple */48 size = ALIGN(count, 4);49 50 /* if start position is unaligned, read extra u32 */51 if (unlikely(pos != *ppos)) {52 skip = *ppos - pos;53 if (pos + size + 4 < dfse->size)54 size += 4;55 }56 57 buf = kzalloc(size, GFP_KERNEL);58 if (!buf)59 return -ENOMEM;60 61 if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) {62#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)63 /*64 * If the DSP is active: copy from IO.65 * If the DSP is suspended:66 * - Copy from IO if the memory is always accessible.67 * - Otherwise, copy from cached buffer.68 */69 if (pm_runtime_active(sdev->dev) ||70 dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) {71 memcpy_fromio(buf, dfse->io_mem + pos, size);72 } else {73 dev_info(sdev->dev,74 "Copying cached debugfs data\n");75 memcpy(buf, dfse->cache_buf + pos, size);76 }77#else78 /* if the DSP is in D3 */79 if (!pm_runtime_active(sdev->dev) &&80 dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {81 dev_err(sdev->dev,82 "error: debugfs entry cannot be read in DSP D3\n");83 kfree(buf);84 return -EINVAL;85 }86 87 memcpy_fromio(buf, dfse->io_mem + pos, size);88#endif89 } else {90 memcpy(buf, ((u8 *)(dfse->buf) + pos), size);91 }92 93 /* copy to userspace */94 size_ret = copy_to_user(buffer, buf + skip, count);95 96 kfree(buf);97 98 /* update count & position if copy succeeded */99 if (size_ret)100 return -EFAULT;101 102 *ppos = pos + count;103 104 return count;105}106 107static const struct file_operations sof_dfs_fops = {108 .open = simple_open,109 .read = sof_dfsentry_read,110 .llseek = default_llseek,111};112 113/* create FS entry for debug files that can expose DSP memories, registers */114static int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,115 void __iomem *base, size_t size,116 const char *name,117 enum sof_debugfs_access_type access_type)118{119 struct snd_sof_dfsentry *dfse;120 121 if (!sdev)122 return -EINVAL;123 124 dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);125 if (!dfse)126 return -ENOMEM;127 128 dfse->type = SOF_DFSENTRY_TYPE_IOMEM;129 dfse->io_mem = base;130 dfse->size = size;131 dfse->sdev = sdev;132 dfse->access_type = access_type;133 134#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)135 /*136 * allocate cache buffer that will be used to save the mem window137 * contents prior to suspend138 */139 if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {140 dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL);141 if (!dfse->cache_buf)142 return -ENOMEM;143 }144#endif145 146 debugfs_create_file(name, 0444, sdev->debugfs_root, dfse,147 &sof_dfs_fops);148 149 /* add to dfsentry list */150 list_add(&dfse->list, &sdev->dfsentry_list);151 152 return 0;153}154 155int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev,156 enum snd_sof_fw_blk_type blk_type, u32 offset,157 size_t size, const char *name,158 enum sof_debugfs_access_type access_type)159{160 int bar = snd_sof_dsp_get_bar_index(sdev, blk_type);161 162 if (bar < 0)163 return bar;164 165 return snd_sof_debugfs_io_item(sdev, sdev->bar[bar] + offset, size, name,166 access_type);167}168EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem);169 170/* create FS entry for debug files to expose kernel memory */171int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,172 void *base, size_t size,173 const char *name, mode_t mode)174{175 struct snd_sof_dfsentry *dfse;176 177 if (!sdev)178 return -EINVAL;179 180 dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);181 if (!dfse)182 return -ENOMEM;183 184 dfse->type = SOF_DFSENTRY_TYPE_BUF;185 dfse->buf = base;186 dfse->size = size;187 dfse->sdev = sdev;188 189 debugfs_create_file(name, mode, sdev->debugfs_root, dfse,190 &sof_dfs_fops);191 /* add to dfsentry list */192 list_add(&dfse->list, &sdev->dfsentry_list);193 194 return 0;195}196EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item);197 198static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_size)199{200 struct sof_ipc_cmd_hdr msg = {201 .size = sizeof(struct sof_ipc_cmd_hdr),202 .cmd = SOF_IPC_GLB_DEBUG | SOF_IPC_DEBUG_MEM_USAGE,203 };204 struct sof_ipc_dbg_mem_usage *reply;205 int len;206 int ret;207 int i;208 209 reply = kmalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL);210 if (!reply)211 return -ENOMEM;212 213 ret = pm_runtime_resume_and_get(sdev->dev);214 if (ret < 0 && ret != -EACCES) {215 dev_err(sdev->dev, "error: enabling device failed: %d\n", ret);216 goto error;217 }218 219 ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE);220 pm_runtime_mark_last_busy(sdev->dev);221 pm_runtime_put_autosuspend(sdev->dev);222 if (ret < 0 || reply->rhdr.error < 0) {223 ret = min(ret, reply->rhdr.error);224 dev_err(sdev->dev, "error: reading memory info failed, %d\n", ret);225 goto error;226 }227 228 if (struct_size(reply, elems, reply->num_elems) != reply->rhdr.hdr.size) {229 dev_err(sdev->dev, "error: invalid memory info ipc struct size, %d\n",230 reply->rhdr.hdr.size);231 ret = -EINVAL;232 goto error;233 }234 235 for (i = 0, len = 0; i < reply->num_elems; i++) {236 ret = scnprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n",237 reply->elems[i].zone, reply->elems[i].id,238 reply->elems[i].used, reply->elems[i].free);239 if (ret < 0)240 goto error;241 len += ret;242 }243 244 ret = len;245error:246 kfree(reply);247 return ret;248}249 250static ssize_t memory_info_read(struct file *file, char __user *to, size_t count, loff_t *ppos)251{252 struct snd_sof_dfsentry *dfse = file->private_data;253 struct snd_sof_dev *sdev = dfse->sdev;254 int data_length;255 256 /* read memory info from FW only once for each file read */257 if (!*ppos) {258 dfse->buf_data_size = 0;259 data_length = memory_info_update(sdev, dfse->buf, dfse->size);260 if (data_length < 0)261 return data_length;262 dfse->buf_data_size = data_length;263 }264 265 return simple_read_from_buffer(to, count, ppos, dfse->buf, dfse->buf_data_size);266}267 268static int memory_info_open(struct inode *inode, struct file *file)269{270 struct snd_sof_dfsentry *dfse = inode->i_private;271 struct snd_sof_dev *sdev = dfse->sdev;272 273 file->private_data = dfse;274 275 /* allocate buffer memory only in first open run, to save memory when unused */276 if (!dfse->buf) {277 dfse->buf = devm_kmalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL);278 if (!dfse->buf)279 return -ENOMEM;280 dfse->size = PAGE_SIZE;281 }282 283 return 0;284}285 286static const struct file_operations memory_info_fops = {287 .open = memory_info_open,288 .read = memory_info_read,289 .llseek = default_llseek,290};291 292int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev)293{294 struct snd_sof_dfsentry *dfse;295 296 dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);297 if (!dfse)298 return -ENOMEM;299 300 /* don't allocate buffer before first usage, to save memory when unused */301 dfse->type = SOF_DFSENTRY_TYPE_BUF;302 dfse->sdev = sdev;303 304 debugfs_create_file("memory_info", 0444, sdev->debugfs_root, dfse, &memory_info_fops);305 306 /* add to dfsentry list */307 list_add(&dfse->list, &sdev->dfsentry_list);308 return 0;309}310EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init);311 312int snd_sof_dbg_init(struct snd_sof_dev *sdev)313{314 const struct snd_sof_dsp_ops *ops = sof_ops(sdev);315 struct snd_sof_pdata *plat_data = sdev->pdata;316 const struct snd_sof_debugfs_map *map;317 struct dentry *fw_profile;318 int i;319 int err;320 321 /* use "sof" as top level debugFS dir */322 sdev->debugfs_root = debugfs_create_dir("sof", NULL);323 324 /* expose firmware/topology prefix/names for test purposes */325 fw_profile = debugfs_create_dir("fw_profile", sdev->debugfs_root);326 327 debugfs_create_str("fw_path", 0444, fw_profile,328 (char **)&plat_data->fw_filename_prefix);329 /* library path is not valid for IPC3 */330 if (plat_data->ipc_type != SOF_IPC_TYPE_3) {331 /*332 * fw_lib_prefix can be NULL if the vendor/platform does not333 * support loadable libraries334 */335 if (plat_data->fw_lib_prefix) {336 debugfs_create_str("fw_lib_path", 0444, fw_profile,337 (char **)&plat_data->fw_lib_prefix);338 } else {339 static char *fw_lib_path;340 341 fw_lib_path = devm_kasprintf(sdev->dev, GFP_KERNEL,342 "Not supported");343 if (!fw_lib_path)344 return -ENOMEM;345 346 debugfs_create_str("fw_lib_path", 0444, fw_profile,347 (char **)&fw_lib_path);348 }349 }350 debugfs_create_str("tplg_path", 0444, fw_profile,351 (char **)&plat_data->tplg_filename_prefix);352 debugfs_create_str("fw_name", 0444, fw_profile,353 (char **)&plat_data->fw_filename);354 debugfs_create_str("tplg_name", 0444, fw_profile,355 (char **)&plat_data->tplg_filename);356 debugfs_create_u32("ipc_type", 0444, fw_profile,357 (u32 *)&plat_data->ipc_type);358 359 /* init dfsentry list */360 INIT_LIST_HEAD(&sdev->dfsentry_list);361 362 /* create debugFS files for platform specific MMIO/DSP memories */363 for (i = 0; i < ops->debug_map_count; i++) {364 map = &ops->debug_map[i];365 366 err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] +367 map->offset, map->size,368 map->name, map->access_type);369 /* errors are only due to memory allocation, not debugfs */370 if (err < 0)371 return err;372 }373 374 return snd_sof_debugfs_buf_item(sdev, &sdev->fw_state,375 sizeof(sdev->fw_state),376 "fw_state", 0444);377}378EXPORT_SYMBOL_GPL(snd_sof_dbg_init);379 380void snd_sof_free_debug(struct snd_sof_dev *sdev)381{382 debugfs_remove_recursive(sdev->debugfs_root);383}384EXPORT_SYMBOL_GPL(snd_sof_free_debug);385 386static const struct soc_fw_state_info {387 enum sof_fw_state state;388 const char *name;389} fw_state_dbg[] = {390 {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"},391 {SOF_DSPLESS_MODE, "SOF_DSPLESS_MODE"},392 {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"},393 {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"},394 {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"},395 {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"},396 {SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK"},397 {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"},398 {SOF_FW_CRASHED, "SOF_FW_CRASHED"},399};400 401static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level)402{403 int i;404 405 for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) {406 if (sdev->fw_state == fw_state_dbg[i].state) {407 dev_printk(level, sdev->dev, "fw_state: %s (%d)\n",408 fw_state_dbg[i].name, i);409 return;410 }411 }412 413 dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state);414}415 416void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags)417{418 char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;419 bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS);420 421 if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all)422 return;423 424 if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) {425 dev_printk(level, sdev->dev,426 "------------[ DSP dump start ]------------\n");427 if (msg)428 dev_printk(level, sdev->dev, "%s\n", msg);429 snd_sof_dbg_print_fw_state(sdev, level);430 sof_ops(sdev)->dbg_dump(sdev, flags);431 dev_printk(level, sdev->dev,432 "------------[ DSP dump end ]------------\n");433 if (!print_all)434 sdev->dbg_dump_printed = true;435 } else if (msg) {436 dev_printk(level, sdev->dev, "%s\n", msg);437 }438}439EXPORT_SYMBOL(snd_sof_dsp_dbg_dump);440 441static void snd_sof_ipc_dump(struct snd_sof_dev *sdev)442{443 if (sof_ops(sdev)->ipc_dump && !sdev->ipc_dump_printed) {444 dev_err(sdev->dev, "------------[ IPC dump start ]------------\n");445 sof_ops(sdev)->ipc_dump(sdev);446 dev_err(sdev->dev, "------------[ IPC dump end ]------------\n");447 if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS))448 sdev->ipc_dump_printed = true;449 }450}451 452void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg)453{454 if ((IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||455 sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) && !sdev->d3_prevented) {456 /* should we prevent DSP entering D3 ? */457 if (!sdev->ipc_dump_printed)458 dev_info(sdev->dev,459 "Attempting to prevent DSP from entering D3 state to preserve context\n");460 461 if (pm_runtime_get_if_in_use(sdev->dev) == 1)462 sdev->d3_prevented = true;463 }464 465 /* dump vital information to the logs */466 snd_sof_ipc_dump(sdev);467 snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);468 sof_fw_trace_fw_crashed(sdev);469}470EXPORT_SYMBOL(snd_sof_handle_fw_exception);471