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 | |
22 | static 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(size: count+1, GFP_KERNEL); |
30 | if (!string) |
31 | return -ENOMEM; |
32 | |
33 | size = simple_write_to_buffer(to: string, available: count, ppos, from: buffer, count); |
34 | ret = size; |
35 | |
36 | kfree(objp: string); |
37 | return ret; |
38 | } |
39 | |
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(dev: 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(to: buffer, from: buf + skip, n: count); |
113 | |
114 | kfree(objp: 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, |
129 | .write = sof_dfsentry_write, |
130 | }; |
131 | |
132 | /* create FS entry for debug files that can expose DSP memories, registers */ |
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) |
137 | { |
138 | struct snd_sof_dfsentry *dfse; |
139 | |
140 | if (!sdev) |
141 | return -EINVAL; |
142 | |
143 | dfse = devm_kzalloc(dev: sdev->dev, size: 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(dev: sdev->dev, size, GFP_KERNEL); |
160 | if (!dfse->cache_buf) |
161 | return -ENOMEM; |
162 | } |
163 | #endif |
164 | |
165 | debugfs_create_file(name, mode: 0444, parent: sdev->debugfs_root, data: dfse, |
166 | fops: &sof_dfs_fops); |
167 | |
168 | /* add to dfsentry list */ |
169 | list_add(new: &dfse->list, head: &sdev->dfsentry_list); |
170 | |
171 | return 0; |
172 | } |
173 | |
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, type: blk_type); |
180 | |
181 | if (bar < 0) |
182 | return bar; |
183 | |
184 | return snd_sof_debugfs_io_item(sdev, base: sdev->bar[bar] + offset, size, name, |
185 | access_type); |
186 | } |
187 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem); |
188 | |
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, |
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(dev: sdev->dev, size: 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, parent: sdev->debugfs_root, data: dfse, |
209 | fops: &sof_dfs_fops); |
210 | /* add to dfsentry list */ |
211 | list_add(new: &dfse->list, head: &sdev->dfsentry_list); |
212 | |
213 | return 0; |
214 | } |
215 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item); |
216 | |
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 | |
232 | ret = pm_runtime_resume_and_get(dev: 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(ipc: sdev->ipc, msg_data: &msg, msg_bytes: msg.size, reply_data: reply, SOF_IPC_MSG_MAX_SIZE); |
239 | pm_runtime_mark_last_busy(dev: sdev->dev); |
240 | pm_runtime_put_autosuspend(dev: 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 = scnprintf(buf: buf + len, size: buff_size - len, fmt: "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; |
264 | error: |
265 | kfree(objp: 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, buf: dfse->buf, buff_size: 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, from: dfse->buf, available: 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(dev: 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(dev: sdev->dev, size: 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(name: "memory_info" , mode: 0444, parent: sdev->debugfs_root, data: dfse, fops: &memory_info_fops); |
324 | |
325 | /* add to dfsentry list */ |
326 | list_add(new: &dfse->list, head: &sdev->dfsentry_list); |
327 | return 0; |
328 | } |
329 | EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init); |
330 | |
331 | int 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(name: "sof" , NULL); |
340 | |
341 | /* init dfsentry list */ |
342 | INIT_LIST_HEAD(list: &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, base: sdev->bar[map->bar] + |
349 | map->offset, size: map->size, |
350 | name: map->name, access_type: map->access_type); |
351 | /* errors are only due to memory allocation, not debugfs */ |
352 | if (err < 0) |
353 | return err; |
354 | } |
355 | |
356 | return snd_sof_debugfs_buf_item(sdev, &sdev->fw_state, |
357 | sizeof(sdev->fw_state), |
358 | "fw_state" , 0444); |
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(dentry: sdev->debugfs_root); |
365 | } |
366 | EXPORT_SYMBOL_GPL(snd_sof_free_debug); |
367 | |
368 | static const struct soc_fw_state_info { |
369 | enum sof_fw_state state; |
370 | const char *name; |
371 | } fw_state_dbg[] = { |
372 | {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED" }, |
373 | {SOF_DSPLESS_MODE, "SOF_DSPLESS_MODE" }, |
374 | {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE" }, |
375 | {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS" }, |
376 | {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED" }, |
377 | {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED" }, |
378 | {SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK" }, |
379 | {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE" }, |
380 | {SOF_FW_CRASHED, "SOF_FW_CRASHED" }, |
381 | }; |
382 | |
383 | static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level) |
384 | { |
385 | int i; |
386 | |
387 | for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) { |
388 | if (sdev->fw_state == fw_state_dbg[i].state) { |
389 | dev_printk(level, sdev->dev, "fw_state: %s (%d)\n" , |
390 | fw_state_dbg[i].name, i); |
391 | return; |
392 | } |
393 | } |
394 | |
395 | dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n" , sdev->fw_state); |
396 | } |
397 | |
398 | void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags) |
399 | { |
400 | char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR; |
401 | bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS); |
402 | |
403 | if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all) |
404 | return; |
405 | |
406 | if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) { |
407 | dev_printk(level, sdev->dev, |
408 | "------------[ DSP dump start ]------------\n" ); |
409 | if (msg) |
410 | dev_printk(level, sdev->dev, "%s\n" , msg); |
411 | snd_sof_dbg_print_fw_state(sdev, level); |
412 | sof_ops(sdev)->dbg_dump(sdev, flags); |
413 | dev_printk(level, sdev->dev, |
414 | "------------[ DSP dump end ]------------\n" ); |
415 | if (!print_all) |
416 | sdev->dbg_dump_printed = true; |
417 | } else if (msg) { |
418 | dev_printk(level, sdev->dev, "%s\n" , msg); |
419 | } |
420 | } |
421 | EXPORT_SYMBOL(snd_sof_dsp_dbg_dump); |
422 | |
423 | static void snd_sof_ipc_dump(struct snd_sof_dev *sdev) |
424 | { |
425 | if (sof_ops(sdev)->ipc_dump && !sdev->ipc_dump_printed) { |
426 | dev_err(sdev->dev, "------------[ IPC dump start ]------------\n" ); |
427 | sof_ops(sdev)->ipc_dump(sdev); |
428 | dev_err(sdev->dev, "------------[ IPC dump end ]------------\n" ); |
429 | if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS)) |
430 | sdev->ipc_dump_printed = true; |
431 | } |
432 | } |
433 | |
434 | void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg) |
435 | { |
436 | if ((IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) || |
437 | sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) && !sdev->d3_prevented) { |
438 | /* should we prevent DSP entering D3 ? */ |
439 | if (!sdev->ipc_dump_printed) |
440 | dev_info(sdev->dev, |
441 | "Attempting to prevent DSP from entering D3 state to preserve context\n" ); |
442 | |
443 | if (pm_runtime_get_if_in_use(dev: sdev->dev) == 1) |
444 | sdev->d3_prevented = true; |
445 | } |
446 | |
447 | /* dump vital information to the logs */ |
448 | snd_sof_ipc_dump(sdev); |
449 | snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); |
450 | sof_fw_trace_fw_crashed(sdev); |
451 | } |
452 | EXPORT_SYMBOL(snd_sof_handle_fw_exception); |
453 | |