debug.c 13 KB

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