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