diff options
| author | Dmitry Ilvokhin <d@ilvokhin.com> | 2026-07-27 09:47:45 -0700 |
|---|---|---|
| committer | Dmitry Ilvokhin <d@ilvokhin.com> | 2026-07-29 06:07:29 -0700 |
| commit | b65e9e77eb73462a2c122c8d53bad1e2d55f8122 (patch) | |
| tree | 96bb44824befb27db380abe66eb5e4a1381ac55f | |
| parent | 7feb6b7578860a23d3fdba5d2ad79cefc0ba79a3 (diff) | |
| download | linux-contended-release-qspinlock.tar.gz linux-contended-release-qspinlock.tar.bz2 linux-contended-release-qspinlock.zip | |
x86/paravirt: Trace contended_release on unlockcontended-release-qspinlock
On PARAVIRT_SPINLOCKS=y kernels queued_spin_unlock() is dispatched
through a static_call(), and those kernels are quite common. Gating
contended_release behind a static branch there would leave a NOP on the
unlock path even while the tracepoint is disabled. Since the
static_call() is already present, swap its target to a traced unlock
while the tracepoint is enabled instead. When it is disabled the target
is the plain unlock (an inline store on native), so the unlock path is
unchanged and the tracepoint is truly zero-cost.
Implement the arch_contended_release_trace_reg() and
arch_contended_release_trace_unreg() hooks to update the
queued_spin_unlock static_call() on the tracepoint's enable and disable
transitions. Provide two traced variants,
native_queued_spin_unlock_traced() and pv_queued_spin_unlock_traced(),
so each tail-calls its own base unlock directly rather than recursing
through the now-traced static_call().
Teach pv_is_native_spin_unlock() that the traced native variant still
counts as native.
Only PARAVIRT_SPINLOCKS=y is affected. PARAVIRT_SPINLOCKS=n keeps the
generic static-branch path.
Signed-off-by: Dmitry Ilvokhin <d@ilvokhin.com>
| -rw-r--r-- | arch/x86/include/asm/paravirt-spinlock.h | 2 | ||||
| -rw-r--r-- | arch/x86/kernel/paravirt-spinlocks.c | 53 |
2 files changed, 53 insertions, 2 deletions
diff --git a/arch/x86/include/asm/paravirt-spinlock.h b/arch/x86/include/asm/paravirt-spinlock.h index ff735830de4a..302bc2ba3a75 100644 --- a/arch/x86/include/asm/paravirt-spinlock.h +++ b/arch/x86/include/asm/paravirt-spinlock.h @@ -99,6 +99,8 @@ bool __raw_callee_save___native_vcpu_is_preempted(long cpu); void __init native_pv_lock_init(void); __visible void __native_queued_spin_unlock(struct qspinlock *lock); +__visible void native_queued_spin_unlock_traced(struct qspinlock *lock); +__visible void pv_queued_spin_unlock_traced(struct qspinlock *lock); bool pv_is_native_spin_unlock(void); __visible bool __native_vcpu_is_preempted(long cpu); bool pv_is_native_vcpu_is_preempted(void); diff --git a/arch/x86/kernel/paravirt-spinlocks.c b/arch/x86/kernel/paravirt-spinlocks.c index ddc19dc28ba1..ca12b3655307 100644 --- a/arch/x86/kernel/paravirt-spinlocks.c +++ b/arch/x86/kernel/paravirt-spinlocks.c @@ -7,6 +7,7 @@ #include <linux/spinlock.h> #include <linux/export.h> #include <linux/jump_label.h> +#include <trace/events/lock.h> DEFINE_STATIC_KEY_FALSE(virt_spin_lock_key); @@ -30,10 +31,58 @@ EXPORT_STATIC_CALL_TRAMP(queued_spin_lock_slowpath); DEFINE_STATIC_CALL(queued_spin_unlock, __raw_callee_save___native_queued_spin_unlock); EXPORT_STATIC_CALL_TRAMP(queued_spin_unlock); +/* + * Traced unlock variants, swapped in via static_call while the + * contended_release tracepoint is enabled. Two of them, so each tail calls its + * own base directly. + */ +__visible void native_queued_spin_unlock_traced(struct qspinlock *lock) +{ + if (queued_spin_is_contended(lock)) + trace_call__contended_release(lock); + native_queued_spin_unlock(lock); +} +PV_CALLEE_SAVE_REGS_THUNK(native_queued_spin_unlock_traced); + +__visible void pv_queued_spin_unlock_traced(struct qspinlock *lock) +{ + if (queued_spin_is_contended(lock)) + trace_call__contended_release(lock); + __raw_callee_save___pv_queued_spin_unlock(lock); +} +PV_CALLEE_SAVE_REGS_THUNK(pv_queued_spin_unlock_traced); + bool pv_is_native_spin_unlock(void) { - return static_call_query(queued_spin_unlock) == - __raw_callee_save___native_queued_spin_unlock; + void *unlock = static_call_query(queued_spin_unlock); + + return unlock == __raw_callee_save___native_queued_spin_unlock || + unlock == __raw_callee_save_native_queued_spin_unlock_traced; +} + +int arch_contended_release_trace_reg(void) +{ + void *cur = static_call_query(queued_spin_unlock); + + if (cur == __raw_callee_save___native_queued_spin_unlock) + static_call_update(queued_spin_unlock, + __raw_callee_save_native_queued_spin_unlock_traced); + else if (cur == __raw_callee_save___pv_queued_spin_unlock) + static_call_update(queued_spin_unlock, + __raw_callee_save_pv_queued_spin_unlock_traced); + return 0; +} + +void arch_contended_release_trace_unreg(void) +{ + void *cur = static_call_query(queued_spin_unlock); + + if (cur == __raw_callee_save_native_queued_spin_unlock_traced) + static_call_update(queued_spin_unlock, + __raw_callee_save___native_queued_spin_unlock); + else if (cur == __raw_callee_save_pv_queued_spin_unlock_traced) + static_call_update(queued_spin_unlock, + __raw_callee_save___pv_queued_spin_unlock); } __visible bool __native_vcpu_is_preempted(long cpu) |