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