ASoC: Intel: avs: Gather remaining logs on strace_release()
[linux.git] / sound / soc / intel / avs / debugfs.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 //
3 // Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
4 //
5 // Authors: Cezary Rojewski <cezary.rojewski@intel.com>
6 //          Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
7 //
8
9 #include <linux/debugfs.h>
10 #include <linux/kfifo.h>
11 #include <linux/wait.h>
12 #include <linux/sched/signal.h>
13 #include <sound/soc.h>
14 #include "avs.h"
15 #include "messages.h"
16
17 static unsigned int __kfifo_fromio(struct kfifo *fifo, const void __iomem *src, unsigned int len)
18 {
19         struct __kfifo *__fifo = &fifo->kfifo;
20         unsigned int l, off;
21
22         len = min(len, kfifo_avail(fifo));
23         off = __fifo->in & __fifo->mask;
24         l = min(len, kfifo_size(fifo) - off);
25
26         memcpy_fromio(__fifo->data + off, src, l);
27         memcpy_fromio(__fifo->data, src + l, len - l);
28         /* Make sure data copied from SRAM is visible to all CPUs. */
29         smp_mb();
30         __fifo->in += len;
31
32         return len;
33 }
34
35 bool avs_logging_fw(struct avs_dev *adev)
36 {
37         return kfifo_initialized(&adev->trace_fifo);
38 }
39
40 void avs_dump_fw_log(struct avs_dev *adev, const void __iomem *src, unsigned int len)
41 {
42         __kfifo_fromio(&adev->trace_fifo, src, len);
43 }
44
45 void avs_dump_fw_log_wakeup(struct avs_dev *adev, const void __iomem *src, unsigned int len)
46 {
47         avs_dump_fw_log(adev, src, len);
48         wake_up(&adev->trace_waitq);
49 }
50
51 static ssize_t probe_points_read(struct file *file, char __user *to, size_t count, loff_t *ppos)
52 {
53         struct avs_dev *adev = file->private_data;
54         struct avs_probe_point_desc *desc;
55         size_t num_desc, len = 0;
56         char *buf;
57         int i, ret;
58
59         /* Prevent chaining, send and dump IPC value just once. */
60         if (*ppos)
61                 return 0;
62
63         buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
64         if (!buf)
65                 return -ENOMEM;
66
67         ret = avs_ipc_probe_get_points(adev, &desc, &num_desc);
68         if (ret) {
69                 ret = AVS_IPC_RET(ret);
70                 goto exit;
71         }
72
73         for (i = 0; i < num_desc; i++) {
74                 ret = snprintf(buf + len, PAGE_SIZE - len,
75                                "Id: %#010x  Purpose: %d  Node id: %#x\n",
76                                desc[i].id.value, desc[i].purpose, desc[i].node_id.val);
77                 if (ret < 0)
78                         goto free_desc;
79                 len += ret;
80         }
81
82         ret = simple_read_from_buffer(to, count, ppos, buf, len);
83 free_desc:
84         kfree(desc);
85 exit:
86         kfree(buf);
87         return ret;
88 }
89
90 static ssize_t probe_points_write(struct file *file, const char __user *from, size_t count,
91                                   loff_t *ppos)
92 {
93         struct avs_dev *adev = file->private_data;
94         struct avs_probe_point_desc *desc;
95         u32 *array, num_elems;
96         size_t bytes;
97         int ret;
98
99         ret = parse_int_array_user(from, count, (int **)&array);
100         if (ret < 0)
101                 return ret;
102
103         num_elems = *array;
104         bytes = sizeof(*array) * num_elems;
105         if (bytes % sizeof(*desc)) {
106                 ret = -EINVAL;
107                 goto exit;
108         }
109
110         desc = (struct avs_probe_point_desc *)&array[1];
111         ret = avs_ipc_probe_connect_points(adev, desc, bytes / sizeof(*desc));
112         if (ret)
113                 ret = AVS_IPC_RET(ret);
114         else
115                 ret = count;
116 exit:
117         kfree(array);
118         return ret;
119 }
120
121 static const struct file_operations probe_points_fops = {
122         .open = simple_open,
123         .read = probe_points_read,
124         .write = probe_points_write,
125         .llseek = no_llseek,
126 };
127
128 static ssize_t probe_points_disconnect_write(struct file *file, const char __user *from,
129                                              size_t count, loff_t *ppos)
130 {
131         struct avs_dev *adev = file->private_data;
132         union avs_probe_point_id *id;
133         u32 *array, num_elems;
134         size_t bytes;
135         int ret;
136
137         ret = parse_int_array_user(from, count, (int **)&array);
138         if (ret < 0)
139                 return ret;
140
141         num_elems = *array;
142         bytes = sizeof(*array) * num_elems;
143         if (bytes % sizeof(*id)) {
144                 ret = -EINVAL;
145                 goto exit;
146         }
147
148         id = (union avs_probe_point_id *)&array[1];
149         ret = avs_ipc_probe_disconnect_points(adev, id, bytes / sizeof(*id));
150         if (ret)
151                 ret = AVS_IPC_RET(ret);
152         else
153                 ret = count;
154 exit:
155         kfree(array);
156         return ret;
157 }
158
159 static const struct file_operations probe_points_disconnect_fops = {
160         .open = simple_open,
161         .write = probe_points_disconnect_write,
162         .llseek = default_llseek,
163 };
164
165 static ssize_t strace_read(struct file *file, char __user *to, size_t count, loff_t *ppos)
166 {
167         struct avs_dev *adev = file->private_data;
168         struct kfifo *fifo = &adev->trace_fifo;
169         unsigned int copied;
170
171         if (kfifo_is_empty(fifo)) {
172                 DEFINE_WAIT(wait);
173
174                 prepare_to_wait(&adev->trace_waitq, &wait, TASK_INTERRUPTIBLE);
175                 if (!signal_pending(current))
176                         schedule();
177                 finish_wait(&adev->trace_waitq, &wait);
178         }
179
180         if (kfifo_to_user(fifo, to, count, &copied))
181                 return -EFAULT;
182         *ppos += copied;
183         return copied;
184 }
185
186 static int strace_open(struct inode *inode, struct file *file)
187 {
188         struct avs_dev *adev = inode->i_private;
189         int ret;
190
191         if (kfifo_initialized(&adev->trace_fifo))
192                 return -EBUSY;
193
194         ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL);
195         if (ret < 0)
196                 return ret;
197
198         file->private_data = adev;
199         return 0;
200 }
201
202 static int strace_release(struct inode *inode, struct file *file)
203 {
204         union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS);
205         struct avs_dev *adev = file->private_data;
206         unsigned long resource_mask;
207         unsigned long flags, i;
208         u32 num_cores;
209
210         resource_mask = adev->logged_resources;
211         num_cores = adev->hw_cfg.dsp_cores;
212
213         spin_lock_irqsave(&adev->trace_lock, flags);
214
215         /* Gather any remaining logs. */
216         for_each_set_bit(i, &resource_mask, num_cores) {
217                 msg.log.core = i;
218                 avs_dsp_op(adev, log_buffer_status, &msg);
219         }
220
221         kfifo_free(&adev->trace_fifo);
222
223         spin_unlock_irqrestore(&adev->trace_lock, flags);
224
225         return 0;
226 }
227
228 static const struct file_operations strace_fops = {
229         .llseek = default_llseek,
230         .read = strace_read,
231         .open = strace_open,
232         .release = strace_release,
233 };
234
235 #define DISABLE_TIMERS  UINT_MAX
236
237 static int enable_logs(struct avs_dev *adev, u32 resource_mask, u32 *priorities)
238 {
239         int ret;
240
241         /* Logging demands D0i0 state from DSP. */
242         if (!adev->logged_resources) {
243                 pm_runtime_get_sync(adev->dev);
244
245                 ret = avs_dsp_disable_d0ix(adev);
246                 if (ret)
247                         goto err_d0ix;
248         }
249
250         ret = avs_ipc_set_system_time(adev);
251         if (ret && ret != AVS_IPC_NOT_SUPPORTED) {
252                 ret = AVS_IPC_RET(ret);
253                 goto err_ipc;
254         }
255
256         ret = avs_dsp_op(adev, enable_logs, AVS_LOG_ENABLE, adev->aging_timer_period,
257                          adev->fifo_full_timer_period, resource_mask, priorities);
258         if (ret)
259                 goto err_ipc;
260
261         adev->logged_resources |= resource_mask;
262         return 0;
263
264 err_ipc:
265         if (!adev->logged_resources) {
266                 avs_dsp_enable_d0ix(adev);
267 err_d0ix:
268                 pm_runtime_mark_last_busy(adev->dev);
269                 pm_runtime_put_autosuspend(adev->dev);
270         }
271
272         return ret;
273 }
274
275 static int disable_logs(struct avs_dev *adev, u32 resource_mask)
276 {
277         int ret;
278
279         /* Check if there's anything to do. */
280         if (!adev->logged_resources)
281                 return 0;
282
283         ret = avs_dsp_op(adev, enable_logs, AVS_LOG_DISABLE, DISABLE_TIMERS, DISABLE_TIMERS,
284                          resource_mask, NULL);
285
286         /*
287          * If IPC fails causing recovery, logged_resources is already zero
288          * so unsetting bits is still safe.
289          */
290         adev->logged_resources &= ~resource_mask;
291
292         /* If that's the last resource, allow for D3. */
293         if (!adev->logged_resources) {
294                 avs_dsp_enable_d0ix(adev);
295                 pm_runtime_mark_last_busy(adev->dev);
296                 pm_runtime_put_autosuspend(adev->dev);
297         }
298
299         return ret;
300 }
301
302 static ssize_t trace_control_read(struct file *file, char __user *to, size_t count, loff_t *ppos)
303 {
304         struct avs_dev *adev = file->private_data;
305         char buf[64];
306         int len;
307
308         len = snprintf(buf, sizeof(buf), "0x%08x\n", adev->logged_resources);
309
310         return simple_read_from_buffer(to, count, ppos, buf, len);
311 }
312
313 static ssize_t trace_control_write(struct file *file, const char __user *from, size_t count,
314                                    loff_t *ppos)
315 {
316         struct avs_dev *adev = file->private_data;
317         u32 *array, num_elems;
318         u32 resource_mask;
319         int ret;
320
321         ret = parse_int_array_user(from, count, (int **)&array);
322         if (ret < 0)
323                 return ret;
324
325         num_elems = *array;
326         resource_mask = array[1];
327
328         /*
329          * Disable if just resource mask is provided - no log priority flags.
330          *
331          * Enable input format:   mask, prio1, .., prioN
332          * Where 'N' equals number of bits set in the 'mask'.
333          */
334         if (num_elems == 1) {
335                 ret = disable_logs(adev, resource_mask);
336         } else {
337                 if (num_elems != (hweight_long(resource_mask) + 1)) {
338                         ret = -EINVAL;
339                         goto free_array;
340                 }
341
342                 ret = enable_logs(adev, resource_mask, &array[2]);
343         }
344
345         if (!ret)
346                 ret = count;
347 free_array:
348         kfree(array);
349         return ret;
350 }
351
352 static const struct file_operations trace_control_fops = {
353         .llseek = default_llseek,
354         .read = trace_control_read,
355         .write = trace_control_write,
356         .open = simple_open,
357 };
358
359 void avs_debugfs_init(struct avs_dev *adev)
360 {
361         init_waitqueue_head(&adev->trace_waitq);
362         spin_lock_init(&adev->trace_lock);
363
364         adev->debugfs_root = debugfs_create_dir("avs", snd_soc_debugfs_root);
365
366         /* Initialize timer periods with recommended defaults. */
367         adev->aging_timer_period = 10;
368         adev->fifo_full_timer_period = 10;
369
370         debugfs_create_file("strace", 0444, adev->debugfs_root, adev, &strace_fops);
371         debugfs_create_file("trace_control", 0644, adev->debugfs_root, adev, &trace_control_fops);
372
373         debugfs_create_u32("trace_aging_period", 0644, adev->debugfs_root,
374                            &adev->aging_timer_period);
375         debugfs_create_u32("trace_fifo_full_period", 0644, adev->debugfs_root,
376                            &adev->fifo_full_timer_period);
377
378         debugfs_create_file("probe_points", 0644, adev->debugfs_root, adev, &probe_points_fops);
379         debugfs_create_file("probe_points_disconnect", 0200, adev->debugfs_root, adev,
380                             &probe_points_disconnect_fops);
381 }
382
383 void avs_debugfs_exit(struct avs_dev *adev)
384 {
385         debugfs_remove_recursive(adev->debugfs_root);
386 }