Commit b041b525 authored by Tony Luck's avatar Tony Luck Committed by Thomas Gleixner

x86/split_lock: Make life miserable for split lockers

In https://lore.kernel.org/all/87y22uujkm.ffs@tglx/ Thomas
said:

  Its's simply wishful thinking that stuff gets fixed because of a
  WARN_ONCE(). This has never worked. The only thing which works is to
  make stuff fail hard or slow it down in a way which makes it annoying
  enough to users to complain.

He was talking about WBINVD. But it made me think about how we use the
split lock detection feature in Linux.

Existing code has three options for applications:

 1) Don't enable split lock detection (allow arbitrary split locks)
 2) Warn once when a process uses split lock, but let the process
    keep running with split lock detection disabled
 3) Kill process that use split locks

Option 2 falls into the "wishful thinking" territory that Thomas warns does
nothing. But option 3 might not be viable in a situation with legacy
applications that need to run.

Hence make option 2 much stricter to "slow it down in a way which makes
it annoying".

Primary reason for this change is to provide better quality of service to
the rest of the applications running on the system. Internal testing shows
that even with many processes splitting locks, performance for the rest of
the system is much more responsive.

The new "warn" mode operates like this.  When an application tries to
execute a bus lock the #AC handler.

 1) Delays (interruptibly) 10 ms before moving to next step.

 2) Blocks (interruptibly) until it can get the semaphore
	If interrupted, just return. Assume the signal will either
	kill the task, or direct execution away from the instruction
	that is trying to get the bus lock.
 3) Disables split lock detection for the current core
 4) Schedules a work queue to re-enable split lock detect in 2 jiffies
 5) Returns

The work queue that re-enables split lock detection also releases the
semaphore.

There is a corner case where a CPU may be taken offline while split lock
detection is disabled. A CPU hotplug handler handles this case.

Old behaviour was to only print the split lock warning on the first
occurrence of a split lock from a task. Preserve that by adding a flag to
the task structure that suppresses subsequent split lock messages from that
task.
Signed-off-by: default avatarTony Luck <tony.luck@intel.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20220310204854.31752-2-tony.luck@intel.com
parent af2d861d
......@@ -7,10 +7,13 @@
#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>
......@@ -999,6 +1002,8 @@ static const struct {
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;
......@@ -1109,18 +1114,52 @@ static void split_lock_init(void)
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)
......@@ -1274,10 +1313,14 @@ static void sld_state_show(void)
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)) {
......
......@@ -941,6 +941,9 @@ struct task_struct {
#ifdef CONFIG_IOMMU_SVA
unsigned pasid_activated:1;
#endif
#ifdef CONFIG_CPU_SUP_INTEL
unsigned reported_split_lock:1;
#endif
unsigned long atomic_flags; /* Flags requiring atomic access. */
......
......@@ -1045,6 +1045,11 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
#ifdef CONFIG_MEMCG
tsk->active_memcg = NULL;
#endif
#ifdef CONFIG_CPU_SUP_INTEL
tsk->reported_split_lock = 0;
#endif
return tsk;
free_stack:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment