#include <linux/smp.h>
 #include <linux/sched.h>
 #include <linux/sched/clock.h>
+#include <linux/semaphore.h>
 #include <linux/thread_info.h>
 #include <linux/init.h>
 #include <linux/uaccess.h>
+#include <linux/workqueue.h>
 #include <linux/delay.h>
+#include <linux/cpuhotplug.h>
 
 #include <asm/cpufeature.h>
 #include <asm/msr.h>
 
 static struct ratelimit_state bld_ratelimit;
 
+static DEFINE_SEMAPHORE(buslock_sem);
+
 static inline bool match_option(const char *arg, int arglen, const char *opt)
 {
        int len = strlen(opt), ratelimit;
                split_lock_verify_msr(sld_state != sld_off);
 }
 
+static void __split_lock_reenable(struct work_struct *work)
+{
+       sld_update_msr(true);
+       up(&buslock_sem);
+}
+
+/*
+ * If a CPU goes offline with pending delayed work to re-enable split lock
+ * detection then the delayed work will be executed on some other CPU. That
+ * handles releasing the buslock_sem, but because it executes on a
+ * different CPU probably won't re-enable split lock detection. This is a
+ * problem on HT systems since the sibling CPU on the same core may then be
+ * left running with split lock detection disabled.
+ *
+ * Unconditionally re-enable detection here.
+ */
+static int splitlock_cpu_offline(unsigned int cpu)
+{
+       sld_update_msr(true);
+
+       return 0;
+}
+
+static DECLARE_DELAYED_WORK(split_lock_reenable, __split_lock_reenable);
+
 static void split_lock_warn(unsigned long ip)
 {
-       pr_warn_ratelimited("#AC: %s/%d took a split_lock trap at address: 0x%lx\n",
-                           current->comm, current->pid, ip);
+       int cpu;
 
-       /*
-        * Disable the split lock detection for this task so it can make
-        * progress and set TIF_SLD so the detection is re-enabled via
-        * switch_to_sld() when the task is scheduled out.
-        */
+       if (!current->reported_split_lock)
+               pr_warn_ratelimited("#AC: %s/%d took a split_lock trap at address: 0x%lx\n",
+                                   current->comm, current->pid, ip);
+       current->reported_split_lock = 1;
+
+       /* misery factor #1, sleep 10ms before trying to execute split lock */
+       if (msleep_interruptible(10) > 0)
+               return;
+       /* Misery factor #2, only allow one buslocked disabled core at a time */
+       if (down_interruptible(&buslock_sem) == -EINTR)
+               return;
+       cpu = get_cpu();
+       schedule_delayed_work_on(cpu, &split_lock_reenable, 2);
+
+       /* Disable split lock detection on this CPU to make progress */
        sld_update_msr(false);
-       set_tsk_thread_flag(current, TIF_SLD);
+       put_cpu();
 }
 
 bool handle_guest_split_lock(unsigned long ip)
                pr_info("disabled\n");
                break;
        case sld_warn:
-               if (boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT))
+               if (boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT)) {
                        pr_info("#AC: crashing the kernel on kernel split_locks and warning on user-space split_locks\n");
-               else if (boot_cpu_has(X86_FEATURE_BUS_LOCK_DETECT))
+                       if (cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+                                             "x86/splitlock", NULL, splitlock_cpu_offline) < 0)
+                               pr_warn("No splitlock CPU offline handler\n");
+               } else if (boot_cpu_has(X86_FEATURE_BUS_LOCK_DETECT)) {
                        pr_info("#DB: warning on user-space bus_locks\n");
+               }
                break;
        case sld_fatal:
                if (boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT)) {