1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2010 Google, Inc. |
4 | * Author: Erik Gilling <konkers@android.com> |
5 | * |
6 | * Copyright (C) 2011-2013 NVIDIA Corporation |
7 | */ |
8 | |
9 | #include <linux/debugfs.h> |
10 | #include <linux/pm_runtime.h> |
11 | #include <linux/seq_file.h> |
12 | #include <linux/uaccess.h> |
13 | |
14 | #include <linux/io.h> |
15 | |
16 | #include "dev.h" |
17 | #include "debug.h" |
18 | #include "channel.h" |
19 | |
20 | static DEFINE_MUTEX(debug_lock); |
21 | |
22 | unsigned int host1x_debug_trace_cmdbuf; |
23 | |
24 | static pid_t host1x_debug_force_timeout_pid; |
25 | static u32 host1x_debug_force_timeout_val; |
26 | static u32 host1x_debug_force_timeout_channel; |
27 | |
28 | void host1x_debug_output(struct output *o, const char *fmt, ...) |
29 | { |
30 | va_list args; |
31 | int len; |
32 | |
33 | va_start(args, fmt); |
34 | len = vsnprintf(buf: o->buf, size: sizeof(o->buf), fmt, args); |
35 | va_end(args); |
36 | |
37 | o->fn(o->ctx, o->buf, len, false); |
38 | } |
39 | |
40 | void host1x_debug_cont(struct output *o, const char *fmt, ...) |
41 | { |
42 | va_list args; |
43 | int len; |
44 | |
45 | va_start(args, fmt); |
46 | len = vsnprintf(buf: o->buf, size: sizeof(o->buf), fmt, args); |
47 | va_end(args); |
48 | |
49 | o->fn(o->ctx, o->buf, len, true); |
50 | } |
51 | |
52 | static int show_channel(struct host1x_channel *ch, void *data, bool show_fifo) |
53 | { |
54 | struct host1x *m = dev_get_drvdata(dev: ch->dev->parent); |
55 | struct output *o = data; |
56 | int err; |
57 | |
58 | err = pm_runtime_resume_and_get(dev: m->dev); |
59 | if (err < 0) |
60 | return err; |
61 | |
62 | mutex_lock(&ch->cdma.lock); |
63 | mutex_lock(&debug_lock); |
64 | |
65 | if (show_fifo) |
66 | host1x_hw_show_channel_fifo(host: m, channel: ch, o); |
67 | |
68 | host1x_hw_show_channel_cdma(host: m, channel: ch, o); |
69 | |
70 | mutex_unlock(lock: &debug_lock); |
71 | mutex_unlock(lock: &ch->cdma.lock); |
72 | |
73 | pm_runtime_put(dev: m->dev); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static void show_syncpts(struct host1x *m, struct output *o, bool show_all) |
79 | { |
80 | unsigned long irqflags; |
81 | struct list_head *pos; |
82 | unsigned int i; |
83 | int err; |
84 | |
85 | host1x_debug_output(o, fmt: "---- syncpts ----\n" ); |
86 | |
87 | err = pm_runtime_resume_and_get(dev: m->dev); |
88 | if (err < 0) |
89 | return; |
90 | |
91 | for (i = 0; i < host1x_syncpt_nb_pts(host: m); i++) { |
92 | u32 max = host1x_syncpt_read_max(sp: m->syncpt + i); |
93 | u32 min = host1x_syncpt_load(sp: m->syncpt + i); |
94 | unsigned int waiters = 0; |
95 | |
96 | spin_lock_irqsave(&m->syncpt[i].fences.lock, irqflags); |
97 | list_for_each(pos, &m->syncpt[i].fences.list) |
98 | waiters++; |
99 | spin_unlock_irqrestore(lock: &m->syncpt[i].fences.lock, flags: irqflags); |
100 | |
101 | if (!kref_read(kref: &m->syncpt[i].ref)) |
102 | continue; |
103 | |
104 | if (!show_all && !min && !max && !waiters) |
105 | continue; |
106 | |
107 | host1x_debug_output(o, |
108 | fmt: "id %u (%s) min %d max %d (%d waiters)\n" , |
109 | i, m->syncpt[i].name, min, max, waiters); |
110 | } |
111 | |
112 | for (i = 0; i < host1x_syncpt_nb_bases(host: m); i++) { |
113 | u32 base_val; |
114 | |
115 | base_val = host1x_syncpt_load_wait_base(sp: m->syncpt + i); |
116 | if (base_val) |
117 | host1x_debug_output(o, fmt: "waitbase id %u val %d\n" , i, |
118 | base_val); |
119 | } |
120 | |
121 | pm_runtime_put(dev: m->dev); |
122 | |
123 | host1x_debug_output(o, fmt: "\n" ); |
124 | } |
125 | |
126 | static void show_all(struct host1x *m, struct output *o, bool show_fifo) |
127 | { |
128 | unsigned int i; |
129 | |
130 | host1x_hw_show_mlocks(host: m, o); |
131 | show_syncpts(m, o, show_all: true); |
132 | host1x_debug_output(o, fmt: "---- channels ----\n" ); |
133 | |
134 | for (i = 0; i < m->info->nb_channels; ++i) { |
135 | struct host1x_channel *ch = host1x_channel_get_index(host: m, index: i); |
136 | |
137 | if (ch) { |
138 | show_channel(ch, data: o, show_fifo); |
139 | host1x_channel_put(channel: ch); |
140 | } |
141 | } |
142 | } |
143 | |
144 | static int host1x_debug_all_show(struct seq_file *s, void *unused) |
145 | { |
146 | struct output o = { |
147 | .fn = write_to_seqfile, |
148 | .ctx = s |
149 | }; |
150 | |
151 | show_all(m: s->private, o: &o, show_fifo: true); |
152 | |
153 | return 0; |
154 | } |
155 | DEFINE_SHOW_ATTRIBUTE(host1x_debug_all); |
156 | |
157 | static int host1x_debug_show(struct seq_file *s, void *unused) |
158 | { |
159 | struct output o = { |
160 | .fn = write_to_seqfile, |
161 | .ctx = s |
162 | }; |
163 | |
164 | show_all(m: s->private, o: &o, show_fifo: false); |
165 | |
166 | return 0; |
167 | } |
168 | DEFINE_SHOW_ATTRIBUTE(host1x_debug); |
169 | |
170 | static void host1x_debugfs_init(struct host1x *host1x) |
171 | { |
172 | struct dentry *de = debugfs_create_dir(name: "tegra-host1x" , NULL); |
173 | |
174 | /* Store the created entry */ |
175 | host1x->debugfs = de; |
176 | |
177 | debugfs_create_file(name: "status" , S_IRUGO, parent: de, data: host1x, fops: &host1x_debug_fops); |
178 | debugfs_create_file(name: "status_all" , S_IRUGO, parent: de, data: host1x, |
179 | fops: &host1x_debug_all_fops); |
180 | |
181 | debugfs_create_u32(name: "trace_cmdbuf" , S_IRUGO|S_IWUSR, parent: de, |
182 | value: &host1x_debug_trace_cmdbuf); |
183 | |
184 | host1x_hw_debug_init(host: host1x, de); |
185 | |
186 | debugfs_create_u32(name: "force_timeout_pid" , S_IRUGO|S_IWUSR, parent: de, |
187 | value: &host1x_debug_force_timeout_pid); |
188 | debugfs_create_u32(name: "force_timeout_val" , S_IRUGO|S_IWUSR, parent: de, |
189 | value: &host1x_debug_force_timeout_val); |
190 | debugfs_create_u32(name: "force_timeout_channel" , S_IRUGO|S_IWUSR, parent: de, |
191 | value: &host1x_debug_force_timeout_channel); |
192 | } |
193 | |
194 | static void host1x_debugfs_exit(struct host1x *host1x) |
195 | { |
196 | debugfs_remove_recursive(dentry: host1x->debugfs); |
197 | } |
198 | |
199 | void host1x_debug_init(struct host1x *host1x) |
200 | { |
201 | if (IS_ENABLED(CONFIG_DEBUG_FS)) |
202 | host1x_debugfs_init(host1x); |
203 | } |
204 | |
205 | void host1x_debug_deinit(struct host1x *host1x) |
206 | { |
207 | if (IS_ENABLED(CONFIG_DEBUG_FS)) |
208 | host1x_debugfs_exit(host1x); |
209 | } |
210 | |
211 | void host1x_debug_dump(struct host1x *host1x) |
212 | { |
213 | struct output o = { |
214 | .fn = write_to_printk |
215 | }; |
216 | |
217 | show_all(m: host1x, o: &o, show_fifo: true); |
218 | } |
219 | |
220 | void host1x_debug_dump_syncpts(struct host1x *host1x) |
221 | { |
222 | struct output o = { |
223 | .fn = write_to_printk |
224 | }; |
225 | |
226 | show_syncpts(m: host1x, o: &o, show_all: false); |
227 | } |
228 | |