diff options
Diffstat (limited to 'drivers/oprofile')
| -rw-r--r-- | drivers/oprofile/nmi_timer_int.c | 173 | ||||
| -rw-r--r-- | drivers/oprofile/oprof.c | 24 | ||||
| -rw-r--r-- | drivers/oprofile/oprof.h | 9 | 
3 files changed, 193 insertions, 13 deletions
| diff --git a/drivers/oprofile/nmi_timer_int.c b/drivers/oprofile/nmi_timer_int.c new file mode 100644 index 000000000000..76f1c9357f39 --- /dev/null +++ b/drivers/oprofile/nmi_timer_int.c @@ -0,0 +1,173 @@ +/** + * @file nmi_timer_int.c + * + * @remark Copyright 2011 Advanced Micro Devices, Inc. + * + * @author Robert Richter <robert.richter@amd.com> + */ + +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/errno.h> +#include <linux/oprofile.h> +#include <linux/perf_event.h> + +#ifdef CONFIG_OPROFILE_NMI_TIMER + +static DEFINE_PER_CPU(struct perf_event *, nmi_timer_events); +static int ctr_running; + +static struct perf_event_attr nmi_timer_attr = { +	.type           = PERF_TYPE_HARDWARE, +	.config         = PERF_COUNT_HW_CPU_CYCLES, +	.size           = sizeof(struct perf_event_attr), +	.pinned         = 1, +	.disabled       = 1, +}; + +static void nmi_timer_callback(struct perf_event *event, +			       struct perf_sample_data *data, +			       struct pt_regs *regs) +{ +	event->hw.interrupts = 0;       /* don't throttle interrupts */ +	oprofile_add_sample(regs, 0); +} + +static int nmi_timer_start_cpu(int cpu) +{ +	struct perf_event *event = per_cpu(nmi_timer_events, cpu); + +	if (!event) { +		event = perf_event_create_kernel_counter(&nmi_timer_attr, cpu, NULL, +							 nmi_timer_callback, NULL); +		if (IS_ERR(event)) +			return PTR_ERR(event); +		per_cpu(nmi_timer_events, cpu) = event; +	} + +	if (event && ctr_running) +		perf_event_enable(event); + +	return 0; +} + +static void nmi_timer_stop_cpu(int cpu) +{ +	struct perf_event *event = per_cpu(nmi_timer_events, cpu); + +	if (event && ctr_running) +		perf_event_disable(event); +} + +static int nmi_timer_cpu_notifier(struct notifier_block *b, unsigned long action, +				  void *data) +{ +	int cpu = (unsigned long)data; +	switch (action) { +	case CPU_DOWN_FAILED: +	case CPU_ONLINE: +		nmi_timer_start_cpu(cpu); +		break; +	case CPU_DOWN_PREPARE: +		nmi_timer_stop_cpu(cpu); +		break; +	} +	return NOTIFY_DONE; +} + +static struct notifier_block nmi_timer_cpu_nb = { +	.notifier_call = nmi_timer_cpu_notifier +}; + +static int nmi_timer_start(void) +{ +	int cpu; + +	get_online_cpus(); +	ctr_running = 1; +	for_each_online_cpu(cpu) +		nmi_timer_start_cpu(cpu); +	put_online_cpus(); + +	return 0; +} + +static void nmi_timer_stop(void) +{ +	int cpu; + +	get_online_cpus(); +	for_each_online_cpu(cpu) +		nmi_timer_stop_cpu(cpu); +	ctr_running = 0; +	put_online_cpus(); +} + +static void nmi_timer_shutdown(void) +{ +	struct perf_event *event; +	int cpu; + +	get_online_cpus(); +	unregister_cpu_notifier(&nmi_timer_cpu_nb); +	for_each_possible_cpu(cpu) { +		event = per_cpu(nmi_timer_events, cpu); +		if (!event) +			continue; +		perf_event_disable(event); +		per_cpu(nmi_timer_events, cpu) = NULL; +		perf_event_release_kernel(event); +	} + +	put_online_cpus(); +} + +static int nmi_timer_setup(void) +{ +	int cpu, err; +	u64 period; + +	/* clock cycles per tick: */ +	period = (u64)cpu_khz * 1000; +	do_div(period, HZ); +	nmi_timer_attr.sample_period = period; + +	get_online_cpus(); +	err = register_cpu_notifier(&nmi_timer_cpu_nb); +	if (err) +		goto out; +	/* can't attach events to offline cpus: */ +	for_each_online_cpu(cpu) { +		err = nmi_timer_start_cpu(cpu); +		if (err) +			break; +	} +	if (err) +		nmi_timer_shutdown(); +out: +	put_online_cpus(); +	return err; +} + +int __init op_nmi_timer_init(struct oprofile_operations *ops) +{ +	int err = 0; + +	err = nmi_timer_setup(); +	if (err) +		return err; +	nmi_timer_shutdown();		/* only check, don't alloc */ + +	ops->create_files	= NULL; +	ops->setup		= nmi_timer_setup; +	ops->shutdown		= nmi_timer_shutdown; +	ops->start		= nmi_timer_start; +	ops->stop		= nmi_timer_stop; +	ops->cpu_type		= "timer"; + +	printk(KERN_INFO "oprofile: using NMI timer interrupt.\n"); + +	return 0; +} + +#endif diff --git a/drivers/oprofile/oprof.c b/drivers/oprofile/oprof.c index f7cd06967aed..ed2c3ec07024 100644 --- a/drivers/oprofile/oprof.c +++ b/drivers/oprofile/oprof.c @@ -246,26 +246,24 @@ 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(); +	} -	timer_mode = err || timer;	/* fall back to timer mode on errors */ -	if (timer_mode) { -		if (!err) -			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;  	} -	err = oprofilefs_register(); -	if (!err) -		return 0; - -	/* failed */ -	if (!timer_mode) -		oprofile_arch_exit(); - -	return err; +	return oprofilefs_register();  } diff --git a/drivers/oprofile/oprof.h b/drivers/oprofile/oprof.h index 177b73de5e5f..769fb0fcac44 100644 --- a/drivers/oprofile/oprof.h +++ b/drivers/oprofile/oprof.h @@ -36,6 +36,15 @@ struct dentry;  void oprofile_create_files(struct super_block *sb, struct dentry *root);  int oprofile_timer_init(struct oprofile_operations *ops);  void oprofile_timer_exit(void); +#ifdef CONFIG_OPROFILE_NMI_TIMER +int op_nmi_timer_init(struct oprofile_operations *ops); +#else +static inline int op_nmi_timer_init(struct oprofile_operations *ops) +{ +	return -ENODEV; +} +#endif +  int oprofile_set_ulong(unsigned long *addr, unsigned long val);  int oprofile_set_timeout(unsigned long time); |