| /** |
| * @file op_nmi.c |
| * Setup and handling of NMI PMC interrupts |
| * |
| * @remark Copyright 2002 OProfile authors |
| * @remark Read the file COPYING |
| * |
| * @author John Levon |
| * @author Philippe Elie |
| */ |
| |
| #include "oprofile.h" |
| #include "op_msr.h" |
| #include "op_apic.h" |
| #include "op_util.h" |
| #include "op_x86_model.h" |
| |
| static struct op_msrs cpu_msrs[NR_CPUS]; |
| static struct op_x86_model_spec const * model = NULL; |
| |
| static struct op_x86_model_spec const * get_model(void) |
| { |
| if (!model) { |
| /* pick out our per-model function table */ |
| switch (sysctl.cpu_type) { |
| case CPU_ATHLON: |
| case CPU_HAMMER: |
| model = &op_athlon_spec; |
| break; |
| case CPU_P4: |
| model = &op_p4_spec; |
| break; |
| #ifdef HT_SUPPORT |
| case CPU_P4_HT2: |
| model = &op_p4_ht2_spec; |
| break; |
| #endif |
| default: |
| model = &op_ppro_spec; |
| break; |
| } |
| } |
| return model; |
| } |
| |
| asmlinkage void op_do_nmi(struct pt_regs * regs) |
| { |
| uint const cpu = op_cpu_id(); |
| struct op_msrs const * const msrs = &cpu_msrs[cpu]; |
| |
| model->check_ctrs(cpu, msrs, regs); |
| } |
| |
| /* ---------------- PMC setup ------------------ */ |
| |
| static void pmc_setup_ctr(void * dummy) |
| { |
| uint const cpu = op_cpu_id(); |
| struct op_msrs const * const msrs = &cpu_msrs[cpu]; |
| get_model()->setup_ctrs(msrs); |
| } |
| |
| |
| static int pmc_setup_all(void) |
| { |
| if (smp_call_function(pmc_setup_ctr, NULL, 0, 1)) |
| return -EFAULT; |
| pmc_setup_ctr(NULL); |
| return 0; |
| } |
| |
| |
| static void pmc_start(void * info) |
| { |
| uint const cpu = op_cpu_id(); |
| struct op_msrs const * const msrs = &cpu_msrs[cpu]; |
| |
| if (info && (*((uint *)info) != cpu)) |
| return; |
| |
| get_model()->start(msrs); |
| } |
| |
| |
| static void pmc_stop(void * info) |
| { |
| uint const cpu = op_cpu_id(); |
| struct op_msrs const * const msrs = &cpu_msrs[cpu]; |
| |
| if (info && (*((uint *)info) != cpu)) |
| return; |
| |
| get_model()->stop(msrs); |
| } |
| |
| |
| static void pmc_select_start(uint cpu) |
| { |
| if (cpu == op_cpu_id()) |
| pmc_start(NULL); |
| else |
| smp_call_function(pmc_start, &cpu, 0, 1); |
| } |
| |
| |
| static void pmc_select_stop(uint cpu) |
| { |
| if (cpu == op_cpu_id()) |
| pmc_stop(NULL); |
| else |
| smp_call_function(pmc_stop, &cpu, 0, 1); |
| } |
| |
| |
| static void pmc_start_all(void) |
| { |
| int cpu, i; |
| |
| for (cpu = 0 ; cpu < smp_num_cpus; cpu++) { |
| struct _oprof_data * data = &oprof_data[cpu]; |
| |
| for (i = 0 ; i < get_model()->num_counters ; ++i) { |
| if (sysctl.ctr[i].enabled) |
| data->ctr_count[i] = sysctl.ctr[i].count; |
| else |
| data->ctr_count[i] = 0; |
| } |
| } |
| |
| install_nmi(); |
| smp_call_function(pmc_start, NULL, 0, 1); |
| pmc_start(NULL); |
| } |
| |
| |
| static void pmc_stop_all(void) |
| { |
| smp_call_function(pmc_stop, NULL, 0, 1); |
| pmc_stop(NULL); |
| restore_nmi(); |
| } |
| |
| |
| static int pmc_check_params(void) |
| { |
| int i; |
| int enabled = 0; |
| |
| for (i = 0; i < get_model()->num_counters; i++) { |
| if (!sysctl.ctr[i].enabled) |
| continue; |
| |
| enabled = 1; |
| |
| if (!sysctl.ctr[i].user && !sysctl.ctr[i].kernel) { |
| printk(KERN_ERR "oprofile: neither kernel nor user " |
| "set for counter %d\n", i); |
| return -EINVAL; |
| } |
| |
| if (check_range(sysctl.ctr[i].count, 1, OP_MAX_PERF_COUNT, |
| "ctr count value %d not in range (%d %ld)\n")) |
| return -EINVAL; |
| |
| } |
| |
| if (!enabled) { |
| printk(KERN_ERR "oprofile: no counters have been enabled.\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| |
| static void free_msr_group(struct op_msr_group * group) |
| { |
| if (group->addrs) |
| kfree(group->addrs); |
| if (group->saved) |
| kfree(group->saved); |
| group->addrs = NULL; |
| group->saved = NULL; |
| } |
| |
| |
| static void pmc_save_registers(void * dummy) |
| { |
| uint i; |
| uint const cpu = op_cpu_id(); |
| uint const nr_ctrs = get_model()->num_counters; |
| uint const nr_ctrls = get_model()->num_controls; |
| struct op_msr_group * counters = &cpu_msrs[cpu].counters; |
| struct op_msr_group * controls = &cpu_msrs[cpu].controls; |
| |
| counters->addrs = NULL; |
| counters->saved = NULL; |
| controls->addrs = NULL; |
| controls->saved = NULL; |
| |
| counters->addrs = kmalloc(nr_ctrs * sizeof(uint), GFP_KERNEL); |
| if (!counters->addrs) |
| goto fault; |
| |
| counters->saved = kmalloc( |
| nr_ctrs * sizeof(struct op_saved_msr), GFP_KERNEL); |
| if (!counters->saved) |
| goto fault; |
| |
| controls->addrs = kmalloc(nr_ctrls * sizeof(uint), GFP_KERNEL); |
| if (!controls->addrs) |
| goto fault; |
| |
| controls->saved = kmalloc( |
| nr_ctrls * sizeof(struct op_saved_msr), GFP_KERNEL); |
| if (!controls->saved) |
| goto fault; |
| |
| model->fill_in_addresses(&cpu_msrs[cpu]); |
| |
| for (i = 0; i < nr_ctrs; ++i) { |
| rdmsr(counters->addrs[i], |
| counters->saved[i].low, |
| counters->saved[i].high); |
| } |
| |
| for (i = 0; i < nr_ctrls; ++i) { |
| rdmsr(controls->addrs[i], |
| controls->saved[i].low, |
| controls->saved[i].high); |
| } |
| return; |
| |
| fault: |
| free_msr_group(counters); |
| free_msr_group(controls); |
| } |
| |
| |
| static void pmc_restore_registers(void * dummy) |
| { |
| uint i; |
| uint const cpu = op_cpu_id(); |
| uint const nr_ctrs = get_model()->num_counters; |
| uint const nr_ctrls = get_model()->num_controls; |
| struct op_msr_group * counters = &cpu_msrs[cpu].counters; |
| struct op_msr_group * controls = &cpu_msrs[cpu].controls; |
| |
| if (controls->addrs) { |
| for (i = 0; i < nr_ctrls; ++i) { |
| wrmsr(controls->addrs[i], |
| controls->saved[i].low, |
| controls->saved[i].high); |
| } |
| } |
| |
| if (counters->addrs) { |
| for (i = 0; i < nr_ctrs; ++i) { |
| wrmsr(counters->addrs[i], |
| counters->saved[i].low, |
| counters->saved[i].high); |
| } |
| } |
| |
| free_msr_group(counters); |
| free_msr_group(controls); |
| } |
| |
| |
| static int pmc_init(void) |
| { |
| int err = 0; |
| |
| if ((err = smp_call_function(pmc_save_registers, NULL, 0, 1))) |
| goto out; |
| |
| pmc_save_registers(NULL); |
| |
| if ((err = apic_setup())) |
| goto out_restore; |
| |
| if ((err = smp_call_function(lvtpc_apic_setup, NULL, 0, 1))) { |
| lvtpc_apic_restore(NULL); |
| goto out_restore; |
| } |
| |
| out: |
| return err; |
| out_restore: |
| smp_call_function(pmc_restore_registers, NULL, 0, 1); |
| pmc_restore_registers(NULL); |
| goto out; |
| } |
| |
| |
| static void pmc_deinit(void) |
| { |
| smp_call_function(lvtpc_apic_restore, NULL, 0, 1); |
| lvtpc_apic_restore(NULL); |
| |
| apic_restore(); |
| |
| smp_call_function(pmc_restore_registers, NULL, 0, 1); |
| pmc_restore_registers(NULL); |
| } |
| |
| |
| static char * names[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8"}; |
| |
| static int pmc_add_sysctls(ctl_table * next) |
| { |
| ctl_table * start = next; |
| ctl_table * tab; |
| int i, j; |
| |
| /* now init the sysctls */ |
| for (i=0; i < get_model()->num_counters; i++) { |
| next->ctl_name = 1; |
| next->procname = names[i]; |
| next->mode = 0755; |
| |
| if (!(tab = kmalloc(sizeof(ctl_table)*7, GFP_KERNEL))) |
| goto cleanup; |
| |
| next->child = tab; |
| |
| memset(tab, 0, sizeof(ctl_table)*7); |
| tab[0] = ((ctl_table) { 1, "enabled", &sysctl_parms.ctr[i].enabled, sizeof(int), 0644, NULL, lproc_dointvec, NULL, }); |
| tab[1] = ((ctl_table) { 1, "event", &sysctl_parms.ctr[i].event, sizeof(int), 0644, NULL, lproc_dointvec, NULL, }); |
| tab[2] = ((ctl_table) { 1, "count", &sysctl_parms.ctr[i].count, sizeof(int), 0644, NULL, lproc_dointvec, NULL, }); |
| tab[3] = ((ctl_table) { 1, "unit_mask", &sysctl_parms.ctr[i].unit_mask, sizeof(int), 0644, NULL, lproc_dointvec, NULL, }); |
| tab[4] = ((ctl_table) { 1, "kernel", &sysctl_parms.ctr[i].kernel, sizeof(int), 0644, NULL, lproc_dointvec, NULL, }); |
| tab[5] = ((ctl_table) { 1, "user", &sysctl_parms.ctr[i].user, sizeof(int), 0644, NULL, lproc_dointvec, NULL, }); |
| next++; |
| } |
| |
| return 0; |
| |
| cleanup: |
| next = start; |
| for (j = 0; j < i; j++) { |
| kfree(next->child); |
| next++; |
| } |
| return -EFAULT; |
| } |
| |
| |
| static void pmc_remove_sysctls(ctl_table * next) |
| { |
| int i; |
| for (i=0; i < get_model()->num_counters; i++) { |
| kfree(next->child); |
| next++; |
| } |
| } |
| |
| |
| static struct op_int_operations op_nmi_ops = { |
| init: pmc_init, |
| deinit: pmc_deinit, |
| add_sysctls: pmc_add_sysctls, |
| remove_sysctls: pmc_remove_sysctls, |
| check_params: pmc_check_params, |
| setup: pmc_setup_all, |
| start: pmc_start_all, |
| stop: pmc_stop_all, |
| start_cpu: pmc_select_start, |
| stop_cpu: pmc_select_stop, |
| }; |
| |
| |
| struct op_int_operations const * op_int_interface() |
| { |
| return &op_nmi_ops; |
| } |