/* * simulate independent hardware watch-dog timer * using local cpu timers and NMIs, one watch-dog per system. */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #include "../port/netif.h" #include "mp.h" typedef struct Wd Wd; struct Wd { Lock; int model; int inuse; uint ticks; }; static Wd x86wd; enum { P6 = 0, /* Pentium Pro/II/III */ P4 = 1, /* P4 */ K6 = 2, /* Athlon */ K8 = 3, /* AMD64 */ Twogigs = 1ul << 31, }; /* * return an interval in cycles of about a second, or as long as * will fit in 31 bits. */ static long interval(void) { if (m->cpuhz > Twogigs - 1) return Twogigs - 1; else return m->cpuhz; } static void runoncpu(int cpu) { if (m->machno != cpu) { if (up == nil) panic("x86watchdog: nil up"); procwired(up, cpu); sched(); if (m->machno != cpu) panic("x86watchdog: runoncpu: can't switch to cpu%d", cpu); } } static void x86wdenable(void) { Wd *wd; vlong r, t; int i, model; u32int evntsel; wd = &x86wd; ilock(wd); if(wd->inuse){ iunlock(wd); error(Einuse); } iunlock(wd); /* * keep this process on cpu 0 so we always see the same timers * and so that this will work even if all other cpus are shut down. */ runoncpu(0); /* * Check the processor is capable of doing performance * monitoring and that it has TSC, RDMSR/WRMSR and a local APIC. */ model = -1; if(strncmp(m->cpuidid, "AuthenticAMD", 12) == 0){ if(X86FAMILY(m->cpuidax) == 0x06) model = K6; else if(X86FAMILY(m->cpuidax) == 0x0F) model = K8; } else if(strncmp(m->cpuidid, "GenuineIntel", 12) == 0){ if(X86FAMILY(m->cpuidax) == 0x06) model = P6; else if(X86FAMILY(m->cpuidax) == 0x0F) model = P4; } if(model == -1 || (m->cpuiddx & (Cpuapic|Cpumsr|Tsc)) != (Cpuapic|Cpumsr|Tsc)) error(Enodev); ilock(wd); if(wd->inuse){ iunlock(wd); error(Einuse); } wd->model = model; wd->inuse = 1; wd->ticks = 0; /* * See the IA-32 Intel Architecture Software * Developer's Manual Volume 3: System Programming Guide, * Chapter 15 and the AMD equivalent for what all this * bit-whacking means. */ t = interval(); switch(model){ case P6: wrmsr(0x186, 0); /* evntsel */ wrmsr(0x187, 0); wrmsr(0xC1, 0); /* perfctr */ wrmsr(0xC2, 0); lapicnmienable(); evntsel = 0x00130000|0x79; wrmsr(0xC1, -t); wrmsr(0x186, 0x00400000|evntsel); break; case P4: rdmsr(0x1A0, &r); if(!(r & 0x0000000000000080LL)) return; for(i = 0; i < 18; i++) wrmsr(0x300+i, 0); /* perfctr */ for(i = 0; i < 18; i++) wrmsr(0x360+i, 0); /* ccr */ for(i = 0; i < 31; i++) wrmsr(0x3A0+i, 0); /* escr */ for(i = 0; i < 6; i++) wrmsr(0x3C0+i, 0); /* escr */ for(i = 0; i < 6; i++) wrmsr(0x3C8+i, 0); /* escr */ for(i = 0; i < 2; i++) wrmsr(0x3E0+i, 0); /* escr */ if(!(r & 0x0000000000001000LL)){ for(i = 0; i < 2; i++) wrmsr(0x3F1+i, 0); /* pebs */ } lapicnmienable(); wrmsr(0x3B8, 0x000000007E00000CLL); /* escr0 */ r = 0x0000000004FF8000ULL; wrmsr(0x36C, r); /* cccr0 */ wrmsr(0x30C, -t); wrmsr(0x36C, 0x0000000000001000LL|r); break; case K6: case K8: /* * PerfEvtSel 0-3, PerfCtr 0-4. */ for(i = 0; i < 8; i++) wrmsr(0xC0010000+i, 0); lapicnmienable(); evntsel = 0x00130000|0x76; wrmsr(0xC0010004, -t); wrmsr(0xC0010000, 0x00400000|evntsel); break; } iunlock(wd); } static void x86wddisable(void) { Wd *wd; wd = &x86wd; ilock(wd); if(!wd->inuse){ /* * Can't error, called at boot by addwatchdog(). */ iunlock(wd); return; } iunlock(wd); runoncpu(0); ilock(wd); lapicnmidisable(); switch(wd->model){ case P6: wrmsr(0x186, 0); break; case P4: wrmsr(0x36C, 0); /* cccr0 */ wrmsr(0x3B8, 0); /* escr0 */ break; case K6: case K8: wrmsr(0xC0010000, 0); break; } wd->inuse = 0; iunlock(wd); } static void x86wdrestart(void) { Wd *wd; vlong r, t; runoncpu(0); t = interval(); wd = &x86wd; ilock(wd); switch(wd->model){ case P6: wrmsr(0xC1, -t); break; case P4: r = 0x0000000004FF8000LL; wrmsr(0x36C, r); lapicnmienable(); wrmsr(0x30C, -t); wrmsr(0x36C, 0x0000000000001000LL|r); break; case K6: case K8: wrmsr(0xC0010004, -t); break; } wd->ticks++; iunlock(wd); } void x86wdstat(char* p, char* ep) { Wd *wd; int inuse; uint ticks; wd = &x86wd; ilock(wd); inuse = wd->inuse; ticks = wd->ticks; iunlock(wd); if(inuse) seprint(p, ep, "enabled %ud restarts\n", ticks); else seprint(p, ep, "disabled %ud restarts\n", ticks); } Watchdog x86watchdog = { x86wdenable, x86wddisable, x86wdrestart, x86wdstat, }; void x86watchdoglink(void) { addwatchdog(&x86watchdog); }