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