|
@@ -0,0 +1,180 @@
|
|
|
+/*
|
|
|
+ * arch/x86/kernel/nmi-selftest.c
|
|
|
+ *
|
|
|
+ * Testsuite for NMI: IPIs
|
|
|
+ *
|
|
|
+ * Started by Don Zickus:
|
|
|
+ * (using lib/locking-selftest.c as a guide)
|
|
|
+ *
|
|
|
+ * Copyright (C) 2011 Red Hat, Inc., Don Zickus <dzickus@redhat.com>
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/smp.h>
|
|
|
+#include <linux/cpumask.h>
|
|
|
+#include <linux/delay.h>
|
|
|
+
|
|
|
+#include <asm/apic.h>
|
|
|
+#include <asm/nmi.h>
|
|
|
+
|
|
|
+#define SUCCESS 0
|
|
|
+#define FAILURE 1
|
|
|
+#define TIMEOUT 2
|
|
|
+
|
|
|
+static int nmi_fail;
|
|
|
+
|
|
|
+/* check to see if NMI IPIs work on this machine */
|
|
|
+static DECLARE_BITMAP(nmi_ipi_mask, NR_CPUS) __read_mostly;
|
|
|
+
|
|
|
+static int testcase_total;
|
|
|
+static int testcase_successes;
|
|
|
+static int expected_testcase_failures;
|
|
|
+static int unexpected_testcase_failures;
|
|
|
+static int unexpected_testcase_unknowns;
|
|
|
+
|
|
|
+static int nmi_unk_cb(unsigned int val, struct pt_regs *regs)
|
|
|
+{
|
|
|
+ unexpected_testcase_unknowns++;
|
|
|
+ return NMI_HANDLED;
|
|
|
+}
|
|
|
+
|
|
|
+static void init_nmi_testsuite(void)
|
|
|
+{
|
|
|
+ /* trap all the unknown NMIs we may generate */
|
|
|
+ register_nmi_handler(NMI_UNKNOWN, nmi_unk_cb, 0, "nmi_selftest_unk");
|
|
|
+}
|
|
|
+
|
|
|
+static void cleanup_nmi_testsuite(void)
|
|
|
+{
|
|
|
+ unregister_nmi_handler(NMI_UNKNOWN, "nmi_selftest_unk");
|
|
|
+}
|
|
|
+
|
|
|
+static int test_nmi_ipi_callback(unsigned int val, struct pt_regs *regs)
|
|
|
+{
|
|
|
+ int cpu = raw_smp_processor_id();
|
|
|
+
|
|
|
+ if (cpumask_test_and_clear_cpu(cpu, to_cpumask(nmi_ipi_mask)))
|
|
|
+ return NMI_HANDLED;
|
|
|
+
|
|
|
+ return NMI_DONE;
|
|
|
+}
|
|
|
+
|
|
|
+static void test_nmi_ipi(struct cpumask *mask)
|
|
|
+{
|
|
|
+ unsigned long timeout;
|
|
|
+
|
|
|
+ if (register_nmi_handler(NMI_LOCAL, test_nmi_ipi_callback,
|
|
|
+ NMI_FLAG_FIRST, "nmi_selftest")) {
|
|
|
+ nmi_fail = FAILURE;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* sync above data before sending NMI */
|
|
|
+ wmb();
|
|
|
+
|
|
|
+ apic->send_IPI_mask(mask, NMI_VECTOR);
|
|
|
+
|
|
|
+ /* Don't wait longer than a second */
|
|
|
+ timeout = USEC_PER_SEC;
|
|
|
+ while (!cpumask_empty(mask) && timeout--)
|
|
|
+ udelay(1);
|
|
|
+
|
|
|
+ /* What happens if we timeout, do we still unregister?? */
|
|
|
+ unregister_nmi_handler(NMI_LOCAL, "nmi_selftest");
|
|
|
+
|
|
|
+ if (!timeout)
|
|
|
+ nmi_fail = TIMEOUT;
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+static void remote_ipi(void)
|
|
|
+{
|
|
|
+ cpumask_copy(to_cpumask(nmi_ipi_mask), cpu_online_mask);
|
|
|
+ cpumask_clear_cpu(smp_processor_id(), to_cpumask(nmi_ipi_mask));
|
|
|
+ if (!cpumask_empty(to_cpumask(nmi_ipi_mask)))
|
|
|
+ test_nmi_ipi(to_cpumask(nmi_ipi_mask));
|
|
|
+}
|
|
|
+
|
|
|
+static void local_ipi(void)
|
|
|
+{
|
|
|
+ cpumask_clear(to_cpumask(nmi_ipi_mask));
|
|
|
+ cpumask_set_cpu(smp_processor_id(), to_cpumask(nmi_ipi_mask));
|
|
|
+ test_nmi_ipi(to_cpumask(nmi_ipi_mask));
|
|
|
+}
|
|
|
+
|
|
|
+static void reset_nmi(void)
|
|
|
+{
|
|
|
+ nmi_fail = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void dotest(void (*testcase_fn)(void), int expected)
|
|
|
+{
|
|
|
+ testcase_fn();
|
|
|
+ /*
|
|
|
+ * Filter out expected failures:
|
|
|
+ */
|
|
|
+ if (nmi_fail != expected) {
|
|
|
+ unexpected_testcase_failures++;
|
|
|
+
|
|
|
+ if (nmi_fail == FAILURE)
|
|
|
+ printk("FAILED |");
|
|
|
+ else if (nmi_fail == TIMEOUT)
|
|
|
+ printk("TIMEOUT|");
|
|
|
+ else
|
|
|
+ printk("ERROR |");
|
|
|
+ dump_stack();
|
|
|
+ } else {
|
|
|
+ testcase_successes++;
|
|
|
+ printk(" ok |");
|
|
|
+ }
|
|
|
+ testcase_total++;
|
|
|
+
|
|
|
+ reset_nmi();
|
|
|
+}
|
|
|
+
|
|
|
+static inline void print_testname(const char *testname)
|
|
|
+{
|
|
|
+ printk("%12s:", testname);
|
|
|
+}
|
|
|
+
|
|
|
+void nmi_selftest(void)
|
|
|
+{
|
|
|
+ init_nmi_testsuite();
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Run the testsuite:
|
|
|
+ */
|
|
|
+ printk("----------------\n");
|
|
|
+ printk("| NMI testsuite:\n");
|
|
|
+ printk("--------------------\n");
|
|
|
+
|
|
|
+ print_testname("remote IPI");
|
|
|
+ dotest(remote_ipi, SUCCESS);
|
|
|
+ printk("\n");
|
|
|
+ print_testname("local IPI");
|
|
|
+ dotest(local_ipi, SUCCESS);
|
|
|
+ printk("\n");
|
|
|
+
|
|
|
+ cleanup_nmi_testsuite();
|
|
|
+
|
|
|
+ if (unexpected_testcase_failures) {
|
|
|
+ printk("--------------------\n");
|
|
|
+ printk("BUG: %3d unexpected failures (out of %3d) - debugging disabled! |\n",
|
|
|
+ unexpected_testcase_failures, testcase_total);
|
|
|
+ printk("-----------------------------------------------------------------\n");
|
|
|
+ } else if (expected_testcase_failures && testcase_successes) {
|
|
|
+ printk("--------------------\n");
|
|
|
+ printk("%3d out of %3d testcases failed, as expected. |\n",
|
|
|
+ expected_testcase_failures, testcase_total);
|
|
|
+ printk("----------------------------------------------------\n");
|
|
|
+ } else if (expected_testcase_failures && !testcase_successes) {
|
|
|
+ printk("--------------------\n");
|
|
|
+ printk("All %3d testcases failed, as expected. |\n",
|
|
|
+ expected_testcase_failures);
|
|
|
+ printk("----------------------------------------\n");
|
|
|
+ } else {
|
|
|
+ printk("--------------------\n");
|
|
|
+ printk("Good, all %3d testcases passed! |\n",
|
|
|
+ testcase_successes);
|
|
|
+ printk("---------------------------------\n");
|
|
|
+ }
|
|
|
+}
|