/**
 * @file op_apic.c
 *
 * APIC setup etc. routines
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 * @author Dave Jones
 * @author Graydon Hoare
 */

#include <linux/mm.h>
#include <linux/init.h>
#include <linux/config.h>
#include <asm/io.h>

#include "oprofile.h"
#include "op_msr.h"
#include "op_apic.h"

/* used to save/restore original kernel nmi */
static struct gate_struct kernel_nmi;
static ulong lvtpc_masked;

/* this masking code is unsafe and nasty but might deal with the small
 * race when installing the NMI entry into the IDT.
 */
static void mask_lvtpc(void * e)
{
	u32 v = apic_read(APIC_LVTPC);
	lvtpc_masked = v & APIC_LVT_MASKED;
	apic_write(APIC_LVTPC, v | APIC_LVT_MASKED);
}

static void unmask_lvtpc(void * e)
{
	if (!lvtpc_masked)
		apic_write(APIC_LVTPC, apic_read(APIC_LVTPC) & ~APIC_LVT_MASKED);
}


void install_nmi(void)
{
	struct _descr descr;

	/* NMI handler is at idt_table[IDT_VECTOR_NUMBER]            */
	/* see Intel Vol.3 Figure 5-2, interrupt gate                */

	smp_call_function(mask_lvtpc, NULL, 0, 1);
	mask_lvtpc(NULL);

	store_idt(descr);
	kernel_nmi = descr.base[NMI_VECTOR_NUM];
	SET_NMI_GATE;

	smp_call_function(unmask_lvtpc, NULL, 0, 1);
	unmask_lvtpc(NULL);
}

void restore_nmi(void)
{
	struct _descr descr;

	smp_call_function(mask_lvtpc, NULL, 0, 1);
	mask_lvtpc(NULL);

	store_idt(descr);
	descr.base[NMI_VECTOR_NUM] = kernel_nmi;

	smp_call_function(unmask_lvtpc, NULL, 0, 1);
	unmask_lvtpc(NULL);
}


/* ---------------- APIC setup ------------------ */
static uint saved_lvtpc[NR_CPUS];

void __init lvtpc_apic_setup(void * dummy)
{
	uint val;

	/* set up LVTPC as we need it */
	/* IA32 V3, Figure 7.8 */
	val = apic_read(APIC_LVTPC);
	saved_lvtpc[op_cpu_id()] = val;
	/* allow PC overflow interrupts */
	val &= ~APIC_LVT_MASKED;
	/* set delivery to NMI */
	val = SET_APIC_DELIVERY_MODE(val, APIC_MODE_NMI);
	apic_write(APIC_LVTPC, val);
}

/* not safe to mark as __exit since used from __init code */
void lvtpc_apic_restore(void * dummy)
{
	/* restoring APIC_LVTPC can trigger an apic error because the delivery
	 * mode and vector nr combination can be illegal. That's by design: on
	 * power on apic lvt contain a zero vector nr which are legal only for
	 * NMI delivrey mode. So inhibit apic err before restoring lvtpc
	 */
	uint v = apic_read(APIC_LVTERR);
	apic_write(APIC_LVTERR, v | APIC_LVT_MASKED);
	apic_write(APIC_LVTPC, saved_lvtpc[op_cpu_id()]);
	apic_write(APIC_LVTERR, v);
}

static int __init enable_apic(void)
{
	uint msr_low, msr_high;
	uint val;

	/* enable local APIC via MSR. Forgetting this is a fun way to
	 * lock the box. But we have to hope this is allowed if the APIC
	 * has already been enabled.
	 *
	 * IA32 V3, 7.4.2 */
	rdmsr(MSR_IA32_APICBASE, msr_low, msr_high);
	if ((msr_low & (1 << 11)) == 0)
		wrmsr(MSR_IA32_APICBASE, msr_low | (1 << 11), msr_high);

	/* even if the apic is up we must check for a good APIC */

	/* IA32 V3, 7.4.15 */
	val = apic_read(APIC_LVR);
	if (!APIC_INTEGRATED(GET_APIC_VERSION(val)))
		goto not_local_apic;

	/* LVT0,LVT1,LVTT,LVTPC */
	if (GET_APIC_MAXLVT(apic_read(APIC_LVR)) < 4)
		goto not_local_apic;

	/* IA32 V3, 7.4.14.1 */
	val = apic_read(APIC_SPIV);
	if (!(val & APIC_SPIV_APIC_ENABLED))
		apic_write(APIC_SPIV, val | APIC_SPIV_APIC_ENABLED);

	return !!(val & APIC_SPIV_APIC_ENABLED);

not_local_apic:
	/* disable the apic only if it was disabled */
	if ((msr_low & (1 << 11)) == 0)
		wrmsr(MSR_IA32_APICBASE, msr_low & ~(1 << 11), msr_high);

	printk(KERN_ERR "oprofile: no suitable local APIC. Falling back to RTC mode.\n");
	return -ENODEV;
}

static void __init do_apic_setup(void)
{
	uint val;

	local_irq_disable();

	val = APIC_LVT_LEVEL_TRIGGER;
	val = SET_APIC_DELIVERY_MODE(val, APIC_MODE_EXINT);
	apic_write(APIC_LVT0, val);

	/* edge triggered, IA 7.4.11 */
	val = SET_APIC_DELIVERY_MODE(0, APIC_MODE_NMI);
	apic_write(APIC_LVT1, val);

	/* clear error register */
	/* IA32 V3, 7.4.17 */
	/* PHE must be cleared after unmasking by a back-to-back write,
	 * but it is probably ok because we mask only, the ESR is not
	 * updated is this a real problem ? */
	apic_write(APIC_ESR, 0);

	/* mask error interrupt */
	/* IA32 V3, Figure 7.8 */
	val = apic_read(APIC_LVTERR);
	val |= APIC_LVT_MASKED;
	apic_write(APIC_LVTERR, val);

	/* setup timer vector */
	/* IA32 V3, 7.4.8 */
	apic_write(APIC_LVTT, APIC_SEND_PENDING | 0x31);

	/* Divide configuration register */
	/* PHE the apic clock is based on the FSB. This should only
	 * changed with a calibration method.  */
	val = APIC_TDR_DIV_1;
	apic_write(APIC_TDCR, val);

	local_irq_enable();
}

/* does the CPU have a local APIC ? */
static int __init check_cpu_ok(void)
{
	if (sysctl.cpu_type != CPU_PPRO &&
		sysctl.cpu_type != CPU_PII &&
		sysctl.cpu_type != CPU_PIII &&
		sysctl.cpu_type != CPU_ATHLON &&
		sysctl.cpu_type != CPU_HAMMER &&
		sysctl.cpu_type != CPU_P4 &&
		sysctl.cpu_type != CPU_P4_HT2)
		return 0;

	return 1;
}

int __init apic_setup(void)
{
	u32 val;

	if (!check_cpu_ok())
		goto nodev;

	fixmap_setup();

	switch (enable_apic()) {
		case 0:
			do_apic_setup();
			val = apic_read(APIC_ESR);
			printk(KERN_INFO "oprofile: enabled local APIC. Err code %.08x\n", val);
			break;
		case 1:
			printk(KERN_INFO "oprofile: APIC was already enabled\n");
			break;
		default:
			goto nodev;
	}

	lvtpc_apic_setup(NULL);
	return 0;
nodev:
	printk(KERN_WARNING "Your CPU does not have a local APIC, e.g. "
	       "mobile P6. Falling back to RTC mode.\n");
	return -ENODEV;
}

void apic_restore(void)
{
	fixmap_restore();
}