blob: 6254f45ca9077cb4522316d86235d4025fc83a2d [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
35struct uid_entry {
36 uid_t uid;
37 unsigned int max_state;
38 struct hlist_node hash;
39 struct rcu_head rcu;
40 u64 time_in_state[0];
41};
Connor O'Brien6e7b83d2018-01-31 18:11:57 -080042
43/**
44 * struct cpu_freqs - per-cpu frequency information
45 * @offset: start of these freqs' stats in task time_in_state array
46 * @max_state: number of entries in freq_table
47 * @last_index: index in freq_table of last frequency switched to
48 * @freq_table: list of available frequencies
49 */
50struct cpu_freqs {
51 unsigned int offset;
52 unsigned int max_state;
53 unsigned int last_index;
54 unsigned int freq_table[0];
55};
56
57static struct cpu_freqs *all_freqs[NR_CPUS];
58
59static unsigned int next_offset;
60
Connor O'Brienf17f4fb2018-01-22 18:28:08 -080061
62/* Caller must hold rcu_read_lock() */
63static struct uid_entry *find_uid_entry_rcu(uid_t uid)
64{
65 struct uid_entry *uid_entry;
66
67 hash_for_each_possible_rcu(uid_hash_table, uid_entry, hash, uid) {
68 if (uid_entry->uid == uid)
69 return uid_entry;
70 }
71 return NULL;
72}
73
Connor O'Brienfcb3db12018-02-06 13:30:27 -080074/* Caller must hold uid lock */
75static struct uid_entry *find_uid_entry_locked(uid_t uid)
76{
77 struct uid_entry *uid_entry;
78
79 hash_for_each_possible(uid_hash_table, uid_entry, hash, uid) {
80 if (uid_entry->uid == uid)
81 return uid_entry;
82 }
83 return NULL;
84}
85
86/* Caller must hold uid lock */
87static struct uid_entry *find_or_register_uid_locked(uid_t uid)
88{
89 struct uid_entry *uid_entry, *temp;
90 unsigned int max_state = READ_ONCE(next_offset);
91 size_t alloc_size = sizeof(*uid_entry) + max_state *
92 sizeof(uid_entry->time_in_state[0]);
93
94 uid_entry = find_uid_entry_locked(uid);
95 if (uid_entry) {
96 if (uid_entry->max_state == max_state)
97 return uid_entry;
98 /* uid_entry->time_in_state is too small to track all freqs, so
99 * expand it.
100 */
101 temp = __krealloc(uid_entry, alloc_size, GFP_ATOMIC);
102 if (!temp)
103 return uid_entry;
104 temp->max_state = max_state;
105 memset(temp->time_in_state + uid_entry->max_state, 0,
106 (max_state - uid_entry->max_state) *
107 sizeof(uid_entry->time_in_state[0]));
108 if (temp != uid_entry) {
109 hlist_replace_rcu(&uid_entry->hash, &temp->hash);
110 kfree_rcu(uid_entry, rcu);
111 }
112 return temp;
113 }
114
115 uid_entry = kzalloc(alloc_size, GFP_ATOMIC);
116 if (!uid_entry)
117 return NULL;
118
119 uid_entry->uid = uid;
120 uid_entry->max_state = max_state;
121
122 hash_add_rcu(uid_hash_table, &uid_entry->hash, uid);
123
124 return uid_entry;
125}
126
127static bool freq_index_invalid(unsigned int index)
128{
129 unsigned int cpu;
130 struct cpu_freqs *freqs;
131
132 for_each_possible_cpu(cpu) {
133 freqs = all_freqs[cpu];
134 if (!freqs || index < freqs->offset ||
135 freqs->offset + freqs->max_state <= index)
136 continue;
137 return freqs->freq_table[index - freqs->offset] ==
138 CPUFREQ_ENTRY_INVALID;
139 }
140 return true;
141}
142
Connor O'Brienf17f4fb2018-01-22 18:28:08 -0800143static int single_uid_time_in_state_show(struct seq_file *m, void *ptr)
144{
145 struct uid_entry *uid_entry;
146 unsigned int i;
147 u64 time;
148 uid_t uid = from_kuid_munged(current_user_ns(), *(kuid_t *)m->private);
149
150 if (uid == overflowuid)
151 return -EINVAL;
152
153 rcu_read_lock();
154
155 uid_entry = find_uid_entry_rcu(uid);
156 if (!uid_entry) {
157 rcu_read_unlock();
158 return 0;
159 }
160
161 for (i = 0; i < uid_entry->max_state; ++i) {
162 if (freq_index_invalid(i))
163 continue;
164 time = cputime_to_clock_t(uid_entry->time_in_state[i]);
165 seq_write(m, &time, sizeof(time));
166 }
167
168 rcu_read_unlock();
169
170 return 0;
171}
172
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800173static void *uid_seq_start(struct seq_file *seq, loff_t *pos)
174{
175 if (*pos >= HASH_SIZE(uid_hash_table))
176 return NULL;
177
178 return &uid_hash_table[*pos];
179}
180
181static void *uid_seq_next(struct seq_file *seq, void *v, loff_t *pos)
182{
183 (*pos)++;
184
185 if (*pos >= HASH_SIZE(uid_hash_table))
186 return NULL;
187
188 return &uid_hash_table[*pos];
189}
190
191static void uid_seq_stop(struct seq_file *seq, void *v) { }
192
193static int uid_time_in_state_seq_show(struct seq_file *m, void *v)
194{
195 struct uid_entry *uid_entry;
196 struct cpu_freqs *freqs, *last_freqs = NULL;
197 int i, cpu;
198
199 if (v == uid_hash_table) {
200 seq_puts(m, "uid:");
201 for_each_possible_cpu(cpu) {
202 freqs = all_freqs[cpu];
203 if (!freqs || freqs == last_freqs)
204 continue;
205 last_freqs = freqs;
Connor O'Brien7cd95612018-04-03 16:05:37 -0700206 for (i = 0; i < freqs->max_state; i++) {
207 if (freqs->freq_table[i] ==
208 CPUFREQ_ENTRY_INVALID)
209 continue;
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800210 seq_printf(m, " %d", freqs->freq_table[i]);
Connor O'Brien7cd95612018-04-03 16:05:37 -0700211 }
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800212 }
213 seq_putc(m, '\n');
214 }
215
216 rcu_read_lock();
217
218 hlist_for_each_entry_rcu(uid_entry, (struct hlist_head *)v, hash) {
219 if (uid_entry->max_state)
220 seq_printf(m, "%d:", uid_entry->uid);
221 for (i = 0; i < uid_entry->max_state; ++i) {
222 if (freq_index_invalid(i))
223 continue;
224 seq_printf(m, " %lu", (unsigned long)cputime_to_clock_t(
225 uid_entry->time_in_state[i]));
226 }
227 if (uid_entry->max_state)
228 seq_putc(m, '\n');
229 }
230
231 rcu_read_unlock();
232 return 0;
233}
234
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800235void cpufreq_task_times_init(struct task_struct *p)
236{
237 void *temp;
238 unsigned long flags;
239 unsigned int max_state;
240
241 spin_lock_irqsave(&task_time_in_state_lock, flags);
242 p->time_in_state = NULL;
243 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
244 p->max_state = 0;
245
246 max_state = READ_ONCE(next_offset);
247
248 /* We use one array to avoid multiple allocs per task */
249 temp = kcalloc(max_state, sizeof(p->time_in_state[0]), GFP_ATOMIC);
250 if (!temp)
251 return;
252
253 spin_lock_irqsave(&task_time_in_state_lock, flags);
254 p->time_in_state = temp;
255 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
256 p->max_state = max_state;
257}
258
259/* Caller must hold task_time_in_state_lock */
260static int cpufreq_task_times_realloc_locked(struct task_struct *p)
261{
262 void *temp;
263 unsigned int max_state = READ_ONCE(next_offset);
264
265 temp = krealloc(p->time_in_state, max_state * sizeof(u64), GFP_ATOMIC);
266 if (!temp)
267 return -ENOMEM;
268 p->time_in_state = temp;
269 memset(p->time_in_state + p->max_state, 0,
270 (max_state - p->max_state) * sizeof(u64));
271 p->max_state = max_state;
272 return 0;
273}
274
275void cpufreq_task_times_exit(struct task_struct *p)
276{
277 unsigned long flags;
278 void *temp;
279
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800280 if (!p->time_in_state)
281 return;
282
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800283 spin_lock_irqsave(&task_time_in_state_lock, flags);
284 temp = p->time_in_state;
285 p->time_in_state = NULL;
286 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
287 kfree(temp);
288}
289
290int proc_time_in_state_show(struct seq_file *m, struct pid_namespace *ns,
291 struct pid *pid, struct task_struct *p)
292{
293 unsigned int cpu, i;
294 cputime_t cputime;
295 unsigned long flags;
296 struct cpu_freqs *freqs;
297 struct cpu_freqs *last_freqs = NULL;
298
299 spin_lock_irqsave(&task_time_in_state_lock, flags);
300 for_each_possible_cpu(cpu) {
301 freqs = all_freqs[cpu];
302 if (!freqs || freqs == last_freqs)
303 continue;
304 last_freqs = freqs;
305
306 seq_printf(m, "cpu%u\n", cpu);
307 for (i = 0; i < freqs->max_state; i++) {
308 if (freqs->freq_table[i] == CPUFREQ_ENTRY_INVALID)
309 continue;
310 cputime = 0;
311 if (freqs->offset + i < p->max_state &&
312 p->time_in_state)
313 cputime = p->time_in_state[freqs->offset + i];
314 seq_printf(m, "%u %lu\n", freqs->freq_table[i],
315 (unsigned long)cputime_to_clock_t(cputime));
316 }
317 }
318 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
319 return 0;
320}
321
322void cpufreq_acct_update_power(struct task_struct *p, cputime_t cputime)
323{
324 unsigned long flags;
325 unsigned int state;
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800326 struct uid_entry *uid_entry;
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800327 struct cpu_freqs *freqs = all_freqs[task_cpu(p)];
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800328 uid_t uid = from_kuid_munged(current_user_ns(), task_uid(p));
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800329
330 if (!freqs || p->flags & PF_EXITING)
331 return;
332
333 state = freqs->offset + READ_ONCE(freqs->last_index);
334
335 spin_lock_irqsave(&task_time_in_state_lock, flags);
336 if ((state < p->max_state || !cpufreq_task_times_realloc_locked(p)) &&
337 p->time_in_state)
338 p->time_in_state[state] += cputime;
339 spin_unlock_irqrestore(&task_time_in_state_lock, flags);
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800340
341 spin_lock_irqsave(&uid_lock, flags);
342 uid_entry = find_or_register_uid_locked(uid);
343 if (uid_entry && state < uid_entry->max_state)
344 uid_entry->time_in_state[state] += cputime;
345 spin_unlock_irqrestore(&uid_lock, flags);
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800346}
347
348void cpufreq_times_create_policy(struct cpufreq_policy *policy)
349{
350 int cpu, index;
351 unsigned int count = 0;
352 struct cpufreq_frequency_table *pos, *table;
353 struct cpu_freqs *freqs;
354 void *tmp;
355
356 if (all_freqs[policy->cpu])
357 return;
358
359 table = policy->freq_table;
360 if (!table)
361 return;
362
363 cpufreq_for_each_entry(pos, table)
364 count++;
365
366 tmp = kzalloc(sizeof(*freqs) + sizeof(freqs->freq_table[0]) * count,
367 GFP_KERNEL);
368 if (!tmp)
369 return;
370
371 freqs = tmp;
372 freqs->max_state = count;
373
374 index = cpufreq_frequency_table_get_index(policy, policy->cur);
375 if (index >= 0)
376 WRITE_ONCE(freqs->last_index, index);
377
378 cpufreq_for_each_entry(pos, table)
379 freqs->freq_table[pos - table] = pos->frequency;
380
381 freqs->offset = next_offset;
382 WRITE_ONCE(next_offset, freqs->offset + count);
383 for_each_cpu(cpu, policy->related_cpus)
384 all_freqs[cpu] = freqs;
385}
386
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800387void cpufreq_task_times_remove_uids(uid_t uid_start, uid_t uid_end)
388{
389 struct uid_entry *uid_entry;
390 struct hlist_node *tmp;
391 unsigned long flags;
392
393 spin_lock_irqsave(&uid_lock, flags);
394
395 for (; uid_start <= uid_end; uid_start++) {
396 hash_for_each_possible_safe(uid_hash_table, uid_entry, tmp,
397 hash, uid_start) {
398 if (uid_start == uid_entry->uid) {
399 hash_del_rcu(&uid_entry->hash);
400 kfree_rcu(uid_entry, rcu);
401 }
402 }
403 }
404
405 spin_unlock_irqrestore(&uid_lock, flags);
406}
407
Connor O'Brien6e7b83d2018-01-31 18:11:57 -0800408void cpufreq_times_record_transition(struct cpufreq_freqs *freq)
409{
410 int index;
411 struct cpu_freqs *freqs = all_freqs[freq->cpu];
412 struct cpufreq_policy *policy;
413
414 if (!freqs)
415 return;
416
417 policy = cpufreq_cpu_get(freq->cpu);
418 if (!policy)
419 return;
420
421 index = cpufreq_frequency_table_get_index(policy, freq->new);
422 if (index >= 0)
423 WRITE_ONCE(freqs->last_index, index);
424
425 cpufreq_cpu_put(policy);
426}
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800427
428static const struct seq_operations uid_time_in_state_seq_ops = {
429 .start = uid_seq_start,
430 .next = uid_seq_next,
431 .stop = uid_seq_stop,
432 .show = uid_time_in_state_seq_show,
433};
434
435static int uid_time_in_state_open(struct inode *inode, struct file *file)
436{
437 return seq_open(file, &uid_time_in_state_seq_ops);
438}
439
Connor O'Brienf17f4fb2018-01-22 18:28:08 -0800440int single_uid_time_in_state_open(struct inode *inode, struct file *file)
441{
442 return single_open(file, single_uid_time_in_state_show,
443 &(inode->i_uid));
444}
445
Connor O'Brienfcb3db12018-02-06 13:30:27 -0800446static const struct file_operations uid_time_in_state_fops = {
447 .open = uid_time_in_state_open,
448 .read = seq_read,
449 .llseek = seq_lseek,
450 .release = seq_release,
451};
452
453static int __init cpufreq_times_init(void)
454{
455 proc_create_data("uid_time_in_state", 0444, NULL,
456 &uid_time_in_state_fops, NULL);
457
458 return 0;
459}
460
461early_initcall(cpufreq_times_init);