/** * @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; }