Commit | Line | Data |
---|---|---|
aa4d7a4d BS |
1 | /* |
2 | * Copyright 2013 Red Hat Inc. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | * Authors: Ben Skeggs | |
23 | */ | |
4d34686e | 24 | #include "priv.h" |
aa4d7a4d | 25 | |
96af8222 | 26 | #include <core/client.h> |
4d34686e | 27 | #include <core/device.h> |
aa4d7a4d | 28 | #include <core/option.h> |
4d34686e | 29 | |
96af8222 BS |
30 | #include <nvif/class.h> |
31 | #include <nvif/ioctl.h> | |
4d34686e | 32 | #include <nvif/unpack.h> |
aa4d7a4d BS |
33 | |
34 | #define QUAD_MASK 0x0f | |
35 | #define QUAD_FREE 0x01 | |
36 | ||
4d34686e BS |
37 | static struct nvkm_perfsig * |
38 | nvkm_perfsig_find_(struct nvkm_perfdom *dom, const char *name, u32 size) | |
aa4d7a4d BS |
39 | { |
40 | char path[64]; | |
41 | int i; | |
42 | ||
43 | if (name[0] != '/') { | |
44 | for (i = 0; i < dom->signal_nr; i++) { | |
45 | if ( dom->signal[i].name && | |
46 | !strncmp(name, dom->signal[i].name, size)) | |
47 | return &dom->signal[i]; | |
48 | } | |
49 | } else { | |
50 | for (i = 0; i < dom->signal_nr; i++) { | |
51 | snprintf(path, sizeof(path), "/%s/%02x", dom->name, i); | |
52 | if (!strncmp(name, path, size)) | |
53 | return &dom->signal[i]; | |
54 | } | |
55 | } | |
56 | ||
57 | return NULL; | |
58 | } | |
59 | ||
4d34686e BS |
60 | struct nvkm_perfsig * |
61 | nvkm_perfsig_find(struct nvkm_pm *ppm, const char *name, u32 size, | |
62 | struct nvkm_perfdom **pdom) | |
aa4d7a4d | 63 | { |
4d34686e BS |
64 | struct nvkm_perfdom *dom = *pdom; |
65 | struct nvkm_perfsig *sig; | |
aa4d7a4d BS |
66 | |
67 | if (dom == NULL) { | |
68 | list_for_each_entry(dom, &ppm->domains, head) { | |
4d34686e | 69 | sig = nvkm_perfsig_find_(dom, name, size); |
aa4d7a4d BS |
70 | if (sig) { |
71 | *pdom = dom; | |
72 | return sig; | |
73 | } | |
74 | } | |
75 | ||
76 | return NULL; | |
77 | } | |
78 | ||
4d34686e | 79 | return nvkm_perfsig_find_(dom, name, size); |
aa4d7a4d BS |
80 | } |
81 | ||
4d34686e BS |
82 | struct nvkm_perfctr * |
83 | nvkm_perfsig_wrap(struct nvkm_pm *ppm, const char *name, | |
84 | struct nvkm_perfdom **pdom) | |
aa4d7a4d | 85 | { |
4d34686e BS |
86 | struct nvkm_perfsig *sig; |
87 | struct nvkm_perfctr *ctr; | |
aa4d7a4d | 88 | |
4d34686e | 89 | sig = nvkm_perfsig_find(ppm, name, strlen(name), pdom); |
aa4d7a4d BS |
90 | if (!sig) |
91 | return NULL; | |
92 | ||
93 | ctr = kzalloc(sizeof(*ctr), GFP_KERNEL); | |
94 | if (ctr) { | |
95 | ctr->signal[0] = sig; | |
96 | ctr->logic_op = 0xaaaa; | |
97 | } | |
98 | ||
99 | return ctr; | |
100 | } | |
101 | ||
102 | /******************************************************************************* | |
103 | * Perfmon object classes | |
104 | ******************************************************************************/ | |
105 | static int | |
4d34686e | 106 | nvkm_perfctr_query(struct nvkm_object *object, void *data, u32 size) |
aa4d7a4d | 107 | { |
96af8222 BS |
108 | union { |
109 | struct nvif_perfctr_query_v0 v0; | |
110 | } *args = data; | |
4d34686e BS |
111 | struct nvkm_device *device = nv_device(object); |
112 | struct nvkm_pm *ppm = (void *)object->engine; | |
113 | struct nvkm_perfdom *dom = NULL, *chk; | |
114 | const bool all = nvkm_boolopt(device->cfgopt, "NvPmShowAll", false); | |
115 | const bool raw = nvkm_boolopt(device->cfgopt, "NvPmUnnamed", all); | |
aa4d7a4d BS |
116 | const char *name; |
117 | int tmp = 0, di, si; | |
96af8222 BS |
118 | int ret; |
119 | ||
120 | nv_ioctl(object, "perfctr query size %d\n", size); | |
121 | if (nvif_unpack(args->v0, 0, 0, false)) { | |
122 | nv_ioctl(object, "perfctr query vers %d iter %08x\n", | |
123 | args->v0.version, args->v0.iter); | |
124 | di = (args->v0.iter & 0xff000000) >> 24; | |
125 | si = (args->v0.iter & 0x00ffffff) - 1; | |
126 | } else | |
127 | return ret; | |
aa4d7a4d BS |
128 | |
129 | list_for_each_entry(chk, &ppm->domains, head) { | |
130 | if (tmp++ == di) { | |
131 | dom = chk; | |
132 | break; | |
133 | } | |
134 | } | |
135 | ||
136 | if (dom == NULL || si >= (int)dom->signal_nr) | |
137 | return -EINVAL; | |
138 | ||
139 | if (si >= 0) { | |
140 | if (raw || !(name = dom->signal[si].name)) { | |
96af8222 BS |
141 | snprintf(args->v0.name, sizeof(args->v0.name), |
142 | "/%s/%02x", dom->name, si); | |
143 | } else { | |
144 | strncpy(args->v0.name, name, sizeof(args->v0.name)); | |
aa4d7a4d | 145 | } |
aa4d7a4d BS |
146 | } |
147 | ||
148 | do { | |
149 | while (++si < dom->signal_nr) { | |
150 | if (all || dom->signal[si].name) { | |
96af8222 | 151 | args->v0.iter = (di << 24) | ++si; |
aa4d7a4d BS |
152 | return 0; |
153 | } | |
154 | } | |
155 | si = -1; | |
156 | di = di + 1; | |
157 | dom = list_entry(dom->head.next, typeof(*dom), head); | |
158 | } while (&dom->head != &ppm->domains); | |
159 | ||
96af8222 | 160 | args->v0.iter = 0xffffffff; |
aa4d7a4d BS |
161 | return 0; |
162 | } | |
163 | ||
164 | static int | |
4d34686e | 165 | nvkm_perfctr_sample(struct nvkm_object *object, void *data, u32 size) |
aa4d7a4d | 166 | { |
96af8222 BS |
167 | union { |
168 | struct nvif_perfctr_sample none; | |
169 | } *args = data; | |
4d34686e BS |
170 | struct nvkm_pm *ppm = (void *)object->engine; |
171 | struct nvkm_perfctr *ctr, *tmp; | |
172 | struct nvkm_perfdom *dom; | |
96af8222 | 173 | int ret; |
aa4d7a4d | 174 | |
96af8222 BS |
175 | nv_ioctl(object, "perfctr sample size %d\n", size); |
176 | if (nvif_unvers(args->none)) { | |
177 | nv_ioctl(object, "perfctr sample\n"); | |
178 | } else | |
179 | return ret; | |
aa4d7a4d BS |
180 | ppm->sequence++; |
181 | ||
182 | list_for_each_entry(dom, &ppm->domains, head) { | |
183 | /* sample previous batch of counters */ | |
184 | if (dom->quad != QUAD_MASK) { | |
185 | dom->func->next(ppm, dom); | |
186 | tmp = NULL; | |
187 | while (!list_empty(&dom->list)) { | |
188 | ctr = list_first_entry(&dom->list, | |
4d34686e | 189 | typeof(*ctr), head); |
aa4d7a4d BS |
190 | if (ctr->slot < 0) break; |
191 | if ( tmp && tmp == ctr) break; | |
192 | if (!tmp) tmp = ctr; | |
193 | dom->func->read(ppm, dom, ctr); | |
194 | ctr->slot = -1; | |
195 | list_move_tail(&ctr->head, &dom->list); | |
196 | } | |
197 | } | |
198 | ||
199 | dom->quad = QUAD_MASK; | |
200 | ||
201 | /* setup next batch of counters for sampling */ | |
202 | list_for_each_entry(ctr, &dom->list, head) { | |
203 | ctr->slot = ffs(dom->quad) - 1; | |
204 | if (ctr->slot < 0) | |
205 | break; | |
206 | dom->quad &= ~(QUAD_FREE << ctr->slot); | |
207 | dom->func->init(ppm, dom, ctr); | |
208 | } | |
209 | ||
210 | if (dom->quad != QUAD_MASK) | |
211 | dom->func->next(ppm, dom); | |
212 | } | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | static int | |
4d34686e | 218 | nvkm_perfctr_read(struct nvkm_object *object, void *data, u32 size) |
aa4d7a4d | 219 | { |
96af8222 BS |
220 | union { |
221 | struct nvif_perfctr_read_v0 v0; | |
222 | } *args = data; | |
4d34686e | 223 | struct nvkm_perfctr *ctr = (void *)object; |
96af8222 BS |
224 | int ret; |
225 | ||
226 | nv_ioctl(object, "perfctr read size %d\n", size); | |
227 | if (nvif_unpack(args->v0, 0, 0, false)) { | |
228 | nv_ioctl(object, "perfctr read vers %d\n", args->v0.version); | |
229 | } else | |
230 | return ret; | |
aa4d7a4d | 231 | |
aa4d7a4d BS |
232 | if (!ctr->clk) |
233 | return -EAGAIN; | |
234 | ||
96af8222 BS |
235 | args->v0.clk = ctr->clk; |
236 | args->v0.ctr = ctr->ctr; | |
aa4d7a4d BS |
237 | return 0; |
238 | } | |
239 | ||
96af8222 | 240 | static int |
4d34686e | 241 | nvkm_perfctr_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) |
96af8222 BS |
242 | { |
243 | switch (mthd) { | |
244 | case NVIF_PERFCTR_V0_QUERY: | |
4d34686e | 245 | return nvkm_perfctr_query(object, data, size); |
96af8222 | 246 | case NVIF_PERFCTR_V0_SAMPLE: |
4d34686e | 247 | return nvkm_perfctr_sample(object, data, size); |
96af8222 | 248 | case NVIF_PERFCTR_V0_READ: |
4d34686e | 249 | return nvkm_perfctr_read(object, data, size); |
96af8222 BS |
250 | default: |
251 | break; | |
252 | } | |
253 | return -EINVAL; | |
254 | } | |
255 | ||
aa4d7a4d | 256 | static void |
4d34686e | 257 | nvkm_perfctr_dtor(struct nvkm_object *object) |
aa4d7a4d | 258 | { |
4d34686e | 259 | struct nvkm_perfctr *ctr = (void *)object; |
aa4d7a4d BS |
260 | if (ctr->head.next) |
261 | list_del(&ctr->head); | |
4d34686e | 262 | nvkm_object_destroy(&ctr->base); |
aa4d7a4d BS |
263 | } |
264 | ||
265 | static int | |
4d34686e BS |
266 | nvkm_perfctr_ctor(struct nvkm_object *parent, struct nvkm_object *engine, |
267 | struct nvkm_oclass *oclass, void *data, u32 size, | |
268 | struct nvkm_object **pobject) | |
aa4d7a4d | 269 | { |
96af8222 BS |
270 | union { |
271 | struct nvif_perfctr_v0 v0; | |
272 | } *args = data; | |
4d34686e BS |
273 | struct nvkm_pm *ppm = (void *)engine; |
274 | struct nvkm_perfdom *dom = NULL; | |
275 | struct nvkm_perfsig *sig[4] = {}; | |
276 | struct nvkm_perfctr *ctr; | |
aa4d7a4d BS |
277 | int ret, i; |
278 | ||
96af8222 BS |
279 | nv_ioctl(parent, "create perfctr size %d\n", size); |
280 | if (nvif_unpack(args->v0, 0, 0, false)) { | |
281 | nv_ioctl(parent, "create perfctr vers %d logic_op %04x\n", | |
282 | args->v0.version, args->v0.logic_op); | |
283 | } else | |
284 | return ret; | |
aa4d7a4d | 285 | |
96af8222 | 286 | for (i = 0; i < ARRAY_SIZE(args->v0.name) && args->v0.name[i][0]; i++) { |
4d34686e BS |
287 | sig[i] = nvkm_perfsig_find(ppm, args->v0.name[i], |
288 | strnlen(args->v0.name[i], | |
289 | sizeof(args->v0.name[i])), | |
290 | &dom); | |
aa4d7a4d BS |
291 | if (!sig[i]) |
292 | return -EINVAL; | |
293 | } | |
294 | ||
4d34686e | 295 | ret = nvkm_object_create(parent, engine, oclass, 0, &ctr); |
aa4d7a4d BS |
296 | *pobject = nv_object(ctr); |
297 | if (ret) | |
298 | return ret; | |
299 | ||
300 | ctr->slot = -1; | |
96af8222 | 301 | ctr->logic_op = args->v0.logic_op; |
aa4d7a4d BS |
302 | ctr->signal[0] = sig[0]; |
303 | ctr->signal[1] = sig[1]; | |
304 | ctr->signal[2] = sig[2]; | |
305 | ctr->signal[3] = sig[3]; | |
306 | if (dom) | |
307 | list_add_tail(&ctr->head, &dom->list); | |
308 | return 0; | |
309 | } | |
310 | ||
4d34686e BS |
311 | static struct nvkm_ofuncs |
312 | nvkm_perfctr_ofuncs = { | |
313 | .ctor = nvkm_perfctr_ctor, | |
314 | .dtor = nvkm_perfctr_dtor, | |
315 | .init = nvkm_object_init, | |
316 | .fini = nvkm_object_fini, | |
317 | .mthd = nvkm_perfctr_mthd, | |
aa4d7a4d BS |
318 | }; |
319 | ||
4d34686e BS |
320 | struct nvkm_oclass |
321 | nvkm_pm_sclass[] = { | |
96af8222 | 322 | { .handle = NVIF_IOCTL_NEW_V0_PERFCTR, |
4d34686e | 323 | .ofuncs = &nvkm_perfctr_ofuncs, |
aa4d7a4d BS |
324 | }, |
325 | {}, | |
326 | }; | |
327 | ||
328 | /******************************************************************************* | |
329 | * PPM context | |
330 | ******************************************************************************/ | |
331 | static void | |
4d34686e | 332 | nvkm_perfctx_dtor(struct nvkm_object *object) |
aa4d7a4d | 333 | { |
4d34686e | 334 | struct nvkm_pm *ppm = (void *)object->engine; |
3693d544 SP |
335 | struct nvkm_perfctx *ctx = (void *)object; |
336 | ||
aa4d7a4d | 337 | mutex_lock(&nv_subdev(ppm)->mutex); |
3693d544 SP |
338 | nvkm_engctx_destroy(&ctx->base); |
339 | if (ppm->context == ctx) | |
340 | ppm->context = NULL; | |
aa4d7a4d BS |
341 | mutex_unlock(&nv_subdev(ppm)->mutex); |
342 | } | |
343 | ||
344 | static int | |
4d34686e BS |
345 | nvkm_perfctx_ctor(struct nvkm_object *parent, struct nvkm_object *engine, |
346 | struct nvkm_oclass *oclass, void *data, u32 size, | |
347 | struct nvkm_object **pobject) | |
aa4d7a4d | 348 | { |
4d34686e BS |
349 | struct nvkm_pm *ppm = (void *)engine; |
350 | struct nvkm_perfctx *ctx; | |
aa4d7a4d BS |
351 | int ret; |
352 | ||
4d34686e | 353 | ret = nvkm_engctx_create(parent, engine, oclass, NULL, 0, 0, 0, &ctx); |
aa4d7a4d BS |
354 | *pobject = nv_object(ctx); |
355 | if (ret) | |
356 | return ret; | |
357 | ||
358 | mutex_lock(&nv_subdev(ppm)->mutex); | |
359 | if (ppm->context == NULL) | |
360 | ppm->context = ctx; | |
aa4d7a4d | 361 | if (ctx != ppm->context) |
305c1959 SP |
362 | ret = -EBUSY; |
363 | mutex_unlock(&nv_subdev(ppm)->mutex); | |
aa4d7a4d | 364 | |
305c1959 | 365 | return ret; |
aa4d7a4d BS |
366 | } |
367 | ||
4d34686e BS |
368 | struct nvkm_oclass |
369 | nvkm_pm_cclass = { | |
d5752b9b | 370 | .handle = NV_ENGCTX(PM, 0x00), |
4d34686e BS |
371 | .ofuncs = &(struct nvkm_ofuncs) { |
372 | .ctor = nvkm_perfctx_ctor, | |
373 | .dtor = nvkm_perfctx_dtor, | |
374 | .init = _nvkm_engctx_init, | |
375 | .fini = _nvkm_engctx_fini, | |
aa4d7a4d BS |
376 | }, |
377 | }; | |
378 | ||
379 | /******************************************************************************* | |
380 | * PPM engine/subdev functions | |
381 | ******************************************************************************/ | |
382 | int | |
4d34686e BS |
383 | nvkm_perfdom_new(struct nvkm_pm *ppm, const char *name, u32 mask, |
384 | u32 base, u32 size_unit, u32 size_domain, | |
385 | const struct nvkm_specdom *spec) | |
aa4d7a4d | 386 | { |
4d34686e BS |
387 | const struct nvkm_specdom *sdom; |
388 | const struct nvkm_specsig *ssig; | |
389 | struct nvkm_perfdom *dom; | |
aa4d7a4d BS |
390 | int i; |
391 | ||
392 | for (i = 0; i == 0 || mask; i++) { | |
393 | u32 addr = base + (i * size_unit); | |
394 | if (i && !(mask & (1 << i))) | |
395 | continue; | |
396 | ||
397 | sdom = spec; | |
398 | while (sdom->signal_nr) { | |
399 | dom = kzalloc(sizeof(*dom) + sdom->signal_nr * | |
400 | sizeof(*dom->signal), GFP_KERNEL); | |
401 | if (!dom) | |
402 | return -ENOMEM; | |
403 | ||
404 | if (mask) { | |
405 | snprintf(dom->name, sizeof(dom->name), | |
406 | "%s/%02x/%02x", name, i, | |
407 | (int)(sdom - spec)); | |
408 | } else { | |
409 | snprintf(dom->name, sizeof(dom->name), | |
410 | "%s/%02x", name, (int)(sdom - spec)); | |
411 | } | |
412 | ||
413 | list_add_tail(&dom->head, &ppm->domains); | |
414 | INIT_LIST_HEAD(&dom->list); | |
415 | dom->func = sdom->func; | |
416 | dom->addr = addr; | |
417 | dom->quad = QUAD_MASK; | |
418 | dom->signal_nr = sdom->signal_nr; | |
419 | ||
420 | ssig = (sdom++)->signal; | |
421 | while (ssig->name) { | |
422 | dom->signal[ssig->signal].name = ssig->name; | |
423 | ssig++; | |
424 | } | |
425 | ||
426 | addr += size_domain; | |
427 | } | |
428 | ||
429 | mask &= ~(1 << i); | |
430 | } | |
431 | ||
432 | return 0; | |
433 | } | |
434 | ||
435 | int | |
4d34686e | 436 | _nvkm_pm_fini(struct nvkm_object *object, bool suspend) |
aa4d7a4d | 437 | { |
4d34686e BS |
438 | struct nvkm_pm *ppm = (void *)object; |
439 | return nvkm_engine_fini(&ppm->base, suspend); | |
aa4d7a4d BS |
440 | } |
441 | ||
442 | int | |
4d34686e | 443 | _nvkm_pm_init(struct nvkm_object *object) |
aa4d7a4d | 444 | { |
4d34686e BS |
445 | struct nvkm_pm *ppm = (void *)object; |
446 | return nvkm_engine_init(&ppm->base); | |
aa4d7a4d BS |
447 | } |
448 | ||
449 | void | |
4d34686e | 450 | _nvkm_pm_dtor(struct nvkm_object *object) |
aa4d7a4d | 451 | { |
4d34686e BS |
452 | struct nvkm_pm *ppm = (void *)object; |
453 | struct nvkm_perfdom *dom, *tmp; | |
aa4d7a4d BS |
454 | |
455 | list_for_each_entry_safe(dom, tmp, &ppm->domains, head) { | |
456 | list_del(&dom->head); | |
457 | kfree(dom); | |
458 | } | |
459 | ||
4d34686e | 460 | nvkm_engine_destroy(&ppm->base); |
aa4d7a4d BS |
461 | } |
462 | ||
463 | int | |
4d34686e BS |
464 | nvkm_pm_create_(struct nvkm_object *parent, struct nvkm_object *engine, |
465 | struct nvkm_oclass *oclass, int length, void **pobject) | |
aa4d7a4d | 466 | { |
4d34686e | 467 | struct nvkm_pm *ppm; |
aa4d7a4d BS |
468 | int ret; |
469 | ||
4d34686e BS |
470 | ret = nvkm_engine_create_(parent, engine, oclass, true, "PPM", |
471 | "pm", length, pobject); | |
aa4d7a4d BS |
472 | ppm = *pobject; |
473 | if (ret) | |
474 | return ret; | |
475 | ||
476 | INIT_LIST_HEAD(&ppm->domains); | |
477 | return 0; | |
478 | } |