1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2013 Red Hat |
4 | * Author: Rob Clark <robdclark@gmail.com> |
5 | */ |
6 | |
7 | /* For profiling, userspace can: |
8 | * |
9 | * tail -f /sys/kernel/debug/dri/<minor>/gpu |
10 | * |
11 | * This will enable performance counters/profiling to track the busy time |
12 | * and any gpu specific performance counters that are supported. |
13 | */ |
14 | |
15 | #ifdef CONFIG_DEBUG_FS |
16 | |
17 | #include <linux/debugfs.h> |
18 | #include <linux/uaccess.h> |
19 | |
20 | #include <drm/drm_file.h> |
21 | |
22 | #include "msm_drv.h" |
23 | #include "msm_gpu.h" |
24 | |
25 | struct msm_perf_state { |
26 | struct drm_device *dev; |
27 | |
28 | bool open; |
29 | int cnt; |
30 | struct mutex read_lock; |
31 | |
32 | char buf[256]; |
33 | int buftot, bufpos; |
34 | |
35 | unsigned long next_jiffies; |
36 | }; |
37 | |
38 | #define SAMPLE_TIME (HZ/4) |
39 | |
40 | /* wait for next sample time: */ |
41 | static int wait_sample(struct msm_perf_state *perf) |
42 | { |
43 | unsigned long start_jiffies = jiffies; |
44 | |
45 | if (time_after(perf->next_jiffies, start_jiffies)) { |
46 | unsigned long remaining_jiffies = |
47 | perf->next_jiffies - start_jiffies; |
48 | int ret = schedule_timeout_interruptible(timeout: remaining_jiffies); |
49 | if (ret > 0) { |
50 | /* interrupted */ |
51 | return -ERESTARTSYS; |
52 | } |
53 | } |
54 | perf->next_jiffies += SAMPLE_TIME; |
55 | return 0; |
56 | } |
57 | |
58 | static int refill_buf(struct msm_perf_state *perf) |
59 | { |
60 | struct msm_drm_private *priv = perf->dev->dev_private; |
61 | struct msm_gpu *gpu = priv->gpu; |
62 | char *ptr = perf->buf; |
63 | int rem = sizeof(perf->buf); |
64 | int i, n; |
65 | |
66 | if ((perf->cnt++ % 32) == 0) { |
67 | /* Header line: */ |
68 | n = snprintf(buf: ptr, size: rem, fmt: "%%BUSY" ); |
69 | ptr += n; |
70 | rem -= n; |
71 | |
72 | for (i = 0; i < gpu->num_perfcntrs; i++) { |
73 | const struct msm_gpu_perfcntr *perfcntr = &gpu->perfcntrs[i]; |
74 | n = snprintf(buf: ptr, size: rem, fmt: "\t%s" , perfcntr->name); |
75 | ptr += n; |
76 | rem -= n; |
77 | } |
78 | } else { |
79 | /* Sample line: */ |
80 | uint32_t activetime = 0, totaltime = 0; |
81 | uint32_t cntrs[5]; |
82 | uint32_t val; |
83 | int ret; |
84 | |
85 | /* sleep until next sample time: */ |
86 | ret = wait_sample(perf); |
87 | if (ret) |
88 | return ret; |
89 | |
90 | ret = msm_gpu_perfcntr_sample(gpu, activetime: &activetime, totaltime: &totaltime, |
91 | ARRAY_SIZE(cntrs), cntrs); |
92 | if (ret < 0) |
93 | return ret; |
94 | |
95 | val = totaltime ? 1000 * activetime / totaltime : 0; |
96 | n = snprintf(buf: ptr, size: rem, fmt: "%3d.%d%%" , val / 10, val % 10); |
97 | ptr += n; |
98 | rem -= n; |
99 | |
100 | for (i = 0; i < ret; i++) { |
101 | /* cycle counters (I think).. convert to MHz.. */ |
102 | val = cntrs[i] / 10000; |
103 | n = snprintf(buf: ptr, size: rem, fmt: "\t%5d.%02d" , |
104 | val / 100, val % 100); |
105 | ptr += n; |
106 | rem -= n; |
107 | } |
108 | } |
109 | |
110 | n = snprintf(buf: ptr, size: rem, fmt: "\n" ); |
111 | ptr += n; |
112 | rem -= n; |
113 | |
114 | perf->bufpos = 0; |
115 | perf->buftot = ptr - perf->buf; |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | static ssize_t perf_read(struct file *file, char __user *buf, |
121 | size_t sz, loff_t *ppos) |
122 | { |
123 | struct msm_perf_state *perf = file->private_data; |
124 | int n = 0, ret = 0; |
125 | |
126 | mutex_lock(&perf->read_lock); |
127 | |
128 | if (perf->bufpos >= perf->buftot) { |
129 | ret = refill_buf(perf); |
130 | if (ret) |
131 | goto out; |
132 | } |
133 | |
134 | n = min((int)sz, perf->buftot - perf->bufpos); |
135 | if (copy_to_user(to: buf, from: &perf->buf[perf->bufpos], n)) { |
136 | ret = -EFAULT; |
137 | goto out; |
138 | } |
139 | |
140 | perf->bufpos += n; |
141 | *ppos += n; |
142 | |
143 | out: |
144 | mutex_unlock(lock: &perf->read_lock); |
145 | if (ret) |
146 | return ret; |
147 | return n; |
148 | } |
149 | |
150 | static int perf_open(struct inode *inode, struct file *file) |
151 | { |
152 | struct msm_perf_state *perf = inode->i_private; |
153 | struct drm_device *dev = perf->dev; |
154 | struct msm_drm_private *priv = dev->dev_private; |
155 | struct msm_gpu *gpu = priv->gpu; |
156 | int ret = 0; |
157 | |
158 | if (!gpu) |
159 | return -ENODEV; |
160 | |
161 | mutex_lock(&gpu->lock); |
162 | |
163 | if (perf->open) { |
164 | ret = -EBUSY; |
165 | goto out; |
166 | } |
167 | |
168 | file->private_data = perf; |
169 | perf->open = true; |
170 | perf->cnt = 0; |
171 | perf->buftot = 0; |
172 | perf->bufpos = 0; |
173 | msm_gpu_perfcntr_start(gpu); |
174 | perf->next_jiffies = jiffies + SAMPLE_TIME; |
175 | |
176 | out: |
177 | mutex_unlock(lock: &gpu->lock); |
178 | return ret; |
179 | } |
180 | |
181 | static int perf_release(struct inode *inode, struct file *file) |
182 | { |
183 | struct msm_perf_state *perf = inode->i_private; |
184 | struct msm_drm_private *priv = perf->dev->dev_private; |
185 | msm_gpu_perfcntr_stop(gpu: priv->gpu); |
186 | perf->open = false; |
187 | return 0; |
188 | } |
189 | |
190 | |
191 | static const struct file_operations perf_debugfs_fops = { |
192 | .owner = THIS_MODULE, |
193 | .open = perf_open, |
194 | .read = perf_read, |
195 | .llseek = no_llseek, |
196 | .release = perf_release, |
197 | }; |
198 | |
199 | int msm_perf_debugfs_init(struct drm_minor *minor) |
200 | { |
201 | struct msm_drm_private *priv = minor->dev->dev_private; |
202 | struct msm_perf_state *perf; |
203 | |
204 | /* only create on first minor: */ |
205 | if (priv->perf) |
206 | return 0; |
207 | |
208 | perf = kzalloc(size: sizeof(*perf), GFP_KERNEL); |
209 | if (!perf) |
210 | return -ENOMEM; |
211 | |
212 | perf->dev = minor->dev; |
213 | |
214 | mutex_init(&perf->read_lock); |
215 | priv->perf = perf; |
216 | |
217 | debugfs_create_file(name: "perf" , S_IFREG | S_IRUGO, parent: minor->debugfs_root, |
218 | data: perf, fops: &perf_debugfs_fops); |
219 | return 0; |
220 | } |
221 | |
222 | void msm_perf_debugfs_cleanup(struct msm_drm_private *priv) |
223 | { |
224 | struct msm_perf_state *perf = priv->perf; |
225 | |
226 | if (!perf) |
227 | return; |
228 | |
229 | priv->perf = NULL; |
230 | |
231 | mutex_destroy(lock: &perf->read_lock); |
232 | |
233 | kfree(objp: perf); |
234 | } |
235 | |
236 | #endif |
237 | |