1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Copyright(c) 2022 Intel Corporation. All rights reserved. |
4 | // |
5 | // Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> |
6 | // Peter Ujfalusi <peter.ujfalusi@linux.intel.com> |
7 | // |
8 | |
9 | #include <linux/auxiliary_bus.h> |
10 | #include <linux/completion.h> |
11 | #include <linux/debugfs.h> |
12 | #include <linux/ktime.h> |
13 | #include <linux/mod_devicetable.h> |
14 | #include <linux/module.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/uaccess.h> |
18 | #include <sound/sof/header.h> |
19 | |
20 | #include "sof-client.h" |
21 | |
22 | #define MAX_IPC_FLOOD_DURATION_MS 1000 |
23 | #define MAX_IPC_FLOOD_COUNT 10000 |
24 | #define IPC_FLOOD_TEST_RESULT_LEN 512 |
25 | #define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 |
26 | |
27 | #define DEBUGFS_IPC_FLOOD_COUNT "ipc_flood_count" |
28 | #define DEBUGFS_IPC_FLOOD_DURATION "ipc_flood_duration_ms" |
29 | |
30 | struct sof_ipc_flood_priv { |
31 | struct dentry *dfs_root; |
32 | struct dentry *dfs_link[2]; |
33 | char *buf; |
34 | }; |
35 | |
36 | static int sof_ipc_flood_dfs_open(struct inode *inode, struct file *file) |
37 | { |
38 | struct sof_client_dev *cdev = inode->i_private; |
39 | int ret; |
40 | |
41 | if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) |
42 | return -ENODEV; |
43 | |
44 | ret = debugfs_file_get(dentry: file->f_path.dentry); |
45 | if (unlikely(ret)) |
46 | return ret; |
47 | |
48 | ret = simple_open(inode, file); |
49 | if (ret) |
50 | debugfs_file_put(dentry: file->f_path.dentry); |
51 | |
52 | return ret; |
53 | } |
54 | |
55 | /* |
56 | * helper function to perform the flood test. Only one of the two params, ipc_duration_ms |
57 | * or ipc_count, will be non-zero and will determine the type of test |
58 | */ |
59 | static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev, |
60 | bool flood_duration_test, |
61 | unsigned long ipc_duration_ms, |
62 | unsigned long ipc_count) |
63 | { |
64 | struct sof_ipc_flood_priv *priv = cdev->data; |
65 | struct device *dev = &cdev->auxdev.dev; |
66 | struct sof_ipc_cmd_hdr hdr; |
67 | u64 min_response_time = U64_MAX; |
68 | ktime_t start, end, test_end; |
69 | u64 avg_response_time = 0; |
70 | u64 max_response_time = 0; |
71 | u64 ipc_response_time; |
72 | int i = 0; |
73 | int ret; |
74 | |
75 | /* configure test IPC */ |
76 | hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; |
77 | hdr.size = sizeof(hdr); |
78 | |
79 | /* set test end time for duration flood test */ |
80 | if (flood_duration_test) |
81 | test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; |
82 | |
83 | /* send test IPC's */ |
84 | while (1) { |
85 | start = ktime_get(); |
86 | ret = sof_client_ipc_tx_message_no_reply(cdev, ipc_msg: &hdr); |
87 | end = ktime_get(); |
88 | |
89 | if (ret < 0) |
90 | break; |
91 | |
92 | /* compute min and max response times */ |
93 | ipc_response_time = ktime_to_ns(ktime_sub(end, start)); |
94 | min_response_time = min(min_response_time, ipc_response_time); |
95 | max_response_time = max(max_response_time, ipc_response_time); |
96 | |
97 | /* sum up response times */ |
98 | avg_response_time += ipc_response_time; |
99 | i++; |
100 | |
101 | /* test complete? */ |
102 | if (flood_duration_test) { |
103 | if (ktime_to_ns(kt: end) >= test_end) |
104 | break; |
105 | } else { |
106 | if (i == ipc_count) |
107 | break; |
108 | } |
109 | } |
110 | |
111 | if (ret < 0) |
112 | dev_err(dev, "ipc flood test failed at %d iterations\n" , i); |
113 | |
114 | /* return if the first IPC fails */ |
115 | if (!i) |
116 | return ret; |
117 | |
118 | /* compute average response time */ |
119 | do_div(avg_response_time, i); |
120 | |
121 | /* clear previous test output */ |
122 | memset(priv->buf, 0, IPC_FLOOD_TEST_RESULT_LEN); |
123 | |
124 | if (!ipc_count) { |
125 | dev_dbg(dev, "IPC Flood test duration: %lums\n" , ipc_duration_ms); |
126 | snprintf(buf: priv->buf, IPC_FLOOD_TEST_RESULT_LEN, |
127 | fmt: "IPC Flood test duration: %lums\n" , ipc_duration_ms); |
128 | } |
129 | |
130 | dev_dbg(dev, "IPC Flood count: %d, Avg response time: %lluns\n" , |
131 | i, avg_response_time); |
132 | dev_dbg(dev, "Max response time: %lluns\n" , max_response_time); |
133 | dev_dbg(dev, "Min response time: %lluns\n" , min_response_time); |
134 | |
135 | /* format output string and save test results */ |
136 | snprintf(buf: priv->buf + strlen(priv->buf), |
137 | IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), |
138 | fmt: "IPC Flood count: %d\nAvg response time: %lluns\n" , |
139 | i, avg_response_time); |
140 | |
141 | snprintf(buf: priv->buf + strlen(priv->buf), |
142 | IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), |
143 | fmt: "Max response time: %lluns\nMin response time: %lluns\n" , |
144 | max_response_time, min_response_time); |
145 | |
146 | return ret; |
147 | } |
148 | |
149 | /* |
150 | * Writing to the debugfs entry initiates the IPC flood test based on |
151 | * the IPC count or the duration specified by the user. |
152 | */ |
153 | static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buffer, |
154 | size_t count, loff_t *ppos) |
155 | { |
156 | struct sof_client_dev *cdev = file->private_data; |
157 | struct device *dev = &cdev->auxdev.dev; |
158 | unsigned long ipc_duration_ms = 0; |
159 | bool flood_duration_test = false; |
160 | unsigned long ipc_count = 0; |
161 | struct dentry *dentry; |
162 | int err; |
163 | size_t size; |
164 | char *string; |
165 | int ret; |
166 | |
167 | string = kzalloc(size: count + 1, GFP_KERNEL); |
168 | if (!string) |
169 | return -ENOMEM; |
170 | |
171 | size = simple_write_to_buffer(to: string, available: count, ppos, from: buffer, count); |
172 | |
173 | /* |
174 | * write op is only supported for ipc_flood_count or |
175 | * ipc_flood_duration_ms debugfs entries atm. |
176 | * ipc_flood_count floods the DSP with the number of IPC's specified. |
177 | * ipc_duration_ms test floods the DSP for the time specified |
178 | * in the debugfs entry. |
179 | */ |
180 | dentry = file->f_path.dentry; |
181 | if (strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) && |
182 | strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { |
183 | ret = -EINVAL; |
184 | goto out; |
185 | } |
186 | |
187 | if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) |
188 | flood_duration_test = true; |
189 | |
190 | /* test completion criterion */ |
191 | if (flood_duration_test) |
192 | ret = kstrtoul(s: string, base: 0, res: &ipc_duration_ms); |
193 | else |
194 | ret = kstrtoul(s: string, base: 0, res: &ipc_count); |
195 | if (ret < 0) |
196 | goto out; |
197 | |
198 | /* limit max duration/ipc count for flood test */ |
199 | if (flood_duration_test) { |
200 | if (!ipc_duration_ms) { |
201 | ret = size; |
202 | goto out; |
203 | } |
204 | |
205 | /* find the minimum. min() is not used to avoid warnings */ |
206 | if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) |
207 | ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; |
208 | } else { |
209 | if (!ipc_count) { |
210 | ret = size; |
211 | goto out; |
212 | } |
213 | |
214 | /* find the minimum. min() is not used to avoid warnings */ |
215 | if (ipc_count > MAX_IPC_FLOOD_COUNT) |
216 | ipc_count = MAX_IPC_FLOOD_COUNT; |
217 | } |
218 | |
219 | ret = pm_runtime_resume_and_get(dev); |
220 | if (ret < 0 && ret != -EACCES) { |
221 | dev_err_ratelimited(dev, "debugfs write failed to resume %d\n" , ret); |
222 | goto out; |
223 | } |
224 | |
225 | /* flood test */ |
226 | ret = sof_debug_ipc_flood_test(cdev, flood_duration_test, |
227 | ipc_duration_ms, ipc_count); |
228 | |
229 | pm_runtime_mark_last_busy(dev); |
230 | err = pm_runtime_put_autosuspend(dev); |
231 | if (err < 0) |
232 | dev_err_ratelimited(dev, "debugfs write failed to idle %d\n" , err); |
233 | |
234 | /* return size if test is successful */ |
235 | if (ret >= 0) |
236 | ret = size; |
237 | out: |
238 | kfree(objp: string); |
239 | return ret; |
240 | } |
241 | |
242 | /* return the result of the last IPC flood test */ |
243 | static ssize_t sof_ipc_flood_dfs_read(struct file *file, char __user *buffer, |
244 | size_t count, loff_t *ppos) |
245 | { |
246 | struct sof_client_dev *cdev = file->private_data; |
247 | struct sof_ipc_flood_priv *priv = cdev->data; |
248 | size_t size_ret; |
249 | |
250 | struct dentry *dentry; |
251 | |
252 | dentry = file->f_path.dentry; |
253 | if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) || |
254 | !strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { |
255 | if (*ppos) |
256 | return 0; |
257 | |
258 | count = min_t(size_t, count, strlen(priv->buf)); |
259 | size_ret = copy_to_user(to: buffer, from: priv->buf, n: count); |
260 | if (size_ret) |
261 | return -EFAULT; |
262 | |
263 | *ppos += count; |
264 | return count; |
265 | } |
266 | return count; |
267 | } |
268 | |
269 | static int sof_ipc_flood_dfs_release(struct inode *inode, struct file *file) |
270 | { |
271 | debugfs_file_put(dentry: file->f_path.dentry); |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | static const struct file_operations sof_ipc_flood_fops = { |
277 | .open = sof_ipc_flood_dfs_open, |
278 | .read = sof_ipc_flood_dfs_read, |
279 | .llseek = default_llseek, |
280 | .write = sof_ipc_flood_dfs_write, |
281 | .release = sof_ipc_flood_dfs_release, |
282 | |
283 | .owner = THIS_MODULE, |
284 | }; |
285 | |
286 | /* |
287 | * The IPC test client creates a couple of debugfs entries that will be used |
288 | * flood tests. Users can write to these entries to execute the IPC flood test |
289 | * by specifying either the number of IPCs to flood the DSP with or the duration |
290 | * (in ms) for which the DSP should be flooded with test IPCs. At the |
291 | * end of each test, the average, min and max response times are reported back. |
292 | * The results of the last flood test can be accessed by reading the debugfs |
293 | * entries. |
294 | */ |
295 | static int sof_ipc_flood_probe(struct auxiliary_device *auxdev, |
296 | const struct auxiliary_device_id *id) |
297 | { |
298 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
299 | struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); |
300 | struct device *dev = &auxdev->dev; |
301 | struct sof_ipc_flood_priv *priv; |
302 | |
303 | /* allocate memory for client data */ |
304 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
305 | if (!priv) |
306 | return -ENOMEM; |
307 | |
308 | priv->buf = devm_kmalloc(dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL); |
309 | if (!priv->buf) |
310 | return -ENOMEM; |
311 | |
312 | cdev->data = priv; |
313 | |
314 | /* create debugfs root folder with device name under parent SOF dir */ |
315 | priv->dfs_root = debugfs_create_dir(name: dev_name(dev), parent: debugfs_root); |
316 | if (!IS_ERR_OR_NULL(ptr: priv->dfs_root)) { |
317 | /* create read-write ipc_flood_count debugfs entry */ |
318 | debugfs_create_file(DEBUGFS_IPC_FLOOD_COUNT, mode: 0644, parent: priv->dfs_root, |
319 | data: cdev, fops: &sof_ipc_flood_fops); |
320 | |
321 | /* create read-write ipc_flood_duration_ms debugfs entry */ |
322 | debugfs_create_file(DEBUGFS_IPC_FLOOD_DURATION, mode: 0644, |
323 | parent: priv->dfs_root, data: cdev, fops: &sof_ipc_flood_fops); |
324 | |
325 | if (auxdev->id == 0) { |
326 | /* |
327 | * Create symlinks for backwards compatibility to the |
328 | * first IPC flood test instance |
329 | */ |
330 | char target[100]; |
331 | |
332 | snprintf(buf: target, size: 100, fmt: "%s/" DEBUGFS_IPC_FLOOD_COUNT, |
333 | dev_name(dev)); |
334 | priv->dfs_link[0] = |
335 | debugfs_create_symlink(DEBUGFS_IPC_FLOOD_COUNT, |
336 | parent: debugfs_root, dest: target); |
337 | |
338 | snprintf(buf: target, size: 100, fmt: "%s/" DEBUGFS_IPC_FLOOD_DURATION, |
339 | dev_name(dev)); |
340 | priv->dfs_link[1] = |
341 | debugfs_create_symlink(DEBUGFS_IPC_FLOOD_DURATION, |
342 | parent: debugfs_root, dest: target); |
343 | } |
344 | } |
345 | |
346 | /* enable runtime PM */ |
347 | pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); |
348 | pm_runtime_use_autosuspend(dev); |
349 | pm_runtime_enable(dev); |
350 | pm_runtime_mark_last_busy(dev); |
351 | pm_runtime_idle(dev); |
352 | |
353 | return 0; |
354 | } |
355 | |
356 | static void sof_ipc_flood_remove(struct auxiliary_device *auxdev) |
357 | { |
358 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
359 | struct sof_ipc_flood_priv *priv = cdev->data; |
360 | |
361 | pm_runtime_disable(dev: &auxdev->dev); |
362 | |
363 | if (auxdev->id == 0) { |
364 | debugfs_remove(dentry: priv->dfs_link[0]); |
365 | debugfs_remove(dentry: priv->dfs_link[1]); |
366 | } |
367 | |
368 | debugfs_remove_recursive(dentry: priv->dfs_root); |
369 | } |
370 | |
371 | static const struct auxiliary_device_id sof_ipc_flood_client_id_table[] = { |
372 | { .name = "snd_sof.ipc_flood" }, |
373 | {}, |
374 | }; |
375 | MODULE_DEVICE_TABLE(auxiliary, sof_ipc_flood_client_id_table); |
376 | |
377 | /* |
378 | * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus |
379 | * type are enough to ensure that the parent SOF device resumes to bring the DSP |
380 | * back to D0. |
381 | * Driver name will be set based on KBUILD_MODNAME. |
382 | */ |
383 | static struct auxiliary_driver sof_ipc_flood_client_drv = { |
384 | .probe = sof_ipc_flood_probe, |
385 | .remove = sof_ipc_flood_remove, |
386 | |
387 | .id_table = sof_ipc_flood_client_id_table, |
388 | }; |
389 | |
390 | module_auxiliary_driver(sof_ipc_flood_client_drv); |
391 | |
392 | MODULE_DESCRIPTION("SOF IPC Flood Test Client Driver" ); |
393 | MODULE_LICENSE("GPL" ); |
394 | MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); |
395 | |