[PATCH v9 14/17] x86/split_lock: Add a debugfs interface to enable/disable split lock detection during run time

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



To workaround or debug a split lock issue, the administrator may need to
disable or enable split lock detection during run time without rebooting
the system.

The interface /sys/kernel/debug/x86/split_lock_detect is added to allow
the administrator to disable or enable split lock detection and show
current split lock detection setting during run time.

Writing [yY1] or [oO][nN] to the file enables split lock detection and
writing [nN0] or [oO][fF] disables split lock detection. Split lock
detection is enabled or disabled on all CPUs.

Reading the file returns current global split lock detection setting:
0: disabled
1: enabled

To simplify the code, Ingo suggests to use the global atomic
split_lock_debug flag both for warning split lock in WARN_ONCE() and for
writing the debugfs interface.

Signed-off-by: Fenghua Yu <fenghua.yu@xxxxxxxxx>
---
 arch/x86/kernel/cpu/intel.c | 121 +++++++++++++++++++++++++++++++++++-
 arch/x86/kernel/traps.c     |   3 +-
 2 files changed, 121 insertions(+), 3 deletions(-)

diff --git a/arch/x86/kernel/cpu/intel.c b/arch/x86/kernel/cpu/intel.c
index 4a854f051cf4..4005342dfdd0 100644
--- a/arch/x86/kernel/cpu/intel.c
+++ b/arch/x86/kernel/cpu/intel.c
@@ -9,6 +9,8 @@
 #include <linux/thread_info.h>
 #include <linux/init.h>
 #include <linux/uaccess.h>
+#include <linux/syscore_ops.h>
+#include <linux/debugfs.h>
 
 #include <asm/cpufeature.h>
 #include <asm/pgtable.h>
@@ -630,8 +632,19 @@ static void init_intel_misc_features(struct cpuinfo_x86 *c)
 	wrmsrl(MSR_MISC_FEATURES_ENABLES, msr);
 }
 
-static void split_lock_update_msr(void)
+static void split_lock_update_msr(void *__unused)
 {
+	unsigned long flags;
+
+	/*
+	 * Need to prevent msr_test_ctl_cached from being changed *and*
+	 * completing its WRMSR between our read and our WRMSR. By turning
+	 * IRQs off here, ensure that no split lock debugfs write happens
+	 * on this CPU and that any concurrent debugfs write from a different
+	 * CPU will not finish updating us via IPI until we're done.
+	 */
+	local_irq_save(flags);
+
 	if (split_lock_detect_enabled) {
 		/* Enable split lock detection */
 		this_cpu_or(msr_test_ctl_cached, MSR_TEST_CTL_SPLIT_LOCK_DETECT);
@@ -640,6 +653,8 @@ static void split_lock_update_msr(void)
 		this_cpu_and(msr_test_ctl_cached, ~MSR_TEST_CTL_SPLIT_LOCK_DETECT);
 	}
 	wrmsrl(MSR_TEST_CTL, this_cpu_read(msr_test_ctl_cached));
+
+	local_irq_restore(flags);
 }
 
 static void split_lock_init(struct cpuinfo_x86 *c)
@@ -651,7 +666,7 @@ static void split_lock_init(struct cpuinfo_x86 *c)
 		rdmsrl(MSR_TEST_CTL, test_ctl_val);
 		this_cpu_write(msr_test_ctl_cached, test_ctl_val);
 
-		split_lock_update_msr();
+		split_lock_update_msr(NULL);
 	}
 }
 
@@ -1077,10 +1092,23 @@ static atomic_t split_lock_debug;
 
 void split_lock_disable(void)
 {
+	unsigned long flags;
+
+	/*
+	 * Need to prevent msr_test_ctl_cached from being changed *and*
+	 * completing its WRMSR between our read and our WRMSR. By turning
+	 * IRQs off here, ensure that no split lock debugfs write happens
+	 * on this CPU and that any concurrent debugfs write from a different
+	 * CPU will not finish updating us via IPI until we're done.
+	 */
+	local_irq_save(flags);
+
 	/* Disable split lock detection on this CPU */
 	this_cpu_and(msr_test_ctl_cached, ~MSR_TEST_CTL_SPLIT_LOCK_DETECT);
 	wrmsrl(MSR_TEST_CTL, this_cpu_read(msr_test_ctl_cached));
 
+	local_irq_restore(flags);
+
 	/*
 	 * Use the atomic variable split_lock_debug to ensure only the
 	 * first CPU hitting split lock issue prints one single complete
@@ -1094,3 +1122,92 @@ void split_lock_disable(void)
 		atomic_set(&split_lock_debug, 0);
 	}
 }
+
+static ssize_t split_lock_detect_rd(struct file *f, char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	unsigned int len;
+	char buf[8];
+
+	len = sprintf(buf, "%u\n", split_lock_detect_enabled);
+
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t split_lock_detect_wr(struct file *f, const char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	unsigned int len;
+	char buf[8];
+	bool val;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	if (kstrtobool(buf, &val))
+		return -EINVAL;
+
+	while (atomic_cmpxchg(&split_lock_debug, 1, 0))
+		cpu_relax();
+
+	if (split_lock_detect_enabled == val)
+		goto out_unlock;
+
+	split_lock_detect_enabled = val;
+
+	/* Update the split lock detection setting in MSR on all online CPUs. */
+	on_each_cpu(split_lock_update_msr, NULL, 1);
+
+	if (split_lock_detect_enabled)
+		pr_info("enabled\n");
+	else
+		pr_info("disabled\n");
+
+out_unlock:
+	atomic_set(&split_lock_debug, 0);
+
+	return count;
+}
+
+static const struct file_operations split_lock_detect_fops = {
+	.read = split_lock_detect_rd,
+	.write = split_lock_detect_wr,
+	.llseek = default_llseek,
+};
+
+/*
+ * Before resume from hibernation, TEST_CTL MSR has been initialized to
+ * default value in split_lock_init() on BP. On resume, restore the MSR
+ * on BP to previous value which could be changed by debugfs and thus could
+ * be different from the default value.
+ *
+ * The MSR on BP is supposed not to be changed during suspend and thus it's
+ * unnecessary to set it again during resume from suspend. But at this point
+ * we don't know resume is from suspend or hibernation. To simplify the
+ * situation, just set up the MSR on resume from suspend.
+ *
+ * Set up the MSR on APs when they are re-added later.
+ */
+static void split_lock_syscore_resume(void)
+{
+	split_lock_update_msr(NULL);
+}
+
+static struct syscore_ops split_lock_syscore_ops = {
+	.resume = split_lock_syscore_resume,
+};
+
+static int __init split_lock_detect_initcall(void)
+{
+	if (boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT)) {
+		debugfs_create_file("split_lock_detect", 0600, arch_debugfs_dir,
+				    NULL, &split_lock_detect_fops);
+
+		register_syscore_ops(&split_lock_syscore_ops);
+	}
+
+	return 0;
+}
+late_initcall(split_lock_detect_initcall);
diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c
index 38143c028f5a..691e34828bdf 100644
--- a/arch/x86/kernel/traps.c
+++ b/arch/x86/kernel/traps.c
@@ -324,7 +324,8 @@ dotraplinkage void do_alignment_check(struct pt_regs *regs, long error_code)
 		 * execution context.
 		 *
 		 * Split-lock detection will remain disabled after this,
-		 * until the next reboot.
+		 * until the next reboot or until it is re-enabled by
+		 * debugfs interface /sys/kernel/debug/x86/split_lock_detect.
 		 */
 		split_lock_disable();
 
-- 
2.19.1




[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux