Commit 99cf983c authored by Mark Rutland's avatar Mark Rutland Committed by Peter Zijlstra
Browse files

sched/preempt: Add PREEMPT_DYNAMIC using static keys



Where an architecture selects HAVE_STATIC_CALL but not
HAVE_STATIC_CALL_INLINE, each static call has an out-of-line trampoline
which will either branch to a callee or return to the caller.

On such architectures, a number of constraints can conspire to make
those trampolines more complicated and potentially less useful than we'd
like. For example:

* Hardware and software control flow integrity schemes can require the
  addition of "landing pad" instructions (e.g. `BTI` for arm64), which
  will also be present at the "real" callee.

* Limited branch ranges can require that trampolines generate or load an
  address into a register and perform an indirect branch (or at least
  have a slow path that does so). This loses some of the benefits of
  having a direct branch.

* Interaction with SW CFI schemes can be complicated and fragile, e.g.
  requiring that we can recognise idiomatic codegen and remove
  indirections understand, at least until clang proves more helpful
  mechanisms for dealing with this.

For PREEMPT_DYNAMIC, we don't need the full power of static calls, as we
really only need to enable/disable specific preemption functions. We can
achieve the same effect without a number of the pain points above by
using static keys to fold early returns into the preemption functions
themselves rather than in an out-of-line trampoline, effectively
inlining the trampoline into the start of the function.

For arm64, this results in good code generation. For example, the
dynamic_cond_resched() wrapper looks as follows when enabled. When
disabled, the first `B` is replaced with a `NOP`, resulting in an early
return.

