|  | /** | 
|  | * @file oprof.c | 
|  | * | 
|  | * @remark Copyright 2002 OProfile authors | 
|  | * @remark Read the file COPYING | 
|  | * | 
|  | * @author John Levon <levon@movementarian.org> | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/oprofile.h> | 
|  | #include <linux/moduleparam.h> | 
|  | #include <linux/workqueue.h> | 
|  | #include <linux/time.h> | 
|  | #include <linux/mutex.h> | 
|  |  | 
|  | #include "oprof.h" | 
|  | #include "event_buffer.h" | 
|  | #include "cpu_buffer.h" | 
|  | #include "buffer_sync.h" | 
|  | #include "oprofile_stats.h" | 
|  |  | 
|  | struct oprofile_operations oprofile_ops; | 
|  |  | 
|  | unsigned long oprofile_started; | 
|  | unsigned long oprofile_backtrace_depth; | 
|  | static unsigned long is_setup; | 
|  | static DEFINE_MUTEX(start_mutex); | 
|  |  | 
|  | /* timer | 
|  | 0 - use performance monitoring hardware if available | 
|  | 1 - use the timer int mechanism regardless | 
|  | */ | 
|  | static int timer = 0; | 
|  |  | 
|  | int oprofile_setup(void) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | mutex_lock(&start_mutex); | 
|  |  | 
|  | if ((err = alloc_cpu_buffers())) | 
|  | goto out; | 
|  |  | 
|  | if ((err = alloc_event_buffer())) | 
|  | goto out1; | 
|  |  | 
|  | if (oprofile_ops.setup && (err = oprofile_ops.setup())) | 
|  | goto out2; | 
|  |  | 
|  | /* Note even though this starts part of the | 
|  | * profiling overhead, it's necessary to prevent | 
|  | * us missing task deaths and eventually oopsing | 
|  | * when trying to process the event buffer. | 
|  | */ | 
|  | if (oprofile_ops.sync_start) { | 
|  | int sync_ret = oprofile_ops.sync_start(); | 
|  | switch (sync_ret) { | 
|  | case 0: | 
|  | goto post_sync; | 
|  | case 1: | 
|  | goto do_generic; | 
|  | case -1: | 
|  | goto out3; | 
|  | default: | 
|  | goto out3; | 
|  | } | 
|  | } | 
|  | do_generic: | 
|  | if ((err = sync_start())) | 
|  | goto out3; | 
|  |  | 
|  | post_sync: | 
|  | is_setup = 1; | 
|  | mutex_unlock(&start_mutex); | 
|  | return 0; | 
|  |  | 
|  | out3: | 
|  | if (oprofile_ops.shutdown) | 
|  | oprofile_ops.shutdown(); | 
|  | out2: | 
|  | free_event_buffer(); | 
|  | out1: | 
|  | free_cpu_buffers(); | 
|  | out: | 
|  | mutex_unlock(&start_mutex); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_OPROFILE_EVENT_MULTIPLEX | 
|  |  | 
|  | static void switch_worker(struct work_struct *work); | 
|  | static DECLARE_DELAYED_WORK(switch_work, switch_worker); | 
|  |  | 
|  | static void start_switch_worker(void) | 
|  | { | 
|  | if (oprofile_ops.switch_events) | 
|  | schedule_delayed_work(&switch_work, oprofile_time_slice); | 
|  | } | 
|  |  | 
|  | static void stop_switch_worker(void) | 
|  | { | 
|  | cancel_delayed_work_sync(&switch_work); | 
|  | } | 
|  |  | 
|  | static void switch_worker(struct work_struct *work) | 
|  | { | 
|  | if (oprofile_ops.switch_events()) | 
|  | return; | 
|  |  | 
|  | atomic_inc(&oprofile_stats.multiplex_counter); | 
|  | start_switch_worker(); | 
|  | } | 
|  |  | 
|  | /* User inputs in ms, converts to jiffies */ | 
|  | int oprofile_set_timeout(unsigned long val_msec) | 
|  | { | 
|  | int err = 0; | 
|  | unsigned long time_slice; | 
|  |  | 
|  | mutex_lock(&start_mutex); | 
|  |  | 
|  | if (oprofile_started) { | 
|  | err = -EBUSY; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (!oprofile_ops.switch_events) { | 
|  | err = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | time_slice = msecs_to_jiffies(val_msec); | 
|  | if (time_slice == MAX_JIFFY_OFFSET) { | 
|  | err = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | oprofile_time_slice = time_slice; | 
|  |  | 
|  | out: | 
|  | mutex_unlock(&start_mutex); | 
|  | return err; | 
|  |  | 
|  | } | 
|  |  | 
|  | #else | 
|  |  | 
|  | static inline void start_switch_worker(void) { } | 
|  | static inline void stop_switch_worker(void) { } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | /* Actually start profiling (echo 1>/dev/oprofile/enable) */ | 
|  | int oprofile_start(void) | 
|  | { | 
|  | int err = -EINVAL; | 
|  |  | 
|  | mutex_lock(&start_mutex); | 
|  |  | 
|  | if (!is_setup) | 
|  | goto out; | 
|  |  | 
|  | err = 0; | 
|  |  | 
|  | if (oprofile_started) | 
|  | goto out; | 
|  |  | 
|  | oprofile_reset_stats(); | 
|  |  | 
|  | if ((err = oprofile_ops.start())) | 
|  | goto out; | 
|  |  | 
|  | start_switch_worker(); | 
|  |  | 
|  | oprofile_started = 1; | 
|  | out: | 
|  | mutex_unlock(&start_mutex); | 
|  | return err; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* echo 0>/dev/oprofile/enable */ | 
|  | void oprofile_stop(void) | 
|  | { | 
|  | mutex_lock(&start_mutex); | 
|  | if (!oprofile_started) | 
|  | goto out; | 
|  | oprofile_ops.stop(); | 
|  | oprofile_started = 0; | 
|  |  | 
|  | stop_switch_worker(); | 
|  |  | 
|  | /* wake up the daemon to read what remains */ | 
|  | wake_up_buffer_waiter(); | 
|  | out: | 
|  | mutex_unlock(&start_mutex); | 
|  | } | 
|  |  | 
|  |  | 
|  | void oprofile_shutdown(void) | 
|  | { | 
|  | mutex_lock(&start_mutex); | 
|  | if (oprofile_ops.sync_stop) { | 
|  | int sync_ret = oprofile_ops.sync_stop(); | 
|  | switch (sync_ret) { | 
|  | case 0: | 
|  | goto post_sync; | 
|  | case 1: | 
|  | goto do_generic; | 
|  | default: | 
|  | goto post_sync; | 
|  | } | 
|  | } | 
|  | do_generic: | 
|  | sync_stop(); | 
|  | post_sync: | 
|  | if (oprofile_ops.shutdown) | 
|  | oprofile_ops.shutdown(); | 
|  | is_setup = 0; | 
|  | free_event_buffer(); | 
|  | free_cpu_buffers(); | 
|  | mutex_unlock(&start_mutex); | 
|  | } | 
|  |  | 
|  | int oprofile_set_ulong(unsigned long *addr, unsigned long val) | 
|  | { | 
|  | int err = -EBUSY; | 
|  |  | 
|  | mutex_lock(&start_mutex); | 
|  | if (!oprofile_started) { | 
|  | *addr = val; | 
|  | err = 0; | 
|  | } | 
|  | mutex_unlock(&start_mutex); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int timer_mode; | 
|  |  | 
|  | static int __init oprofile_init(void) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | /* always init architecture to setup backtrace support */ | 
|  | timer_mode = 0; | 
|  | err = oprofile_arch_init(&oprofile_ops); | 
|  | if (!err) { | 
|  | if (!timer && !oprofilefs_register()) | 
|  | return 0; | 
|  | oprofile_arch_exit(); | 
|  | } | 
|  |  | 
|  | /* setup timer mode: */ | 
|  | timer_mode = 1; | 
|  | /* no nmi timer mode if oprofile.timer is set */ | 
|  | if (timer || op_nmi_timer_init(&oprofile_ops)) { | 
|  | err = oprofile_timer_init(&oprofile_ops); | 
|  | if (err) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | return oprofilefs_register(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void __exit oprofile_exit(void) | 
|  | { | 
|  | oprofilefs_unregister(); | 
|  | if (!timer_mode) | 
|  | oprofile_arch_exit(); | 
|  | } | 
|  |  | 
|  |  | 
|  | module_init(oprofile_init); | 
|  | module_exit(oprofile_exit); | 
|  |  | 
|  | module_param_named(timer, timer, int, 0644); | 
|  | MODULE_PARM_DESC(timer, "force use of timer interrupt"); | 
|  |  | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_AUTHOR("John Levon <levon@movementarian.org>"); | 
|  | MODULE_DESCRIPTION("OProfile system profiler"); |