1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2021 Raspberry Pi |
4 | */ |
5 | |
6 | #include "v3d_drv.h" |
7 | #include "v3d_regs.h" |
8 | |
9 | #define V3D_PERFMONID_MIN 1 |
10 | #define V3D_PERFMONID_MAX U32_MAX |
11 | |
12 | void v3d_perfmon_get(struct v3d_perfmon *perfmon) |
13 | { |
14 | if (perfmon) |
15 | refcount_inc(r: &perfmon->refcnt); |
16 | } |
17 | |
18 | void v3d_perfmon_put(struct v3d_perfmon *perfmon) |
19 | { |
20 | if (perfmon && refcount_dec_and_test(r: &perfmon->refcnt)) { |
21 | mutex_destroy(lock: &perfmon->lock); |
22 | kfree(objp: perfmon); |
23 | } |
24 | } |
25 | |
26 | void v3d_perfmon_start(struct v3d_dev *v3d, struct v3d_perfmon *perfmon) |
27 | { |
28 | unsigned int i; |
29 | u32 mask; |
30 | u8 ncounters; |
31 | |
32 | if (WARN_ON_ONCE(!perfmon || v3d->active_perfmon)) |
33 | return; |
34 | |
35 | ncounters = perfmon->ncounters; |
36 | mask = GENMASK(ncounters - 1, 0); |
37 | |
38 | for (i = 0; i < ncounters; i++) { |
39 | u32 source = i / 4; |
40 | u32 channel = V3D_SET_FIELD(perfmon->counters[i], V3D_PCTR_S0); |
41 | |
42 | i++; |
43 | channel |= V3D_SET_FIELD(i < ncounters ? perfmon->counters[i] : 0, |
44 | V3D_PCTR_S1); |
45 | i++; |
46 | channel |= V3D_SET_FIELD(i < ncounters ? perfmon->counters[i] : 0, |
47 | V3D_PCTR_S2); |
48 | i++; |
49 | channel |= V3D_SET_FIELD(i < ncounters ? perfmon->counters[i] : 0, |
50 | V3D_PCTR_S3); |
51 | V3D_CORE_WRITE(0, V3D_V4_PCTR_0_SRC_X(source), channel); |
52 | } |
53 | |
54 | V3D_CORE_WRITE(0, V3D_V4_PCTR_0_CLR, mask); |
55 | V3D_CORE_WRITE(0, V3D_PCTR_0_OVERFLOW, mask); |
56 | V3D_CORE_WRITE(0, V3D_V4_PCTR_0_EN, mask); |
57 | |
58 | v3d->active_perfmon = perfmon; |
59 | } |
60 | |
61 | void v3d_perfmon_stop(struct v3d_dev *v3d, struct v3d_perfmon *perfmon, |
62 | bool capture) |
63 | { |
64 | unsigned int i; |
65 | |
66 | if (!perfmon || !v3d->active_perfmon) |
67 | return; |
68 | |
69 | mutex_lock(&perfmon->lock); |
70 | if (perfmon != v3d->active_perfmon) { |
71 | mutex_unlock(lock: &perfmon->lock); |
72 | return; |
73 | } |
74 | |
75 | if (capture) |
76 | for (i = 0; i < perfmon->ncounters; i++) |
77 | perfmon->values[i] += V3D_CORE_READ(0, V3D_PCTR_0_PCTRX(i)); |
78 | |
79 | V3D_CORE_WRITE(0, V3D_V4_PCTR_0_EN, 0); |
80 | |
81 | v3d->active_perfmon = NULL; |
82 | mutex_unlock(lock: &perfmon->lock); |
83 | } |
84 | |
85 | struct v3d_perfmon *v3d_perfmon_find(struct v3d_file_priv *v3d_priv, int id) |
86 | { |
87 | struct v3d_perfmon *perfmon; |
88 | |
89 | mutex_lock(&v3d_priv->perfmon.lock); |
90 | perfmon = idr_find(&v3d_priv->perfmon.idr, id); |
91 | v3d_perfmon_get(perfmon); |
92 | mutex_unlock(lock: &v3d_priv->perfmon.lock); |
93 | |
94 | return perfmon; |
95 | } |
96 | |
97 | void v3d_perfmon_open_file(struct v3d_file_priv *v3d_priv) |
98 | { |
99 | mutex_init(&v3d_priv->perfmon.lock); |
100 | idr_init_base(idr: &v3d_priv->perfmon.idr, base: 1); |
101 | } |
102 | |
103 | static int v3d_perfmon_idr_del(int id, void *elem, void *data) |
104 | { |
105 | struct v3d_perfmon *perfmon = elem; |
106 | |
107 | v3d_perfmon_put(perfmon); |
108 | |
109 | return 0; |
110 | } |
111 | |
112 | void v3d_perfmon_close_file(struct v3d_file_priv *v3d_priv) |
113 | { |
114 | mutex_lock(&v3d_priv->perfmon.lock); |
115 | idr_for_each(&v3d_priv->perfmon.idr, fn: v3d_perfmon_idr_del, NULL); |
116 | idr_destroy(&v3d_priv->perfmon.idr); |
117 | mutex_unlock(lock: &v3d_priv->perfmon.lock); |
118 | mutex_destroy(lock: &v3d_priv->perfmon.lock); |
119 | } |
120 | |
121 | int v3d_perfmon_create_ioctl(struct drm_device *dev, void *data, |
122 | struct drm_file *file_priv) |
123 | { |
124 | struct v3d_file_priv *v3d_priv = file_priv->driver_priv; |
125 | struct drm_v3d_perfmon_create *req = data; |
126 | struct v3d_perfmon *perfmon; |
127 | unsigned int i; |
128 | int ret; |
129 | |
130 | /* Number of monitored counters cannot exceed HW limits. */ |
131 | if (req->ncounters > DRM_V3D_MAX_PERF_COUNTERS || |
132 | !req->ncounters) |
133 | return -EINVAL; |
134 | |
135 | /* Make sure all counters are valid. */ |
136 | for (i = 0; i < req->ncounters; i++) { |
137 | if (req->counters[i] >= V3D_PERFCNT_NUM) |
138 | return -EINVAL; |
139 | } |
140 | |
141 | perfmon = kzalloc(struct_size(perfmon, values, req->ncounters), |
142 | GFP_KERNEL); |
143 | if (!perfmon) |
144 | return -ENOMEM; |
145 | |
146 | for (i = 0; i < req->ncounters; i++) |
147 | perfmon->counters[i] = req->counters[i]; |
148 | |
149 | perfmon->ncounters = req->ncounters; |
150 | |
151 | refcount_set(r: &perfmon->refcnt, n: 1); |
152 | mutex_init(&perfmon->lock); |
153 | |
154 | mutex_lock(&v3d_priv->perfmon.lock); |
155 | ret = idr_alloc(&v3d_priv->perfmon.idr, ptr: perfmon, V3D_PERFMONID_MIN, |
156 | V3D_PERFMONID_MAX, GFP_KERNEL); |
157 | mutex_unlock(lock: &v3d_priv->perfmon.lock); |
158 | |
159 | if (ret < 0) { |
160 | mutex_destroy(lock: &perfmon->lock); |
161 | kfree(objp: perfmon); |
162 | return ret; |
163 | } |
164 | |
165 | req->id = ret; |
166 | |
167 | return 0; |
168 | } |
169 | |
170 | int v3d_perfmon_destroy_ioctl(struct drm_device *dev, void *data, |
171 | struct drm_file *file_priv) |
172 | { |
173 | struct v3d_file_priv *v3d_priv = file_priv->driver_priv; |
174 | struct drm_v3d_perfmon_destroy *req = data; |
175 | struct v3d_perfmon *perfmon; |
176 | |
177 | mutex_lock(&v3d_priv->perfmon.lock); |
178 | perfmon = idr_remove(&v3d_priv->perfmon.idr, id: req->id); |
179 | mutex_unlock(lock: &v3d_priv->perfmon.lock); |
180 | |
181 | if (!perfmon) |
182 | return -EINVAL; |
183 | |
184 | v3d_perfmon_put(perfmon); |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | int v3d_perfmon_get_values_ioctl(struct drm_device *dev, void *data, |
190 | struct drm_file *file_priv) |
191 | { |
192 | struct v3d_dev *v3d = to_v3d_dev(dev); |
193 | struct v3d_file_priv *v3d_priv = file_priv->driver_priv; |
194 | struct drm_v3d_perfmon_get_values *req = data; |
195 | struct v3d_perfmon *perfmon; |
196 | int ret = 0; |
197 | |
198 | if (req->pad != 0) |
199 | return -EINVAL; |
200 | |
201 | mutex_lock(&v3d_priv->perfmon.lock); |
202 | perfmon = idr_find(&v3d_priv->perfmon.idr, id: req->id); |
203 | v3d_perfmon_get(perfmon); |
204 | mutex_unlock(lock: &v3d_priv->perfmon.lock); |
205 | |
206 | if (!perfmon) |
207 | return -EINVAL; |
208 | |
209 | v3d_perfmon_stop(v3d, perfmon, capture: true); |
210 | |
211 | if (copy_to_user(u64_to_user_ptr(req->values_ptr), from: perfmon->values, |
212 | n: perfmon->ncounters * sizeof(u64))) |
213 | ret = -EFAULT; |
214 | |
215 | v3d_perfmon_put(perfmon); |
216 | |
217 | return ret; |
218 | } |
219 | |