diff options
Diffstat (limited to 'drivers/gpu/nvgpu/common/linux/debug_pmu.c')
-rw-r--r-- | drivers/gpu/nvgpu/common/linux/debug_pmu.c | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/drivers/gpu/nvgpu/common/linux/debug_pmu.c b/drivers/gpu/nvgpu/common/linux/debug_pmu.c new file mode 100644 index 00000000..f19f5139 --- /dev/null +++ b/drivers/gpu/nvgpu/common/linux/debug_pmu.c | |||
@@ -0,0 +1,479 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 NVIDIA Corporation. All rights reserved. | ||
3 | * | ||
4 | * This software is licensed under the terms of the GNU General Public | ||
5 | * License version 2, as published by the Free Software Foundation, and | ||
6 | * may be copied, distributed, and modified under those terms. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, | ||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | * | ||
13 | */ | ||
14 | |||
15 | #include "debug_pmu.h" | ||
16 | #include "gk20a/platform_gk20a.h" | ||
17 | |||
18 | #include <linux/debugfs.h> | ||
19 | #include <linux/seq_file.h> | ||
20 | #include <linux/uaccess.h> | ||
21 | |||
22 | static int lpwr_debug_show(struct seq_file *s, void *data) | ||
23 | { | ||
24 | struct gk20a *g = s->private; | ||
25 | |||
26 | if (g->ops.pmu.pmu_pg_engines_feature_list && | ||
27 | g->ops.pmu.pmu_pg_engines_feature_list(g, | ||
28 | PMU_PG_ELPG_ENGINE_ID_GRAPHICS) != | ||
29 | PMU_PG_FEATURE_GR_POWER_GATING_ENABLED) { | ||
30 | seq_printf(s, "PSTATE: %u\n" | ||
31 | "RPPG Enabled: %u\n" | ||
32 | "RPPG ref count: %u\n" | ||
33 | "RPPG state: %u\n" | ||
34 | "MSCG Enabled: %u\n" | ||
35 | "MSCG pstate state: %u\n" | ||
36 | "MSCG transition state: %u\n", | ||
37 | g->ops.clk_arb.get_current_pstate(g), | ||
38 | g->elpg_enabled, g->pmu.elpg_refcnt, | ||
39 | g->pmu.elpg_stat, g->mscg_enabled, | ||
40 | g->pmu.mscg_stat, g->pmu.mscg_transition_state); | ||
41 | |||
42 | } else | ||
43 | seq_printf(s, "ELPG Enabled: %u\n" | ||
44 | "ELPG ref count: %u\n" | ||
45 | "ELPG state: %u\n", | ||
46 | g->elpg_enabled, g->pmu.elpg_refcnt, | ||
47 | g->pmu.elpg_stat); | ||
48 | |||
49 | return 0; | ||
50 | |||
51 | } | ||
52 | |||
53 | static int lpwr_debug_open(struct inode *inode, struct file *file) | ||
54 | { | ||
55 | return single_open(file, lpwr_debug_show, inode->i_private); | ||
56 | } | ||
57 | |||
58 | static const struct file_operations lpwr_debug_fops = { | ||
59 | .open = lpwr_debug_open, | ||
60 | .read = seq_read, | ||
61 | .llseek = seq_lseek, | ||
62 | .release = single_release, | ||
63 | }; | ||
64 | |||
65 | static int mscg_stat_show(struct seq_file *s, void *data) | ||
66 | { | ||
67 | struct gk20a *g = s->private; | ||
68 | u64 total_ingating, total_ungating, residency, divisor, dividend; | ||
69 | struct pmu_pg_stats_data pg_stat_data = { 0 }; | ||
70 | int err; | ||
71 | |||
72 | /* Don't unnecessarily power on the device */ | ||
73 | if (g->power_on) { | ||
74 | err = gk20a_busy(g); | ||
75 | if (err) | ||
76 | return err; | ||
77 | |||
78 | gk20a_pmu_get_pg_stats(g, | ||
79 | PMU_PG_ELPG_ENGINE_ID_MS, &pg_stat_data); | ||
80 | gk20a_idle(g); | ||
81 | } | ||
82 | total_ingating = g->pg_ingating_time_us + | ||
83 | (u64)pg_stat_data.ingating_time; | ||
84 | total_ungating = g->pg_ungating_time_us + | ||
85 | (u64)pg_stat_data.ungating_time; | ||
86 | |||
87 | divisor = total_ingating + total_ungating; | ||
88 | |||
89 | /* We compute the residency on a scale of 1000 */ | ||
90 | dividend = total_ingating * 1000; | ||
91 | |||
92 | if (divisor) | ||
93 | residency = div64_u64(dividend, divisor); | ||
94 | else | ||
95 | residency = 0; | ||
96 | |||
97 | seq_printf(s, | ||
98 | "Time in MSCG: %llu us\n" | ||
99 | "Time out of MSCG: %llu us\n" | ||
100 | "MSCG residency ratio: %llu\n" | ||
101 | "MSCG Entry Count: %u\n" | ||
102 | "MSCG Avg Entry latency %u\n" | ||
103 | "MSCG Avg Exit latency %u\n", | ||
104 | total_ingating, total_ungating, | ||
105 | residency, pg_stat_data.gating_cnt, | ||
106 | pg_stat_data.avg_entry_latency_us, | ||
107 | pg_stat_data.avg_exit_latency_us); | ||
108 | return 0; | ||
109 | |||
110 | } | ||
111 | |||
112 | static int mscg_stat_open(struct inode *inode, struct file *file) | ||
113 | { | ||
114 | return single_open(file, mscg_stat_show, inode->i_private); | ||
115 | } | ||
116 | |||
117 | static const struct file_operations mscg_stat_fops = { | ||
118 | .open = mscg_stat_open, | ||
119 | .read = seq_read, | ||
120 | .llseek = seq_lseek, | ||
121 | .release = single_release, | ||
122 | }; | ||
123 | |||
124 | static int mscg_transitions_show(struct seq_file *s, void *data) | ||
125 | { | ||
126 | struct gk20a *g = s->private; | ||
127 | struct pmu_pg_stats_data pg_stat_data = { 0 }; | ||
128 | u32 total_gating_cnt; | ||
129 | int err; | ||
130 | |||
131 | if (g->power_on) { | ||
132 | err = gk20a_busy(g); | ||
133 | if (err) | ||
134 | return err; | ||
135 | |||
136 | gk20a_pmu_get_pg_stats(g, | ||
137 | PMU_PG_ELPG_ENGINE_ID_MS, &pg_stat_data); | ||
138 | gk20a_idle(g); | ||
139 | } | ||
140 | total_gating_cnt = g->pg_gating_cnt + pg_stat_data.gating_cnt; | ||
141 | |||
142 | seq_printf(s, "%u\n", total_gating_cnt); | ||
143 | return 0; | ||
144 | |||
145 | } | ||
146 | |||
147 | static int mscg_transitions_open(struct inode *inode, struct file *file) | ||
148 | { | ||
149 | return single_open(file, mscg_transitions_show, inode->i_private); | ||
150 | } | ||
151 | |||
152 | static const struct file_operations mscg_transitions_fops = { | ||
153 | .open = mscg_transitions_open, | ||
154 | .read = seq_read, | ||
155 | .llseek = seq_lseek, | ||
156 | .release = single_release, | ||
157 | }; | ||
158 | |||
159 | static int elpg_stat_show(struct seq_file *s, void *data) | ||
160 | { | ||
161 | struct gk20a *g = s->private; | ||
162 | struct pmu_pg_stats_data pg_stat_data = { 0 }; | ||
163 | u64 total_ingating, total_ungating, residency, divisor, dividend; | ||
164 | int err; | ||
165 | |||
166 | /* Don't unnecessarily power on the device */ | ||
167 | if (g->power_on) { | ||
168 | err = gk20a_busy(g); | ||
169 | if (err) | ||
170 | return err; | ||
171 | |||
172 | gk20a_pmu_get_pg_stats(g, | ||
173 | PMU_PG_ELPG_ENGINE_ID_GRAPHICS, &pg_stat_data); | ||
174 | gk20a_idle(g); | ||
175 | } | ||
176 | total_ingating = g->pg_ingating_time_us + | ||
177 | (u64)pg_stat_data.ingating_time; | ||
178 | total_ungating = g->pg_ungating_time_us + | ||
179 | (u64)pg_stat_data.ungating_time; | ||
180 | divisor = total_ingating + total_ungating; | ||
181 | |||
182 | /* We compute the residency on a scale of 1000 */ | ||
183 | dividend = total_ingating * 1000; | ||
184 | |||
185 | if (divisor) | ||
186 | residency = div64_u64(dividend, divisor); | ||
187 | else | ||
188 | residency = 0; | ||
189 | |||
190 | seq_printf(s, | ||
191 | "Time in ELPG: %llu us\n" | ||
192 | "Time out of ELPG: %llu us\n" | ||
193 | "ELPG residency ratio: %llu\n" | ||
194 | "ELPG Entry Count: %u\n" | ||
195 | "ELPG Avg Entry latency %u us\n" | ||
196 | "ELPG Avg Exit latency %u us\n", | ||
197 | total_ingating, total_ungating, | ||
198 | residency, pg_stat_data.gating_cnt, | ||
199 | pg_stat_data.avg_entry_latency_us, | ||
200 | pg_stat_data.avg_exit_latency_us); | ||
201 | return 0; | ||
202 | |||
203 | } | ||
204 | |||
205 | static int elpg_stat_open(struct inode *inode, struct file *file) | ||
206 | { | ||
207 | return single_open(file, elpg_stat_show, inode->i_private); | ||
208 | } | ||
209 | |||
210 | static const struct file_operations elpg_stat_fops = { | ||
211 | .open = elpg_stat_open, | ||
212 | .read = seq_read, | ||
213 | .llseek = seq_lseek, | ||
214 | .release = single_release, | ||
215 | }; | ||
216 | |||
217 | static int elpg_transitions_show(struct seq_file *s, void *data) | ||
218 | { | ||
219 | struct gk20a *g = s->private; | ||
220 | struct pmu_pg_stats_data pg_stat_data = { 0 }; | ||
221 | u32 total_gating_cnt; | ||
222 | int err; | ||
223 | |||
224 | if (g->power_on) { | ||
225 | err = gk20a_busy(g); | ||
226 | if (err) | ||
227 | return err; | ||
228 | |||
229 | gk20a_pmu_get_pg_stats(g, | ||
230 | PMU_PG_ELPG_ENGINE_ID_GRAPHICS, &pg_stat_data); | ||
231 | gk20a_idle(g); | ||
232 | } | ||
233 | total_gating_cnt = g->pg_gating_cnt + pg_stat_data.gating_cnt; | ||
234 | |||
235 | seq_printf(s, "%u\n", total_gating_cnt); | ||
236 | return 0; | ||
237 | |||
238 | } | ||
239 | |||
240 | static int elpg_transitions_open(struct inode *inode, struct file *file) | ||
241 | { | ||
242 | return single_open(file, elpg_transitions_show, inode->i_private); | ||
243 | } | ||
244 | |||
245 | static const struct file_operations elpg_transitions_fops = { | ||
246 | .open = elpg_transitions_open, | ||
247 | .read = seq_read, | ||
248 | .llseek = seq_lseek, | ||
249 | .release = single_release, | ||
250 | }; | ||
251 | |||
252 | static int falc_trace_show(struct seq_file *s, void *data) | ||
253 | { | ||
254 | struct gk20a *g = s->private; | ||
255 | struct pmu_gk20a *pmu = &g->pmu; | ||
256 | u32 i = 0, j = 0, k, l, m; | ||
257 | char part_str[40]; | ||
258 | void *tracebuffer; | ||
259 | char *trace; | ||
260 | u32 *trace1; | ||
261 | |||
262 | /* allocate system memory to copy pmu trace buffer */ | ||
263 | tracebuffer = nvgpu_kzalloc(g, GK20A_PMU_TRACE_BUFSIZE); | ||
264 | if (tracebuffer == NULL) | ||
265 | return -ENOMEM; | ||
266 | |||
267 | /* read pmu traces into system memory buffer */ | ||
268 | nvgpu_mem_rd_n(g, &pmu->trace_buf, | ||
269 | 0, tracebuffer, GK20A_PMU_TRACE_BUFSIZE); | ||
270 | |||
271 | trace = (char *)tracebuffer; | ||
272 | trace1 = (u32 *)tracebuffer; | ||
273 | |||
274 | for (i = 0; i < GK20A_PMU_TRACE_BUFSIZE; i += 0x40) { | ||
275 | for (j = 0; j < 0x40; j++) | ||
276 | if (trace1[(i / 4) + j]) | ||
277 | break; | ||
278 | if (j == 0x40) | ||
279 | break; | ||
280 | seq_printf(s, "Index %x: ", trace1[(i / 4)]); | ||
281 | l = 0; | ||
282 | m = 0; | ||
283 | while (nvgpu_find_hex_in_string((trace+i+20+m), g, &k)) { | ||
284 | if (k >= 40) | ||
285 | break; | ||
286 | strncpy(part_str, (trace+i+20+m), k); | ||
287 | part_str[k] = 0; | ||
288 | seq_printf(s, "%s0x%x", part_str, | ||
289 | trace1[(i / 4) + 1 + l]); | ||
290 | l++; | ||
291 | m += k + 2; | ||
292 | } | ||
293 | seq_printf(s, "%s", (trace+i+20+m)); | ||
294 | } | ||
295 | |||
296 | nvgpu_kfree(g, tracebuffer); | ||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | static int falc_trace_open(struct inode *inode, struct file *file) | ||
301 | { | ||
302 | return single_open(file, falc_trace_show, inode->i_private); | ||
303 | } | ||
304 | |||
305 | static const struct file_operations falc_trace_fops = { | ||
306 | .open = falc_trace_open, | ||
307 | .read = seq_read, | ||
308 | .llseek = seq_lseek, | ||
309 | .release = single_release, | ||
310 | }; | ||
311 | |||
312 | static int perfmon_events_enable_show(struct seq_file *s, void *data) | ||
313 | { | ||
314 | struct gk20a *g = s->private; | ||
315 | |||
316 | seq_printf(s, "%u\n", g->pmu.perfmon_sampling_enabled ? 1 : 0); | ||
317 | return 0; | ||
318 | |||
319 | } | ||
320 | |||
321 | static int perfmon_events_enable_open(struct inode *inode, struct file *file) | ||
322 | { | ||
323 | return single_open(file, perfmon_events_enable_show, inode->i_private); | ||
324 | } | ||
325 | |||
326 | static ssize_t perfmon_events_enable_write(struct file *file, | ||
327 | const char __user *userbuf, size_t count, loff_t *ppos) | ||
328 | { | ||
329 | struct seq_file *s = file->private_data; | ||
330 | struct gk20a *g = s->private; | ||
331 | unsigned long val = 0; | ||
332 | char buf[40]; | ||
333 | int buf_size; | ||
334 | int err; | ||
335 | |||
336 | memset(buf, 0, sizeof(buf)); | ||
337 | buf_size = min(count, (sizeof(buf)-1)); | ||
338 | |||
339 | if (copy_from_user(buf, userbuf, buf_size)) | ||
340 | return -EFAULT; | ||
341 | |||
342 | if (kstrtoul(buf, 10, &val) < 0) | ||
343 | return -EINVAL; | ||
344 | |||
345 | /* Don't turn on gk20a unnecessarily */ | ||
346 | if (g->power_on) { | ||
347 | err = gk20a_busy(g); | ||
348 | if (err) | ||
349 | return err; | ||
350 | |||
351 | if (val && !g->pmu.perfmon_sampling_enabled) { | ||
352 | g->pmu.perfmon_sampling_enabled = true; | ||
353 | nvgpu_pmu_perfmon_start_sampling(&(g->pmu)); | ||
354 | } else if (!val && g->pmu.perfmon_sampling_enabled) { | ||
355 | g->pmu.perfmon_sampling_enabled = false; | ||
356 | nvgpu_pmu_perfmon_stop_sampling(&(g->pmu)); | ||
357 | } | ||
358 | gk20a_idle(g); | ||
359 | } else { | ||
360 | g->pmu.perfmon_sampling_enabled = val ? true : false; | ||
361 | } | ||
362 | |||
363 | return count; | ||
364 | } | ||
365 | |||
366 | static const struct file_operations perfmon_events_enable_fops = { | ||
367 | .open = perfmon_events_enable_open, | ||
368 | .read = seq_read, | ||
369 | .write = perfmon_events_enable_write, | ||
370 | .llseek = seq_lseek, | ||
371 | .release = single_release, | ||
372 | }; | ||
373 | |||
374 | static int perfmon_events_count_show(struct seq_file *s, void *data) | ||
375 | { | ||
376 | struct gk20a *g = s->private; | ||
377 | |||
378 | seq_printf(s, "%lu\n", g->pmu.perfmon_events_cnt); | ||
379 | return 0; | ||
380 | |||
381 | } | ||
382 | |||
383 | static int perfmon_events_count_open(struct inode *inode, struct file *file) | ||
384 | { | ||
385 | return single_open(file, perfmon_events_count_show, inode->i_private); | ||
386 | } | ||
387 | |||
388 | static const struct file_operations perfmon_events_count_fops = { | ||
389 | .open = perfmon_events_count_open, | ||
390 | .read = seq_read, | ||
391 | .llseek = seq_lseek, | ||
392 | .release = single_release, | ||
393 | }; | ||
394 | |||
395 | static int security_show(struct seq_file *s, void *data) | ||
396 | { | ||
397 | struct gk20a *g = s->private; | ||
398 | |||
399 | seq_printf(s, "%d\n", g->pmu.pmu_mode); | ||
400 | return 0; | ||
401 | |||
402 | } | ||
403 | |||
404 | static int security_open(struct inode *inode, struct file *file) | ||
405 | { | ||
406 | return single_open(file, security_show, inode->i_private); | ||
407 | } | ||
408 | |||
409 | static const struct file_operations security_fops = { | ||
410 | .open = security_open, | ||
411 | .read = seq_read, | ||
412 | .llseek = seq_lseek, | ||
413 | .release = single_release, | ||
414 | }; | ||
415 | |||
416 | int gk20a_pmu_debugfs_init(struct gk20a *g) | ||
417 | { | ||
418 | struct dentry *d; | ||
419 | struct gk20a_platform *platform = dev_get_drvdata(g->dev); | ||
420 | |||
421 | d = debugfs_create_file( | ||
422 | "lpwr_debug", S_IRUGO|S_IWUSR, platform->debugfs, g, | ||
423 | &lpwr_debug_fops); | ||
424 | if (!d) | ||
425 | goto err_out; | ||
426 | |||
427 | d = debugfs_create_file( | ||
428 | "mscg_residency", S_IRUGO|S_IWUSR, platform->debugfs, g, | ||
429 | &mscg_stat_fops); | ||
430 | if (!d) | ||
431 | goto err_out; | ||
432 | |||
433 | d = debugfs_create_file( | ||
434 | "mscg_transitions", S_IRUGO, platform->debugfs, g, | ||
435 | &mscg_transitions_fops); | ||
436 | if (!d) | ||
437 | goto err_out; | ||
438 | |||
439 | d = debugfs_create_file( | ||
440 | "elpg_residency", S_IRUGO|S_IWUSR, platform->debugfs, g, | ||
441 | &elpg_stat_fops); | ||
442 | if (!d) | ||
443 | goto err_out; | ||
444 | |||
445 | d = debugfs_create_file( | ||
446 | "elpg_transitions", S_IRUGO, platform->debugfs, g, | ||
447 | &elpg_transitions_fops); | ||
448 | if (!d) | ||
449 | goto err_out; | ||
450 | |||
451 | d = debugfs_create_file( | ||
452 | "falc_trace", S_IRUGO, platform->debugfs, g, | ||
453 | &falc_trace_fops); | ||
454 | if (!d) | ||
455 | goto err_out; | ||
456 | |||
457 | d = debugfs_create_file( | ||
458 | "perfmon_events_enable", S_IRUGO, platform->debugfs, g, | ||
459 | &perfmon_events_enable_fops); | ||
460 | if (!d) | ||
461 | goto err_out; | ||
462 | |||
463 | d = debugfs_create_file( | ||
464 | "perfmon_events_count", S_IRUGO, platform->debugfs, g, | ||
465 | &perfmon_events_count_fops); | ||
466 | if (!d) | ||
467 | goto err_out; | ||
468 | |||
469 | d = debugfs_create_file( | ||
470 | "pmu_security", S_IRUGO, platform->debugfs, g, | ||
471 | &security_fops); | ||
472 | if (!d) | ||
473 | goto err_out; | ||
474 | return 0; | ||
475 | err_out: | ||
476 | pr_err("%s: Failed to make debugfs node\n", __func__); | ||
477 | debugfs_remove_recursive(platform->debugfs); | ||
478 | return -ENOMEM; | ||
479 | } | ||