diff options
Diffstat (limited to 'arch/x86/oprofile/nmi_int.c')
| -rw-r--r-- | arch/x86/oprofile/nmi_int.c | 402 | 
1 files changed, 287 insertions, 115 deletions
diff --git a/arch/x86/oprofile/nmi_int.c b/arch/x86/oprofile/nmi_int.c index 89b9a5cd63d..cb88b1a0bd5 100644 --- a/arch/x86/oprofile/nmi_int.c +++ b/arch/x86/oprofile/nmi_int.c @@ -1,11 +1,14 @@  /**   * @file nmi_int.c   * - * @remark Copyright 2002-2008 OProfile authors + * @remark Copyright 2002-2009 OProfile authors   * @remark Read the file COPYING   *   * @author John Levon <levon@movementarian.org>   * @author Robert Richter <robert.richter@amd.com> + * @author Barry Kasindorf <barry.kasindorf@amd.com> + * @author Jason Yeh <jason.yeh@amd.com> + * @author Suravee Suthikulpanit <suravee.suthikulpanit@amd.com>   */  #include <linux/init.h> @@ -24,13 +27,35 @@  #include "op_counter.h"  #include "op_x86_model.h" -static struct op_x86_model_spec const *model; +static struct op_x86_model_spec *model;  static DEFINE_PER_CPU(struct op_msrs, cpu_msrs);  static DEFINE_PER_CPU(unsigned long, saved_lvtpc);  /* 0 == registered but off, 1 == registered and on */  static int nmi_enabled = 0; +struct op_counter_config counter_config[OP_MAX_COUNTER]; + +/* common functions */ + +u64 op_x86_get_ctrl(struct op_x86_model_spec const *model, +		    struct op_counter_config *counter_config) +{ +	u64 val = 0; +	u16 event = (u16)counter_config->event; + +	val |= ARCH_PERFMON_EVENTSEL_INT; +	val |= counter_config->user ? ARCH_PERFMON_EVENTSEL_USR : 0; +	val |= counter_config->kernel ? ARCH_PERFMON_EVENTSEL_OS : 0; +	val |= (counter_config->unit_mask & 0xFF) << 8; +	event &= model->event_mask ? model->event_mask : 0xFF; +	val |= event & 0xFF; +	val |= (event & 0x0F00) << 24; + +	return val; +} + +  static int profile_exceptions_notify(struct notifier_block *self,  				     unsigned long val, void *data)  { @@ -52,36 +77,214 @@ static int profile_exceptions_notify(struct notifier_block *self,  static void nmi_cpu_save_registers(struct op_msrs *msrs)  { -	unsigned int const nr_ctrs = model->num_counters; -	unsigned int const nr_ctrls = model->num_controls;  	struct op_msr *counters = msrs->counters;  	struct op_msr *controls = msrs->controls;  	unsigned int i; -	for (i = 0; i < nr_ctrs; ++i) { -		if (counters[i].addr) { -			rdmsr(counters[i].addr, -				counters[i].saved.low, -				counters[i].saved.high); -		} +	for (i = 0; i < model->num_counters; ++i) { +		if (counters[i].addr) +			rdmsrl(counters[i].addr, counters[i].saved);  	} -	for (i = 0; i < nr_ctrls; ++i) { -		if (controls[i].addr) { -			rdmsr(controls[i].addr, -				controls[i].saved.low, -				controls[i].saved.high); +	for (i = 0; i < model->num_controls; ++i) { +		if (controls[i].addr) +			rdmsrl(controls[i].addr, controls[i].saved); +	} +} + +static void nmi_cpu_start(void *dummy) +{ +	struct op_msrs const *msrs = &__get_cpu_var(cpu_msrs); +	model->start(msrs); +} + +static int nmi_start(void) +{ +	on_each_cpu(nmi_cpu_start, NULL, 1); +	return 0; +} + +static void nmi_cpu_stop(void *dummy) +{ +	struct op_msrs const *msrs = &__get_cpu_var(cpu_msrs); +	model->stop(msrs); +} + +static void nmi_stop(void) +{ +	on_each_cpu(nmi_cpu_stop, NULL, 1); +} + +#ifdef CONFIG_OPROFILE_EVENT_MULTIPLEX + +static DEFINE_PER_CPU(int, switch_index); + +static inline int has_mux(void) +{ +	return !!model->switch_ctrl; +} + +inline int op_x86_phys_to_virt(int phys) +{ +	return __get_cpu_var(switch_index) + phys; +} + +inline int op_x86_virt_to_phys(int virt) +{ +	return virt % model->num_counters; +} + +static void nmi_shutdown_mux(void) +{ +	int i; + +	if (!has_mux()) +		return; + +	for_each_possible_cpu(i) { +		kfree(per_cpu(cpu_msrs, i).multiplex); +		per_cpu(cpu_msrs, i).multiplex = NULL; +		per_cpu(switch_index, i) = 0; +	} +} + +static int nmi_setup_mux(void) +{ +	size_t multiplex_size = +		sizeof(struct op_msr) * model->num_virt_counters; +	int i; + +	if (!has_mux()) +		return 1; + +	for_each_possible_cpu(i) { +		per_cpu(cpu_msrs, i).multiplex = +			kmalloc(multiplex_size, GFP_KERNEL); +		if (!per_cpu(cpu_msrs, i).multiplex) +			return 0; +	} + +	return 1; +} + +static void nmi_cpu_setup_mux(int cpu, struct op_msrs const * const msrs) +{ +	int i; +	struct op_msr *multiplex = msrs->multiplex; + +	if (!has_mux()) +		return; + +	for (i = 0; i < model->num_virt_counters; ++i) { +		if (counter_config[i].enabled) { +			multiplex[i].saved = -(u64)counter_config[i].count; +		} else { +			multiplex[i].addr  = 0; +			multiplex[i].saved = 0;  		}  	} + +	per_cpu(switch_index, cpu) = 0;  } -static void nmi_save_registers(void *dummy) +static void nmi_cpu_save_mpx_registers(struct op_msrs *msrs) +{ +	struct op_msr *multiplex = msrs->multiplex; +	int i; + +	for (i = 0; i < model->num_counters; ++i) { +		int virt = op_x86_phys_to_virt(i); +		if (multiplex[virt].addr) +			rdmsrl(multiplex[virt].addr, multiplex[virt].saved); +	} +} + +static void nmi_cpu_restore_mpx_registers(struct op_msrs *msrs) +{ +	struct op_msr *multiplex = msrs->multiplex; +	int i; + +	for (i = 0; i < model->num_counters; ++i) { +		int virt = op_x86_phys_to_virt(i); +		if (multiplex[virt].addr) +			wrmsrl(multiplex[virt].addr, multiplex[virt].saved); +	} +} + +static void nmi_cpu_switch(void *dummy)  {  	int cpu = smp_processor_id(); +	int si = per_cpu(switch_index, cpu);  	struct op_msrs *msrs = &per_cpu(cpu_msrs, cpu); -	nmi_cpu_save_registers(msrs); + +	nmi_cpu_stop(NULL); +	nmi_cpu_save_mpx_registers(msrs); + +	/* move to next set */ +	si += model->num_counters; +	if ((si > model->num_virt_counters) || (counter_config[si].count == 0)) +		per_cpu(switch_index, cpu) = 0; +	else +		per_cpu(switch_index, cpu) = si; + +	model->switch_ctrl(model, msrs); +	nmi_cpu_restore_mpx_registers(msrs); + +	nmi_cpu_start(NULL); +} + + +/* + * Quick check to see if multiplexing is necessary. + * The check should be sufficient since counters are used + * in ordre. + */ +static int nmi_multiplex_on(void) +{ +	return counter_config[model->num_counters].count ? 0 : -EINVAL; +} + +static int nmi_switch_event(void) +{ +	if (!has_mux()) +		return -ENOSYS;		/* not implemented */ +	if (nmi_multiplex_on() < 0) +		return -EINVAL;		/* not necessary */ + +	on_each_cpu(nmi_cpu_switch, NULL, 1); + +	return 0; +} + +static inline void mux_init(struct oprofile_operations *ops) +{ +	if (has_mux()) +		ops->switch_events = nmi_switch_event; +} + +static void mux_clone(int cpu) +{ +	if (!has_mux()) +		return; + +	memcpy(per_cpu(cpu_msrs, cpu).multiplex, +	       per_cpu(cpu_msrs, 0).multiplex, +	       sizeof(struct op_msr) * model->num_virt_counters);  } +#else + +inline int op_x86_phys_to_virt(int phys) { return phys; } +inline int op_x86_virt_to_phys(int virt) { return virt; } +static inline void nmi_shutdown_mux(void) { } +static inline int nmi_setup_mux(void) { return 1; } +static inline void +nmi_cpu_setup_mux(int cpu, struct op_msrs const * const msrs) { } +static inline void mux_init(struct oprofile_operations *ops) { } +static void mux_clone(int cpu) { } + +#endif +  static void free_msrs(void)  {  	int i; @@ -95,38 +298,32 @@ static void free_msrs(void)  static int allocate_msrs(void)  { -	int success = 1;  	size_t controls_size = sizeof(struct op_msr) * model->num_controls;  	size_t counters_size = sizeof(struct op_msr) * model->num_counters;  	int i;  	for_each_possible_cpu(i) {  		per_cpu(cpu_msrs, i).counters = kmalloc(counters_size, -								GFP_KERNEL); -		if (!per_cpu(cpu_msrs, i).counters) { -			success = 0; -			break; -		} +							GFP_KERNEL); +		if (!per_cpu(cpu_msrs, i).counters) +			return 0;  		per_cpu(cpu_msrs, i).controls = kmalloc(controls_size, -								GFP_KERNEL); -		if (!per_cpu(cpu_msrs, i).controls) { -			success = 0; -			break; -		} +							GFP_KERNEL); +		if (!per_cpu(cpu_msrs, i).controls) +			return 0;  	} -	if (!success) -		free_msrs(); - -	return success; +	return 1;  }  static void nmi_cpu_setup(void *dummy)  {  	int cpu = smp_processor_id();  	struct op_msrs *msrs = &per_cpu(cpu_msrs, cpu); +	nmi_cpu_save_registers(msrs);  	spin_lock(&oprofilefs_lock); -	model->setup_ctrs(msrs); +	model->setup_ctrs(model, msrs); +	nmi_cpu_setup_mux(cpu, msrs);  	spin_unlock(&oprofilefs_lock);  	per_cpu(saved_lvtpc, cpu) = apic_read(APIC_LVTPC);  	apic_write(APIC_LVTPC, APIC_DM_NMI); @@ -144,11 +341,15 @@ static int nmi_setup(void)  	int cpu;  	if (!allocate_msrs()) -		return -ENOMEM; +		err = -ENOMEM; +	else if (!nmi_setup_mux()) +		err = -ENOMEM; +	else +		err = register_die_notifier(&profile_exceptions_nb); -	err = register_die_notifier(&profile_exceptions_nb);  	if (err) {  		free_msrs(); +		nmi_shutdown_mux();  		return err;  	} @@ -159,45 +360,38 @@ static int nmi_setup(void)  	/* Assume saved/restored counters are the same on all CPUs */  	model->fill_in_addresses(&per_cpu(cpu_msrs, 0));  	for_each_possible_cpu(cpu) { -		if (cpu != 0) { -			memcpy(per_cpu(cpu_msrs, cpu).counters, -				per_cpu(cpu_msrs, 0).counters, -				sizeof(struct op_msr) * model->num_counters); +		if (!cpu) +			continue; -			memcpy(per_cpu(cpu_msrs, cpu).controls, -				per_cpu(cpu_msrs, 0).controls, -				sizeof(struct op_msr) * model->num_controls); -		} +		memcpy(per_cpu(cpu_msrs, cpu).counters, +		       per_cpu(cpu_msrs, 0).counters, +		       sizeof(struct op_msr) * model->num_counters); + +		memcpy(per_cpu(cpu_msrs, cpu).controls, +		       per_cpu(cpu_msrs, 0).controls, +		       sizeof(struct op_msr) * model->num_controls); +		mux_clone(cpu);  	} -	on_each_cpu(nmi_save_registers, NULL, 1);  	on_each_cpu(nmi_cpu_setup, NULL, 1);  	nmi_enabled = 1;  	return 0;  } -static void nmi_restore_registers(struct op_msrs *msrs) +static void nmi_cpu_restore_registers(struct op_msrs *msrs)  { -	unsigned int const nr_ctrs = model->num_counters; -	unsigned int const nr_ctrls = model->num_controls;  	struct op_msr *counters = msrs->counters;  	struct op_msr *controls = msrs->controls;  	unsigned int i; -	for (i = 0; i < nr_ctrls; ++i) { -		if (controls[i].addr) { -			wrmsr(controls[i].addr, -				controls[i].saved.low, -				controls[i].saved.high); -		} +	for (i = 0; i < model->num_controls; ++i) { +		if (controls[i].addr) +			wrmsrl(controls[i].addr, controls[i].saved);  	} -	for (i = 0; i < nr_ctrs; ++i) { -		if (counters[i].addr) { -			wrmsr(counters[i].addr, -				counters[i].saved.low, -				counters[i].saved.high); -		} +	for (i = 0; i < model->num_counters; ++i) { +		if (counters[i].addr) +			wrmsrl(counters[i].addr, counters[i].saved);  	}  } @@ -205,7 +399,7 @@ static void nmi_cpu_shutdown(void *dummy)  {  	unsigned int v;  	int cpu = smp_processor_id(); -	struct op_msrs *msrs = &__get_cpu_var(cpu_msrs); +	struct op_msrs *msrs = &per_cpu(cpu_msrs, cpu);  	/* restoring APIC_LVTPC can trigger an apic error because the delivery  	 * mode and vector nr combination can be illegal. That's by design: on @@ -216,7 +410,7 @@ static void nmi_cpu_shutdown(void *dummy)  	apic_write(APIC_LVTERR, v | APIC_LVT_MASKED);  	apic_write(APIC_LVTPC, per_cpu(saved_lvtpc, cpu));  	apic_write(APIC_LVTERR, v); -	nmi_restore_registers(msrs); +	nmi_cpu_restore_registers(msrs);  }  static void nmi_shutdown(void) @@ -226,42 +420,18 @@ static void nmi_shutdown(void)  	nmi_enabled = 0;  	on_each_cpu(nmi_cpu_shutdown, NULL, 1);  	unregister_die_notifier(&profile_exceptions_nb); +	nmi_shutdown_mux();  	msrs = &get_cpu_var(cpu_msrs);  	model->shutdown(msrs);  	free_msrs();  	put_cpu_var(cpu_msrs);  } -static void nmi_cpu_start(void *dummy) -{ -	struct op_msrs const *msrs = &__get_cpu_var(cpu_msrs); -	model->start(msrs); -} - -static int nmi_start(void) -{ -	on_each_cpu(nmi_cpu_start, NULL, 1); -	return 0; -} - -static void nmi_cpu_stop(void *dummy) -{ -	struct op_msrs const *msrs = &__get_cpu_var(cpu_msrs); -	model->stop(msrs); -} - -static void nmi_stop(void) -{ -	on_each_cpu(nmi_cpu_stop, NULL, 1); -} - -struct op_counter_config counter_config[OP_MAX_COUNTER]; -  static int nmi_create_files(struct super_block *sb, struct dentry *root)  {  	unsigned int i; -	for (i = 0; i < model->num_counters; ++i) { +	for (i = 0; i < model->num_virt_counters; ++i) {  		struct dentry *dir;  		char buf[4]; @@ -270,7 +440,7 @@ static int nmi_create_files(struct super_block *sb, struct dentry *root)  		 * NOTE:  assumes 1:1 mapping here (that counters are organized  		 *        sequentially in their struct assignment).  		 */ -		if (unlikely(!avail_to_resrv_perfctr_nmi_bit(i))) +		if (!avail_to_resrv_perfctr_nmi_bit(op_x86_virt_to_phys(i)))  			continue;  		snprintf(buf,  sizeof(buf), "%d", i); @@ -402,6 +572,7 @@ module_param_call(cpu_type, force_cpu_type, NULL, NULL, 0);  static int __init ppro_init(char **cpu_type)  {  	__u8 cpu_model = boot_cpu_data.x86_model; +	struct op_x86_model_spec *spec = &op_ppro_spec;	/* default */  	if (force_arch_perfmon && cpu_has_arch_perfmon)  		return 0; @@ -428,7 +599,7 @@ static int __init ppro_init(char **cpu_type)  		*cpu_type = "i386/core_2";  		break;  	case 26: -		arch_perfmon_setup_counters(); +		spec = &op_arch_perfmon_spec;  		*cpu_type = "i386/core_i7";  		break;  	case 28: @@ -439,17 +610,7 @@ static int __init ppro_init(char **cpu_type)  		return 0;  	} -	model = &op_ppro_spec; -	return 1; -} - -static int __init arch_perfmon_init(char **cpu_type) -{ -	if (!cpu_has_arch_perfmon) -		return 0; -	*cpu_type = "i386/arch_perfmon"; -	model = &op_arch_perfmon_spec; -	arch_perfmon_setup_counters(); +	model = spec;  	return 1;  } @@ -471,27 +632,26 @@ int __init op_nmi_init(struct oprofile_operations *ops)  		/* Needs to be at least an Athlon (or hammer in 32bit mode) */  		switch (family) { -		default: -			return -ENODEV;  		case 6: -			model = &op_amd_spec;  			cpu_type = "i386/athlon";  			break;  		case 0xf: -			model = &op_amd_spec; -			/* Actually it could be i386/hammer too, but give -			 user space an consistent name. */ +			/* +			 * Actually it could be i386/hammer too, but +			 * give user space an consistent name. +			 */  			cpu_type = "x86-64/hammer";  			break;  		case 0x10: -			model = &op_amd_spec;  			cpu_type = "x86-64/family10";  			break;  		case 0x11: -			model = &op_amd_spec;  			cpu_type = "x86-64/family11h";  			break; +		default: +			return -ENODEV;  		} +		model = &op_amd_spec;  		break;  	case X86_VENDOR_INTEL: @@ -510,8 +670,15 @@ int __init op_nmi_init(struct oprofile_operations *ops)  			break;  		} -		if (!cpu_type && !arch_perfmon_init(&cpu_type)) +		if (cpu_type) +			break; + +		if (!cpu_has_arch_perfmon)  			return -ENODEV; + +		/* use arch perfmon as fallback */ +		cpu_type = "i386/arch_perfmon"; +		model = &op_arch_perfmon_spec;  		break;  	default: @@ -522,18 +689,23 @@ int __init op_nmi_init(struct oprofile_operations *ops)  	register_cpu_notifier(&oprofile_cpu_nb);  #endif  	/* default values, can be overwritten by model */ -	ops->create_files = nmi_create_files; -	ops->setup = nmi_setup; -	ops->shutdown = nmi_shutdown; -	ops->start = nmi_start; -	ops->stop = nmi_stop; -	ops->cpu_type = cpu_type; +	ops->create_files	= nmi_create_files; +	ops->setup		= nmi_setup; +	ops->shutdown		= nmi_shutdown; +	ops->start		= nmi_start; +	ops->stop		= nmi_stop; +	ops->cpu_type		= cpu_type;  	if (model->init)  		ret = model->init(ops);  	if (ret)  		return ret; +	if (!model->num_virt_counters) +		model->num_virt_counters = model->num_counters; + +	mux_init(ops); +  	init_sysfs();  	using_nmi = 1;  	printk(KERN_INFO "oprofile: using NMI interrupt.\n");  |