Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: factor out EL1 SSBS emulation hook

Currently call_undef_hook() is used to handle UNDEFINED exceptions from
EL0 and EL1. As support for deprecated instructions may be enabled
independently, the handlers for individual instructions are organised as
a linked list of struct undef_hook which can be manipulated dynamically.
As this can be manipulated dynamically, the list is protected with a
raw_spinlock which must be acquired when handling UNDEFINED exceptions
or when manipulating the list of handlers.

This locking is unfortunate as it serialises handling of UNDEFINED
exceptions, and requires RCU to be enabled for lockdep, requiring the
use of RCU_NONIDLE() in resume path of cpu_suspend() since commit:

a2c42bbabbe260b7 ("arm64: spectre: Prevent lockdep splat on v4 mitigation enable path")

The list of UNDEFINED handlers largely consist of handlers for
exceptions taken from EL0, and the only handler for exceptions taken
from EL1 handles `MSR SSBS, #imm` on CPUs which feature PSTATE.SSBS but
lack the corresponding MSR (Immediate) instruction. Other than this we
never expect to take an UNDEFINED exception from EL1 in normal
operation.

This patch reworks do_el0_undef() to invoke the EL1 SSBS handler
directly, relegating call_undef_hook() to only handle EL0 UNDEFs. This
removes redundant work to iterate the list for EL1 UNDEFs, and removes
the need for locking, permitting EL1 UNDEFs to be handled in parallel
without contention.

The RCU_NONIDLE() call in cpu_suspend() will be removed in a subsequent
patch, as there are other potential issues with the use of
instrumentable code and RCU in the CPU suspend code.

I've tested this by forcing the detection of SSBS on a CPU that doesn't
have it, and verifying that the try_emulate_el1_ssbs() callback is
invoked.

Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Joey Gouly <joey.gouly@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20221019144123.612388-4-mark.rutland@arm.com
Signed-off-by: Will Deacon <will@kernel.org>

authored by

Mark Rutland and committed by
Will Deacon
bff8f413 61d64a37

+17 -26
+2
arch/arm64/include/asm/spectre.h
··· 26 26 SPECTRE_VULNERABLE, 27 27 }; 28 28 29 + struct pt_regs; 29 30 struct task_struct; 30 31 31 32 /* ··· 99 98 bool is_spectre_bhb_affected(const struct arm64_cpu_capabilities *entry, int scope); 100 99 u8 spectre_bhb_loop_affected(int scope); 101 100 void spectre_bhb_enable_mitigation(const struct arm64_cpu_capabilities *__unused); 101 + bool try_emulate_el1_ssbs(struct pt_regs *regs, u32 instr); 102 102 #endif /* __ASSEMBLY__ */ 103 103 #endif /* __ASM_SPECTRE_H */
+7 -19
arch/arm64/kernel/proton-pack.c
··· 521 521 return state != SPECTRE_UNAFFECTED; 522 522 } 523 523 524 - static int ssbs_emulation_handler(struct pt_regs *regs, u32 instr) 524 + bool try_emulate_el1_ssbs(struct pt_regs *regs, u32 instr) 525 525 { 526 - if (user_mode(regs)) 527 - return 1; 526 + const u32 instr_mask = ~(1U << PSTATE_Imm_shift); 527 + const u32 instr_val = 0xd500401f | PSTATE_SSBS; 528 + 529 + if ((instr & instr_mask) != instr_val) 530 + return false; 528 531 529 532 if (instr & BIT(PSTATE_Imm_shift)) 530 533 regs->pstate |= PSR_SSBS_BIT; ··· 535 532 regs->pstate &= ~PSR_SSBS_BIT; 536 533 537 534 arm64_skip_faulting_instruction(regs, 4); 538 - return 0; 535 + return true; 539 536 } 540 - 541 - static struct undef_hook ssbs_emulation_hook = { 542 - .instr_mask = ~(1U << PSTATE_Imm_shift), 543 - .instr_val = 0xd500401f | PSTATE_SSBS, 544 - .fn = ssbs_emulation_handler, 545 - }; 546 537 547 538 static enum mitigation_state spectre_v4_enable_hw_mitigation(void) 548 539 { 549 - static bool undef_hook_registered = false; 550 - static DEFINE_RAW_SPINLOCK(hook_lock); 551 540 enum mitigation_state state; 552 541 553 542 /* ··· 549 554 state = spectre_v4_get_cpu_hw_mitigation_state(); 550 555 if (state != SPECTRE_MITIGATED || !this_cpu_has_cap(ARM64_SSBS)) 551 556 return state; 552 - 553 - raw_spin_lock(&hook_lock); 554 - if (!undef_hook_registered) { 555 - register_undef_hook(&ssbs_emulation_hook); 556 - undef_hook_registered = true; 557 - } 558 - raw_spin_unlock(&hook_lock); 559 557 560 558 if (spectre_v4_mitigations_off()) { 561 559 sysreg_clear_set(sctlr_el1, 0, SCTLR_ELx_DSSBS);
+8 -7
arch/arm64/kernel/traps.c
··· 402 402 int (*fn)(struct pt_regs *regs, u32 instr) = NULL; 403 403 unsigned long pc = instruction_pointer(regs); 404 404 405 - if (!user_mode(regs)) { 406 - __le32 instr_le; 407 - if (get_kernel_nofault(instr_le, (__le32 *)pc)) 408 - goto exit; 409 - instr = le32_to_cpu(instr_le); 410 - } else if (compat_thumb_mode(regs)) { 405 + if (compat_thumb_mode(regs)) { 411 406 /* 16-bit Thumb instruction */ 412 407 __le16 instr_le; 413 408 if (get_user(instr_le, (__le16 __user *)pc)) ··· 495 500 496 501 void do_el1_undef(struct pt_regs *regs, unsigned long esr) 497 502 { 498 - if (call_undef_hook(regs) == 0) 503 + u32 insn; 504 + 505 + if (aarch64_insn_read((void *)regs->pc, &insn)) 506 + goto out_err; 507 + 508 + if (try_emulate_el1_ssbs(regs, insn)) 499 509 return; 500 510 511 + out_err: 501 512 die("Oops - Undefined instruction", regs, esr); 502 513 } 503 514