1*4882a593Smuzhiyun // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2*4882a593Smuzhiyun //
3*4882a593Smuzhiyun // This file is provided under a dual BSD/GPLv2 license. When using or
4*4882a593Smuzhiyun // redistributing this file, you may do so under either license.
5*4882a593Smuzhiyun //
6*4882a593Smuzhiyun // Copyright(c) 2018 Intel Corporation. All rights reserved.
7*4882a593Smuzhiyun //
8*4882a593Smuzhiyun // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
9*4882a593Smuzhiyun //
10*4882a593Smuzhiyun // Generic debug routines used to export DSP MMIO and memories to userspace
11*4882a593Smuzhiyun // for firmware debugging.
12*4882a593Smuzhiyun //
13*4882a593Smuzhiyun
14*4882a593Smuzhiyun #include <linux/debugfs.h>
15*4882a593Smuzhiyun #include <linux/io.h>
16*4882a593Smuzhiyun #include <linux/pm_runtime.h>
17*4882a593Smuzhiyun #include "sof-priv.h"
18*4882a593Smuzhiyun #include "ops.h"
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
21*4882a593Smuzhiyun #include "probe.h"
22*4882a593Smuzhiyun
23*4882a593Smuzhiyun /**
24*4882a593Smuzhiyun * strsplit_u32 - Split string into sequence of u32 tokens
25*4882a593Smuzhiyun * @buf: String to split into tokens.
26*4882a593Smuzhiyun * @delim: String containing delimiter characters.
27*4882a593Smuzhiyun * @tkns: Returned u32 sequence pointer.
28*4882a593Smuzhiyun * @num_tkns: Returned number of tokens obtained.
29*4882a593Smuzhiyun */
30*4882a593Smuzhiyun static int
strsplit_u32(char ** buf,const char * delim,u32 ** tkns,size_t * num_tkns)31*4882a593Smuzhiyun strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns)
32*4882a593Smuzhiyun {
33*4882a593Smuzhiyun char *s;
34*4882a593Smuzhiyun u32 *data, *tmp;
35*4882a593Smuzhiyun size_t count = 0;
36*4882a593Smuzhiyun size_t cap = 32;
37*4882a593Smuzhiyun int ret = 0;
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun *tkns = NULL;
40*4882a593Smuzhiyun *num_tkns = 0;
41*4882a593Smuzhiyun data = kcalloc(cap, sizeof(*data), GFP_KERNEL);
42*4882a593Smuzhiyun if (!data)
43*4882a593Smuzhiyun return -ENOMEM;
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun while ((s = strsep(buf, delim)) != NULL) {
46*4882a593Smuzhiyun ret = kstrtouint(s, 0, data + count);
47*4882a593Smuzhiyun if (ret)
48*4882a593Smuzhiyun goto exit;
49*4882a593Smuzhiyun if (++count >= cap) {
50*4882a593Smuzhiyun cap *= 2;
51*4882a593Smuzhiyun tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL);
52*4882a593Smuzhiyun if (!tmp) {
53*4882a593Smuzhiyun ret = -ENOMEM;
54*4882a593Smuzhiyun goto exit;
55*4882a593Smuzhiyun }
56*4882a593Smuzhiyun data = tmp;
57*4882a593Smuzhiyun }
58*4882a593Smuzhiyun }
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun if (!count)
61*4882a593Smuzhiyun goto exit;
62*4882a593Smuzhiyun *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL);
63*4882a593Smuzhiyun if (*tkns == NULL) {
64*4882a593Smuzhiyun ret = -ENOMEM;
65*4882a593Smuzhiyun goto exit;
66*4882a593Smuzhiyun }
67*4882a593Smuzhiyun *num_tkns = count;
68*4882a593Smuzhiyun
69*4882a593Smuzhiyun exit:
70*4882a593Smuzhiyun kfree(data);
71*4882a593Smuzhiyun return ret;
72*4882a593Smuzhiyun }
73*4882a593Smuzhiyun
tokenize_input(const char __user * from,size_t count,loff_t * ppos,u32 ** tkns,size_t * num_tkns)74*4882a593Smuzhiyun static int tokenize_input(const char __user *from, size_t count,
75*4882a593Smuzhiyun loff_t *ppos, u32 **tkns, size_t *num_tkns)
76*4882a593Smuzhiyun {
77*4882a593Smuzhiyun char *buf;
78*4882a593Smuzhiyun int ret;
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun buf = kmalloc(count + 1, GFP_KERNEL);
81*4882a593Smuzhiyun if (!buf)
82*4882a593Smuzhiyun return -ENOMEM;
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun ret = simple_write_to_buffer(buf, count, ppos, from, count);
85*4882a593Smuzhiyun if (ret != count) {
86*4882a593Smuzhiyun ret = ret >= 0 ? -EIO : ret;
87*4882a593Smuzhiyun goto exit;
88*4882a593Smuzhiyun }
89*4882a593Smuzhiyun
90*4882a593Smuzhiyun buf[count] = '\0';
91*4882a593Smuzhiyun ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns);
92*4882a593Smuzhiyun exit:
93*4882a593Smuzhiyun kfree(buf);
94*4882a593Smuzhiyun return ret;
95*4882a593Smuzhiyun }
96*4882a593Smuzhiyun
probe_points_read(struct file * file,char __user * to,size_t count,loff_t * ppos)97*4882a593Smuzhiyun static ssize_t probe_points_read(struct file *file,
98*4882a593Smuzhiyun char __user *to, size_t count, loff_t *ppos)
99*4882a593Smuzhiyun {
100*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse = file->private_data;
101*4882a593Smuzhiyun struct snd_sof_dev *sdev = dfse->sdev;
102*4882a593Smuzhiyun struct sof_probe_point_desc *desc;
103*4882a593Smuzhiyun size_t num_desc, len = 0;
104*4882a593Smuzhiyun char *buf;
105*4882a593Smuzhiyun int i, ret;
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
108*4882a593Smuzhiyun dev_warn(sdev->dev, "no extractor stream running\n");
109*4882a593Smuzhiyun return -ENOENT;
110*4882a593Smuzhiyun }
111*4882a593Smuzhiyun
112*4882a593Smuzhiyun buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
113*4882a593Smuzhiyun if (!buf)
114*4882a593Smuzhiyun return -ENOMEM;
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc);
117*4882a593Smuzhiyun if (ret < 0)
118*4882a593Smuzhiyun goto exit;
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun for (i = 0; i < num_desc; i++) {
121*4882a593Smuzhiyun ret = snprintf(buf + len, PAGE_SIZE - len,
122*4882a593Smuzhiyun "Id: %#010x Purpose: %d Node id: %#x\n",
123*4882a593Smuzhiyun desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag);
124*4882a593Smuzhiyun if (ret < 0)
125*4882a593Smuzhiyun goto free_desc;
126*4882a593Smuzhiyun len += ret;
127*4882a593Smuzhiyun }
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun ret = simple_read_from_buffer(to, count, ppos, buf, len);
130*4882a593Smuzhiyun free_desc:
131*4882a593Smuzhiyun kfree(desc);
132*4882a593Smuzhiyun exit:
133*4882a593Smuzhiyun kfree(buf);
134*4882a593Smuzhiyun return ret;
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun
probe_points_write(struct file * file,const char __user * from,size_t count,loff_t * ppos)137*4882a593Smuzhiyun static ssize_t probe_points_write(struct file *file,
138*4882a593Smuzhiyun const char __user *from, size_t count, loff_t *ppos)
139*4882a593Smuzhiyun {
140*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse = file->private_data;
141*4882a593Smuzhiyun struct snd_sof_dev *sdev = dfse->sdev;
142*4882a593Smuzhiyun struct sof_probe_point_desc *desc;
143*4882a593Smuzhiyun size_t num_tkns, bytes;
144*4882a593Smuzhiyun u32 *tkns;
145*4882a593Smuzhiyun int ret;
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
148*4882a593Smuzhiyun dev_warn(sdev->dev, "no extractor stream running\n");
149*4882a593Smuzhiyun return -ENOENT;
150*4882a593Smuzhiyun }
151*4882a593Smuzhiyun
152*4882a593Smuzhiyun ret = tokenize_input(from, count, ppos, &tkns, &num_tkns);
153*4882a593Smuzhiyun if (ret < 0)
154*4882a593Smuzhiyun return ret;
155*4882a593Smuzhiyun bytes = sizeof(*tkns) * num_tkns;
156*4882a593Smuzhiyun if (!num_tkns || (bytes % sizeof(*desc))) {
157*4882a593Smuzhiyun ret = -EINVAL;
158*4882a593Smuzhiyun goto exit;
159*4882a593Smuzhiyun }
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun desc = (struct sof_probe_point_desc *)tkns;
162*4882a593Smuzhiyun ret = sof_ipc_probe_points_add(sdev,
163*4882a593Smuzhiyun desc, bytes / sizeof(*desc));
164*4882a593Smuzhiyun if (!ret)
165*4882a593Smuzhiyun ret = count;
166*4882a593Smuzhiyun exit:
167*4882a593Smuzhiyun kfree(tkns);
168*4882a593Smuzhiyun return ret;
169*4882a593Smuzhiyun }
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun static const struct file_operations probe_points_fops = {
172*4882a593Smuzhiyun .open = simple_open,
173*4882a593Smuzhiyun .read = probe_points_read,
174*4882a593Smuzhiyun .write = probe_points_write,
175*4882a593Smuzhiyun .llseek = default_llseek,
176*4882a593Smuzhiyun };
177*4882a593Smuzhiyun
probe_points_remove_write(struct file * file,const char __user * from,size_t count,loff_t * ppos)178*4882a593Smuzhiyun static ssize_t probe_points_remove_write(struct file *file,
179*4882a593Smuzhiyun const char __user *from, size_t count, loff_t *ppos)
180*4882a593Smuzhiyun {
181*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse = file->private_data;
182*4882a593Smuzhiyun struct snd_sof_dev *sdev = dfse->sdev;
183*4882a593Smuzhiyun size_t num_tkns;
184*4882a593Smuzhiyun u32 *tkns;
185*4882a593Smuzhiyun int ret;
186*4882a593Smuzhiyun
187*4882a593Smuzhiyun if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
188*4882a593Smuzhiyun dev_warn(sdev->dev, "no extractor stream running\n");
189*4882a593Smuzhiyun return -ENOENT;
190*4882a593Smuzhiyun }
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun ret = tokenize_input(from, count, ppos, &tkns, &num_tkns);
193*4882a593Smuzhiyun if (ret < 0)
194*4882a593Smuzhiyun return ret;
195*4882a593Smuzhiyun if (!num_tkns) {
196*4882a593Smuzhiyun ret = -EINVAL;
197*4882a593Smuzhiyun goto exit;
198*4882a593Smuzhiyun }
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns);
201*4882a593Smuzhiyun if (!ret)
202*4882a593Smuzhiyun ret = count;
203*4882a593Smuzhiyun exit:
204*4882a593Smuzhiyun kfree(tkns);
205*4882a593Smuzhiyun return ret;
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun static const struct file_operations probe_points_remove_fops = {
209*4882a593Smuzhiyun .open = simple_open,
210*4882a593Smuzhiyun .write = probe_points_remove_write,
211*4882a593Smuzhiyun .llseek = default_llseek,
212*4882a593Smuzhiyun };
213*4882a593Smuzhiyun
snd_sof_debugfs_probe_item(struct snd_sof_dev * sdev,const char * name,mode_t mode,const struct file_operations * fops)214*4882a593Smuzhiyun static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev,
215*4882a593Smuzhiyun const char *name, mode_t mode,
216*4882a593Smuzhiyun const struct file_operations *fops)
217*4882a593Smuzhiyun {
218*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse;
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
221*4882a593Smuzhiyun if (!dfse)
222*4882a593Smuzhiyun return -ENOMEM;
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun dfse->type = SOF_DFSENTRY_TYPE_BUF;
225*4882a593Smuzhiyun dfse->sdev = sdev;
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops);
228*4882a593Smuzhiyun /* add to dfsentry list */
229*4882a593Smuzhiyun list_add(&dfse->list, &sdev->dfsentry_list);
230*4882a593Smuzhiyun
231*4882a593Smuzhiyun return 0;
232*4882a593Smuzhiyun }
233*4882a593Smuzhiyun #endif
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
236*4882a593Smuzhiyun #define MAX_IPC_FLOOD_DURATION_MS 1000
237*4882a593Smuzhiyun #define MAX_IPC_FLOOD_COUNT 10000
238*4882a593Smuzhiyun #define IPC_FLOOD_TEST_RESULT_LEN 512
239*4882a593Smuzhiyun
sof_debug_ipc_flood_test(struct snd_sof_dev * sdev,struct snd_sof_dfsentry * dfse,bool flood_duration_test,unsigned long ipc_duration_ms,unsigned long ipc_count)240*4882a593Smuzhiyun static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev,
241*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse,
242*4882a593Smuzhiyun bool flood_duration_test,
243*4882a593Smuzhiyun unsigned long ipc_duration_ms,
244*4882a593Smuzhiyun unsigned long ipc_count)
245*4882a593Smuzhiyun {
246*4882a593Smuzhiyun struct sof_ipc_cmd_hdr hdr;
247*4882a593Smuzhiyun struct sof_ipc_reply reply;
248*4882a593Smuzhiyun u64 min_response_time = U64_MAX;
249*4882a593Smuzhiyun ktime_t start, end, test_end;
250*4882a593Smuzhiyun u64 avg_response_time = 0;
251*4882a593Smuzhiyun u64 max_response_time = 0;
252*4882a593Smuzhiyun u64 ipc_response_time;
253*4882a593Smuzhiyun int i = 0;
254*4882a593Smuzhiyun int ret;
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun /* configure test IPC */
257*4882a593Smuzhiyun hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD;
258*4882a593Smuzhiyun hdr.size = sizeof(hdr);
259*4882a593Smuzhiyun
260*4882a593Smuzhiyun /* set test end time for duration flood test */
261*4882a593Smuzhiyun if (flood_duration_test)
262*4882a593Smuzhiyun test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC;
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun /* send test IPC's */
265*4882a593Smuzhiyun while (1) {
266*4882a593Smuzhiyun start = ktime_get();
267*4882a593Smuzhiyun ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size,
268*4882a593Smuzhiyun &reply, sizeof(reply));
269*4882a593Smuzhiyun end = ktime_get();
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun if (ret < 0)
272*4882a593Smuzhiyun break;
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun /* compute min and max response times */
275*4882a593Smuzhiyun ipc_response_time = ktime_to_ns(ktime_sub(end, start));
276*4882a593Smuzhiyun min_response_time = min(min_response_time, ipc_response_time);
277*4882a593Smuzhiyun max_response_time = max(max_response_time, ipc_response_time);
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun /* sum up response times */
280*4882a593Smuzhiyun avg_response_time += ipc_response_time;
281*4882a593Smuzhiyun i++;
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun /* test complete? */
284*4882a593Smuzhiyun if (flood_duration_test) {
285*4882a593Smuzhiyun if (ktime_to_ns(end) >= test_end)
286*4882a593Smuzhiyun break;
287*4882a593Smuzhiyun } else {
288*4882a593Smuzhiyun if (i == ipc_count)
289*4882a593Smuzhiyun break;
290*4882a593Smuzhiyun }
291*4882a593Smuzhiyun }
292*4882a593Smuzhiyun
293*4882a593Smuzhiyun if (ret < 0)
294*4882a593Smuzhiyun dev_err(sdev->dev,
295*4882a593Smuzhiyun "error: ipc flood test failed at %d iterations\n", i);
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun /* return if the first IPC fails */
298*4882a593Smuzhiyun if (!i)
299*4882a593Smuzhiyun return ret;
300*4882a593Smuzhiyun
301*4882a593Smuzhiyun /* compute average response time */
302*4882a593Smuzhiyun do_div(avg_response_time, i);
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun /* clear previous test output */
305*4882a593Smuzhiyun memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN);
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun if (flood_duration_test) {
308*4882a593Smuzhiyun dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n",
309*4882a593Smuzhiyun ipc_duration_ms);
310*4882a593Smuzhiyun snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN,
311*4882a593Smuzhiyun "IPC Flood test duration: %lums\n", ipc_duration_ms);
312*4882a593Smuzhiyun }
313*4882a593Smuzhiyun
314*4882a593Smuzhiyun dev_dbg(sdev->dev,
315*4882a593Smuzhiyun "IPC Flood count: %d, Avg response time: %lluns\n",
316*4882a593Smuzhiyun i, avg_response_time);
317*4882a593Smuzhiyun dev_dbg(sdev->dev, "Max response time: %lluns\n",
318*4882a593Smuzhiyun max_response_time);
319*4882a593Smuzhiyun dev_dbg(sdev->dev, "Min response time: %lluns\n",
320*4882a593Smuzhiyun min_response_time);
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun /* format output string */
323*4882a593Smuzhiyun snprintf(dfse->cache_buf + strlen(dfse->cache_buf),
324*4882a593Smuzhiyun IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf),
325*4882a593Smuzhiyun "IPC Flood count: %d\nAvg response time: %lluns\n",
326*4882a593Smuzhiyun i, avg_response_time);
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun snprintf(dfse->cache_buf + strlen(dfse->cache_buf),
329*4882a593Smuzhiyun IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf),
330*4882a593Smuzhiyun "Max response time: %lluns\nMin response time: %lluns\n",
331*4882a593Smuzhiyun max_response_time, min_response_time);
332*4882a593Smuzhiyun
333*4882a593Smuzhiyun return ret;
334*4882a593Smuzhiyun }
335*4882a593Smuzhiyun #endif
336*4882a593Smuzhiyun
sof_dfsentry_write(struct file * file,const char __user * buffer,size_t count,loff_t * ppos)337*4882a593Smuzhiyun static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer,
338*4882a593Smuzhiyun size_t count, loff_t *ppos)
339*4882a593Smuzhiyun {
340*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
341*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse = file->private_data;
342*4882a593Smuzhiyun struct snd_sof_dev *sdev = dfse->sdev;
343*4882a593Smuzhiyun unsigned long ipc_duration_ms = 0;
344*4882a593Smuzhiyun bool flood_duration_test = false;
345*4882a593Smuzhiyun unsigned long ipc_count = 0;
346*4882a593Smuzhiyun struct dentry *dentry;
347*4882a593Smuzhiyun int err;
348*4882a593Smuzhiyun #endif
349*4882a593Smuzhiyun size_t size;
350*4882a593Smuzhiyun char *string;
351*4882a593Smuzhiyun int ret;
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun string = kzalloc(count+1, GFP_KERNEL);
354*4882a593Smuzhiyun if (!string)
355*4882a593Smuzhiyun return -ENOMEM;
356*4882a593Smuzhiyun
357*4882a593Smuzhiyun size = simple_write_to_buffer(string, count, ppos, buffer, count);
358*4882a593Smuzhiyun ret = size;
359*4882a593Smuzhiyun
360*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
361*4882a593Smuzhiyun /*
362*4882a593Smuzhiyun * write op is only supported for ipc_flood_count or
363*4882a593Smuzhiyun * ipc_flood_duration_ms debugfs entries atm.
364*4882a593Smuzhiyun * ipc_flood_count floods the DSP with the number of IPC's specified.
365*4882a593Smuzhiyun * ipc_duration_ms test floods the DSP for the time specified
366*4882a593Smuzhiyun * in the debugfs entry.
367*4882a593Smuzhiyun */
368*4882a593Smuzhiyun dentry = file->f_path.dentry;
369*4882a593Smuzhiyun if (strcmp(dentry->d_name.name, "ipc_flood_count") &&
370*4882a593Smuzhiyun strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) {
371*4882a593Smuzhiyun ret = -EINVAL;
372*4882a593Smuzhiyun goto out;
373*4882a593Smuzhiyun }
374*4882a593Smuzhiyun
375*4882a593Smuzhiyun if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))
376*4882a593Smuzhiyun flood_duration_test = true;
377*4882a593Smuzhiyun
378*4882a593Smuzhiyun /* test completion criterion */
379*4882a593Smuzhiyun if (flood_duration_test)
380*4882a593Smuzhiyun ret = kstrtoul(string, 0, &ipc_duration_ms);
381*4882a593Smuzhiyun else
382*4882a593Smuzhiyun ret = kstrtoul(string, 0, &ipc_count);
383*4882a593Smuzhiyun if (ret < 0)
384*4882a593Smuzhiyun goto out;
385*4882a593Smuzhiyun
386*4882a593Smuzhiyun /* limit max duration/ipc count for flood test */
387*4882a593Smuzhiyun if (flood_duration_test) {
388*4882a593Smuzhiyun if (!ipc_duration_ms) {
389*4882a593Smuzhiyun ret = size;
390*4882a593Smuzhiyun goto out;
391*4882a593Smuzhiyun }
392*4882a593Smuzhiyun
393*4882a593Smuzhiyun /* find the minimum. min() is not used to avoid warnings */
394*4882a593Smuzhiyun if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)
395*4882a593Smuzhiyun ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;
396*4882a593Smuzhiyun } else {
397*4882a593Smuzhiyun if (!ipc_count) {
398*4882a593Smuzhiyun ret = size;
399*4882a593Smuzhiyun goto out;
400*4882a593Smuzhiyun }
401*4882a593Smuzhiyun
402*4882a593Smuzhiyun /* find the minimum. min() is not used to avoid warnings */
403*4882a593Smuzhiyun if (ipc_count > MAX_IPC_FLOOD_COUNT)
404*4882a593Smuzhiyun ipc_count = MAX_IPC_FLOOD_COUNT;
405*4882a593Smuzhiyun }
406*4882a593Smuzhiyun
407*4882a593Smuzhiyun ret = pm_runtime_get_sync(sdev->dev);
408*4882a593Smuzhiyun if (ret < 0 && ret != -EACCES) {
409*4882a593Smuzhiyun dev_err_ratelimited(sdev->dev,
410*4882a593Smuzhiyun "error: debugfs write failed to resume %d\n",
411*4882a593Smuzhiyun ret);
412*4882a593Smuzhiyun pm_runtime_put_noidle(sdev->dev);
413*4882a593Smuzhiyun goto out;
414*4882a593Smuzhiyun }
415*4882a593Smuzhiyun
416*4882a593Smuzhiyun /* flood test */
417*4882a593Smuzhiyun ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test,
418*4882a593Smuzhiyun ipc_duration_ms, ipc_count);
419*4882a593Smuzhiyun
420*4882a593Smuzhiyun pm_runtime_mark_last_busy(sdev->dev);
421*4882a593Smuzhiyun err = pm_runtime_put_autosuspend(sdev->dev);
422*4882a593Smuzhiyun if (err < 0)
423*4882a593Smuzhiyun dev_err_ratelimited(sdev->dev,
424*4882a593Smuzhiyun "error: debugfs write failed to idle %d\n",
425*4882a593Smuzhiyun err);
426*4882a593Smuzhiyun
427*4882a593Smuzhiyun /* return size if test is successful */
428*4882a593Smuzhiyun if (ret >= 0)
429*4882a593Smuzhiyun ret = size;
430*4882a593Smuzhiyun out:
431*4882a593Smuzhiyun #endif
432*4882a593Smuzhiyun kfree(string);
433*4882a593Smuzhiyun return ret;
434*4882a593Smuzhiyun }
435*4882a593Smuzhiyun
sof_dfsentry_read(struct file * file,char __user * buffer,size_t count,loff_t * ppos)436*4882a593Smuzhiyun static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer,
437*4882a593Smuzhiyun size_t count, loff_t *ppos)
438*4882a593Smuzhiyun {
439*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse = file->private_data;
440*4882a593Smuzhiyun struct snd_sof_dev *sdev = dfse->sdev;
441*4882a593Smuzhiyun loff_t pos = *ppos;
442*4882a593Smuzhiyun size_t size_ret;
443*4882a593Smuzhiyun int skip = 0;
444*4882a593Smuzhiyun int size;
445*4882a593Smuzhiyun u8 *buf;
446*4882a593Smuzhiyun
447*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
448*4882a593Smuzhiyun struct dentry *dentry;
449*4882a593Smuzhiyun
450*4882a593Smuzhiyun dentry = file->f_path.dentry;
451*4882a593Smuzhiyun if ((!strcmp(dentry->d_name.name, "ipc_flood_count") ||
452*4882a593Smuzhiyun !strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) &&
453*4882a593Smuzhiyun dfse->cache_buf) {
454*4882a593Smuzhiyun if (*ppos)
455*4882a593Smuzhiyun return 0;
456*4882a593Smuzhiyun
457*4882a593Smuzhiyun count = strlen(dfse->cache_buf);
458*4882a593Smuzhiyun size_ret = copy_to_user(buffer, dfse->cache_buf, count);
459*4882a593Smuzhiyun if (size_ret)
460*4882a593Smuzhiyun return -EFAULT;
461*4882a593Smuzhiyun
462*4882a593Smuzhiyun *ppos += count;
463*4882a593Smuzhiyun return count;
464*4882a593Smuzhiyun }
465*4882a593Smuzhiyun #endif
466*4882a593Smuzhiyun size = dfse->size;
467*4882a593Smuzhiyun
468*4882a593Smuzhiyun /* validate position & count */
469*4882a593Smuzhiyun if (pos < 0)
470*4882a593Smuzhiyun return -EINVAL;
471*4882a593Smuzhiyun if (pos >= size || !count)
472*4882a593Smuzhiyun return 0;
473*4882a593Smuzhiyun /* find the minimum. min() is not used since it adds sparse warnings */
474*4882a593Smuzhiyun if (count > size - pos)
475*4882a593Smuzhiyun count = size - pos;
476*4882a593Smuzhiyun
477*4882a593Smuzhiyun /* align io read start to u32 multiple */
478*4882a593Smuzhiyun pos = ALIGN_DOWN(pos, 4);
479*4882a593Smuzhiyun
480*4882a593Smuzhiyun /* intermediate buffer size must be u32 multiple */
481*4882a593Smuzhiyun size = ALIGN(count, 4);
482*4882a593Smuzhiyun
483*4882a593Smuzhiyun /* if start position is unaligned, read extra u32 */
484*4882a593Smuzhiyun if (unlikely(pos != *ppos)) {
485*4882a593Smuzhiyun skip = *ppos - pos;
486*4882a593Smuzhiyun if (pos + size + 4 < dfse->size)
487*4882a593Smuzhiyun size += 4;
488*4882a593Smuzhiyun }
489*4882a593Smuzhiyun
490*4882a593Smuzhiyun buf = kzalloc(size, GFP_KERNEL);
491*4882a593Smuzhiyun if (!buf)
492*4882a593Smuzhiyun return -ENOMEM;
493*4882a593Smuzhiyun
494*4882a593Smuzhiyun if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) {
495*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
496*4882a593Smuzhiyun /*
497*4882a593Smuzhiyun * If the DSP is active: copy from IO.
498*4882a593Smuzhiyun * If the DSP is suspended:
499*4882a593Smuzhiyun * - Copy from IO if the memory is always accessible.
500*4882a593Smuzhiyun * - Otherwise, copy from cached buffer.
501*4882a593Smuzhiyun */
502*4882a593Smuzhiyun if (pm_runtime_active(sdev->dev) ||
503*4882a593Smuzhiyun dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) {
504*4882a593Smuzhiyun memcpy_fromio(buf, dfse->io_mem + pos, size);
505*4882a593Smuzhiyun } else {
506*4882a593Smuzhiyun dev_info(sdev->dev,
507*4882a593Smuzhiyun "Copying cached debugfs data\n");
508*4882a593Smuzhiyun memcpy(buf, dfse->cache_buf + pos, size);
509*4882a593Smuzhiyun }
510*4882a593Smuzhiyun #else
511*4882a593Smuzhiyun /* if the DSP is in D3 */
512*4882a593Smuzhiyun if (!pm_runtime_active(sdev->dev) &&
513*4882a593Smuzhiyun dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {
514*4882a593Smuzhiyun dev_err(sdev->dev,
515*4882a593Smuzhiyun "error: debugfs entry cannot be read in DSP D3\n");
516*4882a593Smuzhiyun kfree(buf);
517*4882a593Smuzhiyun return -EINVAL;
518*4882a593Smuzhiyun }
519*4882a593Smuzhiyun
520*4882a593Smuzhiyun memcpy_fromio(buf, dfse->io_mem + pos, size);
521*4882a593Smuzhiyun #endif
522*4882a593Smuzhiyun } else {
523*4882a593Smuzhiyun memcpy(buf, ((u8 *)(dfse->buf) + pos), size);
524*4882a593Smuzhiyun }
525*4882a593Smuzhiyun
526*4882a593Smuzhiyun /* copy to userspace */
527*4882a593Smuzhiyun size_ret = copy_to_user(buffer, buf + skip, count);
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun kfree(buf);
530*4882a593Smuzhiyun
531*4882a593Smuzhiyun /* update count & position if copy succeeded */
532*4882a593Smuzhiyun if (size_ret)
533*4882a593Smuzhiyun return -EFAULT;
534*4882a593Smuzhiyun
535*4882a593Smuzhiyun *ppos = pos + count;
536*4882a593Smuzhiyun
537*4882a593Smuzhiyun return count;
538*4882a593Smuzhiyun }
539*4882a593Smuzhiyun
540*4882a593Smuzhiyun static const struct file_operations sof_dfs_fops = {
541*4882a593Smuzhiyun .open = simple_open,
542*4882a593Smuzhiyun .read = sof_dfsentry_read,
543*4882a593Smuzhiyun .llseek = default_llseek,
544*4882a593Smuzhiyun .write = sof_dfsentry_write,
545*4882a593Smuzhiyun };
546*4882a593Smuzhiyun
547*4882a593Smuzhiyun /* create FS entry for debug files that can expose DSP memories, registers */
snd_sof_debugfs_io_item(struct snd_sof_dev * sdev,void __iomem * base,size_t size,const char * name,enum sof_debugfs_access_type access_type)548*4882a593Smuzhiyun int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,
549*4882a593Smuzhiyun void __iomem *base, size_t size,
550*4882a593Smuzhiyun const char *name,
551*4882a593Smuzhiyun enum sof_debugfs_access_type access_type)
552*4882a593Smuzhiyun {
553*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse;
554*4882a593Smuzhiyun
555*4882a593Smuzhiyun if (!sdev)
556*4882a593Smuzhiyun return -EINVAL;
557*4882a593Smuzhiyun
558*4882a593Smuzhiyun dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
559*4882a593Smuzhiyun if (!dfse)
560*4882a593Smuzhiyun return -ENOMEM;
561*4882a593Smuzhiyun
562*4882a593Smuzhiyun dfse->type = SOF_DFSENTRY_TYPE_IOMEM;
563*4882a593Smuzhiyun dfse->io_mem = base;
564*4882a593Smuzhiyun dfse->size = size;
565*4882a593Smuzhiyun dfse->sdev = sdev;
566*4882a593Smuzhiyun dfse->access_type = access_type;
567*4882a593Smuzhiyun
568*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
569*4882a593Smuzhiyun /*
570*4882a593Smuzhiyun * allocate cache buffer that will be used to save the mem window
571*4882a593Smuzhiyun * contents prior to suspend
572*4882a593Smuzhiyun */
573*4882a593Smuzhiyun if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) {
574*4882a593Smuzhiyun dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL);
575*4882a593Smuzhiyun if (!dfse->cache_buf)
576*4882a593Smuzhiyun return -ENOMEM;
577*4882a593Smuzhiyun }
578*4882a593Smuzhiyun #endif
579*4882a593Smuzhiyun
580*4882a593Smuzhiyun debugfs_create_file(name, 0444, sdev->debugfs_root, dfse,
581*4882a593Smuzhiyun &sof_dfs_fops);
582*4882a593Smuzhiyun
583*4882a593Smuzhiyun /* add to dfsentry list */
584*4882a593Smuzhiyun list_add(&dfse->list, &sdev->dfsentry_list);
585*4882a593Smuzhiyun
586*4882a593Smuzhiyun return 0;
587*4882a593Smuzhiyun }
588*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item);
589*4882a593Smuzhiyun
590*4882a593Smuzhiyun /* create FS entry for debug files to expose kernel memory */
snd_sof_debugfs_buf_item(struct snd_sof_dev * sdev,void * base,size_t size,const char * name,mode_t mode)591*4882a593Smuzhiyun int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,
592*4882a593Smuzhiyun void *base, size_t size,
593*4882a593Smuzhiyun const char *name, mode_t mode)
594*4882a593Smuzhiyun {
595*4882a593Smuzhiyun struct snd_sof_dfsentry *dfse;
596*4882a593Smuzhiyun
597*4882a593Smuzhiyun if (!sdev)
598*4882a593Smuzhiyun return -EINVAL;
599*4882a593Smuzhiyun
600*4882a593Smuzhiyun dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
601*4882a593Smuzhiyun if (!dfse)
602*4882a593Smuzhiyun return -ENOMEM;
603*4882a593Smuzhiyun
604*4882a593Smuzhiyun dfse->type = SOF_DFSENTRY_TYPE_BUF;
605*4882a593Smuzhiyun dfse->buf = base;
606*4882a593Smuzhiyun dfse->size = size;
607*4882a593Smuzhiyun dfse->sdev = sdev;
608*4882a593Smuzhiyun
609*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
610*4882a593Smuzhiyun /*
611*4882a593Smuzhiyun * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries.
612*4882a593Smuzhiyun * So, use it to save the results of the last IPC flood test.
613*4882a593Smuzhiyun */
614*4882a593Smuzhiyun dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN,
615*4882a593Smuzhiyun GFP_KERNEL);
616*4882a593Smuzhiyun if (!dfse->cache_buf)
617*4882a593Smuzhiyun return -ENOMEM;
618*4882a593Smuzhiyun #endif
619*4882a593Smuzhiyun
620*4882a593Smuzhiyun debugfs_create_file(name, mode, sdev->debugfs_root, dfse,
621*4882a593Smuzhiyun &sof_dfs_fops);
622*4882a593Smuzhiyun /* add to dfsentry list */
623*4882a593Smuzhiyun list_add(&dfse->list, &sdev->dfsentry_list);
624*4882a593Smuzhiyun
625*4882a593Smuzhiyun return 0;
626*4882a593Smuzhiyun }
627*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item);
628*4882a593Smuzhiyun
snd_sof_dbg_init(struct snd_sof_dev * sdev)629*4882a593Smuzhiyun int snd_sof_dbg_init(struct snd_sof_dev *sdev)
630*4882a593Smuzhiyun {
631*4882a593Smuzhiyun const struct snd_sof_dsp_ops *ops = sof_ops(sdev);
632*4882a593Smuzhiyun const struct snd_sof_debugfs_map *map;
633*4882a593Smuzhiyun int i;
634*4882a593Smuzhiyun int err;
635*4882a593Smuzhiyun
636*4882a593Smuzhiyun /* use "sof" as top level debugFS dir */
637*4882a593Smuzhiyun sdev->debugfs_root = debugfs_create_dir("sof", NULL);
638*4882a593Smuzhiyun
639*4882a593Smuzhiyun /* init dfsentry list */
640*4882a593Smuzhiyun INIT_LIST_HEAD(&sdev->dfsentry_list);
641*4882a593Smuzhiyun
642*4882a593Smuzhiyun /* create debugFS files for platform specific MMIO/DSP memories */
643*4882a593Smuzhiyun for (i = 0; i < ops->debug_map_count; i++) {
644*4882a593Smuzhiyun map = &ops->debug_map[i];
645*4882a593Smuzhiyun
646*4882a593Smuzhiyun err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] +
647*4882a593Smuzhiyun map->offset, map->size,
648*4882a593Smuzhiyun map->name, map->access_type);
649*4882a593Smuzhiyun /* errors are only due to memory allocation, not debugfs */
650*4882a593Smuzhiyun if (err < 0)
651*4882a593Smuzhiyun return err;
652*4882a593Smuzhiyun }
653*4882a593Smuzhiyun
654*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
655*4882a593Smuzhiyun err = snd_sof_debugfs_probe_item(sdev, "probe_points",
656*4882a593Smuzhiyun 0644, &probe_points_fops);
657*4882a593Smuzhiyun if (err < 0)
658*4882a593Smuzhiyun return err;
659*4882a593Smuzhiyun err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove",
660*4882a593Smuzhiyun 0200, &probe_points_remove_fops);
661*4882a593Smuzhiyun if (err < 0)
662*4882a593Smuzhiyun return err;
663*4882a593Smuzhiyun #endif
664*4882a593Smuzhiyun
665*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
666*4882a593Smuzhiyun /* create read-write ipc_flood_count debugfs entry */
667*4882a593Smuzhiyun err = snd_sof_debugfs_buf_item(sdev, NULL, 0,
668*4882a593Smuzhiyun "ipc_flood_count", 0666);
669*4882a593Smuzhiyun
670*4882a593Smuzhiyun /* errors are only due to memory allocation, not debugfs */
671*4882a593Smuzhiyun if (err < 0)
672*4882a593Smuzhiyun return err;
673*4882a593Smuzhiyun
674*4882a593Smuzhiyun /* create read-write ipc_flood_duration_ms debugfs entry */
675*4882a593Smuzhiyun err = snd_sof_debugfs_buf_item(sdev, NULL, 0,
676*4882a593Smuzhiyun "ipc_flood_duration_ms", 0666);
677*4882a593Smuzhiyun
678*4882a593Smuzhiyun /* errors are only due to memory allocation, not debugfs */
679*4882a593Smuzhiyun if (err < 0)
680*4882a593Smuzhiyun return err;
681*4882a593Smuzhiyun #endif
682*4882a593Smuzhiyun
683*4882a593Smuzhiyun return 0;
684*4882a593Smuzhiyun }
685*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(snd_sof_dbg_init);
686*4882a593Smuzhiyun
snd_sof_free_debug(struct snd_sof_dev * sdev)687*4882a593Smuzhiyun void snd_sof_free_debug(struct snd_sof_dev *sdev)
688*4882a593Smuzhiyun {
689*4882a593Smuzhiyun debugfs_remove_recursive(sdev->debugfs_root);
690*4882a593Smuzhiyun }
691*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(snd_sof_free_debug);
692*4882a593Smuzhiyun
snd_sof_handle_fw_exception(struct snd_sof_dev * sdev)693*4882a593Smuzhiyun void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev)
694*4882a593Smuzhiyun {
695*4882a593Smuzhiyun if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||
696*4882a593Smuzhiyun (sof_core_debug & SOF_DBG_RETAIN_CTX)) {
697*4882a593Smuzhiyun /* should we prevent DSP entering D3 ? */
698*4882a593Smuzhiyun dev_info(sdev->dev, "info: preventing DSP entering D3 state to preserve context\n");
699*4882a593Smuzhiyun pm_runtime_get_noresume(sdev->dev);
700*4882a593Smuzhiyun }
701*4882a593Smuzhiyun
702*4882a593Smuzhiyun /* dump vital information to the logs */
703*4882a593Smuzhiyun snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX);
704*4882a593Smuzhiyun snd_sof_ipc_dump(sdev);
705*4882a593Smuzhiyun snd_sof_trace_notify_for_error(sdev);
706*4882a593Smuzhiyun }
707*4882a593Smuzhiyun EXPORT_SYMBOL(snd_sof_handle_fw_exception);
708