[PATCH] Pentium4 local APIC NMI watchdog for 2.4.15-pre3

Mikael Pettersson (mikpe@csd.uu.se)
Sun, 11 Nov 2001 23:38:02 +0100 (MET)


This patch adds local APIC NMI watchdog support for the Pentium4,
in a manner similar to the existing support for P6 and K7.
The patch also includes two minor cleanups:
- replaced a number of for() loops to clear MSRs in nmi.c
by calls to a new local procedure
- renamed MSR_IA32_PERFCTR0/PERFCTR1/EVNTSEL0/EVNTSEL1 to
MSR_P6_PERFCTR0/PERFCTR1/EVNTSEL0/EVNTSEL1 since these MSRs
are P6-specific

/Mikael

--- linux-2.4.15-pre3-p4watchdog/arch/i386/kernel/nmi.c.~1~ Sun Sep 23 21:06:30 2001
+++ linux-2.4.15-pre3-p4watchdog/arch/i386/kernel/nmi.c Sun Nov 11 22:58:00 2001
@@ -8,6 +8,7 @@
* Fixes:
* Mikael Pettersson : AMD K7 support for local APIC NMI watchdog.
* Mikael Pettersson : Power Management for local APIC NMI watchdog.
+ * Mikael Pettersson : Pentium 4 support for local APIC NMI watchdog.
*/

#include <linux/config.h>
@@ -25,6 +26,7 @@
#include <asm/mpspec.h>

unsigned int nmi_watchdog = NMI_NONE;
+static unsigned char nmi_is_p4;
static unsigned int nmi_hz = HZ;
unsigned int nmi_perfctr_msr; /* the MSR to reset in NMI handler */
extern void show_registers(struct pt_regs *regs);
@@ -43,6 +45,31 @@
#define P6_EVENT_CPU_CLOCKS_NOT_HALTED 0x79
#define P6_NMI_EVENT P6_EVENT_CPU_CLOCKS_NOT_HALTED

+#define MSR_P4_MISC_ENABLE 0x1A0
+#define MSR_P4_MISC_ENABLE_PMAVAIL (1<<7)
+#define MSR_P4_PERFCTR0 0x300
+#define MSR_P4_CCCR0 0x360
+#define P4_ESCR_EVENT_SELECT(N) ((N)<<25)
+#define P4_ESCR_OS (1<<3)
+#define P4_ESCR_USR (1<<2)
+#define P4_CCCR_OVF_PMI (1<<26)
+#define P4_CCCR_THRESHOLD(N) ((N)<<20)
+#define P4_CCCR_COMPLEMENT (1<<19)
+#define P4_CCCR_COMPARE (1<<18)
+#define P4_CCCR_REQUIRED (3<<16)
+#define P4_CCCR_ESCR_SELECT(N) ((N)<<13)
+#define P4_CCCR_ENABLE (1<<12)
+/* Set up IQ_COUNTER0 to behave like a clock, by having IQ_CCCR0 filter
+ CRU_ESCR0 (with any non-null event selector) through a complemented
+ max threshold. [IA32-Vol3, Section 14.9.9] */
+#define MSR_P4_IQ_COUNTER0 0x30C
+#define MSR_P4_IQ_CCCR0 0x36C
+#define MSR_P4_CRU_ESCR0 0x3B8
+#define P4_NMI_CRU_ESCR0 (P4_ESCR_EVENT_SELECT(0x3F)|P4_ESCR_OS|P4_ESCR_USR)
+#define P4_NMI_IQ_CCCR0 \
+ (P4_CCCR_OVF_PMI|P4_CCCR_THRESHOLD(15)|P4_CCCR_COMPLEMENT| \
+ P4_CCCR_COMPARE|P4_CCCR_REQUIRED|P4_CCCR_ESCR_SELECT(4)|P4_CCCR_ENABLE)
+
int __init check_nmi_watchdog (void)
{
irq_cpustat_t tmp[NR_CPUS];
@@ -84,11 +111,11 @@
/*
* If any other x86 CPU has a local APIC, then
* please test the NMI stuff there and send me the
- * missing bits. Right now Intel P6 and AMD K7 only.
+ * missing bits. Right now Intel P6/P4 and AMD K7 only.
*/
if ((nmi == NMI_LOCAL_APIC) &&
(boot_cpu_data.x86_vendor == X86_VENDOR_INTEL) &&
- (boot_cpu_data.x86 == 6))
+ (boot_cpu_data.x86 == 6 || boot_cpu_data.x86 == 15))
nmi_watchdog = nmi;
if ((nmi == NMI_LOCAL_APIC) &&
(boot_cpu_data.x86_vendor == X86_VENDOR_AMD) &&
@@ -118,7 +145,15 @@
wrmsr(MSR_K7_EVNTSEL0, 0, 0);
break;
case X86_VENDOR_INTEL:
- wrmsr(MSR_IA32_EVNTSEL0, 0, 0);
+ switch (boot_cpu_data.x86) {
+ case 6:
+ wrmsr(MSR_P6_EVNTSEL0, 0, 0);
+ break;
+ case 15:
+ wrmsr(MSR_P4_IQ_CCCR0, 0, 0);
+ wrmsr(MSR_P4_CRU_ESCR0, 0, 0);
+ break;
+ }
break;
}
}
@@ -157,17 +192,22 @@
* Original code written by Keith Owens.
*/

