1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Copyright(c) 2019-2022 Intel Corporation. All rights reserved. |
4 | // |
5 | // Author: Cezary Rojewski <cezary.rojewski@intel.com> |
6 | // |
7 | // SOF client support: |
8 | // Ranjani Sridharan <ranjani.sridharan@linux.intel.com> |
9 | // Peter Ujfalusi <peter.ujfalusi@linux.intel.com> |
10 | // |
11 | |
12 | #include <linux/debugfs.h> |
13 | #include <linux/module.h> |
14 | #include <linux/pm_runtime.h> |
15 | #include <linux/string_helpers.h> |
16 | #include <linux/stddef.h> |
17 | |
18 | #include <sound/soc.h> |
19 | #include <sound/sof/header.h> |
20 | #include "sof-client.h" |
21 | #include "sof-client-probes.h" |
22 | |
23 | #define SOF_PROBES_SUSPEND_DELAY_MS 3000 |
24 | /* only extraction supported for now */ |
25 | #define SOF_PROBES_NUM_DAI_LINKS 1 |
26 | |
27 | #define SOF_PROBES_INVALID_NODE_ID UINT_MAX |
28 | |
29 | static bool __read_mostly sof_probes_enabled; |
30 | module_param_named(enable, sof_probes_enabled, bool, 0444); |
31 | MODULE_PARM_DESC(enable, "Enable SOF probes support" ); |
32 | |
33 | static int sof_probes_compr_startup(struct snd_compr_stream *cstream, |
34 | struct snd_soc_dai *dai) |
35 | { |
36 | struct snd_soc_card *card = snd_soc_component_get_drvdata(c: dai->component); |
37 | struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); |
38 | struct sof_probes_priv *priv = cdev->data; |
39 | const struct sof_probes_host_ops *ops = priv->host_ops; |
40 | int ret; |
41 | |
42 | if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) |
43 | return -ENODEV; |
44 | |
45 | ret = sof_client_core_module_get(cdev); |
46 | if (ret) |
47 | return ret; |
48 | |
49 | ret = ops->startup(cdev, cstream, dai, &priv->extractor_stream_tag); |
50 | if (ret) { |
51 | dev_err(dai->dev, "Failed to startup probe stream: %d\n" , ret); |
52 | priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; |
53 | sof_client_core_module_put(cdev); |
54 | } |
55 | |
56 | return ret; |
57 | } |
58 | |
59 | static int sof_probes_compr_shutdown(struct snd_compr_stream *cstream, |
60 | struct snd_soc_dai *dai) |
61 | { |
62 | struct snd_soc_card *card = snd_soc_component_get_drvdata(c: dai->component); |
63 | struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); |
64 | struct sof_probes_priv *priv = cdev->data; |
65 | const struct sof_probes_host_ops *ops = priv->host_ops; |
66 | const struct sof_probes_ipc_ops *ipc = priv->ipc_ops; |
67 | struct sof_probe_point_desc *desc; |
68 | size_t num_desc; |
69 | int i, ret; |
70 | |
71 | /* disconnect all probe points */ |
72 | ret = ipc->points_info(cdev, &desc, &num_desc); |
73 | if (ret < 0) { |
74 | dev_err(dai->dev, "Failed to get probe points: %d\n" , ret); |
75 | goto exit; |
76 | } |
77 | |
78 | for (i = 0; i < num_desc; i++) |
79 | ipc->points_remove(cdev, &desc[i].buffer_id, 1); |
80 | kfree(objp: desc); |
81 | |
82 | exit: |
83 | ret = ipc->deinit(cdev); |
84 | if (ret < 0) |
85 | dev_err(dai->dev, "Failed to deinit probe: %d\n" , ret); |
86 | |
87 | priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; |
88 | snd_compr_free_pages(stream: cstream); |
89 | |
90 | ret = ops->shutdown(cdev, cstream, dai); |
91 | |
92 | sof_client_core_module_put(cdev); |
93 | |
94 | return ret; |
95 | } |
96 | |
97 | static int sof_probes_compr_set_params(struct snd_compr_stream *cstream, |
98 | struct snd_compr_params *params, |
99 | struct snd_soc_dai *dai) |
100 | { |
101 | struct snd_soc_card *card = snd_soc_component_get_drvdata(c: dai->component); |
102 | struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); |
103 | struct snd_compr_runtime *rtd = cstream->runtime; |
104 | struct sof_probes_priv *priv = cdev->data; |
105 | const struct sof_probes_host_ops *ops = priv->host_ops; |
106 | const struct sof_probes_ipc_ops *ipc = priv->ipc_ops; |
107 | int ret; |
108 | |
109 | cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; |
110 | cstream->dma_buffer.dev.dev = sof_client_get_dma_dev(cdev); |
111 | ret = snd_compr_malloc_pages(stream: cstream, size: rtd->buffer_size); |
112 | if (ret < 0) |
113 | return ret; |
114 | |
115 | ret = ops->set_params(cdev, cstream, params, dai); |
116 | if (ret) |
117 | return ret; |
118 | |
119 | ret = ipc->init(cdev, priv->extractor_stream_tag, rtd->dma_bytes); |
120 | if (ret < 0) { |
121 | dev_err(dai->dev, "Failed to init probe: %d\n" , ret); |
122 | return ret; |
123 | } |
124 | |
125 | return 0; |
126 | } |
127 | |
128 | static int sof_probes_compr_trigger(struct snd_compr_stream *cstream, int cmd, |
129 | struct snd_soc_dai *dai) |
130 | { |
131 | struct snd_soc_card *card = snd_soc_component_get_drvdata(c: dai->component); |
132 | struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); |
133 | struct sof_probes_priv *priv = cdev->data; |
134 | const struct sof_probes_host_ops *ops = priv->host_ops; |
135 | |
136 | return ops->trigger(cdev, cstream, cmd, dai); |
137 | } |
138 | |
139 | static int sof_probes_compr_pointer(struct snd_compr_stream *cstream, |
140 | struct snd_compr_tstamp *tstamp, |
141 | struct snd_soc_dai *dai) |
142 | { |
143 | struct snd_soc_card *card = snd_soc_component_get_drvdata(c: dai->component); |
144 | struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); |
145 | struct sof_probes_priv *priv = cdev->data; |
146 | const struct sof_probes_host_ops *ops = priv->host_ops; |
147 | |
148 | return ops->pointer(cdev, cstream, tstamp, dai); |
149 | } |
150 | |
151 | static const struct snd_soc_cdai_ops sof_probes_compr_ops = { |
152 | .startup = sof_probes_compr_startup, |
153 | .shutdown = sof_probes_compr_shutdown, |
154 | .set_params = sof_probes_compr_set_params, |
155 | .trigger = sof_probes_compr_trigger, |
156 | .pointer = sof_probes_compr_pointer, |
157 | }; |
158 | |
159 | static int sof_probes_compr_copy(struct snd_soc_component *component, |
160 | struct snd_compr_stream *cstream, |
161 | char __user *buf, size_t count) |
162 | { |
163 | struct snd_compr_runtime *rtd = cstream->runtime; |
164 | unsigned int offset, n; |
165 | void *ptr; |
166 | int ret; |
167 | |
168 | if (count > rtd->buffer_size) |
169 | count = rtd->buffer_size; |
170 | |
171 | div_u64_rem(dividend: rtd->total_bytes_transferred, divisor: rtd->buffer_size, remainder: &offset); |
172 | ptr = rtd->dma_area + offset; |
173 | n = rtd->buffer_size - offset; |
174 | |
175 | if (count < n) { |
176 | ret = copy_to_user(to: buf, from: ptr, n: count); |
177 | } else { |
178 | ret = copy_to_user(to: buf, from: ptr, n); |
179 | ret += copy_to_user(to: buf + n, from: rtd->dma_area, n: count - n); |
180 | } |
181 | |
182 | if (ret) |
183 | return count - ret; |
184 | return count; |
185 | } |
186 | |
187 | static const struct snd_compress_ops sof_probes_compressed_ops = { |
188 | .copy = sof_probes_compr_copy, |
189 | }; |
190 | |
191 | static ssize_t sof_probes_dfs_points_read(struct file *file, char __user *to, |
192 | size_t count, loff_t *ppos) |
193 | { |
194 | struct sof_client_dev *cdev = file->private_data; |
195 | struct sof_probes_priv *priv = cdev->data; |
196 | struct device *dev = &cdev->auxdev.dev; |
197 | struct sof_probe_point_desc *desc; |
198 | const struct sof_probes_ipc_ops *ipc = priv->ipc_ops; |
199 | int remaining, offset; |
200 | size_t num_desc; |
201 | char *buf; |
202 | int i, ret, err; |
203 | |
204 | if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { |
205 | dev_warn(dev, "no extractor stream running\n" ); |
206 | return -ENOENT; |
207 | } |
208 | |
209 | buf = kzalloc(PAGE_SIZE, GFP_KERNEL); |
210 | if (!buf) |
211 | return -ENOMEM; |
212 | |
213 | ret = pm_runtime_resume_and_get(dev); |
214 | if (ret < 0 && ret != -EACCES) { |
215 | dev_err_ratelimited(dev, "debugfs read failed to resume %d\n" , ret); |
216 | goto exit; |
217 | } |
218 | |
219 | ret = ipc->points_info(cdev, &desc, &num_desc); |
220 | if (ret < 0) |
221 | goto pm_error; |
222 | |
223 | for (i = 0; i < num_desc; i++) { |
224 | offset = strlen(buf); |
225 | remaining = PAGE_SIZE - offset; |
226 | ret = snprintf(buf: buf + offset, size: remaining, |
227 | fmt: "Id: %#010x Purpose: %u Node id: %#x\n" , |
228 | desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); |
229 | if (ret < 0 || ret >= remaining) { |
230 | /* truncate the output buffer at the last full line */ |
231 | buf[offset] = '\0'; |
232 | break; |
233 | } |
234 | } |
235 | |
236 | ret = simple_read_from_buffer(to, count, ppos, from: buf, strlen(buf)); |
237 | |
238 | kfree(objp: desc); |
239 | |
240 | pm_error: |
241 | pm_runtime_mark_last_busy(dev); |
242 | err = pm_runtime_put_autosuspend(dev); |
243 | if (err < 0) |
244 | dev_err_ratelimited(dev, "debugfs read failed to idle %d\n" , err); |
245 | |
246 | exit: |
247 | kfree(objp: buf); |
248 | return ret; |
249 | } |
250 | |
251 | static ssize_t |
252 | sof_probes_dfs_points_write(struct file *file, const char __user *from, |
253 | size_t count, loff_t *ppos) |
254 | { |
255 | struct sof_client_dev *cdev = file->private_data; |
256 | struct sof_probes_priv *priv = cdev->data; |
257 | const struct sof_probes_ipc_ops *ipc = priv->ipc_ops; |
258 | struct device *dev = &cdev->auxdev.dev; |
259 | struct sof_probe_point_desc *desc; |
260 | u32 num_elems, *array; |
261 | size_t bytes; |
262 | int ret, err; |
263 | |
264 | if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { |
265 | dev_warn(dev, "no extractor stream running\n" ); |
266 | return -ENOENT; |
267 | } |
268 | |
269 | ret = parse_int_array_user(from, count, array: (int **)&array); |
270 | if (ret < 0) |
271 | return ret; |
272 | |
273 | num_elems = *array; |
274 | bytes = sizeof(*array) * num_elems; |
275 | if (bytes % sizeof(*desc)) { |
276 | ret = -EINVAL; |
277 | goto exit; |
278 | } |
279 | |
280 | desc = (struct sof_probe_point_desc *)&array[1]; |
281 | |
282 | ret = pm_runtime_resume_and_get(dev); |
283 | if (ret < 0 && ret != -EACCES) { |
284 | dev_err_ratelimited(dev, "debugfs write failed to resume %d\n" , ret); |
285 | goto exit; |
286 | } |
287 | |
288 | ret = ipc->points_add(cdev, desc, bytes / sizeof(*desc)); |
289 | if (!ret) |
290 | ret = count; |
291 | |
292 | pm_runtime_mark_last_busy(dev); |
293 | err = pm_runtime_put_autosuspend(dev); |
294 | if (err < 0) |
295 | dev_err_ratelimited(dev, "debugfs write failed to idle %d\n" , err); |
296 | exit: |
297 | kfree(objp: array); |
298 | return ret; |
299 | } |
300 | |
301 | static const struct file_operations sof_probes_points_fops = { |
302 | .open = simple_open, |
303 | .read = sof_probes_dfs_points_read, |
304 | .write = sof_probes_dfs_points_write, |
305 | .llseek = default_llseek, |
306 | |
307 | .owner = THIS_MODULE, |
308 | }; |
309 | |
310 | static ssize_t |
311 | sof_probes_dfs_points_remove_write(struct file *file, const char __user *from, |
312 | size_t count, loff_t *ppos) |
313 | { |
314 | struct sof_client_dev *cdev = file->private_data; |
315 | struct sof_probes_priv *priv = cdev->data; |
316 | const struct sof_probes_ipc_ops *ipc = priv->ipc_ops; |
317 | struct device *dev = &cdev->auxdev.dev; |
318 | int ret, err; |
319 | u32 *array; |
320 | |
321 | if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { |
322 | dev_warn(dev, "no extractor stream running\n" ); |
323 | return -ENOENT; |
324 | } |
325 | |
326 | ret = parse_int_array_user(from, count, array: (int **)&array); |
327 | if (ret < 0) |
328 | return ret; |
329 | |
330 | ret = pm_runtime_resume_and_get(dev); |
331 | if (ret < 0) { |
332 | dev_err_ratelimited(dev, "debugfs write failed to resume %d\n" , ret); |
333 | goto exit; |
334 | } |
335 | |
336 | ret = ipc->points_remove(cdev, &array[1], array[0]); |
337 | if (!ret) |
338 | ret = count; |
339 | |
340 | pm_runtime_mark_last_busy(dev); |
341 | err = pm_runtime_put_autosuspend(dev); |
342 | if (err < 0) |
343 | dev_err_ratelimited(dev, "debugfs write failed to idle %d\n" , err); |
344 | exit: |
345 | kfree(objp: array); |
346 | return ret; |
347 | } |
348 | |
349 | static const struct file_operations sof_probes_points_remove_fops = { |
350 | .open = simple_open, |
351 | .write = sof_probes_dfs_points_remove_write, |
352 | .llseek = default_llseek, |
353 | |
354 | .owner = THIS_MODULE, |
355 | }; |
356 | |
357 | static const struct snd_soc_dai_ops sof_probes_dai_ops = { |
358 | .compress_new = snd_soc_new_compress, |
359 | }; |
360 | |
361 | static struct snd_soc_dai_driver sof_probes_dai_drv[] = { |
362 | { |
363 | .name = "Probe Extraction CPU DAI" , |
364 | .ops = &sof_probes_dai_ops, |
365 | .cops = &sof_probes_compr_ops, |
366 | .capture = { |
367 | .stream_name = "Probe Extraction" , |
368 | .channels_min = 1, |
369 | .channels_max = 8, |
370 | .rates = SNDRV_PCM_RATE_48000, |
371 | .rate_min = 48000, |
372 | .rate_max = 48000, |
373 | }, |
374 | }, |
375 | }; |
376 | |
377 | static const struct snd_soc_component_driver sof_probes_component = { |
378 | .name = "sof-probes-component" , |
379 | .compress_ops = &sof_probes_compressed_ops, |
380 | .module_get_upon_open = 1, |
381 | .legacy_dai_naming = 1, |
382 | }; |
383 | |
384 | static int sof_probes_client_probe(struct auxiliary_device *auxdev, |
385 | const struct auxiliary_device_id *id) |
386 | { |
387 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
388 | struct dentry *dfsroot = sof_client_get_debugfs_root(cdev); |
389 | struct device *dev = &auxdev->dev; |
390 | struct snd_soc_dai_link_component platform_component[] = { |
391 | { |
392 | .name = dev_name(dev), |
393 | } |
394 | }; |
395 | struct snd_soc_card *card; |
396 | struct sof_probes_priv *priv; |
397 | struct snd_soc_dai_link_component *cpus; |
398 | struct sof_probes_host_ops *ops; |
399 | struct snd_soc_dai_link *links; |
400 | int ret; |
401 | |
402 | /* do not set up the probes support if it is not enabled */ |
403 | if (!sof_probes_enabled) |
404 | return -ENXIO; |
405 | |
406 | ops = dev_get_platdata(dev); |
407 | if (!ops) { |
408 | dev_err(dev, "missing platform data\n" ); |
409 | return -ENODEV; |
410 | } |
411 | if (!ops->startup || !ops->shutdown || !ops->set_params || !ops->trigger || |
412 | !ops->pointer) { |
413 | dev_err(dev, "missing platform callback(s)\n" ); |
414 | return -ENODEV; |
415 | } |
416 | |
417 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
418 | if (!priv) |
419 | return -ENOMEM; |
420 | |
421 | priv->host_ops = ops; |
422 | |
423 | switch (sof_client_get_ipc_type(cdev)) { |
424 | #ifdef CONFIG_SND_SOC_SOF_IPC4 |
425 | case SOF_IPC_TYPE_4: |
426 | priv->ipc_ops = &ipc4_probe_ops; |
427 | break; |
428 | #endif |
429 | #ifdef CONFIG_SND_SOC_SOF_IPC3 |
430 | case SOF_IPC_TYPE_3: |
431 | priv->ipc_ops = &ipc3_probe_ops; |
432 | break; |
433 | #endif |
434 | default: |
435 | dev_err(dev, "Matching IPC ops not found." ); |
436 | return -ENODEV; |
437 | } |
438 | |
439 | cdev->data = priv; |
440 | |
441 | /* register probes component driver and dai */ |
442 | ret = devm_snd_soc_register_component(dev, component_driver: &sof_probes_component, |
443 | dai_drv: sof_probes_dai_drv, |
444 | ARRAY_SIZE(sof_probes_dai_drv)); |
445 | if (ret < 0) { |
446 | dev_err(dev, "failed to register SOF probes DAI driver %d\n" , ret); |
447 | return ret; |
448 | } |
449 | |
450 | /* set client data */ |
451 | priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; |
452 | |
453 | /* create read-write probes_points debugfs entry */ |
454 | priv->dfs_points = debugfs_create_file(name: "probe_points" , mode: 0644, parent: dfsroot, |
455 | data: cdev, fops: &sof_probes_points_fops); |
456 | |
457 | /* create read-write probe_points_remove debugfs entry */ |
458 | priv->dfs_points_remove = debugfs_create_file(name: "probe_points_remove" , mode: 0644, |
459 | parent: dfsroot, data: cdev, |
460 | fops: &sof_probes_points_remove_fops); |
461 | |
462 | links = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, size: sizeof(*links), GFP_KERNEL); |
463 | cpus = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, size: sizeof(*cpus), GFP_KERNEL); |
464 | if (!links || !cpus) { |
465 | debugfs_remove(dentry: priv->dfs_points); |
466 | debugfs_remove(dentry: priv->dfs_points_remove); |
467 | return -ENOMEM; |
468 | } |
469 | |
470 | /* extraction DAI link */ |
471 | links[0].name = "Compress Probe Capture" ; |
472 | links[0].id = 0; |
473 | links[0].cpus = &cpus[0]; |
474 | links[0].num_cpus = 1; |
475 | links[0].cpus->dai_name = "Probe Extraction CPU DAI" ; |
476 | links[0].codecs = &snd_soc_dummy_dlc; |
477 | links[0].num_codecs = 1; |
478 | links[0].platforms = platform_component; |
479 | links[0].num_platforms = ARRAY_SIZE(platform_component); |
480 | links[0].nonatomic = 1; |
481 | |
482 | card = &priv->card; |
483 | |
484 | card->dev = dev; |
485 | card->name = "sof-probes" ; |
486 | card->owner = THIS_MODULE; |
487 | card->num_links = SOF_PROBES_NUM_DAI_LINKS; |
488 | card->dai_link = links; |
489 | |
490 | /* set idle_bias_off to prevent the core from resuming the card->dev */ |
491 | card->dapm.idle_bias_off = true; |
492 | |
493 | snd_soc_card_set_drvdata(card, data: cdev); |
494 | |
495 | ret = devm_snd_soc_register_card(dev, card); |
496 | if (ret < 0) { |
497 | debugfs_remove(dentry: priv->dfs_points); |
498 | debugfs_remove(dentry: priv->dfs_points_remove); |
499 | dev_err(dev, "Probes card register failed %d\n" , ret); |
500 | return ret; |
501 | } |
502 | |
503 | /* enable runtime PM */ |
504 | pm_runtime_set_autosuspend_delay(dev, SOF_PROBES_SUSPEND_DELAY_MS); |
505 | pm_runtime_use_autosuspend(dev); |
506 | pm_runtime_enable(dev); |
507 | pm_runtime_mark_last_busy(dev); |
508 | pm_runtime_idle(dev); |
509 | |
510 | return 0; |
511 | } |
512 | |
513 | static void sof_probes_client_remove(struct auxiliary_device *auxdev) |
514 | { |
515 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
516 | struct sof_probes_priv *priv = cdev->data; |
517 | |
518 | if (!sof_probes_enabled) |
519 | return; |
520 | |
521 | pm_runtime_disable(dev: &auxdev->dev); |
522 | debugfs_remove(dentry: priv->dfs_points); |
523 | debugfs_remove(dentry: priv->dfs_points_remove); |
524 | } |
525 | |
526 | static const struct auxiliary_device_id sof_probes_client_id_table[] = { |
527 | { .name = "snd_sof.hda-probes" , }, |
528 | { .name = "snd_sof.acp-probes" , }, |
529 | {}, |
530 | }; |
531 | MODULE_DEVICE_TABLE(auxiliary, sof_probes_client_id_table); |
532 | |
533 | /* driver name will be set based on KBUILD_MODNAME */ |
534 | static struct auxiliary_driver sof_probes_client_drv = { |
535 | .probe = sof_probes_client_probe, |
536 | .remove = sof_probes_client_remove, |
537 | |
538 | .id_table = sof_probes_client_id_table, |
539 | }; |
540 | |
541 | module_auxiliary_driver(sof_probes_client_drv); |
542 | |
543 | MODULE_DESCRIPTION("SOF Probes Client Driver" ); |
544 | MODULE_LICENSE("GPL v2" ); |
545 | MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); |
546 | |