nmi.c
上传用户:lgb322
上传日期:2013-02-24
资源大小:30529k
文件大小:7k
源码类别:

嵌入式Linux

开发平台:

Unix_Linux

  1. /*
  2.  *  linux/arch/i386/nmi.c
  3.  *
  4.  *  NMI watchdog support on APIC systems
  5.  *
  6.  *  Started by Ingo Molnar <mingo@redhat.com>
  7.  *
  8.  *  Fixes:
  9.  *  Mikael Pettersson : AMD K7 support for local APIC NMI watchdog.
  10.  *  Mikael Pettersson : Power Management for local APIC NMI watchdog.
  11.  */
  12. #include <linux/config.h>
  13. #include <linux/mm.h>
  14. #include <linux/irq.h>
  15. #include <linux/delay.h>
  16. #include <linux/bootmem.h>
  17. #include <linux/smp_lock.h>
  18. #include <linux/interrupt.h>
  19. #include <linux/mc146818rtc.h>
  20. #include <linux/kernel_stat.h>
  21. #include <asm/smp.h>
  22. #include <asm/mtrr.h>
  23. #include <asm/mpspec.h>
  24. unsigned int nmi_watchdog = NMI_NONE;
  25. static unsigned int nmi_hz = HZ;
  26. unsigned int nmi_perfctr_msr; /* the MSR to reset in NMI handler */
  27. extern void show_registers(struct pt_regs *regs);
  28. #define K7_EVNTSEL_ENABLE (1 << 22)
  29. #define K7_EVNTSEL_INT (1 << 20)
  30. #define K7_EVNTSEL_OS (1 << 17)
  31. #define K7_EVNTSEL_USR (1 << 16)
  32. #define K7_EVENT_CYCLES_PROCESSOR_IS_RUNNING 0x76
  33. #define K7_NMI_EVENT K7_EVENT_CYCLES_PROCESSOR_IS_RUNNING
  34. #define P6_EVNTSEL0_ENABLE (1 << 22)
  35. #define P6_EVNTSEL_INT (1 << 20)
  36. #define P6_EVNTSEL_OS (1 << 17)
  37. #define P6_EVNTSEL_USR (1 << 16)
  38. #define P6_EVENT_CPU_CLOCKS_NOT_HALTED 0x79
  39. #define P6_NMI_EVENT P6_EVENT_CPU_CLOCKS_NOT_HALTED
  40. int __init check_nmi_watchdog (void)
  41. {
  42. irq_cpustat_t tmp[NR_CPUS];
  43. int j, cpu;
  44. printk(KERN_INFO "testing NMI watchdog ... ");
  45. memcpy(tmp, irq_stat, sizeof(tmp));
  46. sti();
  47. mdelay((10*1000)/nmi_hz); // wait 10 ticks
  48. for (j = 0; j < smp_num_cpus; j++) {
  49. cpu = cpu_logical_map(j);
  50. if (nmi_count(cpu) - tmp[cpu].__nmi_count <= 5) {
  51. printk("CPU#%d: NMI appears to be stuck!n", cpu);
  52. return -1;
  53. }
  54. }
  55. printk("OK.n");
  56. /* now that we know it works we can reduce NMI frequency to
  57.    something more reasonable; makes a difference in some configs */
  58. if (nmi_watchdog == NMI_LOCAL_APIC)
  59. nmi_hz = 1;
  60. return 0;
  61. }
  62. static int __init setup_nmi_watchdog(char *str)
  63. {
  64. int nmi;
  65. get_option(&str, &nmi);
  66. if (nmi >= NMI_INVALID)
  67. return 0;
  68. if (nmi == NMI_NONE)
  69. nmi_watchdog = nmi;
  70. /*
  71.  * If any other x86 CPU has a local APIC, then
  72.  * please test the NMI stuff there and send me the
  73.  * missing bits. Right now Intel P6 and AMD K7 only.
  74.  */
  75. if ((nmi == NMI_LOCAL_APIC) &&
  76. (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL) &&
  77. (boot_cpu_data.x86 == 6))
  78. nmi_watchdog = nmi;
  79. if ((nmi == NMI_LOCAL_APIC) &&
  80. (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) &&
  81. (boot_cpu_data.x86 == 6))
  82. nmi_watchdog = nmi;
  83. /*
  84.  * We can enable the IO-APIC watchdog
  85.  * unconditionally.
  86.  */
  87. if (nmi == NMI_IO_APIC)
  88. nmi_watchdog = nmi;
  89. return 1;
  90. }
  91. __setup("nmi_watchdog=", setup_nmi_watchdog);
  92. #ifdef CONFIG_PM
  93. #include <linux/pm.h>
  94. struct pm_dev *nmi_pmdev;
  95. static void disable_apic_nmi_watchdog(void)
  96. {
  97. switch (boot_cpu_data.x86_vendor) {
  98. case X86_VENDOR_AMD:
  99. wrmsr(MSR_K7_EVNTSEL0, 0, 0);
  100. break;
  101. case X86_VENDOR_INTEL:
  102. wrmsr(MSR_IA32_EVNTSEL0, 0, 0);
  103. break;
  104. }
  105. }
  106. static int nmi_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data)
  107. {
  108. switch (rqst) {
  109. case PM_SUSPEND:
  110. disable_apic_nmi_watchdog();
  111. break;
  112. case PM_RESUME:
  113. setup_apic_nmi_watchdog();
  114. break;
  115. }
  116. return 0;
  117. }
  118. static void nmi_pm_init(void)
  119. {
  120. if (!nmi_pmdev)
  121. nmi_pmdev = apic_pm_register(PM_SYS_DEV, 0, nmi_pm_callback);
  122. }
  123. #define __pminit /*empty*/
  124. #else /* CONFIG_PM */
  125. static inline void nmi_pm_init(void) { }
  126. #define __pminit __init
  127. #endif /* CONFIG_PM */
  128. /*
  129.  * Activate the NMI watchdog via the local APIC.
  130.  * Original code written by Keith Owens.
  131.  */
  132. static void __pminit setup_k7_watchdog(void)
  133. {
  134. int i;
  135. unsigned int evntsel;
  136. nmi_perfctr_msr = MSR_K7_PERFCTR0;
  137. for(i = 0; i < 4; ++i) {
  138. wrmsr(MSR_K7_EVNTSEL0+i, 0, 0);
  139. wrmsr(MSR_K7_PERFCTR0+i, 0, 0);
  140. }
  141. evntsel = K7_EVNTSEL_INT
  142. | K7_EVNTSEL_OS
  143. | K7_EVNTSEL_USR
  144. | K7_NMI_EVENT;
  145. wrmsr(MSR_K7_EVNTSEL0, evntsel, 0);
  146. Dprintk("setting K7_PERFCTR0 to %08lxn", -(cpu_khz/nmi_hz*1000));
  147. wrmsr(MSR_K7_PERFCTR0, -(cpu_khz/nmi_hz*1000), -1);
  148. apic_write(APIC_LVTPC, APIC_DM_NMI);
  149. evntsel |= K7_EVNTSEL_ENABLE;
  150. wrmsr(MSR_K7_EVNTSEL0, evntsel, 0);
  151. }
  152. static void __pminit setup_p6_watchdog(void)
  153. {
  154. int i;
  155. unsigned int evntsel;
  156. nmi_perfctr_msr = MSR_IA32_PERFCTR0;
  157. for(i = 0; i < 2; ++i) {
  158. wrmsr(MSR_IA32_EVNTSEL0+i, 0, 0);
  159. wrmsr(MSR_IA32_PERFCTR0+i, 0, 0);
  160. }
  161. evntsel = P6_EVNTSEL_INT
  162. | P6_EVNTSEL_OS
  163. | P6_EVNTSEL_USR
  164. | P6_NMI_EVENT;
  165. wrmsr(MSR_IA32_EVNTSEL0, evntsel, 0);
  166. Dprintk("setting IA32_PERFCTR0 to %08lxn", -(cpu_khz/nmi_hz*1000));
  167. wrmsr(MSR_IA32_PERFCTR0, -(cpu_khz/nmi_hz*1000), 0);
  168. apic_write(APIC_LVTPC, APIC_DM_NMI);
  169. evntsel |= P6_EVNTSEL0_ENABLE;
  170. wrmsr(MSR_IA32_EVNTSEL0, evntsel, 0);
  171. }
  172. void __pminit setup_apic_nmi_watchdog (void)
  173. {
  174. switch (boot_cpu_data.x86_vendor) {
  175. case X86_VENDOR_AMD:
  176. if (boot_cpu_data.x86 != 6)
  177. return;
  178. setup_k7_watchdog();
  179. break;
  180. case X86_VENDOR_INTEL:
  181. if (boot_cpu_data.x86 != 6)
  182. return;
  183. setup_p6_watchdog();
  184. break;
  185. default:
  186. return;
  187. }
  188. nmi_pm_init();
  189. }
  190. static spinlock_t nmi_print_lock = SPIN_LOCK_UNLOCKED;
  191. /*
  192.  * the best way to detect whether a CPU has a 'hard lockup' problem
  193.  * is to check it's local APIC timer IRQ counts. If they are not
  194.  * changing then that CPU has some problem.
  195.  *
  196.  * as these watchdog NMI IRQs are generated on every CPU, we only
  197.  * have to check the current processor.
  198.  *
  199.  * since NMIs dont listen to _any_ locks, we have to be extremely
  200.  * careful not to rely on unsafe variables. The printk might lock
  201.  * up though, so we have to break up any console locks first ...
  202.  * [when there will be more tty-related locks, break them up
  203.  *  here too!]
  204.  */
  205. static unsigned int
  206. last_irq_sums [NR_CPUS],
  207. alert_counter [NR_CPUS];
  208. void touch_nmi_watchdog (void)
  209. {
  210. int i;
  211. /*
  212.  * Just reset the alert counters, (other CPUs might be
  213.  * spinning on locks we hold):
  214.  */
  215. for (i = 0; i < smp_num_cpus; i++)
  216. alert_counter[i] = 0;
  217. }
  218. void nmi_watchdog_tick (struct pt_regs * regs)
  219. {
  220. /*
  221.  * Since current-> is always on the stack, and we always switch
  222.  * the stack NMI-atomically, it's safe to use smp_processor_id().
  223.  */
  224. int sum, cpu = smp_processor_id();
  225. sum = apic_timer_irqs[cpu];
  226. if (last_irq_sums[cpu] == sum) {
  227. /*
  228.  * Ayiee, looks like this CPU is stuck ...
  229.  * wait a few IRQs (5 seconds) before doing the oops ...
  230.  */
  231. alert_counter[cpu]++;
  232. if (alert_counter[cpu] == 5*nmi_hz) {
  233. spin_lock(&nmi_print_lock);
  234. /*
  235.  * We are in trouble anyway, lets at least try
  236.  * to get a message out.
  237.  */
  238. bust_spinlocks(1);
  239. printk("NMI Watchdog detected LOCKUP on CPU%d, registers:n", cpu);
  240. show_registers(regs);
  241. printk("console shuts up ...n");
  242. console_silent();
  243. spin_unlock(&nmi_print_lock);
  244. bust_spinlocks(0);
  245. do_exit(SIGSEGV);
  246. }
  247. } else {
  248. last_irq_sums[cpu] = sum;
  249. alert_counter[cpu] = 0;
  250. }
  251. if (nmi_perfctr_msr)
  252. wrmsr(nmi_perfctr_msr, -(cpu_khz/nmi_hz*1000), -1);
  253. }