1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <linux/export.h>
18#include <linux/profile.h>
19#include <linux/bootmem.h>
20#include <linux/notifier.h>
21#include <linux/mm.h>
22#include <linux/cpumask.h>
23#include <linux/cpu.h>
24#include <linux/highmem.h>
25#include <linux/mutex.h>
26#include <linux/slab.h>
27#include <linux/vmalloc.h>
28#include <linux/sched.h>
29#include <asm/sections.h>
30#include <asm/irq_regs.h>
31#include <asm/ptrace.h>
32
33struct profile_hit {
34 u32 pc, hits;
35};
36#define PROFILE_GRPSHIFT 3
37#define PROFILE_GRPSZ (1 << PROFILE_GRPSHIFT)
38#define NR_PROFILE_HIT (PAGE_SIZE/sizeof(struct profile_hit))
39#define NR_PROFILE_GRP (NR_PROFILE_HIT/PROFILE_GRPSZ)
40
41static atomic_t *prof_buffer;
42static unsigned long prof_len, prof_shift;
43
44int prof_on __read_mostly;
45EXPORT_SYMBOL_GPL(prof_on);
46
47static cpumask_var_t prof_cpu_mask;
48#ifdef CONFIG_SMP
49static DEFINE_PER_CPU(struct profile_hit *[2], cpu_profile_hits);
50static DEFINE_PER_CPU(int, cpu_profile_flip);
51static DEFINE_MUTEX(profile_flip_mutex);
52#endif
53
54int profile_setup(char *str)
55{
56 static char schedstr[] = "schedule";
57 static char sleepstr[] = "sleep";
58 static char kvmstr[] = "kvm";
59 int par;
60
61 if (!strncmp(str, sleepstr, strlen(sleepstr))) {
62#ifdef CONFIG_SCHEDSTATS
63 force_schedstat_enabled();
64 prof_on = SLEEP_PROFILING;
65 if (str[strlen(sleepstr)] == ',')
66 str += strlen(sleepstr) + 1;
67 if (get_option(&str, &par))
68 prof_shift = par;
69 printk(KERN_INFO
70 "kernel sleep profiling enabled (shift: %ld)\n",
71 prof_shift);
72#else
73 printk(KERN_WARNING
74 "kernel sleep profiling requires CONFIG_SCHEDSTATS\n");
75#endif
76 } else if (!strncmp(str, schedstr, strlen(schedstr))) {
77 prof_on = SCHED_PROFILING;
78 if (str[strlen(schedstr)] == ',')
79 str += strlen(schedstr) + 1;
80 if (get_option(&str, &par))
81 prof_shift = par;
82 printk(KERN_INFO
83 "kernel schedule profiling enabled (shift: %ld)\n",
84 prof_shift);
85 } else if (!strncmp(str, kvmstr, strlen(kvmstr))) {
86 prof_on = KVM_PROFILING;
87 if (str[strlen(kvmstr)] == ',')
88 str += strlen(kvmstr) + 1;
89 if (get_option(&str, &par))
90 prof_shift = par;
91 printk(KERN_INFO
92 "kernel KVM profiling enabled (shift: %ld)\n",
93 prof_shift);
94 } else if (get_option(&str, &par)) {
95 prof_shift = par;
96 prof_on = CPU_PROFILING;
97 printk(KERN_INFO "kernel profiling enabled (shift: %ld)\n",
98 prof_shift);
99 }
100 return 1;
101}
102__setup("profile=", profile_setup);
103
104
105int __ref profile_init(void)
106{
107 int buffer_bytes;
108 if (!prof_on)
109 return 0;
110
111
112 prof_len = (_etext - _stext) >> prof_shift;
113 buffer_bytes = prof_len*sizeof(atomic_t);
114
115 if (!alloc_cpumask_var(&prof_cpu_mask, GFP_KERNEL))
116 return -ENOMEM;
117
118 cpumask_copy(prof_cpu_mask, cpu_possible_mask);
119
120 prof_buffer = kzalloc(buffer_bytes, GFP_KERNEL|__GFP_NOWARN);
121 if (prof_buffer)
122 return 0;
123
124 prof_buffer = alloc_pages_exact(buffer_bytes,
125 GFP_KERNEL|__GFP_ZERO|__GFP_NOWARN);
126 if (prof_buffer)
127 return 0;
128
129 prof_buffer = vzalloc(buffer_bytes);
130 if (prof_buffer)
131 return 0;
132
133 free_cpumask_var(prof_cpu_mask);
134 return -ENOMEM;
135}
136
137
138
139static BLOCKING_NOTIFIER_HEAD(task_exit_notifier);
140static ATOMIC_NOTIFIER_HEAD(task_free_notifier);
141static BLOCKING_NOTIFIER_HEAD(munmap_notifier);
142
143void profile_task_exit(struct task_struct *task)
144{
145 blocking_notifier_call_chain(&task_exit_notifier, 0, task);
146}
147
148int profile_handoff_task(struct task_struct *task)
149{
150 int ret;
151 ret = atomic_notifier_call_chain(&task_free_notifier, 0, task);
152 return (ret == NOTIFY_OK) ? 1 : 0;
153}
154
155void profile_munmap(unsigned long addr)
156{
157 blocking_notifier_call_chain(&munmap_notifier, 0, (void *)addr);
158}
159
160int task_handoff_register(struct notifier_block *n)
161{
162 return atomic_notifier_chain_register(&task_free_notifier, n);
163}
164EXPORT_SYMBOL_GPL(task_handoff_register);
165
166int task_handoff_unregister(struct notifier_block *n)
167{
168 return atomic_notifier_chain_unregister(&task_free_notifier, n);
169}
170EXPORT_SYMBOL_GPL(task_handoff_unregister);
171
172int profile_event_register(enum profile_type type, struct notifier_block *n)
173{
174 int err = -EINVAL;
175
176 switch (type) {
177 case PROFILE_TASK_EXIT:
178 err = blocking_notifier_chain_register(
179 &task_exit_notifier, n);
180 break;
181 case PROFILE_MUNMAP:
182 err = blocking_notifier_chain_register(
183 &munmap_notifier, n);
184 break;
185 }
186
187 return err;
188}
189EXPORT_SYMBOL_GPL(profile_event_register);
190
191int profile_event_unregister(enum profile_type type, struct notifier_block *n)
192{
193 int err = -EINVAL;
194
195 switch (type) {
196 case PROFILE_TASK_EXIT:
197 err = blocking_notifier_chain_unregister(
198 &task_exit_notifier, n);
199 break;
200 case PROFILE_MUNMAP:
201 err = blocking_notifier_chain_unregister(
202 &munmap_notifier, n);
203 break;
204 }
205
206 return err;
207}
208EXPORT_SYMBOL_GPL(profile_event_unregister);
209
210#ifdef CONFIG_SMP
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242static void __profile_flip_buffers(void *unused)
243{
244 int cpu = smp_processor_id();
245
246 per_cpu(cpu_profile_flip, cpu) = !per_cpu(cpu_profile_flip, cpu);
247}
248
249static void profile_flip_buffers(void)
250{
251 int i, j, cpu;
252
253 mutex_lock(&profile_flip_mutex);
254 j = per_cpu(cpu_profile_flip, get_cpu());
255 put_cpu();
256 on_each_cpu(__profile_flip_buffers, NULL, 1);
257 for_each_online_cpu(cpu) {
258 struct profile_hit *hits = per_cpu(cpu_profile_hits, cpu)[j];
259 for (i = 0; i < NR_PROFILE_HIT; ++i) {
260 if (!hits[i].hits) {
261 if (hits[i].pc)
262 hits[i].pc = 0;
263 continue;
264 }
265 atomic_add(hits[i].hits, &prof_buffer[hits[i].pc]);
266 hits[i].hits = hits[i].pc = 0;
267 }
268 }
269 mutex_unlock(&profile_flip_mutex);
270}
271
272static void profile_discard_flip_buffers(void)
273{
274 int i, cpu;
275
276 mutex_lock(&profile_flip_mutex);
277 i = per_cpu(cpu_profile_flip, get_cpu());
278 put_cpu();
279 on_each_cpu(__profile_flip_buffers, NULL, 1);
280 for_each_online_cpu(cpu) {
281 struct profile_hit *hits = per_cpu(cpu_profile_hits, cpu)[i];
282 memset(hits, 0, NR_PROFILE_HIT*sizeof(struct profile_hit));
283 }
284 mutex_unlock(&profile_flip_mutex);
285}
286
287static void do_profile_hits(int type, void *__pc, unsigned int nr_hits)
288{
289 unsigned long primary, secondary, flags, pc = (unsigned long)__pc;
290 int i, j, cpu;
291 struct profile_hit *hits;
292
293 pc = min((pc - (unsigned long)_stext) >> prof_shift, prof_len - 1);
294 i = primary = (pc & (NR_PROFILE_GRP - 1)) << PROFILE_GRPSHIFT;
295 secondary = (~(pc << 1) & (NR_PROFILE_GRP - 1)) << PROFILE_GRPSHIFT;
296 cpu = get_cpu();
297 hits = per_cpu(cpu_profile_hits, cpu)[per_cpu(cpu_profile_flip, cpu)];
298 if (!hits) {
299 put_cpu();
300 return;
301 }
302
303
304
305
306
307 local_irq_save(flags);
308 do {
309 for (j = 0; j < PROFILE_GRPSZ; ++j) {
310 if (hits[i + j].pc == pc) {
311 hits[i + j].hits += nr_hits;
312 goto out;
313 } else if (!hits[i + j].hits) {
314 hits[i + j].pc = pc;
315 hits[i + j].hits = nr_hits;
316 goto out;
317 }
318 }
319 i = (i + secondary) & (NR_PROFILE_HIT - 1);
320 } while (i != primary);
321
322
323
324
325
326 atomic_add(nr_hits, &prof_buffer[pc]);
327 for (i = 0; i < NR_PROFILE_HIT; ++i) {
328 atomic_add(hits[i].hits, &prof_buffer[hits[i].pc]);
329 hits[i].pc = hits[i].hits = 0;
330 }
331out:
332 local_irq_restore(flags);
333 put_cpu();
334}
335
336static int profile_cpu_callback(struct notifier_block *info,
337 unsigned long action, void *__cpu)
338{
339 int node, cpu = (unsigned long)__cpu;
340 struct page *page;
341
342 switch (action) {
343 case CPU_UP_PREPARE:
344 case CPU_UP_PREPARE_FROZEN:
345 node = cpu_to_mem(cpu);
346 per_cpu(cpu_profile_flip, cpu) = 0;
347 if (!per_cpu(cpu_profile_hits, cpu)[1]) {
348 page = alloc_pages_exact_node(node,
349 GFP_KERNEL | __GFP_ZERO,
350 0);
351 if (!page)
352 return notifier_from_errno(-ENOMEM);
353 per_cpu(cpu_profile_hits, cpu)[1] = page_address(page);
354 }
355 if (!per_cpu(cpu_profile_hits, cpu)[0]) {
356 page = alloc_pages_exact_node(node,
357 GFP_KERNEL | __GFP_ZERO,
358 0);
359 if (!page)
360 goto out_free;
361 per_cpu(cpu_profile_hits, cpu)[0] = page_address(page);
362 }
363 break;
364out_free:
365 page = virt_to_page(per_cpu(cpu_profile_hits, cpu)[1]);
366 per_cpu(cpu_profile_hits, cpu)[1] = NULL;
367 __free_page(page);
368 return notifier_from_errno(-ENOMEM);
369 case CPU_ONLINE:
370 case CPU_ONLINE_FROZEN:
371 if (prof_cpu_mask != NULL)
372 cpumask_set_cpu(cpu, prof_cpu_mask);
373 break;
374 case CPU_UP_CANCELED:
375 case CPU_UP_CANCELED_FROZEN:
376 case CPU_DEAD:
377 case CPU_DEAD_FROZEN:
378 if (prof_cpu_mask != NULL)
379 cpumask_clear_cpu(cpu, prof_cpu_mask);
380 if (per_cpu(cpu_profile_hits, cpu)[0]) {
381 page = virt_to_page(per_cpu(cpu_profile_hits, cpu)[0]);
382 per_cpu(cpu_profile_hits, cpu)[0] = NULL;
383 __free_page(page);
384 }
385 if (per_cpu(cpu_profile_hits, cpu)[1]) {
386 page = virt_to_page(per_cpu(cpu_profile_hits, cpu)[1]);
387 per_cpu(cpu_profile_hits, cpu)[1] = NULL;
388 __free_page(page);
389 }
390 break;
391 }
392 return NOTIFY_OK;
393}
394#else
395#define profile_flip_buffers() do { } while (0)
396#define profile_discard_flip_buffers() do { } while (0)
397#define profile_cpu_callback NULL
398
399static void do_profile_hits(int type, void *__pc, unsigned int nr_hits)
400{
401 unsigned long pc;
402 pc = ((unsigned long)__pc - (unsigned long)_stext) >> prof_shift;
403 atomic_add(nr_hits, &prof_buffer[min(pc, prof_len - 1)]);
404}
405#endif
406
407void profile_hits(int type, void *__pc, unsigned int nr_hits)
408{
409 if (prof_on != type || !prof_buffer)
410 return;
411 do_profile_hits(type, __pc, nr_hits);
412}
413EXPORT_SYMBOL_GPL(profile_hits);
414
415void profile_tick(int type)
416{
417 struct pt_regs *regs = get_irq_regs();
418
419 if (!user_mode(regs) && prof_cpu_mask != NULL &&
420 cpumask_test_cpu(smp_processor_id(), prof_cpu_mask))
421 profile_hit(type, (void *)profile_pc(regs));
422}
423
424#ifdef CONFIG_PROC_FS
425#include <linux/proc_fs.h>
426#include <linux/seq_file.h>
427#include <asm/uaccess.h>
428
429static int prof_cpu_mask_proc_show(struct seq_file *m, void *v)
430{
431 seq_cpumask(m, prof_cpu_mask);
432 seq_putc(m, '\n');
433 return 0;
434}
435
436static int prof_cpu_mask_proc_open(struct inode *inode, struct file *file)
437{
438 return single_open(file, prof_cpu_mask_proc_show, NULL);
439}
440
441static ssize_t prof_cpu_mask_proc_write(struct file *file,
442 const char __user *buffer, size_t count, loff_t *pos)
443{
444 cpumask_var_t new_value;
445 int err;
446
447 if (!alloc_cpumask_var(&new_value, GFP_KERNEL))
448 return -ENOMEM;
449
450 err = cpumask_parse_user(buffer, count, new_value);
451 if (!err) {
452 cpumask_copy(prof_cpu_mask, new_value);
453 err = count;
454 }
455 free_cpumask_var(new_value);
456 return err;
457}
458
459static const struct file_operations prof_cpu_mask_proc_fops = {
460 .open = prof_cpu_mask_proc_open,
461 .read = seq_read,
462 .llseek = seq_lseek,
463 .release = single_release,
464 .write = prof_cpu_mask_proc_write,
465};
466
467void create_prof_cpu_mask(void)
468{
469
470 proc_create("irq/prof_cpu_mask", 0600, NULL, &prof_cpu_mask_proc_fops);
471}
472
473
474
475
476
477
478
479static ssize_t
480read_profile(struct file *file, char __user *buf, size_t count, loff_t *ppos)
481{
482 unsigned long p = *ppos;
483 ssize_t read;
484 char *pnt;
485 unsigned int sample_step = 1 << prof_shift;
486
487 profile_flip_buffers();
488 if (p >= (prof_len+1)*sizeof(unsigned int))
489 return 0;
490 if (count > (prof_len+1)*sizeof(unsigned int) - p)
491 count = (prof_len+1)*sizeof(unsigned int) - p;
492 read = 0;
493
494 while (p < sizeof(unsigned int) && count > 0) {
495 if (put_user(*((char *)(&sample_step)+p), buf))
496 return -EFAULT;
497 buf++; p++; count--; read++;
498 }
499 pnt = (char *)prof_buffer + p - sizeof(atomic_t);
500 if (copy_to_user(buf, (void *)pnt, count))
501 return -EFAULT;
502 read += count;
503 *ppos += read;
504 return read;
505}
506
507
508
509
510
511
512
513static ssize_t write_profile(struct file *file, const char __user *buf,
514 size_t count, loff_t *ppos)
515{
516#ifdef CONFIG_SMP
517 extern int setup_profiling_timer(unsigned int multiplier);
518
519 if (count == sizeof(int)) {
520 unsigned int multiplier;
521
522 if (copy_from_user(&multiplier, buf, sizeof(int)))
523 return -EFAULT;
524
525 if (setup_profiling_timer(multiplier))
526 return -EINVAL;
527 }
528#endif
529 profile_discard_flip_buffers();
530 memset(prof_buffer, 0, prof_len * sizeof(atomic_t));
531 return count;
532}
533
534static const struct file_operations proc_profile_operations = {
535 .read = read_profile,
536 .write = write_profile,
537 .llseek = default_llseek,
538};
539
540#ifdef CONFIG_SMP
541static void profile_nop(void *unused)
542{
543}
544
545static int create_hash_tables(void)
546{
547 int cpu;
548
549 for_each_online_cpu(cpu) {
550 int node = cpu_to_mem(cpu);
551 struct page *page;
552
553 page = alloc_pages_exact_node(node,
554 GFP_KERNEL | __GFP_ZERO | __GFP_THISNODE,
555 0);
556 if (!page)
557 goto out_cleanup;
558 per_cpu(cpu_profile_hits, cpu)[1]
559 = (struct profile_hit *)page_address(page);
560 page = alloc_pages_exact_node(node,
561 GFP_KERNEL | __GFP_ZERO | __GFP_THISNODE,
562 0);
563 if (!page)
564 goto out_cleanup;
565 per_cpu(cpu_profile_hits, cpu)[0]
566 = (struct profile_hit *)page_address(page);
567 }
568 return 0;
569out_cleanup:
570 prof_on = 0;
571 smp_mb();
572 on_each_cpu(profile_nop, NULL, 1);
573 for_each_online_cpu(cpu) {
574 struct page *page;
575
576 if (per_cpu(cpu_profile_hits, cpu)[0]) {
577 page = virt_to_page(per_cpu(cpu_profile_hits, cpu)[0]);
578 per_cpu(cpu_profile_hits, cpu)[0] = NULL;
579 __free_page(page);
580 }
581 if (per_cpu(cpu_profile_hits, cpu)[1]) {
582 page = virt_to_page(per_cpu(cpu_profile_hits, cpu)[1]);
583 per_cpu(cpu_profile_hits, cpu)[1] = NULL;
584 __free_page(page);
585 }
586 }
587 return -1;
588}
589#else
590#define create_hash_tables() ({ 0; })
591#endif
592
593int __ref create_proc_profile(void)
594{
595 struct proc_dir_entry *entry;
596 int err = 0;
597
598 if (!prof_on)
599 return 0;
600
601 cpu_notifier_register_begin();
602
603 if (create_hash_tables()) {
604 err = -ENOMEM;
605 goto out;
606 }
607
608 entry = proc_create("profile", S_IWUSR | S_IRUGO,
609 NULL, &proc_profile_operations);
610 if (!entry)
611 goto out;
612 proc_set_size(entry, (1 + prof_len) * sizeof(atomic_t));
613 __hotcpu_notifier(profile_cpu_callback, 0);
614
615out:
616 cpu_notifier_register_done();
617 return err;
618}
619subsys_initcall(create_proc_profile);
620#endif
621