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