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