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