blob: cd0df201594c7a35237e9a3a79c04f2677913ec1 [file] [log] [blame]
Connor O'Brien6e7b83d2018-01-31 18:11:57 -08001/* drivers/cpufreq/cpufreq_times.c
2 *
3 * Copyright (C) 2018 Google, Inc.
4 *
5 * This software is licensed under the terms of the GNU General Public
6 * License version 2, as published by the Free Software Foundation, and
7 * may be copied, distributed, and modified under those terms.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 */
15
16#include <linux/cpufreq.h>
17#include <linux/cpufreq_times.h>
18#include <linux/cputime.h>
Connor O'Brienfcb3db12018-02-06 13:30:27 -080019#include <linux/hashtable.h>
20#include <linux/init.h>
21#include <linux/proc_fs.h>
Connor O'Brien6e7b83d2018-01-31 18:11:57 -080022#include <linux/sched.h>
23#include <linux/seq_file.h>
24#include <linux/slab.h>
25#include <linux/spinlock.h>
26#include <linux/threads.h>
27
Connor O'Brienfcb3db12018-02-06 13:30:27 -080028#define UID_HASH_BITS 10
29
30static DECLARE_HASHTABLE(uid_hash_table, UID_HASH_BITS);
31
Connor O'Brien6e7b83d2018-01-31 18:11:57 -080032static DEFINE_SPINLOCK(task_time_in_state_lock); /* task->time_in_state */
Connor O'Brienfcb3db12018-02-06 13:30:27 -080033static DEFINE_SPINLOCK(uid_lock); /* uid_hash_table */
34
Connor O'Brien605e2ec2018-10-05 19:20:54 -070035struct concurrent_times {
36 atomic64_t active[NR_CPUS];
37 atomic64_t policy[NR_CPUS];
38};
39
Connor O'Brienfcb3db12018-02-06 13:30:27 -080040struct uid_entry {
41 uid_t uid;
42 unsigned int max_state;
43 struct hlist_node hash;
44 struct rcu_head rcu;
Connor O'Brien605e2ec2018-10-05 19:20:54 -070045 struct concurrent_times *concurrent_times;
Connor O'Brienfcb3db12018-02-06 13:30:27 -080046 u64 time_in_state[0];
47};
Connor O'Brien6e7b83d2018-01-31 18:11:57 -080048
49/**
50 * struct cpu_freqs - per-cpu frequency information
51 * @offset: start of these freqs' stats in task time_in_state array
52 * @max_state: number of entries in freq_table
53 * @last_index: index in freq_table of last frequency switched to
54 * @freq_table: list of available frequencies
55 */
56struct cpu_freqs {
57 unsigned int offset;
58 unsigned int max_state;
59 unsigned int last_index;
60 unsigned int freq_table[0];
61};
62
63static struct cpu_freqs *all_freqs[NR_CPUS];
64
65static unsigned int next_offset;
66
Connor O'Brienf17f4fb2018-01-22 18:28:08 -080067
68/* Caller must hold rcu_read_lock() */
69static struct uid_entry *find_uid_entry_rcu(uid_t uid)
70{
71 struct uid_entry *uid_entry;
72
73 hash_for_each_possible_rcu(uid_hash_table, uid_entry, hash, uid) {
74 if (uid_entry->uid == uid)
75 return uid_entry;
76 }
77 return NULL;
78}
79
Connor O'Brienfcb3db12018-02-06 13:30:27 -080080/* Caller must hold uid lock */
81static struct uid_entry *find_uid_entry_locked(uid_t uid)
82{
83 struct uid_entry *uid_entry;
84
85 hash_for_each_possible(uid_hash_table, uid_entry, hash, uid) {
86 if (uid_entry->uid == uid)
87 return uid_entry;
88 }
89 return NULL;
90}
91
92/* Caller must hold uid lock */
93static struct uid_entry *find_or_register_uid_locked(uid_t uid)
94{
95 struct uid_entry *uid_entry, *temp;
Connor O'Brien605e2ec2018-10-05 19:20:54 -070096 struct concurrent_times *times;
Connor O'Brienfcb3db12018-02-06 13:30:27 -080097 unsigned int max_state = READ_ONCE(next_offset);
98 size_t alloc_size = sizeof(*uid_entry) + max_state *
99 sizeof(uid_entry->time_in_state[0]);
100
101 uid_entry = find_uid_entry_locked(uid);
102 if (uid_entry) {
103 if (uid_entry->max_state == max_state)
104 return uid_entry;
105 /* uid_entry->time_in_state is too small to track all freqs, so
106 * expand it.
107 */
108 temp = __krealloc(uid_entry, alloc_size, GFP_ATOMIC);
109 if (!temp)
110 return uid_entry;
111 temp->max_state = max_state;
112 memset(temp->time_in_state + uid_entry->max_state, 0,
113 (max_state - uid_entry->max_state) *
114 sizeof(uid_entry->time_in_state[0]));
115 if (temp != uid_entry) {
116 hlist_replace_rcu(&uid_entry->hash, &temp->hash);
117 kfree_rcu(uid_entry, rcu);
118 }
119 return temp;
120 }
121
122 uid_entry = kzalloc(alloc_size, GFP_ATOMIC);
123 if (!uid_entry)
124 return NULL;
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700125 times = kzalloc(sizeof(*times), GFP_ATOMIC);
126 if (!times) {
127 kfree(uid_entry);
128 return NULL;
129 }
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800130
131 uid_entry->uid = uid;
132 uid_entry->max_state = max_state;
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700133 uid_entry->concurrent_times = times;
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800134
135 hash_add_rcu(uid_hash_table, &uid_entry->hash, uid);
136
137 return uid_entry;
138}
139
140static bool freq_index_invalid(unsigned int index)
141{
142 unsigned int cpu;
143 struct cpu_freqs *freqs;
144
145 for_each_possible_cpu(cpu) {
146 freqs = all_freqs[cpu];
147 if (!freqs || index < freqs->offset ||
148 freqs->offset + freqs->max_state <= index)
149 continue;
150 return freqs->freq_table[index - freqs->offset] ==
151 CPUFREQ_ENTRY_INVALID;
152 }
153 return true;
154}
155
Connor O'Brienf17f4fb2018-01-22 18:28:08 -0800156static int single_uid_time_in_state_show(struct seq_file *m, void *ptr)
157{
158 struct uid_entry *uid_entry;
159 unsigned int i;
160 u64 time;
161 uid_t uid = from_kuid_munged(current_user_ns(), *(kuid_t *)m->private);
162
163 if (uid == overflowuid)
164 return -EINVAL;
165
166 rcu_read_lock();
167
168 uid_entry = find_uid_entry_rcu(uid);
169 if (!uid_entry) {
170 rcu_read_unlock();
171 return 0;
172 }
173
174 for (i = 0; i < uid_entry->max_state; ++i) {
175 if (freq_index_invalid(i))
176 continue;
177 time = cputime_to_clock_t(uid_entry->time_in_state[i]);
178 seq_write(m, &time, sizeof(time));
179 }
180
181 rcu_read_unlock();
182
183 return 0;
184}
185
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800186static void *uid_seq_start(struct seq_file *seq, loff_t *pos)
187{
188 if (*pos >= HASH_SIZE(uid_hash_table))
189 return NULL;
190
191 return &uid_hash_table[*pos];
192}
193
194static void *uid_seq_next(struct seq_file *seq, void *v, loff_t *pos)
195{
196 (*pos)++;
197
198 if (*pos >= HASH_SIZE(uid_hash_table))
199 return NULL;
200
201 return &uid_hash_table[*pos];
202}
203
204static void uid_seq_stop(struct seq_file *seq, void *v) { }
205
206static int uid_time_in_state_seq_show(struct seq_file *m, void *v)
207{
208 struct uid_entry *uid_entry;
209 struct cpu_freqs *freqs, *last_freqs = NULL;
210 int i, cpu;
211
212 if (v == uid_hash_table) {
213 seq_puts(m, "uid:");
214 for_each_possible_cpu(cpu) {
215 freqs = all_freqs[cpu];
216 if (!freqs || freqs == last_freqs)
217 continue;
218 last_freqs = freqs;
Connor O'Brien7cd95612018-04-03 16:05:37 -0700219 for (i = 0; i < freqs->max_state; i++) {
220 if (freqs->freq_table[i] ==
221 CPUFREQ_ENTRY_INVALID)
222 continue;
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800223 seq_printf(m, " %d", freqs->freq_table[i]);
Connor O'Brien7cd95612018-04-03 16:05:37 -0700224 }
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800225 }
226 seq_putc(m, '\n');
227 }
228
229 rcu_read_lock();
230
231 hlist_for_each_entry_rcu(uid_entry, (struct hlist_head *)v, hash) {
232 if (uid_entry->max_state)
233 seq_printf(m, "%d:", uid_entry->uid);
234 for (i = 0; i < uid_entry->max_state; ++i) {
235 if (freq_index_invalid(i))
236 continue;
237 seq_printf(m, " %lu", (unsigned long)cputime_to_clock_t(
238 uid_entry->time_in_state[i]));
239 }
240 if (uid_entry->max_state)
241 seq_putc(m, '\n');
242 }
243
244 rcu_read_unlock();
245 return 0;
246}
247
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700248static int concurrent_time_seq_show(struct seq_file *m, void *v,
249 atomic64_t *(*get_times)(struct concurrent_times *))
250{
251 struct uid_entry *uid_entry;
252 int i, num_possible_cpus = num_possible_cpus();
253
254 rcu_read_lock();
255
256 hlist_for_each_entry_rcu(uid_entry, (struct hlist_head *)v, hash) {
257 atomic64_t *times = get_times(uid_entry->concurrent_times);
258
259 seq_put_decimal_ull(m, "", (u64)uid_entry->uid);
260 seq_putc(m, ':');
261
262 for (i = 0; i < num_possible_cpus; ++i) {
263 u64 time = cputime_to_clock_t(atomic64_read(&times[i]));
264
265 seq_put_decimal_ull(m, " ", time);
266 }
267 seq_putc(m, '\n');
268 }
269
270 rcu_read_unlock();
271
272 return 0;
273}
274
275static inline atomic64_t *get_active_times(struct concurrent_times *times)
276{
277 return times->active;
278}
279
280static int concurrent_active_time_seq_show(struct seq_file *m, void *v)
281{
282 if (v == uid_hash_table) {
283 seq_put_decimal_ull(m, "cpus: ", num_possible_cpus());
284 seq_putc(m, '\n');
285 }
286
287 return concurrent_time_seq_show(m, v, get_active_times);
288}
289
290static inline atomic64_t *get_policy_times(struct concurrent_times *times)
291{
292 return times->policy;
293}
294
295static int concurrent_policy_time_seq_show(struct seq_file *m, void *v)
296{
297 int i;
298 struct cpu_freqs *freqs, *last_freqs = NULL;
299
300 if (v == uid_hash_table) {
301 int cnt = 0;
302
303 for_each_possible_cpu(i) {
304 freqs = all_freqs[i];
305 if (!freqs)
306 continue;
307 if (freqs != last_freqs) {
308 if (last_freqs) {
309 seq_put_decimal_ull(m, ": ", cnt);
310 seq_putc(m, ' ');
311 cnt = 0;
312 }
313 seq_put_decimal_ull(m, "policy", i);
314
315 last_freqs = freqs;
316 }
317 cnt++;
318 }
319 if (last_freqs) {
320 seq_put_decimal_ull(m, ": ", cnt);
321 seq_putc(m, '\n');
322 }
323 }
324
325 return concurrent_time_seq_show(m, v, get_policy_times);
326}
327
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800328void cpufreq_task_times_init(struct task_struct *p)
329{
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800330 unsigned long flags;
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800331
332 spin_lock_irqsave(&task_time_in_state_lock, flags);
333 p->time_in_state = NULL;
334 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
335 p->max_state = 0;
Sultan Alsawaf47bbcd62018-06-03 10:47:51 -0700336}
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800337
Sultan Alsawaf47bbcd62018-06-03 10:47:51 -0700338void cpufreq_task_times_alloc(struct task_struct *p)
339{
340 void *temp;
341 unsigned long flags;
342 unsigned int max_state = READ_ONCE(next_offset);
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800343
344 /* We use one array to avoid multiple allocs per task */
345 temp = kcalloc(max_state, sizeof(p->time_in_state[0]), GFP_ATOMIC);
346 if (!temp)
347 return;
348
349 spin_lock_irqsave(&task_time_in_state_lock, flags);
350 p->time_in_state = temp;
351 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
352 p->max_state = max_state;
353}
354
355/* Caller must hold task_time_in_state_lock */
356static int cpufreq_task_times_realloc_locked(struct task_struct *p)
357{
358 void *temp;
359 unsigned int max_state = READ_ONCE(next_offset);
360
361 temp = krealloc(p->time_in_state, max_state * sizeof(u64), GFP_ATOMIC);
362 if (!temp)
363 return -ENOMEM;
364 p->time_in_state = temp;
365 memset(p->time_in_state + p->max_state, 0,
366 (max_state - p->max_state) * sizeof(u64));
367 p->max_state = max_state;
368 return 0;
369}
370
371void cpufreq_task_times_exit(struct task_struct *p)
372{
373 unsigned long flags;
374 void *temp;
375
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800376 if (!p->time_in_state)
377 return;
378
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800379 spin_lock_irqsave(&task_time_in_state_lock, flags);
380 temp = p->time_in_state;
381 p->time_in_state = NULL;
382 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
383 kfree(temp);
384}
385
386int proc_time_in_state_show(struct seq_file *m, struct pid_namespace *ns,
387 struct pid *pid, struct task_struct *p)
388{
389 unsigned int cpu, i;
390 cputime_t cputime;
391 unsigned long flags;
392 struct cpu_freqs *freqs;
393 struct cpu_freqs *last_freqs = NULL;
394
395 spin_lock_irqsave(&task_time_in_state_lock, flags);
396 for_each_possible_cpu(cpu) {
397 freqs = all_freqs[cpu];
398 if (!freqs || freqs == last_freqs)
399 continue;
400 last_freqs = freqs;
401
402 seq_printf(m, "cpu%u\n", cpu);
403 for (i = 0; i < freqs->max_state; i++) {
404 if (freqs->freq_table[i] == CPUFREQ_ENTRY_INVALID)
405 continue;
406 cputime = 0;
407 if (freqs->offset + i < p->max_state &&
408 p->time_in_state)
409 cputime = p->time_in_state[freqs->offset + i];
410 seq_printf(m, "%u %lu\n", freqs->freq_table[i],
411 (unsigned long)cputime_to_clock_t(cputime));
412 }
413 }
414 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
415 return 0;
416}
417
418void cpufreq_acct_update_power(struct task_struct *p, cputime_t cputime)
419{
420 unsigned long flags;
421 unsigned int state;
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700422 unsigned int active_cpu_cnt = 0;
423 unsigned int policy_cpu_cnt = 0;
424 unsigned int policy_first_cpu;
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800425 struct uid_entry *uid_entry;
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800426 struct cpu_freqs *freqs = all_freqs[task_cpu(p)];
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700427 struct cpufreq_policy *policy;
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800428 uid_t uid = from_kuid_munged(current_user_ns(), task_uid(p));
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700429 int cpu = 0;
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800430
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700431 if (!freqs || is_idle_task(p) || p->flags & PF_EXITING)
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800432 return;
433
434 state = freqs->offset + READ_ONCE(freqs->last_index);
435
436 spin_lock_irqsave(&task_time_in_state_lock, flags);
437 if ((state < p->max_state || !cpufreq_task_times_realloc_locked(p)) &&
438 p->time_in_state)
439 p->time_in_state[state] += cputime;
440 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800441
442 spin_lock_irqsave(&uid_lock, flags);
443 uid_entry = find_or_register_uid_locked(uid);
444 if (uid_entry && state < uid_entry->max_state)
445 uid_entry->time_in_state[state] += cputime;
446 spin_unlock_irqrestore(&uid_lock, flags);
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700447
448 rcu_read_lock();
449 uid_entry = find_uid_entry_rcu(uid);
450 if (!uid_entry) {
451 rcu_read_unlock();
452 return;
453 }
454
455 for_each_possible_cpu(cpu)
456 if (!idle_cpu(cpu))
457 ++active_cpu_cnt;
458
459 atomic64_add(cputime,
460 &uid_entry->concurrent_times->active[active_cpu_cnt - 1]);
461
462 policy = cpufreq_cpu_get(task_cpu(p));
463 if (!policy) {
464 /*
465 * This CPU may have just come up and not have a cpufreq policy
466 * yet.
467 */
468 rcu_read_unlock();
469 return;
470 }
471
472 for_each_cpu(cpu, policy->related_cpus)
473 if (!idle_cpu(cpu))
474 ++policy_cpu_cnt;
475
476 policy_first_cpu = cpumask_first(policy->related_cpus);
477 cpufreq_cpu_put(policy);
478
479 atomic64_add(cputime,
480 &uid_entry->concurrent_times->policy[policy_first_cpu +
481 policy_cpu_cnt - 1]);
482 rcu_read_unlock();
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800483}
484
485void cpufreq_times_create_policy(struct cpufreq_policy *policy)
486{
487 int cpu, index;
488 unsigned int count = 0;
489 struct cpufreq_frequency_table *pos, *table;
490 struct cpu_freqs *freqs;
491 void *tmp;
492
493 if (all_freqs[policy->cpu])
494 return;
495
496 table = policy->freq_table;
497 if (!table)
498 return;
499
500 cpufreq_for_each_entry(pos, table)
501 count++;
502
503 tmp = kzalloc(sizeof(*freqs) + sizeof(freqs->freq_table[0]) * count,
504 GFP_KERNEL);
505 if (!tmp)
506 return;
507
508 freqs = tmp;
509 freqs->max_state = count;
510
511 index = cpufreq_frequency_table_get_index(policy, policy->cur);
512 if (index >= 0)
513 WRITE_ONCE(freqs->last_index, index);
514
515 cpufreq_for_each_entry(pos, table)
516 freqs->freq_table[pos - table] = pos->frequency;
517
518 freqs->offset = next_offset;
519 WRITE_ONCE(next_offset, freqs->offset + count);
520 for_each_cpu(cpu, policy->related_cpus)
521 all_freqs[cpu] = freqs;
522}
523
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700524static void uid_entry_reclaim(struct rcu_head *rcu)
525{
526 struct uid_entry *uid_entry = container_of(rcu, struct uid_entry, rcu);
527
528 kfree(uid_entry->concurrent_times);
529 kfree(uid_entry);
530}
531
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800532void cpufreq_task_times_remove_uids(uid_t uid_start, uid_t uid_end)
533{
534 struct uid_entry *uid_entry;
535 struct hlist_node *tmp;
536 unsigned long flags;
537
538 spin_lock_irqsave(&uid_lock, flags);
539
540 for (; uid_start <= uid_end; uid_start++) {
541 hash_for_each_possible_safe(uid_hash_table, uid_entry, tmp,
542 hash, uid_start) {
543 if (uid_start == uid_entry->uid) {
544 hash_del_rcu(&uid_entry->hash);
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700545 call_rcu(&uid_entry->rcu, uid_entry_reclaim);
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800546 }
547 }
548 }
549
550 spin_unlock_irqrestore(&uid_lock, flags);
551}
552
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800553void cpufreq_times_record_transition(struct cpufreq_freqs *freq)
554{
555 int index;
556 struct cpu_freqs *freqs = all_freqs[freq->cpu];
557 struct cpufreq_policy *policy;
558
559 if (!freqs)
560 return;
561
562 policy = cpufreq_cpu_get(freq->cpu);
563 if (!policy)
564 return;
565
566 index = cpufreq_frequency_table_get_index(policy, freq->new);
567 if (index >= 0)
568 WRITE_ONCE(freqs->last_index, index);
569
570 cpufreq_cpu_put(policy);
571}
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800572
573static const struct seq_operations uid_time_in_state_seq_ops = {
574 .start = uid_seq_start,
575 .next = uid_seq_next,
576 .stop = uid_seq_stop,
577 .show = uid_time_in_state_seq_show,
578};
579
580static int uid_time_in_state_open(struct inode *inode, struct file *file)
581{
582 return seq_open(file, &uid_time_in_state_seq_ops);
583}
584
Connor O'Brienf17f4fb2018-01-22 18:28:08 -0800585int single_uid_time_in_state_open(struct inode *inode, struct file *file)
586{
587 return single_open(file, single_uid_time_in_state_show,
588 &(inode->i_uid));
589}
590
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800591static const struct file_operations uid_time_in_state_fops = {
592 .open = uid_time_in_state_open,
593 .read = seq_read,
594 .llseek = seq_lseek,
595 .release = seq_release,
596};
597
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700598static const struct seq_operations concurrent_active_time_seq_ops = {
599 .start = uid_seq_start,
600 .next = uid_seq_next,
601 .stop = uid_seq_stop,
602 .show = concurrent_active_time_seq_show,
603};
604
605static int concurrent_active_time_open(struct inode *inode, struct file *file)
606{
607 return seq_open(file, &concurrent_active_time_seq_ops);
608}
609
610static const struct file_operations concurrent_active_time_fops = {
611 .open = concurrent_active_time_open,
612 .read = seq_read,
613 .llseek = seq_lseek,
614 .release = seq_release,
615};
616
617static const struct seq_operations concurrent_policy_time_seq_ops = {
618 .start = uid_seq_start,
619 .next = uid_seq_next,
620 .stop = uid_seq_stop,
621 .show = concurrent_policy_time_seq_show,
622};
623
624static int concurrent_policy_time_open(struct inode *inode, struct file *file)
625{
626 return seq_open(file, &concurrent_policy_time_seq_ops);
627}
628
629static const struct file_operations concurrent_policy_time_fops = {
630 .open = concurrent_policy_time_open,
631 .read = seq_read,
632 .llseek = seq_lseek,
633 .release = seq_release,
634};
635
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800636static int __init cpufreq_times_init(void)
637{
638 proc_create_data("uid_time_in_state", 0444, NULL,
639 &uid_time_in_state_fops, NULL);
640
Connor O'Brien605e2ec2018-10-05 19:20:54 -0700641 proc_create_data("uid_concurrent_active_time", 0444, NULL,
642 &concurrent_active_time_fops, NULL);
643
644 proc_create_data("uid_concurrent_policy_time", 0444, NULL,
645 &concurrent_policy_time_fops, NULL);
646
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800647 return 0;
648}
649
650early_initcall(cpufreq_times_init);