Commit | Line | Data |
---|---|---|
e149ca29 | 1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
86b02f71 LG |
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> | |
5b10b629 KT |
17 | #include <sound/sof/ext_manifest.h> |
18 | #include <sound/sof/debug.h> | |
86b02f71 LG |
19 | #include "sof-priv.h" |
20 | #include "ops.h" | |
21 | ||
091c12e1 RS |
22 | static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, |
23 | size_t count, loff_t *ppos) | |
24 | { | |
091c12e1 RS |
25 | size_t size; |
26 | char *string; | |
27 | int ret; | |
28 | ||
9037c3bd | 29 | string = kzalloc(count+1, GFP_KERNEL); |
091c12e1 RS |
30 | if (!string) |
31 | return -ENOMEM; | |
32 | ||
33 | size = simple_write_to_buffer(string, count, ppos, buffer, count); | |
34 | ret = size; | |
35 | ||
091c12e1 RS |
36 | kfree(string); |
37 | return ret; | |
38 | } | |
39 | ||
86b02f71 LG |
40 | static 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, | |
3ff3a4f6 | 100 | "error: debugfs entry cannot be read in DSP D3\n"); |
86b02f71 LG |
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 | ||
125 | static const struct file_operations sof_dfs_fops = { | |
126 | .open = simple_open, | |
127 | .read = sof_dfsentry_read, | |
128 | .llseek = default_llseek, | |
091c12e1 | 129 | .write = sof_dfsentry_write, |
86b02f71 LG |
130 | }; |
131 | ||
132 | /* create FS entry for debug files that can expose DSP memories, registers */ | |
bde4f08c PU |
133 | static 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) | |
86b02f71 LG |
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 | ||
3ff3a4f6 GKH |
165 | debugfs_create_file(name, 0444, sdev->debugfs_root, dfse, |
166 | &sof_dfs_fops); | |
86b02f71 | 167 | |
3ff3a4f6 GKH |
168 | /* add to dfsentry list */ |
169 | list_add(&dfse->list, &sdev->dfsentry_list); | |
86b02f71 LG |
170 | |
171 | return 0; | |
172 | } | |
86b02f71 | 173 | |
07e833b4 PU |
174 | int 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 | } | |
187 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem); | |
188 | ||
86b02f71 LG |
189 | /* create FS entry for debug files to expose kernel memory */ |
190 | int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, | |
191 | void *base, size_t size, | |
5c9714f6 | 192 | const char *name, mode_t mode) |
86b02f71 LG |
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 | ||
3ff3a4f6 GKH |
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); | |
86b02f71 LG |
212 | |
213 | return 0; | |
214 | } | |
215 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item); | |
216 | ||
5b10b629 KT |
217 | static 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 | ||
c106f46e | 232 | ret = pm_runtime_resume_and_get(sdev->dev); |
5b10b629 | 233 | if (ret < 0 && ret != -EACCES) { |
5b10b629 KT |
234 | dev_err(sdev->dev, "error: enabling device failed: %d\n", ret); |
235 | goto error; | |
236 | } | |
237 | ||
2a51c0f8 | 238 | ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE); |
5b10b629 KT |
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++) { | |
1eb123ce TI |
255 | ret = scnprintf(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); | |
5b10b629 KT |
258 | if (ret < 0) |
259 | goto error; | |
260 | len += ret; | |
261 | } | |
262 | ||
263 | ret = len; | |
264 | error: | |
265 | kfree(reply); | |
266 | return ret; | |
267 | } | |
268 | ||
269 | static 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 | ||
287 | static 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 | ||
305 | static const struct file_operations memory_info_fops = { | |
306 | .open = memory_info_open, | |
307 | .read = memory_info_read, | |
308 | .llseek = default_llseek, | |
309 | }; | |
310 | ||
311 | int 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 | } | |
329 | EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init); | |
330 | ||
86b02f71 LG |
331 | int snd_sof_dbg_init(struct snd_sof_dev *sdev) |
332 | { | |
856601e5 | 333 | struct snd_sof_dsp_ops *ops = sof_ops(sdev); |
86b02f71 LG |
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); | |
86b02f71 LG |
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 | ||
9a9134fd CM |
356 | return snd_sof_debugfs_buf_item(sdev, &sdev->fw_state, |
357 | sizeof(sdev->fw_state), | |
358 | "fw_state", 0444); | |
86b02f71 LG |
359 | } |
360 | EXPORT_SYMBOL_GPL(snd_sof_dbg_init); | |
361 | ||
362 | void snd_sof_free_debug(struct snd_sof_dev *sdev) | |
363 | { | |
364 | debugfs_remove_recursive(sdev->debugfs_root); | |
365 | } | |
366 | EXPORT_SYMBOL_GPL(snd_sof_free_debug); | |
9a06508b | 367 | |
c05ec071 | 368 | static const struct soc_fw_state_info { |
d41607d3 | 369 | enum sof_fw_state state; |
c05ec071 PU |
370 | const char *name; |
371 | } fw_state_dbg[] = { | |
372 | {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"}, | |
373 | {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"}, | |
374 | {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"}, | |
375 | {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"}, | |
376 | {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"}, | |
b2e9eb3a | 377 | {SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK"}, |
c05ec071 | 378 | {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"}, |
4e1f8648 | 379 | {SOF_FW_CRASHED, "SOF_FW_CRASHED"}, |
c05ec071 PU |
380 | }; |
381 | ||
0152b8a2 | 382 | static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level) |
c05ec071 PU |
383 | { |
384 | int i; | |
385 | ||
386 | for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) { | |
387 | if (sdev->fw_state == fw_state_dbg[i].state) { | |
0152b8a2 PU |
388 | dev_printk(level, sdev->dev, "fw_state: %s (%d)\n", |
389 | fw_state_dbg[i].name, i); | |
c05ec071 PU |
390 | return; |
391 | } | |
392 | } | |
393 | ||
0152b8a2 | 394 | dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state); |
c05ec071 PU |
395 | } |
396 | ||
2f148430 | 397 | void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags) |
360fa323 | 398 | { |
91888125 | 399 | char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR; |
12b401f4 | 400 | bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS); |
34346a38 PU |
401 | |
402 | if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all) | |
403 | return; | |
404 | ||
360fa323 | 405 | if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) { |
0152b8a2 PU |
406 | dev_printk(level, sdev->dev, |
407 | "------------[ DSP dump start ]------------\n"); | |
2f148430 | 408 | if (msg) |
0152b8a2 PU |
409 | dev_printk(level, sdev->dev, "%s\n", msg); |
410 | snd_sof_dbg_print_fw_state(sdev, level); | |
360fa323 | 411 | sof_ops(sdev)->dbg_dump(sdev, flags); |
0152b8a2 PU |
412 | dev_printk(level, sdev->dev, |
413 | "------------[ DSP dump end ]------------\n"); | |
34346a38 | 414 | if (!print_all) |
360fa323 | 415 | sdev->dbg_dump_printed = true; |
2f148430 | 416 | } else if (msg) { |
0152b8a2 | 417 | dev_printk(level, sdev->dev, "%s\n", msg); |
360fa323 PU |
418 | } |
419 | } | |
420 | EXPORT_SYMBOL(snd_sof_dsp_dbg_dump); | |
421 | ||
422 | static void snd_sof_ipc_dump(struct snd_sof_dev *sdev) | |
423 | { | |
424 | if (sof_ops(sdev)->ipc_dump && !sdev->ipc_dump_printed) { | |
425 | dev_err(sdev->dev, "------------[ IPC dump start ]------------\n"); | |
426 | sof_ops(sdev)->ipc_dump(sdev); | |
427 | dev_err(sdev->dev, "------------[ IPC dump end ]------------\n"); | |
12b401f4 | 428 | if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS)) |
360fa323 PU |
429 | sdev->ipc_dump_printed = true; |
430 | } | |
431 | } | |
432 | ||
145cb4e7 | 433 | void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg) |
9a06508b LG |
434 | { |
435 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) || | |
12b401f4 | 436 | sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) { |
9a06508b | 437 | /* should we prevent DSP entering D3 ? */ |
9ff90859 PU |
438 | if (!sdev->ipc_dump_printed) |
439 | dev_info(sdev->dev, | |
440 | "preventing DSP entering D3 state to preserve context\n"); | |
9a06508b LG |
441 | pm_runtime_get_noresume(sdev->dev); |
442 | } | |
443 | ||
444 | /* dump vital information to the logs */ | |
9a06508b | 445 | snd_sof_ipc_dump(sdev); |
145cb4e7 | 446 | snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); |
1dedbe4f | 447 | sof_fw_trace_fw_crashed(sdev); |
9a06508b LG |
448 | } |
449 | EXPORT_SYMBOL(snd_sof_handle_fw_exception); |