Commit | Line | Data |
---|---|---|
86b02f71 LG |
1 | // SPDX-License-Identifier: (GPL-2.0 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 "sof-priv.h" | |
18 | #include "ops.h" | |
19 | ||
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 | ||
31 | size = dfse->size; | |
32 | ||
33 | /* validate position & count */ | |
34 | if (pos < 0) | |
35 | return -EINVAL; | |
36 | if (pos >= size || !count) | |
37 | return 0; | |
38 | /* find the minimum. min() is not used since it adds sparse warnings */ | |
39 | if (count > size - pos) | |
40 | count = size - pos; | |
41 | ||
42 | /* align io read start to u32 multiple */ | |
43 | pos = ALIGN_DOWN(pos, 4); | |
44 | ||
45 | /* intermediate buffer size must be u32 multiple */ | |
46 | size = ALIGN(count, 4); | |
47 | ||
48 | /* if start position is unaligned, read extra u32 */ | |
49 | if (unlikely(pos != *ppos)) { | |
50 | skip = *ppos - pos; | |
51 | if (pos + size + 4 < dfse->size) | |
52 | size += 4; | |
53 | } | |
54 | ||
55 | buf = kzalloc(size, GFP_KERNEL); | |
56 | if (!buf) | |
57 | return -ENOMEM; | |
58 | ||
59 | if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) { | |
60 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) | |
61 | /* | |
62 | * If the DSP is active: copy from IO. | |
63 | * If the DSP is suspended: | |
64 | * - Copy from IO if the memory is always accessible. | |
65 | * - Otherwise, copy from cached buffer. | |
66 | */ | |
67 | if (pm_runtime_active(sdev->dev) || | |
68 | dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) { | |
69 | memcpy_fromio(buf, dfse->io_mem + pos, size); | |
70 | } else { | |
71 | dev_info(sdev->dev, | |
72 | "Copying cached debugfs data\n"); | |
73 | memcpy(buf, dfse->cache_buf + pos, size); | |
74 | } | |
75 | #else | |
76 | /* if the DSP is in D3 */ | |
77 | if (!pm_runtime_active(sdev->dev) && | |
78 | dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { | |
79 | dev_err(sdev->dev, | |
80 | "error: debugfs entry %s cannot be read in DSP D3\n", | |
81 | dfse->dfsentry->d_name.name); | |
82 | kfree(buf); | |
83 | return -EINVAL; | |
84 | } | |
85 | ||
86 | memcpy_fromio(buf, dfse->io_mem + pos, size); | |
87 | #endif | |
88 | } else { | |
89 | memcpy(buf, ((u8 *)(dfse->buf) + pos), size); | |
90 | } | |
91 | ||
92 | /* copy to userspace */ | |
93 | size_ret = copy_to_user(buffer, buf + skip, count); | |
94 | ||
95 | kfree(buf); | |
96 | ||
97 | /* update count & position if copy succeeded */ | |
98 | if (size_ret) | |
99 | return -EFAULT; | |
100 | ||
101 | *ppos = pos + count; | |
102 | ||
103 | return count; | |
104 | } | |
105 | ||
106 | static const struct file_operations sof_dfs_fops = { | |
107 | .open = simple_open, | |
108 | .read = sof_dfsentry_read, | |
109 | .llseek = default_llseek, | |
110 | }; | |
111 | ||
112 | /* create FS entry for debug files that can expose DSP memories, registers */ | |
113 | int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, | |
114 | void __iomem *base, size_t size, | |
115 | const char *name, | |
116 | enum sof_debugfs_access_type access_type) | |
117 | { | |
118 | struct snd_sof_dfsentry *dfse; | |
119 | ||
120 | if (!sdev) | |
121 | return -EINVAL; | |
122 | ||
123 | dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); | |
124 | if (!dfse) | |
125 | return -ENOMEM; | |
126 | ||
127 | dfse->type = SOF_DFSENTRY_TYPE_IOMEM; | |
128 | dfse->io_mem = base; | |
129 | dfse->size = size; | |
130 | dfse->sdev = sdev; | |
131 | dfse->access_type = access_type; | |
132 | ||
133 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) | |
134 | /* | |
135 | * allocate cache buffer that will be used to save the mem window | |
136 | * contents prior to suspend | |
137 | */ | |
138 | if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { | |
139 | dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL); | |
140 | if (!dfse->cache_buf) | |
141 | return -ENOMEM; | |
142 | } | |
143 | #endif | |
144 | ||
145 | dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, | |
146 | dfse, &sof_dfs_fops); | |
147 | if (!dfse->dfsentry) { | |
148 | /* can't rely on debugfs, only log error and keep going */ | |
149 | dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", | |
150 | name); | |
151 | } else { | |
152 | /* add to dfsentry list */ | |
153 | list_add(&dfse->list, &sdev->dfsentry_list); | |
154 | ||
155 | } | |
156 | ||
157 | return 0; | |
158 | } | |
159 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item); | |
160 | ||
161 | /* create FS entry for debug files to expose kernel memory */ | |
162 | int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, | |
163 | void *base, size_t size, | |
164 | const char *name) | |
165 | { | |
166 | struct snd_sof_dfsentry *dfse; | |
167 | ||
168 | if (!sdev) | |
169 | return -EINVAL; | |
170 | ||
171 | dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); | |
172 | if (!dfse) | |
173 | return -ENOMEM; | |
174 | ||
175 | dfse->type = SOF_DFSENTRY_TYPE_BUF; | |
176 | dfse->buf = base; | |
177 | dfse->size = size; | |
178 | dfse->sdev = sdev; | |
179 | ||
180 | dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, | |
181 | dfse, &sof_dfs_fops); | |
182 | if (!dfse->dfsentry) { | |
183 | /* can't rely on debugfs, only log error and keep going */ | |
184 | dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", | |
185 | name); | |
186 | } else { | |
187 | /* add to dfsentry list */ | |
188 | list_add(&dfse->list, &sdev->dfsentry_list); | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item); | |
194 | ||
195 | int snd_sof_dbg_init(struct snd_sof_dev *sdev) | |
196 | { | |
197 | const struct snd_sof_dsp_ops *ops = sof_ops(sdev); | |
198 | const struct snd_sof_debugfs_map *map; | |
199 | int i; | |
200 | int err; | |
201 | ||
202 | /* use "sof" as top level debugFS dir */ | |
203 | sdev->debugfs_root = debugfs_create_dir("sof", NULL); | |
204 | if (IS_ERR_OR_NULL(sdev->debugfs_root)) { | |
205 | dev_err(sdev->dev, "error: failed to create debugfs directory\n"); | |
206 | return 0; | |
207 | } | |
208 | ||
209 | /* init dfsentry list */ | |
210 | INIT_LIST_HEAD(&sdev->dfsentry_list); | |
211 | ||
212 | /* create debugFS files for platform specific MMIO/DSP memories */ | |
213 | for (i = 0; i < ops->debug_map_count; i++) { | |
214 | map = &ops->debug_map[i]; | |
215 | ||
216 | err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] + | |
217 | map->offset, map->size, | |
218 | map->name, map->access_type); | |
219 | /* errors are only due to memory allocation, not debugfs */ | |
220 | if (err < 0) | |
221 | return err; | |
222 | } | |
223 | ||
224 | return 0; | |
225 | } | |
226 | EXPORT_SYMBOL_GPL(snd_sof_dbg_init); | |
227 | ||
228 | void snd_sof_free_debug(struct snd_sof_dev *sdev) | |
229 | { | |
230 | debugfs_remove_recursive(sdev->debugfs_root); | |
231 | } | |
232 | EXPORT_SYMBOL_GPL(snd_sof_free_debug); |