+static void __pminit clear_msr_range(unsigned int base, unsigned int n)
+{
+ unsigned int i;
+
+ for(i = 0; i < n; ++i)
+ wrmsr(base+i, 0, 0);
+}
+
static void __pminit setup_k7_watchdog(void)
{
- int i;
unsigned int evntsel;

nmi_perfctr_msr = MSR_K7_PERFCTR0;

- for(i = 0; i < 4; ++i) {
- wrmsr(MSR_K7_EVNTSEL0+i, 0, 0);
- wrmsr(MSR_K7_PERFCTR0+i, 0, 0);
- }
+ clear_msr_range(MSR_K7_EVNTSEL0, 4);
+ clear_msr_range(MSR_K7_PERFCTR0, 4);

evntsel = K7_EVNTSEL_INT
| K7_EVNTSEL_OS
@@ -184,27 +224,52 @@

static void __pminit setup_p6_watchdog(void)
{
- int i;
unsigned int evntsel;

- nmi_perfctr_msr = MSR_IA32_PERFCTR0;
+ nmi_perfctr_msr = MSR_P6_PERFCTR0;

- for(i = 0; i < 2; ++i) {
- wrmsr(MSR_IA32_EVNTSEL0+i, 0, 0);
- wrmsr(MSR_IA32_PERFCTR0+i, 0, 0);
- }
+ clear_msr_range(MSR_P6_EVNTSEL0, 2);
+ clear_msr_range(MSR_P6_PERFCTR0, 2);

evntsel = P6_EVNTSEL_INT
| P6_EVNTSEL_OS
| P6_EVNTSEL_USR
| P6_NMI_EVENT;

- wrmsr(MSR_IA32_EVNTSEL0, evntsel, 0);
- Dprintk("setting IA32_PERFCTR0 to %08lx\n", -(cpu_khz/nmi_hz*1000));
- wrmsr(MSR_IA32_PERFCTR0, -(cpu_khz/nmi_hz*1000), 0);
+ wrmsr(MSR_P6_EVNTSEL0, evntsel, 0);
+ Dprintk("setting P6_PERFCTR0 to %08lx\n", -(cpu_khz/nmi_hz*1000));
+ wrmsr(MSR_P6_PERFCTR0, -(cpu_khz/nmi_hz*1000), 0);
apic_write(APIC_LVTPC, APIC_DM_NMI);
evntsel |= P6_EVNTSEL0_ENABLE;
- wrmsr(MSR_IA32_EVNTSEL0, evntsel, 0);
+ wrmsr(MSR_P6_EVNTSEL0, evntsel, 0);
+}
+
+static int __pminit setup_p4_watchdog(void)
+{
+ unsigned int misc_enable, dummy;
+
+ rdmsr(MSR_P4_MISC_ENABLE, misc_enable, dummy);
+ if (!(misc_enable & MSR_P4_MISC_ENABLE_PMAVAIL))
+ return 0;
+
+ nmi_perfctr_msr = MSR_P4_IQ_COUNTER0;
+ nmi_is_p4 = 1;
+
+ clear_msr_range(MSR_P4_PERFCTR0, 18);
+ clear_msr_range(MSR_P4_CCCR0, 18);
+ clear_msr_range(0x3A0, (0x3BE - 0x3A0)+1);
+ clear_msr_range(0x3C0, (0x3C5 - 0x3C0)+1);
+ clear_msr_range(0x3C8, (0x3CD - 0x3C8)+1);
+ clear_msr_range(0x3E0, (0x3E1 - 0x3E0)+1);
+ /* XXX: should we also clear 0x3F0-0x3F2 ? */
+
+ wrmsr(MSR_P4_CRU_ESCR0, P4_NMI_CRU_ESCR0, 0);
+ wrmsr(MSR_P4_IQ_CCCR0, P4_NMI_IQ_CCCR0 & ~P4_CCCR_ENABLE, 0);
+ Dprintk("setting P4_IQ_COUNTER0 to 0x%08lx\n", -(cpu_khz/nmi_hz*1000));
+ wrmsr(MSR_P4_IQ_COUNTER0, -(cpu_khz/nmi_hz*1000), -1);
+ apic_write(APIC_LVTPC, APIC_DM_NMI);
+ wrmsr(MSR_P4_IQ_CCCR0, P4_NMI_IQ_CCCR0, 0);
+ return 1;
}

void __pminit setup_apic_nmi_watchdog (void)
@@ -216,9 +281,17 @@
setup_k7_watchdog();
break;
case X86_VENDOR_INTEL:
- if (boot_cpu_data.x86 != 6)
+ switch (boot_cpu_data.x86) {
+ case 6:
+ setup_p6_watchdog();
+ break;
+ case 15:
+ if (!setup_p4_watchdog())
+ return;
+ break;
+ default:
return;
- setup_p6_watchdog();
+ }
break;
default:
return;
@@ -295,6 +368,18 @@
last_irq_sums[cpu] = sum;
alert_counter[cpu] = 0;
}
- if (nmi_perfctr_msr)
+ if (nmi_perfctr_msr) {
+ if (nmi_is_p4) {
+ /*
+ * P4 quirks:
+ * - An overflown perfctr will assert its interrupt
+ * until the OVF flag in its CCCR is cleared.
+ * - LVTPC is masked on interrupt and must be
+ * unmasked by the LVTPC handler.
+ */
+ wrmsr(MSR_P4_IQ_CCCR0, P4_NMI_IQ_CCCR0, 0);
+ apic_write(APIC_LVTPC, APIC_DM_NMI);
+ }
wrmsr(nmi_perfctr_msr, -(cpu_khz/nmi_hz*1000), -1);
+ }
}
--- linux-2.4.15-pre3-p4watchdog/include/asm-i386/msr.h.~1~ Sun Sep 23 21:06:37 2001
+++ linux-2.4.15-pre3-p4watchdog/include/asm-i386/msr.h Sun Nov 11 22:58:00 2001
@@ -48,18 +48,12 @@
#define MSR_IA32_UCODE_WRITE 0x79
#define MSR_IA32_UCODE_REV 0x8b

-#define MSR_IA32_PERFCTR0 0xc1
-#define MSR_IA32_PERFCTR1 0xc2
-
#define MSR_IA32_BBL_CR_CTL 0x119

#define MSR_IA32_MCG_CAP 0x179
#define MSR_IA32_MCG_STATUS 0x17a
#define MSR_IA32_MCG_CTL 0x17b

-#define MSR_IA32_EVNTSEL0 0x186
-#define MSR_IA32_EVNTSEL1 0x187
-
#define MSR_IA32_DEBUGCTLMSR 0x1d9
#define MSR_IA32_LASTBRANCHFROMIP 0x1db
#define MSR_IA32_LASTBRANCHTOIP 0x1dc
@@ -71,6 +65,11 @@
#define MSR_IA32_MC0_ADDR 0x402
#define MSR_IA32_MC0_MISC 0x403

+#define MSR_P6_PERFCTR0 0xc1
+#define MSR_P6_PERFCTR1 0xc2
+#define MSR_P6_EVNTSEL0 0x186
+#define MSR_P6_EVNTSEL1 0x187
+
/* AMD Defined MSRs */
#define MSR_K6_EFER 0xC0000080
#define MSR_K6_STAR 0xC0000081
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/