Commit ebb1064e authored by Fenghua Yu's avatar Fenghua Yu Committed by Thomas Gleixner

x86/traps: Handle #DB for bus lock

Bus locks degrade performance for the whole system, not just for the CPU
that requested the bus lock. Two CPU features "#AC for split lock" and
"#DB for bus lock" provide hooks so that the operating system may choose
one of several mitigation strategies.

#AC for split lock is already implemented. Add code to use the #DB for
bus lock feature to cover additional situations with new options to
mitigate.

split_lock_detect=
		#AC for split lock		#DB for bus lock

off		Do nothing			Do nothing

warn		Kernel OOPs			Warn once per task and
		Warn once per task and		and continues to run.
		disable future checking
	 	When both features are
		supported, warn in #AC

fatal		Kernel OOPs			Send SIGBUS to user.
		Send SIGBUS to user
		When both features are
		supported, fatal in #AC

ratelimit:N	Do nothing			Limit bus lock rate to
						N per second in the
						current non-root user.

Default option is "warn".

Hardware only generates #DB for bus lock detect when CPL>0 to avoid
nested #DB from multiple bus locks while the first #DB is being handled.
So no need to handle #DB for bus lock detected in the kernel.

#DB for bus lock is enabled by bus lock detection bit 2 in DEBUGCTL MSR
while #AC for split lock is enabled by split lock detection bit 29 in
TEST_CTRL MSR.

Both breakpoint and bus lock in the same instruction can trigger one #DB.
The bus lock is handled before the breakpoint in the #DB handler.

