cfg80211: handle failed skb allocation
[cascardo/linux.git] / drivers / gpu / drm / nouveau / nvkm / engine / pm / base.c
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  */
24 #include "priv.h"
25
26 #include <core/client.h>
27 #include <core/option.h>
28
29 #include <nvif/class.h>
30 #include <nvif/if0002.h>
31 #include <nvif/if0003.h>
32 #include <nvif/ioctl.h>
33 #include <nvif/unpack.h>
34
35 static u8
36 nvkm_pm_count_perfdom(struct nvkm_pm *pm)
37 {
38         struct nvkm_perfdom *dom;
39         u8 domain_nr = 0;
40
41         list_for_each_entry(dom, &pm->domains, head)
42                 domain_nr++;
43         return domain_nr;
44 }
45
46 static u16
47 nvkm_perfdom_count_perfsig(struct nvkm_perfdom *dom)
48 {
49         u16 signal_nr = 0;
50         int i;
51
52         if (dom) {
53                 for (i = 0; i < dom->signal_nr; i++) {
54                         if (dom->signal[i].name)
55                                 signal_nr++;
56                 }
57         }
58         return signal_nr;
59 }
60
61 static struct nvkm_perfdom *
62 nvkm_perfdom_find(struct nvkm_pm *pm, int di)
63 {
64         struct nvkm_perfdom *dom;
65         int tmp = 0;
66
67         list_for_each_entry(dom, &pm->domains, head) {
68                 if (tmp++ == di)
69                         return dom;
70         }
71         return NULL;
72 }
73
74 struct nvkm_perfsig *
75 nvkm_perfsig_find(struct nvkm_pm *pm, u8 di, u8 si, struct nvkm_perfdom **pdom)
76 {
77         struct nvkm_perfdom *dom = *pdom;
78
79         if (dom == NULL) {
80                 dom = nvkm_perfdom_find(pm, di);
81                 if (dom == NULL)
82                         return NULL;
83                 *pdom = dom;
84         }
85
86         if (!dom->signal[si].name)
87                 return NULL;
88         return &dom->signal[si];
89 }
90
91 static u8
92 nvkm_perfsig_count_perfsrc(struct nvkm_perfsig *sig)
93 {
94         u8 source_nr = 0, i;
95
96         for (i = 0; i < ARRAY_SIZE(sig->source); i++) {
97                 if (sig->source[i])
98                         source_nr++;
99         }
100         return source_nr;
101 }
102
103 static struct nvkm_perfsrc *
104 nvkm_perfsrc_find(struct nvkm_pm *pm, struct nvkm_perfsig *sig, int si)
105 {
106         struct nvkm_perfsrc *src;
107         bool found = false;
108         int tmp = 1; /* Sources ID start from 1 */
109         u8 i;
110
111         for (i = 0; i < ARRAY_SIZE(sig->source) && sig->source[i]; i++) {
112                 if (sig->source[i] == si) {
113                         found = true;
114                         break;
115                 }
116         }
117
118         if (found) {
119                 list_for_each_entry(src, &pm->sources, head) {
120                         if (tmp++ == si)
121                                 return src;
122                 }
123         }
124
125         return NULL;
126 }
127
128 static int
129 nvkm_perfsrc_enable(struct nvkm_pm *pm, struct nvkm_perfctr *ctr)
130 {
131         struct nvkm_subdev *subdev = &pm->engine.subdev;
132         struct nvkm_device *device = subdev->device;
133         struct nvkm_perfdom *dom = NULL;
134         struct nvkm_perfsig *sig;
135         struct nvkm_perfsrc *src;
136         u32 mask, value;
137         int i, j;
138
139         for (i = 0; i < 4; i++) {
140                 for (j = 0; j < 8 && ctr->source[i][j]; j++) {
141                         sig = nvkm_perfsig_find(pm, ctr->domain,
142                                                 ctr->signal[i], &dom);
143                         if (!sig)
144                                 return -EINVAL;
145
146                         src = nvkm_perfsrc_find(pm, sig, ctr->source[i][j]);
147                         if (!src)
148                                 return -EINVAL;
149
150                         /* set enable bit if needed */
151                         mask = value = 0x00000000;
152                         if (src->enable)
153                                 mask = value = 0x80000000;
154                         mask  |= (src->mask << src->shift);
155                         value |= ((ctr->source[i][j] >> 32) << src->shift);
156
157                         /* enable the source */
158                         nvkm_mask(device, src->addr, mask, value);
159                         nvkm_debug(subdev,
160                                    "enabled source %08x %08x %08x\n",
161                                    src->addr, mask, value);
162                 }
163         }
164         return 0;
165 }
166
167 static int
168 nvkm_perfsrc_disable(struct nvkm_pm *pm, struct nvkm_perfctr *ctr)
169 {
170         struct nvkm_subdev *subdev = &pm->engine.subdev;
171         struct nvkm_device *device = subdev->device;
172         struct nvkm_perfdom *dom = NULL;
173         struct nvkm_perfsig *sig;
174         struct nvkm_perfsrc *src;
175         u32 mask;
176         int i, j;
177
178         for (i = 0; i < 4; i++) {
179                 for (j = 0; j < 8 && ctr->source[i][j]; j++) {
180                         sig = nvkm_perfsig_find(pm, ctr->domain,
181                                                 ctr->signal[i], &dom);
182                         if (!sig)
183                                 return -EINVAL;
184
185                         src = nvkm_perfsrc_find(pm, sig, ctr->source[i][j]);
186                         if (!src)
187                                 return -EINVAL;
188
189                         /* unset enable bit if needed */
190                         mask = 0x00000000;
191                         if (src->enable)
192                                 mask = 0x80000000;
193                         mask |= (src->mask << src->shift);
194
195                         /* disable the source */
196                         nvkm_mask(device, src->addr, mask, 0);
197                         nvkm_debug(subdev, "disabled source %08x %08x\n",
198                                    src->addr, mask);
199                 }
200         }
201         return 0;
202 }
203
204 /*******************************************************************************
205  * Perfdom object classes
206  ******************************************************************************/
207 static int
208 nvkm_perfdom_init(struct nvkm_perfdom *dom, void *data, u32 size)
209 {
210         union {
211                 struct nvif_perfdom_init none;
212         } *args = data;
213         struct nvkm_object *object = &dom->object;
214         struct nvkm_pm *pm = dom->perfmon->pm;
215         int ret = -ENOSYS, i;
216
217         nvif_ioctl(object, "perfdom init size %d\n", size);
218         if (!(ret = nvif_unvers(ret, &data, &size, args->none))) {
219                 nvif_ioctl(object, "perfdom init\n");
220         } else
221                 return ret;
222
223         for (i = 0; i < 4; i++) {
224                 if (dom->ctr[i]) {
225                         dom->func->init(pm, dom, dom->ctr[i]);
226
227                         /* enable sources */
228                         nvkm_perfsrc_enable(pm, dom->ctr[i]);
229                 }
230         }
231
232         /* start next batch of counters for sampling */
233         dom->func->next(pm, dom);
234         return 0;
235 }
236
237 static int
238 nvkm_perfdom_sample(struct nvkm_perfdom *dom, void *data, u32 size)
239 {
240         union {
241                 struct nvif_perfdom_sample none;
242         } *args = data;
243         struct nvkm_object *object = &dom->object;
244         struct nvkm_pm *pm = dom->perfmon->pm;
245         int ret = -ENOSYS;
246
247         nvif_ioctl(object, "perfdom sample size %d\n", size);
248         if (!(ret = nvif_unvers(ret, &data, &size, args->none))) {
249                 nvif_ioctl(object, "perfdom sample\n");
250         } else
251                 return ret;
252         pm->sequence++;
253
254         /* sample previous batch of counters */
255         list_for_each_entry(dom, &pm->domains, head)
256                 dom->func->next(pm, dom);
257
258         return 0;
259 }
260
261 static int
262 nvkm_perfdom_read(struct nvkm_perfdom *dom, void *data, u32 size)
263 {
264         union {
265                 struct nvif_perfdom_read_v0 v0;
266         } *args = data;
267         struct nvkm_object *object = &dom->object;
268         struct nvkm_pm *pm = dom->perfmon->pm;
269         int ret = -ENOSYS, i;
270
271         nvif_ioctl(object, "perfdom read size %d\n", size);
272         if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
273                 nvif_ioctl(object, "perfdom read vers %d\n", args->v0.version);
274         } else
275                 return ret;
276
277         for (i = 0; i < 4; i++) {
278                 if (dom->ctr[i])
279                         dom->func->read(pm, dom, dom->ctr[i]);
280         }
281
282         if (!dom->clk)
283                 return -EAGAIN;
284
285         for (i = 0; i < 4; i++)
286                 if (dom->ctr[i])
287                         args->v0.ctr[i] = dom->ctr[i]->ctr;
288         args->v0.clk = dom->clk;
289         return 0;
290 }
291
292 static int
293 nvkm_perfdom_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size)
294 {
295         struct nvkm_perfdom *dom = nvkm_perfdom(object);
296         switch (mthd) {
297         case NVIF_PERFDOM_V0_INIT:
298                 return nvkm_perfdom_init(dom, data, size);
299         case NVIF_PERFDOM_V0_SAMPLE:
300                 return nvkm_perfdom_sample(dom, data, size);
301         case NVIF_PERFDOM_V0_READ:
302                 return nvkm_perfdom_read(dom, data, size);
303         default:
304                 break;
305         }
306         return -EINVAL;
307 }
308
309 static void *
310 nvkm_perfdom_dtor(struct nvkm_object *object)
311 {
312         struct nvkm_perfdom *dom = nvkm_perfdom(object);
313         struct nvkm_pm *pm = dom->perfmon->pm;
314         int i;
315
316         for (i = 0; i < 4; i++) {
317                 struct nvkm_perfctr *ctr = dom->ctr[i];
318                 if (ctr) {
319                         nvkm_perfsrc_disable(pm, ctr);
320                         if (ctr->head.next)
321                                 list_del(&ctr->head);
322                 }
323                 kfree(ctr);
324         }
325
326         return dom;
327 }
328
329 static int
330 nvkm_perfctr_new(struct nvkm_perfdom *dom, int slot, u8 domain,
331                  struct nvkm_perfsig *signal[4], u64 source[4][8],
332                  u16 logic_op, struct nvkm_perfctr **pctr)
333 {
334         struct nvkm_perfctr *ctr;
335         int i, j;
336
337         if (!dom)
338                 return -EINVAL;
339
340         ctr = *pctr = kzalloc(sizeof(*ctr), GFP_KERNEL);
341         if (!ctr)
342                 return -ENOMEM;
343
344         ctr->domain   = domain;
345         ctr->logic_op = logic_op;
346         ctr->slot     = slot;
347         for (i = 0; i < 4; i++) {
348                 if (signal[i]) {
349                         ctr->signal[i] = signal[i] - dom->signal;
350                         for (j = 0; j < 8; j++)
351                                 ctr->source[i][j] = source[i][j];
352                 }
353         }
354         list_add_tail(&ctr->head, &dom->list);
355
356         return 0;
357 }
358
359 static const struct nvkm_object_func
360 nvkm_perfdom = {
361         .dtor = nvkm_perfdom_dtor,
362         .mthd = nvkm_perfdom_mthd,
363 };
364
365 static int
366 nvkm_perfdom_new_(struct nvkm_perfmon *perfmon,
367                   const struct nvkm_oclass *oclass, void *data, u32 size,
368                   struct nvkm_object **pobject)
369 {
370         union {
371                 struct nvif_perfdom_v0 v0;
372         } *args = data;
373         struct nvkm_pm *pm = perfmon->pm;
374         struct nvkm_object *parent = oclass->parent;
375         struct nvkm_perfdom *sdom = NULL;
376         struct nvkm_perfctr *ctr[4] = {};
377         struct nvkm_perfdom *dom;
378         int c, s, m;
379         int ret = -ENOSYS;
380
381         nvif_ioctl(parent, "create perfdom size %d\n", size);
382         if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
383                 nvif_ioctl(parent, "create perfdom vers %d dom %d mode %02x\n",
384                            args->v0.version, args->v0.domain, args->v0.mode);
385         } else
386                 return ret;
387
388         for (c = 0; c < ARRAY_SIZE(args->v0.ctr); c++) {
389                 struct nvkm_perfsig *sig[4] = {};
390                 u64 src[4][8] = {};
391
392                 for (s = 0; s < ARRAY_SIZE(args->v0.ctr[c].signal); s++) {
393                         sig[s] = nvkm_perfsig_find(pm, args->v0.domain,
394                                                    args->v0.ctr[c].signal[s],
395                                                    &sdom);
396                         if (args->v0.ctr[c].signal[s] && !sig[s])
397                                 return -EINVAL;
398
399                         for (m = 0; m < 8; m++) {
400                                 src[s][m] = args->v0.ctr[c].source[s][m];
401                                 if (src[s][m] && !nvkm_perfsrc_find(pm, sig[s],
402                                                                     src[s][m]))
403                                         return -EINVAL;
404                         }
405                 }
406
407                 ret = nvkm_perfctr_new(sdom, c, args->v0.domain, sig, src,
408                                        args->v0.ctr[c].logic_op, &ctr[c]);
409                 if (ret)
410                         return ret;
411         }
412
413         if (!sdom)
414                 return -EINVAL;
415
416         if (!(dom = kzalloc(sizeof(*dom), GFP_KERNEL)))
417                 return -ENOMEM;
418         nvkm_object_ctor(&nvkm_perfdom, oclass, &dom->object);
419         dom->perfmon = perfmon;
420         *pobject = &dom->object;
421
422         dom->func = sdom->func;
423         dom->addr = sdom->addr;
424         dom->mode = args->v0.mode;
425         for (c = 0; c < ARRAY_SIZE(ctr); c++)
426                 dom->ctr[c] = ctr[c];
427         return 0;
428 }
429
430 /*******************************************************************************
431  * Perfmon object classes
432  ******************************************************************************/
433 static int
434 nvkm_perfmon_mthd_query_domain(struct nvkm_perfmon *perfmon,
435                                void *data, u32 size)
436 {
437         union {
438                 struct nvif_perfmon_query_domain_v0 v0;
439         } *args = data;
440         struct nvkm_object *object = &perfmon->object;
441         struct nvkm_pm *pm = perfmon->pm;
442         struct nvkm_perfdom *dom;
443         u8 domain_nr;
444         int di, ret = -ENOSYS;
445
446         nvif_ioctl(object, "perfmon query domain size %d\n", size);
447         if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
448                 nvif_ioctl(object, "perfmon domain vers %d iter %02x\n",
449                            args->v0.version, args->v0.iter);
450                 di = (args->v0.iter & 0xff) - 1;
451         } else
452                 return ret;
453
454         domain_nr = nvkm_pm_count_perfdom(pm);
455         if (di >= (int)domain_nr)
456                 return -EINVAL;
457
458         if (di >= 0) {
459                 dom = nvkm_perfdom_find(pm, di);
460                 if (dom == NULL)
461                         return -EINVAL;
462
463                 args->v0.id         = di;
464                 args->v0.signal_nr  = nvkm_perfdom_count_perfsig(dom);
465                 strncpy(args->v0.name, dom->name, sizeof(args->v0.name));
466
467                 /* Currently only global counters (PCOUNTER) are implemented
468                  * but this will be different for local counters (MP). */
469                 args->v0.counter_nr = 4;
470         }
471
472         if (++di < domain_nr) {
473                 args->v0.iter = ++di;
474                 return 0;
475         }
476
477         args->v0.iter = 0xff;
478         return 0;
479 }
480
481 static int
482 nvkm_perfmon_mthd_query_signal(struct nvkm_perfmon *perfmon,
483                                void *data, u32 size)
484 {
485         union {
486                 struct nvif_perfmon_query_signal_v0 v0;
487         } *args = data;
488         struct nvkm_object *object = &perfmon->object;
489         struct nvkm_pm *pm = perfmon->pm;
490         struct nvkm_device *device = pm->engine.subdev.device;
491         struct nvkm_perfdom *dom;
492         struct nvkm_perfsig *sig;
493         const bool all = nvkm_boolopt(device->cfgopt, "NvPmShowAll", false);
494         const bool raw = nvkm_boolopt(device->cfgopt, "NvPmUnnamed", all);
495         int ret = -ENOSYS, si;
496
497         nvif_ioctl(object, "perfmon query signal size %d\n", size);
498         if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
499                 nvif_ioctl(object,
500                            "perfmon query signal vers %d dom %d iter %04x\n",
501                            args->v0.version, args->v0.domain, args->v0.iter);
502                 si = (args->v0.iter & 0xffff) - 1;
503         } else
504                 return ret;
505
506         dom = nvkm_perfdom_find(pm, args->v0.domain);
507         if (dom == NULL || si >= (int)dom->signal_nr)
508                 return -EINVAL;
509
510         if (si >= 0) {
511                 sig = &dom->signal[si];
512                 if (raw || !sig->name) {
513                         snprintf(args->v0.name, sizeof(args->v0.name),
514                                  "/%s/%02x", dom->name, si);
515                 } else {
516                         strncpy(args->v0.name, sig->name,
517                                 sizeof(args->v0.name));
518                 }
519
520                 args->v0.signal = si;
521                 args->v0.source_nr = nvkm_perfsig_count_perfsrc(sig);
522         }
523
524         while (++si < dom->signal_nr) {
525                 if (all || dom->signal[si].name) {
526                         args->v0.iter = ++si;
527                         return 0;
528                 }
529         }
530
531         args->v0.iter = 0xffff;
532         return 0;
533 }
534
535 static int
536 nvkm_perfmon_mthd_query_source(struct nvkm_perfmon *perfmon,
537                                void *data, u32 size)
538 {
539         union {
540                 struct nvif_perfmon_query_source_v0 v0;
541         } *args = data;
542         struct nvkm_object *object = &perfmon->object;
543         struct nvkm_pm *pm = perfmon->pm;
544         struct nvkm_perfdom *dom = NULL;
545         struct nvkm_perfsig *sig;
546         struct nvkm_perfsrc *src;
547         u8 source_nr = 0;
548         int si, ret = -ENOSYS;
549
550         nvif_ioctl(object, "perfmon query source size %d\n", size);
551         if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
552                 nvif_ioctl(object,
553                            "perfmon source vers %d dom %d sig %02x iter %02x\n",
554                            args->v0.version, args->v0.domain, args->v0.signal,
555                            args->v0.iter);
556                 si = (args->v0.iter & 0xff) - 1;
557         } else
558                 return ret;
559
560         sig = nvkm_perfsig_find(pm, args->v0.domain, args->v0.signal, &dom);
561         if (!sig)
562                 return -EINVAL;
563
564         source_nr = nvkm_perfsig_count_perfsrc(sig);
565         if (si >= (int)source_nr)
566                 return -EINVAL;
567
568         if (si >= 0) {
569                 src = nvkm_perfsrc_find(pm, sig, sig->source[si]);
570                 if (!src)
571                         return -EINVAL;
572
573                 args->v0.source = sig->source[si];
574                 args->v0.mask   = src->mask;
575                 strncpy(args->v0.name, src->name, sizeof(args->v0.name));
576         }
577
578         if (++si < source_nr) {
579                 args->v0.iter = ++si;
580                 return 0;
581         }
582
583         args->v0.iter = 0xff;
584         return 0;
585 }
586
587 static int
588 nvkm_perfmon_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size)
589 {
590         struct nvkm_perfmon *perfmon = nvkm_perfmon(object);
591         switch (mthd) {
592         case NVIF_PERFMON_V0_QUERY_DOMAIN:
593                 return nvkm_perfmon_mthd_query_domain(perfmon, data, size);
594         case NVIF_PERFMON_V0_QUERY_SIGNAL:
595                 return nvkm_perfmon_mthd_query_signal(perfmon, data, size);
596         case NVIF_PERFMON_V0_QUERY_SOURCE:
597                 return nvkm_perfmon_mthd_query_source(perfmon, data, size);
598         default:
599                 break;
600         }
601         return -EINVAL;
602 }
603
604 static int
605 nvkm_perfmon_child_new(const struct nvkm_oclass *oclass, void *data, u32 size,
606                        struct nvkm_object **pobject)
607 {
608         struct nvkm_perfmon *perfmon = nvkm_perfmon(oclass->parent);
609         return nvkm_perfdom_new_(perfmon, oclass, data, size, pobject);
610 }
611
612 static int
613 nvkm_perfmon_child_get(struct nvkm_object *object, int index,
614                        struct nvkm_oclass *oclass)
615 {
616         if (index == 0) {
617                 oclass->base.oclass = NVIF_CLASS_PERFDOM;
618                 oclass->base.minver = 0;
619                 oclass->base.maxver = 0;
620                 oclass->ctor = nvkm_perfmon_child_new;
621                 return 0;
622         }
623         return -EINVAL;
624 }
625
626 static void *
627 nvkm_perfmon_dtor(struct nvkm_object *object)
628 {
629         struct nvkm_perfmon *perfmon = nvkm_perfmon(object);
630         struct nvkm_pm *pm = perfmon->pm;
631         mutex_lock(&pm->engine.subdev.mutex);
632         if (pm->perfmon == &perfmon->object)
633                 pm->perfmon = NULL;
634         mutex_unlock(&pm->engine.subdev.mutex);
635         return perfmon;
636 }
637
638 static const struct nvkm_object_func
639 nvkm_perfmon = {
640         .dtor = nvkm_perfmon_dtor,
641         .mthd = nvkm_perfmon_mthd,
642         .sclass = nvkm_perfmon_child_get,
643 };
644
645 static int
646 nvkm_perfmon_new(struct nvkm_pm *pm, const struct nvkm_oclass *oclass,
647                  void *data, u32 size, struct nvkm_object **pobject)
648 {
649         struct nvkm_perfmon *perfmon;
650
651         if (!(perfmon = kzalloc(sizeof(*perfmon), GFP_KERNEL)))
652                 return -ENOMEM;
653         nvkm_object_ctor(&nvkm_perfmon, oclass, &perfmon->object);
654         perfmon->pm = pm;
655         *pobject = &perfmon->object;
656         return 0;
657 }
658
659 /*******************************************************************************
660  * PPM engine/subdev functions
661  ******************************************************************************/
662
663 static int
664 nvkm_pm_oclass_new(struct nvkm_device *device, const struct nvkm_oclass *oclass,
665                    void *data, u32 size, struct nvkm_object **pobject)
666 {
667         struct nvkm_pm *pm = nvkm_pm(oclass->engine);
668         int ret;
669
670         ret = nvkm_perfmon_new(pm, oclass, data, size, pobject);
671         if (ret)
672                 return ret;
673
674         mutex_lock(&pm->engine.subdev.mutex);
675         if (pm->perfmon == NULL)
676                 pm->perfmon = *pobject;
677         ret = (pm->perfmon == *pobject) ? 0 : -EBUSY;
678         mutex_unlock(&pm->engine.subdev.mutex);
679         return ret;
680 }
681
682 static const struct nvkm_device_oclass
683 nvkm_pm_oclass = {
684         .base.oclass = NVIF_CLASS_PERFMON,
685         .base.minver = -1,
686         .base.maxver = -1,
687         .ctor = nvkm_pm_oclass_new,
688 };
689
690 static int
691 nvkm_pm_oclass_get(struct nvkm_oclass *oclass, int index,
692                    const struct nvkm_device_oclass **class)
693 {
694         if (index == 0) {
695                 oclass->base = nvkm_pm_oclass.base;
696                 *class = &nvkm_pm_oclass;
697                 return index;
698         }
699         return 1;
700 }
701
702 int
703 nvkm_perfsrc_new(struct nvkm_pm *pm, struct nvkm_perfsig *sig,
704                  const struct nvkm_specsrc *spec)
705 {
706         const struct nvkm_specsrc *ssrc;
707         const struct nvkm_specmux *smux;
708         struct nvkm_perfsrc *src;
709         u8 source_nr = 0;
710
711         if (!spec) {
712                 /* No sources are defined for this signal. */
713                 return 0;
714         }
715
716         ssrc = spec;
717         while (ssrc->name) {
718                 smux = ssrc->mux;
719                 while (smux->name) {
720                         bool found = false;
721                         u8 source_id = 0;
722                         u32 len;
723
724                         list_for_each_entry(src, &pm->sources, head) {
725                                 if (src->addr == ssrc->addr &&
726                                     src->shift == smux->shift) {
727                                         found = true;
728                                         break;
729                                 }
730                                 source_id++;
731                         }
732
733                         if (!found) {
734                                 src = kzalloc(sizeof(*src), GFP_KERNEL);
735                                 if (!src)
736                                         return -ENOMEM;
737
738                                 src->addr   = ssrc->addr;
739                                 src->mask   = smux->mask;
740                                 src->shift  = smux->shift;
741                                 src->enable = smux->enable;
742
743                                 len = strlen(ssrc->name) +
744                                       strlen(smux->name) + 2;
745                                 src->name = kzalloc(len, GFP_KERNEL);
746                                 if (!src->name) {
747                                         kfree(src);
748                                         return -ENOMEM;
749                                 }
750                                 snprintf(src->name, len, "%s_%s", ssrc->name,
751                                          smux->name);
752
753                                 list_add_tail(&src->head, &pm->sources);
754                         }
755
756                         sig->source[source_nr++] = source_id + 1;
757                         smux++;
758                 }
759                 ssrc++;
760         }
761
762         return 0;
763 }
764
765 int
766 nvkm_perfdom_new(struct nvkm_pm *pm, const char *name, u32 mask,
767                  u32 base, u32 size_unit, u32 size_domain,
768                  const struct nvkm_specdom *spec)
769 {
770         const struct nvkm_specdom *sdom;
771         const struct nvkm_specsig *ssig;
772         struct nvkm_perfdom *dom;
773         int ret, i;
774
775         for (i = 0; i == 0 || mask; i++) {
776                 u32 addr = base + (i * size_unit);
777                 if (i && !(mask & (1 << i)))
778                         continue;
779
780                 sdom = spec;
781                 while (sdom->signal_nr) {
782                         dom = kzalloc(sizeof(*dom) + sdom->signal_nr *
783                                       sizeof(*dom->signal), GFP_KERNEL);
784                         if (!dom)
785                                 return -ENOMEM;
786
787                         if (mask) {
788                                 snprintf(dom->name, sizeof(dom->name),
789                                          "%s/%02x/%02x", name, i,
790                                          (int)(sdom - spec));
791                         } else {
792                                 snprintf(dom->name, sizeof(dom->name),
793                                          "%s/%02x", name, (int)(sdom - spec));
794                         }
795
796                         list_add_tail(&dom->head, &pm->domains);
797                         INIT_LIST_HEAD(&dom->list);
798                         dom->func = sdom->func;
799                         dom->addr = addr;
800                         dom->signal_nr = sdom->signal_nr;
801
802                         ssig = (sdom++)->signal;
803                         while (ssig->name) {
804                                 struct nvkm_perfsig *sig =
805                                         &dom->signal[ssig->signal];
806                                 sig->name = ssig->name;
807                                 ret = nvkm_perfsrc_new(pm, sig, ssig->source);
808                                 if (ret)
809                                         return ret;
810                                 ssig++;
811                         }
812
813                         addr += size_domain;
814                 }
815
816                 mask &= ~(1 << i);
817         }
818
819         return 0;
820 }
821
822 static int
823 nvkm_pm_fini(struct nvkm_engine *engine, bool suspend)
824 {
825         struct nvkm_pm *pm = nvkm_pm(engine);
826         if (pm->func->fini)
827                 pm->func->fini(pm);
828         return 0;
829 }
830
831 static void *
832 nvkm_pm_dtor(struct nvkm_engine *engine)
833 {
834         struct nvkm_pm *pm = nvkm_pm(engine);
835         struct nvkm_perfdom *dom, *next_dom;
836         struct nvkm_perfsrc *src, *next_src;
837
838         list_for_each_entry_safe(dom, next_dom, &pm->domains, head) {
839                 list_del(&dom->head);
840                 kfree(dom);
841         }
842
843         list_for_each_entry_safe(src, next_src, &pm->sources, head) {
844                 list_del(&src->head);
845                 kfree(src->name);
846                 kfree(src);
847         }
848
849         return pm;
850 }
851
852 static const struct nvkm_engine_func
853 nvkm_pm = {
854         .dtor = nvkm_pm_dtor,
855         .fini = nvkm_pm_fini,
856         .base.sclass = nvkm_pm_oclass_get,
857 };
858
859 int
860 nvkm_pm_ctor(const struct nvkm_pm_func *func, struct nvkm_device *device,
861              int index, struct nvkm_pm *pm)
862 {
863         pm->func = func;
864         INIT_LIST_HEAD(&pm->domains);
865         INIT_LIST_HEAD(&pm->sources);
866         return nvkm_engine_ctor(&nvkm_pm, device, index, 0, true, &pm->engine);
867 }