| <dynamic_cond_resched>:
|        bti     c
|        b       <dynamic_cond_resched+0x10>     // or `nop`
|        mov     w0, #0x0
|        ret
|        mrs     x0, sp_el0
|        ldr     x0, [x0, #8]
|        cbnz    x0, <dynamic_cond_resched+0x8>
|        paciasp
|        stp     x29, x30, [sp, #-16]!
|        mov     x29, sp
|        bl      <preempt_schedule_common>
|        mov     w0, #0x1
|        ldp     x29, x30, [sp], #16
|        autiasp
|        ret

... compared to the regular form of the function:

| <__cond_resched>:
|        bti     c
|        mrs     x0, sp_el0
|        ldr     x1, [x0, #8]
|        cbz     x1, <__cond_resched+0x18>
|        mov     w0, #0x0
|        ret
|        paciasp
|        stp     x29, x30, [sp, #-16]!
|        mov     x29, sp
|        bl      <preempt_schedule_common>
|        mov     w0, #0x1
|        ldp     x29, x30, [sp], #16
|        autiasp
|        ret

Any architecture which implements static keys should be able to use this
to implement PREEMPT_DYNAMIC with similar cost to non-inlined static
calls. Since this is likely to have greater overhead than (inlined)
static calls, PREEMPT_DYNAMIC is only defaulted to enabled when
HAVE_PREEMPT_DYNAMIC_CALL is selected.

Signed-off-by: default avatarMark Rutland <mark.rutland@arm.com>
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: default avatarArd Biesheuvel <ardb@kernel.org>
Acked-by: default avatarFrederic Weisbecker <frederic@kernel.org>
Link: https://lore.kernel.org/r/20220214165216.2231574-6-mark.rutland@arm.com
parent 33c64734
Loading
Loading
Loading
Loading
+33 −3
Original line number Diff line number Diff line
@@ -1278,11 +1278,41 @@ config HAVE_STATIC_CALL_INLINE

config HAVE_PREEMPT_DYNAMIC
	bool

config HAVE_PREEMPT_DYNAMIC_CALL
	bool
	depends on HAVE_STATIC_CALL
	select HAVE_PREEMPT_DYNAMIC
	help
	   An architecture should select this if it can handle the preemption
	   model being selected at boot time using static calls.

	   Where an architecture selects HAVE_STATIC_CALL_INLINE, any call to a
	   preemption function will be patched directly.

	   Where an architecture does not select HAVE_STATIC_CALL_INLINE, any
	   call to a preemption function will go through a trampoline, and the
	   trampoline will be patched.

	   It is strongly advised to support inline static call to avoid any
	   overhead.

config HAVE_PREEMPT_DYNAMIC_KEY
	bool
	depends on HAVE_ARCH_JUMP_LABEL && CC_HAS_ASM_GOTO
	select HAVE_PREEMPT_DYNAMIC
	help
	   Select this if the architecture support boot time preempt setting
	   on top of static calls. It is strongly advised to support inline
	   static call to avoid any overhead.
	   An architecture should select this if it can handle the preemption
	   model being selected at boot time using static keys.

	   Each preemption function will be given an early return based on a
	   static key. This should have slightly lower overhead than non-inline
	   static calls, as this effectively inlines each trampoline into the
	   start of its callee. This may avoid redundant work, and may
	   integrate better with CFI schemes.

	   This will have greater overhead than using inline static calls as
	   the call to the preemption function cannot be entirely elided.

config ARCH_WANT_LD_ORPHAN_WARN
	bool
+1 −1
Original line number Diff line number Diff line
@@ -245,7 +245,7 @@ config X86
	select HAVE_STACK_VALIDATION		if X86_64
	select HAVE_STATIC_CALL
	select HAVE_STATIC_CALL_INLINE		if HAVE_STACK_VALIDATION
	select HAVE_PREEMPT_DYNAMIC
	select HAVE_PREEMPT_DYNAMIC_CALL
	select HAVE_RSEQ
	select HAVE_SYSCALL_TRACEPOINTS
	select HAVE_UNSTABLE_SCHED_CLOCK
+8 −2
Original line number Diff line number Diff line
@@ -456,13 +456,19 @@ irqentry_state_t noinstr irqentry_enter(struct pt_regs *regs);
 */
void raw_irqentry_exit_cond_resched(void);
#ifdef CONFIG_PREEMPT_DYNAMIC
#if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)
#define irqentry_exit_cond_resched_dynamic_enabled	raw_irqentry_exit_cond_resched
#define irqentry_exit_cond_resched_dynamic_disabled	NULL
DECLARE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);
#define irqentry_exit_cond_resched()	static_call(irqentry_exit_cond_resched)()
#else
#define irqentry_exit_cond_resched()	raw_irqentry_exit_cond_resched()
#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
void dynamic_irqentry_exit_cond_resched(void);
#define irqentry_exit_cond_resched()	dynamic_irqentry_exit_cond_resched()
#endif
#else /* CONFIG_PREEMPT_DYNAMIC */
#define irqentry_exit_cond_resched()	raw_irqentry_exit_cond_resched()
#endif /* CONFIG_PREEMPT_DYNAMIC */

/**
 * irqentry_exit - Handle return from exception that used irqentry_enter()
+6 −1
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ struct user;
extern int __cond_resched(void);
# define might_resched() __cond_resched()

#elif defined(CONFIG_PREEMPT_DYNAMIC)
#elif defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)

extern int __cond_resched(void);

@@ -110,6 +110,11 @@ static __always_inline void might_resched(void)
	static_call_mod(might_resched)();
}

#elif defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)

extern int dynamic_might_resched(void);
# define might_resched() dynamic_might_resched()

#else

# define might_resched() do { } while (0)
+9 −1
Original line number Diff line number Diff line
@@ -2020,7 +2020,7 @@ static inline int test_tsk_need_resched(struct task_struct *tsk)
#if !defined(CONFIG_PREEMPTION) || defined(CONFIG_PREEMPT_DYNAMIC)
extern int __cond_resched(void);

#ifdef CONFIG_PREEMPT_DYNAMIC
#if defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)

DECLARE_STATIC_CALL(cond_resched, __cond_resched);

@@ -2029,6 +2029,14 @@ static __always_inline int _cond_resched(void)
	return static_call_mod(cond_resched)();
}

#elif defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
extern int dynamic_cond_resched(void);

static __always_inline int _cond_resched(void)
{
	return dynamic_cond_resched();
}

#else

static inline int _cond_resched(void)
Loading