Delivery of #DB for bus lock in userspace clears DR6[11], which is set by
the #DB handler right after reading DR6.
Signed-off-by: default avatarFenghua Yu <fenghua.yu@intel.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Reviewed-by: default avatarTony Luck <tony.luck@intel.com>
Link: https://lore.kernel.org/r/20210322135325.682257-3-fenghua.yu@intel.com
parent f21d4d3b
...@@ -41,12 +41,13 @@ unsigned int x86_family(unsigned int sig); ...@@ -41,12 +41,13 @@ unsigned int x86_family(unsigned int sig);
unsigned int x86_model(unsigned int sig); unsigned int x86_model(unsigned int sig);
unsigned int x86_stepping(unsigned int sig); unsigned int x86_stepping(unsigned int sig);
#ifdef CONFIG_CPU_SUP_INTEL #ifdef CONFIG_CPU_SUP_INTEL
extern void __init cpu_set_core_cap_bits(struct cpuinfo_x86 *c); extern void __init sld_setup(struct cpuinfo_x86 *c);
extern void switch_to_sld(unsigned long tifn); extern void switch_to_sld(unsigned long tifn);
extern bool handle_user_split_lock(struct pt_regs *regs, long error_code); extern bool handle_user_split_lock(struct pt_regs *regs, long error_code);
extern bool handle_guest_split_lock(unsigned long ip); extern bool handle_guest_split_lock(unsigned long ip);
extern void handle_bus_lock(struct pt_regs *regs);
#else #else
static inline void __init cpu_set_core_cap_bits(struct cpuinfo_x86 *c) {} static inline void __init sld_setup(struct cpuinfo_x86 *c) {}
static inline void switch_to_sld(unsigned long tifn) {} static inline void switch_to_sld(unsigned long tifn) {}
static inline bool handle_user_split_lock(struct pt_regs *regs, long error_code) static inline bool handle_user_split_lock(struct pt_regs *regs, long error_code)
{ {
...@@ -57,6 +58,8 @@ static inline bool handle_guest_split_lock(unsigned long ip) ...@@ -57,6 +58,8 @@ static inline bool handle_guest_split_lock(unsigned long ip)
{ {
return false; return false;
} }
static inline void handle_bus_lock(struct pt_regs *regs) {}
#endif #endif
#ifdef CONFIG_IA32_FEAT_CTL #ifdef CONFIG_IA32_FEAT_CTL
void init_ia32_feat_ctl(struct cpuinfo_x86 *c); void init_ia32_feat_ctl(struct cpuinfo_x86 *c);
......
...@@ -265,6 +265,7 @@ ...@@ -265,6 +265,7 @@
#define DEBUGCTLMSR_LBR (1UL << 0) /* last branch recording */ #define DEBUGCTLMSR_LBR (1UL << 0) /* last branch recording */
#define DEBUGCTLMSR_BTF_SHIFT 1 #define DEBUGCTLMSR_BTF_SHIFT 1
#define DEBUGCTLMSR_BTF (1UL << 1) /* single-step on branches */ #define DEBUGCTLMSR_BTF (1UL << 1) /* single-step on branches */
#define DEBUGCTLMSR_BUS_LOCK_DETECT (1UL << 2)
#define DEBUGCTLMSR_TR (1UL << 6) #define DEBUGCTLMSR_TR (1UL << 6)
#define DEBUGCTLMSR_BTS (1UL << 7) #define DEBUGCTLMSR_BTS (1UL << 7)
#define DEBUGCTLMSR_BTINT (1UL << 8) #define DEBUGCTLMSR_BTINT (1UL << 8)
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#define DR_TRAP3 (0x8) /* db3 */ #define DR_TRAP3 (0x8) /* db3 */
#define DR_TRAP_BITS (DR_TRAP0|DR_TRAP1|DR_TRAP2|DR_TRAP3) #define DR_TRAP_BITS (DR_TRAP0|DR_TRAP1|DR_TRAP2|DR_TRAP3)
#define DR_BUS_LOCK (0x800) /* bus_lock */
#define DR_STEP (0x4000) /* single-step */ #define DR_STEP (0x4000) /* single-step */
#define DR_SWITCH (0x8000) /* task switch */ #define DR_SWITCH (0x8000) /* task switch */
......
...@@ -1330,7 +1330,7 @@ static void __init early_identify_cpu(struct cpuinfo_x86 *c) ...@@ -1330,7 +1330,7 @@ static void __init early_identify_cpu(struct cpuinfo_x86 *c)
cpu_set_bug_bits(c); cpu_set_bug_bits(c);
cpu_set_core_cap_bits(c); sld_setup(c);
fpu__init_system(c); fpu__init_system(c);
......
...@@ -44,9 +44,9 @@ enum split_lock_detect_state { ...@@ -44,9 +44,9 @@ enum split_lock_detect_state {
}; };
/* /*
* Default to sld_off because most systems do not support split lock detection * Default to sld_off because most systems do not support split lock detection.
* split_lock_setup() will switch this to sld_warn on systems that support * sld_state_setup() will switch this to sld_warn on systems that support
* split lock detect, unless there is a command line override. * split lock/bus lock detect, unless there is a command line override.
*/ */
static enum split_lock_detect_state sld_state __ro_after_init = sld_off; static enum split_lock_detect_state sld_state __ro_after_init = sld_off;
static u64 msr_test_ctrl_cache __ro_after_init; static u64 msr_test_ctrl_cache __ro_after_init;
...@@ -603,6 +603,7 @@ static void init_intel_misc_features(struct cpuinfo_x86 *c) ...@@ -603,6 +603,7 @@ static void init_intel_misc_features(struct cpuinfo_x86 *c)
} }
static void split_lock_init(void); static void split_lock_init(void);
static void bus_lock_init(void);
static void init_intel(struct cpuinfo_x86 *c) static void init_intel(struct cpuinfo_x86 *c)
{ {
...@@ -720,6 +721,7 @@ static void init_intel(struct cpuinfo_x86 *c) ...@@ -720,6 +721,7 @@ static void init_intel(struct cpuinfo_x86 *c)
tsx_disable(); tsx_disable();
split_lock_init(); split_lock_init();
bus_lock_init();
intel_init_thermal(c); intel_init_thermal(c);
} }
...@@ -1020,16 +1022,15 @@ static bool split_lock_verify_msr(bool on) ...@@ -1020,16 +1022,15 @@ static bool split_lock_verify_msr(bool on)
return ctrl == tmp; return ctrl == tmp;
} }
static void __init split_lock_setup(void) static void __init sld_state_setup(void)
{ {
enum split_lock_detect_state state = sld_warn; enum split_lock_detect_state state = sld_warn;
char arg[20]; char arg[20];
int i, ret; int i, ret;
if (!split_lock_verify_msr(false)) { if (!boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT) &&
pr_info("MSR access failed: Disabled\n"); !boot_cpu_has(X86_FEATURE_BUS_LOCK_DETECT))
return; return;
}
ret = cmdline_find_option(boot_command_line, "split_lock_detect", ret = cmdline_find_option(boot_command_line, "split_lock_detect",
arg, sizeof(arg)); arg, sizeof(arg));
...@@ -1041,17 +1042,14 @@ static void __init split_lock_setup(void) ...@@ -1041,17 +1042,14 @@ static void __init split_lock_setup(void)
} }
} }
} }
sld_state = state;
}
switch (state) { static void __init __split_lock_setup(void)
case sld_off: {
pr_info("disabled\n"); if (!split_lock_verify_msr(false)) {
pr_info("MSR access failed: Disabled\n");
return; return;
case sld_warn:
pr_info("warning about user-space split_locks\n");
break;
case sld_fatal:
pr_info("sending SIGBUS on user-space split_locks\n");
break;
} }
rdmsrl(MSR_TEST_CTRL, msr_test_ctrl_cache); rdmsrl(MSR_TEST_CTRL, msr_test_ctrl_cache);
...@@ -1061,7 +1059,9 @@ static void __init split_lock_setup(void) ...@@ -1061,7 +1059,9 @@ static void __init split_lock_setup(void)
return; return;
} }
sld_state = state; /* Restore the MSR to its cached value. */
wrmsrl(MSR_TEST_CTRL, msr_test_ctrl_cache);
setup_force_cpu_cap(X86_FEATURE_SPLIT_LOCK_DETECT); setup_force_cpu_cap(X86_FEATURE_SPLIT_LOCK_DETECT);
} }
...@@ -1118,6 +1118,29 @@ bool handle_guest_split_lock(unsigned long ip) ...@@ -1118,6 +1118,29 @@ bool handle_guest_split_lock(unsigned long ip)
} }
EXPORT_SYMBOL_GPL(handle_guest_split_lock); EXPORT_SYMBOL_GPL(handle_guest_split_lock);
static void bus_lock_init(void)
{
u64 val;
/*
* Warn and fatal are handled by #AC for split lock if #AC for
* split lock is supported.
*/
if (!boot_cpu_has(X86_FEATURE_BUS_LOCK_DETECT) ||
(boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT) &&
(sld_state == sld_warn || sld_state == sld_fatal)) ||
sld_state == sld_off)
return;
/*
* Enable #DB for bus lock. All bus locks are handled in #DB except
* split locks are handled in #AC in the fatal case.
*/
rdmsrl(MSR_IA32_DEBUGCTLMSR, val);
val |= DEBUGCTLMSR_BUS_LOCK_DETECT;
wrmsrl(MSR_IA32_DEBUGCTLMSR, val);
}
bool handle_user_split_lock(struct pt_regs *regs, long error_code) bool handle_user_split_lock(struct pt_regs *regs, long error_code)
{ {
if ((regs->flags & X86_EFLAGS_AC) || sld_state == sld_fatal) if ((regs->flags & X86_EFLAGS_AC) || sld_state == sld_fatal)
...@@ -1126,6 +1149,21 @@ bool handle_user_split_lock(struct pt_regs *regs, long error_code) ...@@ -1126,6 +1149,21 @@ bool handle_user_split_lock(struct pt_regs *regs, long error_code)
return true; return true;
} }
void handle_bus_lock(struct pt_regs *regs)
{
switch (sld_state) {
case sld_off:
break;
case sld_warn:
pr_warn_ratelimited("#DB: %s/%d took a bus_lock trap at address: 0x%lx\n",
current->comm, current->pid, regs->ip);
break;
case sld_fatal:
force_sig_fault(SIGBUS, BUS_ADRALN, NULL);
break;
}
}
/* /*
* This function is called only when switching between tasks with * This function is called only when switching between tasks with
* different split-lock detection modes. It sets the MSR for the * different split-lock detection modes. It sets the MSR for the
...@@ -1166,7 +1204,7 @@ static const struct x86_cpu_id split_lock_cpu_ids[] __initconst = { ...@@ -1166,7 +1204,7 @@ static const struct x86_cpu_id split_lock_cpu_ids[] __initconst = {
{} {}
}; };
void __init cpu_set_core_cap_bits(struct cpuinfo_x86 *c) static void __init split_lock_setup(struct cpuinfo_x86 *c)
{ {
const struct x86_cpu_id *m; const struct x86_cpu_id *m;
u64 ia32_core_caps; u64 ia32_core_caps;
...@@ -1193,5 +1231,40 @@ void __init cpu_set_core_cap_bits(struct cpuinfo_x86 *c) ...@@ -1193,5 +1231,40 @@ void __init cpu_set_core_cap_bits(struct cpuinfo_x86 *c)
} }
cpu_model_supports_sld = true; cpu_model_supports_sld = true;
split_lock_setup(); __split_lock_setup();
}
static void sld_state_show(void)
{
if (!boot_cpu_has(X86_FEATURE_BUS_LOCK_DETECT) &&
!boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT))
return;
switch (sld_state) {
case sld_off:
pr_info("disabled\n");
break;
case sld_warn:
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))
pr_info("#DB: warning on user-space bus_locks\n");
break;
case sld_fatal:
if (boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT)) {
pr_info("#AC: crashing the kernel on kernel split_locks and sending SIGBUS on user-space split_locks\n");
} else if (boot_cpu_has(X86_FEATURE_BUS_LOCK_DETECT)) {
pr_info("#DB: sending SIGBUS on user-space bus_locks%s\n",
boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT) ?
" from non-WB" : "");
}
break;
}
}
void __init sld_setup(struct cpuinfo_x86 *c)
{
split_lock_setup(c);
sld_state_setup();
sld_state_show();
} }
...@@ -978,6 +978,10 @@ static __always_inline void exc_debug_user(struct pt_regs *regs, ...@@ -978,6 +978,10 @@ static __always_inline void exc_debug_user(struct pt_regs *regs,
goto out_irq; goto out_irq;
} }
/* #DB for bus lock can only be triggered from userspace. */
if (dr6 & DR_BUS_LOCK)
handle_bus_lock(regs);
/* Add the virtual_dr6 bits for signals. */ /* Add the virtual_dr6 bits for signals. */
dr6 |= current->thread.virtual_dr6; dr6 |= current->thread.virtual_dr6;
if (dr6 & (DR_STEP | DR_TRAP_BITS) || icebp) if (dr6 & (DR_STEP | DR_TRAP_BITS) || icebp)
......
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