Commit 120790b8 authored by John Levon's avatar John Levon Committed by Linus Torvalds

[PATCH] oprofile - timer hook

This implements a simple hook into the profiling timer for x86 so that
non-perfctr machines can still use oprofile.  This has proven useful for
laptops and the like.

It also reduces header dependencies a bit by centralising readprofile
code
parent 7e1aee05
...@@ -27,6 +27,7 @@ obj-$(CONFIG_X86_LOCAL_APIC) += apic.o nmi.o ...@@ -27,6 +27,7 @@ obj-$(CONFIG_X86_LOCAL_APIC) += apic.o nmi.o
obj-$(CONFIG_X86_IO_APIC) += io_apic.o obj-$(CONFIG_X86_IO_APIC) += io_apic.o
obj-$(CONFIG_SOFTWARE_SUSPEND) += suspend.o obj-$(CONFIG_SOFTWARE_SUSPEND) += suspend.o
obj-$(CONFIG_X86_NUMAQ) += numaq.o obj-$(CONFIG_X86_NUMAQ) += numaq.o
obj-$(CONFIG_PROFILING) += profile.o
EXTRA_AFLAGS := -traditional EXTRA_AFLAGS := -traditional
......
...@@ -1008,17 +1008,9 @@ int setup_profiling_timer(unsigned int multiplier) ...@@ -1008,17 +1008,9 @@ int setup_profiling_timer(unsigned int multiplier)
inline void smp_local_timer_interrupt(struct pt_regs * regs) inline void smp_local_timer_interrupt(struct pt_regs * regs)
{ {
int user = user_mode(regs);
int cpu = smp_processor_id(); int cpu = smp_processor_id();
/* x86_do_profile(regs);
* The profiling function is SMP safe. (nothing can mess
* around with "current", and the profiling counters are
* updated with atomic operations). This is especially
* useful with a profiling multiplier != 1
*/
if (!user)
x86_do_profile(regs->eip);
if (--prof_counter[cpu] <= 0) { if (--prof_counter[cpu] <= 0) {
/* /*
...@@ -1036,7 +1028,7 @@ inline void smp_local_timer_interrupt(struct pt_regs * regs) ...@@ -1036,7 +1028,7 @@ inline void smp_local_timer_interrupt(struct pt_regs * regs)
} }
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
update_process_times(user); update_process_times(user_mode(regs));
#endif #endif
} }
......
...@@ -167,6 +167,9 @@ EXPORT_SYMBOL(get_wchan); ...@@ -167,6 +167,9 @@ EXPORT_SYMBOL(get_wchan);
EXPORT_SYMBOL(rtc_lock); EXPORT_SYMBOL(rtc_lock);
EXPORT_SYMBOL_GPL(register_profile_notifier);
EXPORT_SYMBOL_GPL(unregister_profile_notifier);
#undef memcpy #undef memcpy
#undef memset #undef memset
extern void * memset(void *,int,__kernel_size_t); extern void * memset(void *,int,__kernel_size_t);
......
/*
* linux/arch/i386/kernel/profile.c
*
* (C) 2002 John Levon <levon@movementarian.org>
*
*/
#include <linux/profile.h>
#include <linux/spinlock.h>
#include <linux/notifier.h>
#include <linux/irq.h>
#include <asm/hw_irq.h>
static struct notifier_block * profile_listeners;
static rwlock_t profile_lock = RW_LOCK_UNLOCKED;
int register_profile_notifier(struct notifier_block * nb)
{
int err;
write_lock_irq(&profile_lock);
err = notifier_chain_register(&profile_listeners, nb);
write_unlock_irq(&profile_lock);
return err;
}
int unregister_profile_notifier(struct notifier_block * nb)
{
int err;
write_lock_irq(&profile_lock);
err = notifier_chain_unregister(&profile_listeners, nb);
write_unlock_irq(&profile_lock);
return err;
}
void x86_profile_hook(struct pt_regs * regs)
{
/* we would not even need this lock if
* we had a global cli() on register/unregister
*/
read_lock(&profile_lock);
notifier_call_chain(&profile_listeners, 0, regs);
read_unlock(&profile_lock);
}
...@@ -64,11 +64,6 @@ extern spinlock_t i8259A_lock; ...@@ -64,11 +64,6 @@ extern spinlock_t i8259A_lock;
#include "do_timer.h" #include "do_timer.h"
/*
* for x86_do_profile()
*/
#include <linux/irq.h>
u64 jiffies_64; u64 jiffies_64;
unsigned long cpu_khz; /* Detected as we calibrate the TSC */ unsigned long cpu_khz; /* Detected as we calibrate the TSC */
......
...@@ -20,8 +20,7 @@ static inline void do_timer_interrupt_hook(struct pt_regs *regs) ...@@ -20,8 +20,7 @@ static inline void do_timer_interrupt_hook(struct pt_regs *regs)
* system, in that case we have to call the local interrupt handler. * system, in that case we have to call the local interrupt handler.
*/ */
#ifndef CONFIG_X86_LOCAL_APIC #ifndef CONFIG_X86_LOCAL_APIC
if (!user_mode(regs)) x86_do_profile(regs);
x86_do_profile(regs->eip);
#else #else
if (!using_apic_timer) if (!using_apic_timer)
smp_local_timer_interrupt(regs); smp_local_timer_interrupt(regs);
......
...@@ -15,8 +15,7 @@ static inline void do_timer_interrupt_hook(struct pt_regs *regs) ...@@ -15,8 +15,7 @@ static inline void do_timer_interrupt_hook(struct pt_regs *regs)
* system, in that case we have to call the local interrupt handler. * system, in that case we have to call the local interrupt handler.
*/ */
#ifndef CONFIG_X86_LOCAL_APIC #ifndef CONFIG_X86_LOCAL_APIC
if (!user_mode(regs)) x86_do_profile(regs);
x86_do_profile(regs->eip);
#else #else
if (!using_apic_timer) if (!using_apic_timer)
smp_local_timer_interrupt(regs); smp_local_timer_interrupt(regs);
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <linux/times.h> #include <linux/times.h>
#include <linux/profile.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/pgtable.h> #include <asm/pgtable.h>
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
*/ */
#include <linux/config.h> #include <linux/config.h>
#include <linux/profile.h>
#include <asm/atomic.h> #include <asm/atomic.h>
#include <asm/irq.h> #include <asm/irq.h>
...@@ -65,20 +66,31 @@ extern char _stext, _etext; ...@@ -65,20 +66,31 @@ extern char _stext, _etext;
#define IO_APIC_IRQ(x) (((x) >= 16) || ((1<<(x)) & io_apic_irqs)) #define IO_APIC_IRQ(x) (((x) >= 16) || ((1<<(x)) & io_apic_irqs))
extern unsigned long prof_cpu_mask;
extern unsigned int * prof_buffer;
extern unsigned long prof_len;
extern unsigned long prof_shift;
/* /*
* x86 profiling function, SMP safe. We might want to do this in * The profiling function is SMP safe. (nothing can mess
* assembly totally? * around with "current", and the profiling counters are
* updated with atomic operations). This is especially
* useful with a profiling multiplier != 1
*/ */
static inline void x86_do_profile (unsigned long eip) static inline void x86_do_profile(struct pt_regs * regs)
{ {
unsigned long eip;
extern unsigned long prof_cpu_mask;
extern char _stext;
#ifdef CONFIG_PROFILING
extern void x86_profile_hook(struct pt_regs *);
x86_profile_hook(regs);
#endif
if (user_mode(regs))
return;
if (!prof_buffer) if (!prof_buffer)
return; return;
eip = regs->eip;
/* /*
* Only measure the CPUs specified by /proc/irq/prof_cpu_mask. * Only measure the CPUs specified by /proc/irq/prof_cpu_mask.
* (default is all CPUs.) * (default is all CPUs.)
...@@ -97,7 +109,28 @@ static inline void x86_do_profile (unsigned long eip) ...@@ -97,7 +109,28 @@ static inline void x86_do_profile (unsigned long eip)
eip = prof_len-1; eip = prof_len-1;
atomic_inc((atomic_t *)&prof_buffer[eip]); atomic_inc((atomic_t *)&prof_buffer[eip]);
} }
struct notifier_block;
#ifdef CONFIG_PROFILING
int register_profile_notifier(struct notifier_block * nb);
int unregister_profile_notifier(struct notifier_block * nb);
#else
static inline int register_profile_notifier(struct notifier_block * nb)
{
return -ENOSYS;
}
static inline int unregister_profile_notifier(struct notifier_block * nb)
{
return -ENOSYS;
}
#endif /* CONFIG_PROFILING */
#ifdef CONFIG_SMP /*more of this file should probably be ifdefed SMP */ #ifdef CONFIG_SMP /*more of this file should probably be ifdefed SMP */
static inline void hw_resend_irq(struct hw_interrupt_type *h, unsigned int i) { static inline void hw_resend_irq(struct hw_interrupt_type *h, unsigned int i) {
if (IO_APIC_IRQ(i)) if (IO_APIC_IRQ(i))
......
...@@ -8,6 +8,17 @@ ...@@ -8,6 +8,17 @@
#include <linux/init.h> #include <linux/init.h>
#include <asm/errno.h> #include <asm/errno.h>
/* parse command line */
int __init profile_setup(char * str);
/* init basic kernel profiler */
void __init profile_init(void);
extern unsigned int * prof_buffer;
extern unsigned long prof_len;
extern unsigned long prof_shift;
enum profile_type { enum profile_type {
EXIT_TASK, EXIT_TASK,
EXIT_MMAP, EXIT_MMAP,
......
...@@ -492,10 +492,6 @@ extern unsigned long itimer_ticks; ...@@ -492,10 +492,6 @@ extern unsigned long itimer_ticks;
extern unsigned long itimer_next; extern unsigned long itimer_next;
extern void do_timer(struct pt_regs *); extern void do_timer(struct pt_regs *);
extern unsigned int * prof_buffer;
extern unsigned long prof_len;
extern unsigned long prof_shift;
extern void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr)); extern void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr));
extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode)); extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode));
extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr)); extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr));
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <linux/kernel_stat.h> #include <linux/kernel_stat.h>
#include <linux/security.h> #include <linux/security.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/profile.h>
#include <asm/io.h> #include <asm/io.h>
#include <asm/bugs.h> #include <asm/bugs.h>
...@@ -52,7 +53,6 @@ ...@@ -52,7 +53,6 @@
#error Sorry, your GCC is too old. It builds incorrect kernels. #error Sorry, your GCC is too old. It builds incorrect kernels.
#endif #endif
extern char _stext, _etext;
extern char *linux_banner; extern char *linux_banner;
static int init(void *); static int init(void *);
...@@ -130,13 +130,6 @@ __setup("maxcpus=", maxcpus); ...@@ -130,13 +130,6 @@ __setup("maxcpus=", maxcpus);
static char * argv_init[MAX_INIT_ARGS+2] = { "init", NULL, }; static char * argv_init[MAX_INIT_ARGS+2] = { "init", NULL, };
char * envp_init[MAX_INIT_ENVS+2] = { "HOME=/", "TERM=linux", NULL, }; char * envp_init[MAX_INIT_ENVS+2] = { "HOME=/", "TERM=linux", NULL, };
static int __init profile_setup(char *str)
{
int par;
if (get_option(&str,&par)) prof_shift = par;
return 1;
}
__setup("profile=", profile_setup); __setup("profile=", profile_setup);
static int __init checksetup(char *line) static int __init checksetup(char *line)
...@@ -411,16 +404,7 @@ asmlinkage void __init start_kernel(void) ...@@ -411,16 +404,7 @@ asmlinkage void __init start_kernel(void)
#ifdef CONFIG_MODULES #ifdef CONFIG_MODULES
init_modules(); init_modules();
#endif #endif
if (prof_shift) { profile_init();
unsigned int size;
/* only text is profiled */
prof_len = (unsigned long) &_etext - (unsigned long) &_stext;
prof_len >>= prof_shift;
size = prof_len * sizeof(unsigned int) + PAGE_SIZE-1;
prof_buffer = (unsigned int *) alloc_bootmem(size);
}
kmem_cache_init(); kmem_cache_init();
local_irq_enable(); local_irq_enable();
calibrate_delay(); calibrate_delay();
......
...@@ -9,6 +9,36 @@ ...@@ -9,6 +9,36 @@
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/mm.h> #include <linux/mm.h>
extern char _stext, _etext;
unsigned int * prof_buffer;
unsigned long prof_len;
unsigned long prof_shift;
int __init profile_setup(char * str)
{
int par;
if (get_option(&str,&par))
prof_shift = par;
return 1;
}
void __init profile_init(void)
{
unsigned int size;
if (!prof_shift)
return;
/* only text is profiled */
prof_len = (unsigned long) &_etext - (unsigned long) &_stext;
prof_len >>= prof_shift;
size = prof_len * sizeof(unsigned int) + PAGE_SIZE - 1;
prof_buffer = (unsigned int *) alloc_bootmem(size);
}
/* Profile event notifications */ /* Profile event notifications */
#ifdef CONFIG_PROFILING #ifdef CONFIG_PROFILING
......
...@@ -406,10 +406,6 @@ long time_adj; /* tick adjust (scaled 1 / HZ) */ ...@@ -406,10 +406,6 @@ long time_adj; /* tick adjust (scaled 1 / HZ) */
long time_reftime; /* time at last adjustment (s) */ long time_reftime; /* time at last adjustment (s) */
long time_adjust; long time_adjust;
unsigned int * prof_buffer;
unsigned long prof_len;
unsigned long prof_shift;
/* /*
* this routine handles the overflow of the microsecond field * this routine handles the overflow of the microsecond field
* *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment